Merge branch 'beta'

This commit is contained in:
Luke Pulverenti 2016-02-01 14:24:15 -05:00
commit 37352785ac
61 changed files with 831 additions and 602 deletions

View File

@ -1,5 +1,4 @@
using MediaBrowser.Common.IO; using MediaBrowser.Common.Net;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Net;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using MediaBrowser.Model.Net; using MediaBrowser.Model.Net;
@ -46,6 +45,11 @@ namespace MediaBrowser.Api
/// <value><c>true</c> if [include hidden]; otherwise, <c>false</c>.</value> /// <value><c>true</c> if [include hidden]; otherwise, <c>false</c>.</value>
[ApiMember(Name = "IncludeHidden", Description = "An optional filter to include or exclude hidden files and folders. true/false", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")] [ApiMember(Name = "IncludeHidden", Description = "An optional filter to include or exclude hidden files and folders. true/false", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
public bool IncludeHidden { get; set; } public bool IncludeHidden { get; set; }
public GetDirectoryContents()
{
IncludeHidden = true;
}
} }
[Route("/Environment/NetworkShares", "GET", Summary = "Gets shares from a network device")] [Route("/Environment/NetworkShares", "GET", Summary = "Gets shares from a network device")]

View File

@ -2192,7 +2192,7 @@ namespace MediaBrowser.Api.Playback
{ {
if (string.Equals(state.OutputContainer, "mkv", StringComparison.OrdinalIgnoreCase)) if (string.Equals(state.OutputContainer, "mkv", StringComparison.OrdinalIgnoreCase))
{ {
inputModifier += " -noaccurate_seek"; //inputModifier += " -noaccurate_seek";
} }
} }

View File

@ -141,10 +141,10 @@ namespace MediaBrowser.Api.Playback
var profile = request.DeviceProfile; var profile = request.DeviceProfile;
if (profile == null)
{
var caps = _deviceManager.GetCapabilities(authInfo.DeviceId); var caps = _deviceManager.GetCapabilities(authInfo.DeviceId);
if (caps != null) if (caps != null)
{
if (profile == null)
{ {
profile = caps.DeviceProfile; profile = caps.DeviceProfile;
} }

View File

@ -66,13 +66,12 @@ namespace MediaBrowser.Api
{ {
_config.Configuration.IsStartupWizardCompleted = true; _config.Configuration.IsStartupWizardCompleted = true;
_config.Configuration.EnableLocalizedGuids = true; _config.Configuration.EnableLocalizedGuids = true;
_config.Configuration.MergeMetadataAndImagesByName = true;
_config.Configuration.EnableStandaloneMetadata = true;
_config.Configuration.EnableLibraryMetadataSubFolder = true; _config.Configuration.EnableLibraryMetadataSubFolder = true;
_config.Configuration.EnableCustomPathSubFolders = true; _config.Configuration.EnableCustomPathSubFolders = true;
_config.Configuration.DisableStartupScan = true; _config.Configuration.DisableStartupScan = true;
_config.Configuration.EnableUserViews = true; _config.Configuration.EnableUserViews = true;
_config.Configuration.EnableDateLastRefresh = true; _config.Configuration.EnableDateLastRefresh = true;
_config.Configuration.MergeMetadataAndImagesByName = true;
_config.SaveConfiguration(); _config.SaveConfiguration();
} }

View File

@ -370,6 +370,8 @@ namespace MediaBrowser.Api.UserLibrary
public void Post(ReportPlaybackStopped request) public void Post(ReportPlaybackStopped request)
{ {
Logger.Debug("ReportPlaybackStopped PlaySessionId: {0}", request.PlaySessionId ?? string.Empty);
if (!string.IsNullOrWhiteSpace(request.PlaySessionId)) if (!string.IsNullOrWhiteSpace(request.PlaySessionId))
{ {
ApiEntryPoint.Instance.KillTranscodingJobs(AuthorizationContext.GetAuthorizationInfo(Request).DeviceId, request.PlaySessionId, s => true); ApiEntryPoint.Instance.KillTranscodingJobs(AuthorizationContext.GetAuthorizationInfo(Request).DeviceId, request.PlaySessionId, s => true);

View File

@ -6,7 +6,6 @@ using System.Linq;
using System.Net; using System.Net;
using System.Net.NetworkInformation; using System.Net.NetworkInformation;
using System.Net.Sockets; using System.Net.Sockets;
using System.Threading;
using MoreLinq; using MoreLinq;
namespace MediaBrowser.Common.Implementations.Networking namespace MediaBrowser.Common.Implementations.Networking
@ -14,22 +13,11 @@ namespace MediaBrowser.Common.Implementations.Networking
public abstract class BaseNetworkManager public abstract class BaseNetworkManager
{ {
protected ILogger Logger { get; private set; } protected ILogger Logger { get; private set; }
private Timer _clearCacheTimer; private DateTime _lastRefresh;
protected BaseNetworkManager(ILogger logger) protected BaseNetworkManager(ILogger logger)
{ {
Logger = logger; Logger = logger;
// Can't use network change events due to a crash in Linux
_clearCacheTimer = new Timer(ClearCacheTimerCallback, null, TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1));
}
private void ClearCacheTimerCallback(object state)
{
lock (_localIpAddressSyncLock)
{
_localIpAddresses = null;
}
} }
private volatile List<IPAddress> _localIpAddresses; private volatile List<IPAddress> _localIpAddresses;
@ -41,15 +29,21 @@ namespace MediaBrowser.Common.Implementations.Networking
/// <returns>IPAddress.</returns> /// <returns>IPAddress.</returns>
public IEnumerable<IPAddress> GetLocalIpAddresses() public IEnumerable<IPAddress> GetLocalIpAddresses()
{ {
if (_localIpAddresses == null) const int cacheMinutes = 3;
var forceRefresh = (DateTime.UtcNow - _lastRefresh).TotalMinutes >= cacheMinutes;
if (_localIpAddresses == null || forceRefresh)
{ {
lock (_localIpAddressSyncLock) lock (_localIpAddressSyncLock)
{ {
if (_localIpAddresses == null) forceRefresh = (DateTime.UtcNow - _lastRefresh).TotalMinutes >= cacheMinutes;
if (_localIpAddresses == null || forceRefresh)
{ {
var addresses = GetLocalIpAddressesInternal().ToList(); var addresses = GetLocalIpAddressesInternal().ToList();
_localIpAddresses = addresses; _localIpAddresses = addresses;
_lastRefresh = DateTime.UtcNow;
return addresses; return addresses;
} }

View File

@ -233,7 +233,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
/// <summary> /// <summary>
/// The _triggers /// The _triggers
/// </summary> /// </summary>
private IEnumerable<ITaskTrigger> _triggers; private volatile List<ITaskTrigger> _triggers;
/// <summary> /// <summary>
/// The _triggers sync lock /// The _triggers sync lock
/// </summary> /// </summary>
@ -532,7 +532,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
/// Loads the triggers. /// Loads the triggers.
/// </summary> /// </summary>
/// <returns>IEnumerable{BaseTaskTrigger}.</returns> /// <returns>IEnumerable{BaseTaskTrigger}.</returns>
private IEnumerable<ITaskTrigger> LoadTriggers() private List<ITaskTrigger> LoadTriggers()
{ {
try try
{ {
@ -543,12 +543,12 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
catch (FileNotFoundException) catch (FileNotFoundException)
{ {
// File doesn't exist. No biggie. Return defaults. // File doesn't exist. No biggie. Return defaults.
return ScheduledTask.GetDefaultTriggers(); return ScheduledTask.GetDefaultTriggers().ToList();
} }
catch (DirectoryNotFoundException) catch (DirectoryNotFoundException)
{ {
// File doesn't exist. No biggie. Return defaults. // File doesn't exist. No biggie. Return defaults.
return ScheduledTask.GetDefaultTriggers(); return ScheduledTask.GetDefaultTriggers().ToList();
} }
} }

View File

@ -90,6 +90,7 @@
<Compile Include="Security\IRequiresRegistration.cs" /> <Compile Include="Security\IRequiresRegistration.cs" />
<Compile Include="Security\ISecurityManager.cs" /> <Compile Include="Security\ISecurityManager.cs" />
<Compile Include="Security\PaymentRequiredException.cs" /> <Compile Include="Security\PaymentRequiredException.cs" />
<Compile Include="Threading\PeriodicTimer.cs" />
<Compile Include="Updates\IInstallationManager.cs" /> <Compile Include="Updates\IInstallationManager.cs" />
<Compile Include="Updates\InstallationEventArgs.cs" /> <Compile Include="Updates\InstallationEventArgs.cs" />
<Compile Include="Updates\InstallationFailedEventArgs.cs" /> <Compile Include="Updates\InstallationFailedEventArgs.cs" />

View File

@ -0,0 +1,72 @@
using System;
using System.Threading;
using Microsoft.Win32;
namespace MediaBrowser.Common.Threading
{
public class PeriodicTimer : IDisposable
{
public Action<object> Callback { get; set; }
private Timer _timer;
private readonly object _state;
private readonly object _timerLock = new object();
private readonly TimeSpan _period;
public PeriodicTimer(Action<object> callback, object state, TimeSpan dueTime, TimeSpan period)
{
if (callback == null)
{
throw new ArgumentNullException("callback");
}
Callback = callback;
_period = period;
_state = state;
StartTimer(dueTime);
}
void SystemEvents_PowerModeChanged(object sender, PowerModeChangedEventArgs e)
{
if (e.Mode == PowerModes.Resume)
{
DisposeTimer();
StartTimer(Timeout.InfiniteTimeSpan);
}
}
private void TimerCallback(object state)
{
Callback(state);
}
private void StartTimer(TimeSpan dueTime)
{
lock (_timerLock)
{
_timer = new Timer(TimerCallback, _state, dueTime, _period);
SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged;
}
}
private void DisposeTimer()
{
SystemEvents.PowerModeChanged -= SystemEvents_PowerModeChanged;
lock (_timerLock)
{
if (_timer != null)
{
_timer.Dispose();
_timer = null;
}
}
}
public void Dispose()
{
DisposeTimer();
}
}
}

View File

@ -1,17 +1,21 @@
using MediaBrowser.Controller.Providers; using System;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Users; using MediaBrowser.Model.Users;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Runtime.Serialization; using System.Runtime.Serialization;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Controller.Library;
namespace MediaBrowser.Controller.Entities.Audio namespace MediaBrowser.Controller.Entities.Audio
{ {
/// <summary> /// <summary>
/// Class MusicAlbum /// Class MusicAlbum
/// </summary> /// </summary>
public class MusicAlbum : Folder, IHasAlbumArtist, IHasArtist, IHasMusicGenres, IHasLookupInfo<AlbumInfo> public class MusicAlbum : Folder, IHasAlbumArtist, IHasArtist, IHasMusicGenres, IHasLookupInfo<AlbumInfo>, IMetadataContainer
{ {
public MusicAlbum() public MusicAlbum()
{ {
@ -139,5 +143,58 @@ namespace MediaBrowser.Controller.Entities.Audio
return id; return id;
} }
public async Task RefreshAllMetadata(MetadataRefreshOptions refreshOptions, IProgress<double> progress, CancellationToken cancellationToken)
{
var items = GetRecursiveChildren().ToList();
var songs = items.OfType<Audio>().ToList();
var others = items.Except(songs).ToList();
var totalItems = songs.Count + others.Count;
var numComplete = 0;
var childUpdateType = ItemUpdateType.None;
// Refresh songs
foreach (var item in songs)
{
cancellationToken.ThrowIfCancellationRequested();
var updateType = await item.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false);
childUpdateType = childUpdateType | updateType;
numComplete++;
double percent = numComplete;
percent /= totalItems;
progress.Report(percent * 100);
}
var parentRefreshOptions = refreshOptions;
if (childUpdateType > ItemUpdateType.None)
{
parentRefreshOptions = new MetadataRefreshOptions(refreshOptions);
parentRefreshOptions.MetadataRefreshMode = MetadataRefreshMode.FullRefresh;
}
// Refresh current item
await RefreshMetadata(parentRefreshOptions, cancellationToken).ConfigureAwait(false);
// Refresh all non-songs
foreach (var item in others)
{
cancellationToken.ThrowIfCancellationRequested();
var updateType = await item.RefreshMetadata(parentRefreshOptions, cancellationToken).ConfigureAwait(false);
numComplete++;
double percent = numComplete;
percent /= totalItems;
progress.Report(percent * 100);
}
progress.Report(100);
}
} }
} }

View File

@ -109,7 +109,7 @@ namespace MediaBrowser.Controller.Entities
/// <value>The last activity date.</value> /// <value>The last activity date.</value>
public DateTime? LastActivityDate { get; set; } public DateTime? LastActivityDate { get; set; }
private UserConfiguration _config; private volatile UserConfiguration _config;
private readonly object _configSyncLock = new object(); private readonly object _configSyncLock = new object();
[IgnoreDataMember] [IgnoreDataMember]
public UserConfiguration Configuration public UserConfiguration Configuration
@ -132,7 +132,7 @@ namespace MediaBrowser.Controller.Entities
set { _config = value; } set { _config = value; }
} }
private UserPolicy _policy; private volatile UserPolicy _policy;
private readonly object _policySyncLock = new object(); private readonly object _policySyncLock = new object();
[IgnoreDataMember] [IgnoreDataMember]
public UserPolicy Policy public UserPolicy Policy

View File

@ -265,6 +265,7 @@
<Compile Include="Playlists\IPlaylistManager.cs" /> <Compile Include="Playlists\IPlaylistManager.cs" />
<Compile Include="Playlists\Playlist.cs" /> <Compile Include="Playlists\Playlist.cs" />
<Compile Include="Plugins\ILocalizablePlugin.cs" /> <Compile Include="Plugins\ILocalizablePlugin.cs" />
<Compile Include="Power\IPowerManagement.cs" />
<Compile Include="Providers\AlbumInfo.cs" /> <Compile Include="Providers\AlbumInfo.cs" />
<Compile Include="Providers\ArtistInfo.cs" /> <Compile Include="Providers\ArtistInfo.cs" />
<Compile Include="Providers\BookInfo.cs" /> <Compile Include="Providers\BookInfo.cs" />

View File

@ -0,0 +1,13 @@
using System;
namespace MediaBrowser.Controller.Power
{
public interface IPowerManagement
{
/// <summary>
/// Schedules the wake.
/// </summary>
/// <param name="utcTime">The UTC time.</param>
void ScheduleWake(DateTime utcTime);
}
}

View File

@ -22,14 +22,26 @@ namespace MediaBrowser.Dlna.PlayTo
#region Fields & Properties #region Fields & Properties
private Timer _timer; private Timer _timer;
private Timer _volumeTimer;
public DeviceInfo Properties { get; set; } public DeviceInfo Properties { get; set; }
private int _muteVol; private int _muteVol;
public bool IsMuted { get; set; } public bool IsMuted { get; set; }
public int Volume { get; set; } private int _volume;
public int Volume
{
get
{
RefreshVolumeIfNeeded();
return _volume;
}
set
{
_volume = value;
}
}
public TimeSpan? Duration { get; set; } public TimeSpan? Duration { get; set; }
@ -93,11 +105,6 @@ namespace MediaBrowser.Dlna.PlayTo
return 1000; return 1000;
} }
private int GetVolumeTimerIntervalMs()
{
return 5000;
}
private int GetInactiveTimerIntervalMs() private int GetInactiveTimerIntervalMs()
{ {
return 20000; return 20000;
@ -107,11 +114,37 @@ namespace MediaBrowser.Dlna.PlayTo
{ {
_timer = new Timer(TimerCallback, null, GetPlaybackTimerIntervalMs(), GetInactiveTimerIntervalMs()); _timer = new Timer(TimerCallback, null, GetPlaybackTimerIntervalMs(), GetInactiveTimerIntervalMs());
_volumeTimer = new Timer(VolumeTimerCallback, null, Timeout.Infinite, Timeout.Infinite);
_timerActive = false; _timerActive = false;
} }
private DateTime _lastVolumeRefresh;
private void RefreshVolumeIfNeeded()
{
if (!_timerActive)
{
return;
}
if (DateTime.UtcNow >= _lastVolumeRefresh.AddSeconds(5))
{
_lastVolumeRefresh = DateTime.UtcNow;
RefreshVolume();
}
}
private async void RefreshVolume()
{
try
{
await GetVolume().ConfigureAwait(false);
await GetMute().ConfigureAwait(false);
}
catch (Exception ex)
{
_logger.ErrorException("Error updating device volume info for {0}", ex, Properties.Name);
}
}
private readonly object _timerLock = new object(); private readonly object _timerLock = new object();
private bool _timerActive; private bool _timerActive;
private void RestartTimer() private void RestartTimer()
@ -124,7 +157,6 @@ namespace MediaBrowser.Dlna.PlayTo
{ {
_logger.Debug("RestartTimer"); _logger.Debug("RestartTimer");
_timer.Change(10, GetPlaybackTimerIntervalMs()); _timer.Change(10, GetPlaybackTimerIntervalMs());
_volumeTimer.Change(100, GetVolumeTimerIntervalMs());
} }
_timerActive = true; _timerActive = true;
@ -150,10 +182,6 @@ namespace MediaBrowser.Dlna.PlayTo
{ {
_timer.Change(interval, interval); _timer.Change(interval, interval);
} }
if (_volumeTimer != null)
{
_volumeTimer.Change(Timeout.Infinite, Timeout.Infinite);
}
} }
_timerActive = false; _timerActive = false;
@ -440,19 +468,6 @@ namespace MediaBrowser.Dlna.PlayTo
} }
} }
private async void VolumeTimerCallback(object sender)
{
try
{
await GetVolume().ConfigureAwait(false);
await GetMute().ConfigureAwait(false);
}
catch (Exception ex)
{
_logger.ErrorException("Error updating device volume info for {0}", ex, Properties.Name);
}
}
private async Task GetVolume() private async Task GetVolume()
{ {
var command = RendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetVolume"); var command = RendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetVolume");
@ -1012,7 +1027,6 @@ namespace MediaBrowser.Dlna.PlayTo
_disposed = true; _disposed = true;
DisposeTimer(); DisposeTimer();
DisposeVolumeTimer();
} }
} }
@ -1025,15 +1039,6 @@ namespace MediaBrowser.Dlna.PlayTo
} }
} }
private void DisposeVolumeTimer()
{
if (_volumeTimer != null)
{
_volumeTimer.Dispose();
_volumeTimer = null;
}
}
#endregion #endregion
public override string ToString() public override string ToString()

View File

@ -37,11 +37,28 @@ namespace MediaBrowser.Dlna.PlayTo
private readonly IDeviceDiscovery _deviceDiscovery; private readonly IDeviceDiscovery _deviceDiscovery;
private readonly string _serverAddress; private readonly string _serverAddress;
private readonly string _accessToken; private readonly string _accessToken;
private readonly DateTime _creationTime;
public bool IsSessionActive public bool IsSessionActive
{ {
get get
{ {
var lastDateKnownActivity = new[] { _creationTime, _device.DateLastActivity }.Max();
if (DateTime.UtcNow >= lastDateKnownActivity.AddSeconds(120))
{
try
{
// Session is inactive, mark it for Disposal and don't start the elapsed timer.
_sessionManager.ReportSessionEnded(_session.Id);
}
catch (Exception ex)
{
_logger.ErrorException("Error in ReportSessionEnded", ex);
}
return false;
}
return _device != null; return _device != null;
} }
} }
@ -55,8 +72,6 @@ namespace MediaBrowser.Dlna.PlayTo
get { return IsSessionActive; } get { return IsSessionActive; }
} }
private Timer _updateTimer;
public PlayToController(SessionInfo session, ISessionManager sessionManager, ILibraryManager libraryManager, ILogger logger, IDlnaManager dlnaManager, IUserManager userManager, IImageProcessor imageProcessor, string serverAddress, string accessToken, IDeviceDiscovery deviceDiscovery, IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager) public PlayToController(SessionInfo session, ISessionManager sessionManager, ILibraryManager libraryManager, ILogger logger, IDlnaManager dlnaManager, IUserManager userManager, IImageProcessor imageProcessor, string serverAddress, string accessToken, IDeviceDiscovery deviceDiscovery, IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager)
{ {
_session = session; _session = session;
@ -72,6 +87,7 @@ namespace MediaBrowser.Dlna.PlayTo
_mediaSourceManager = mediaSourceManager; _mediaSourceManager = mediaSourceManager;
_accessToken = accessToken; _accessToken = accessToken;
_logger = logger; _logger = logger;
_creationTime = DateTime.UtcNow;
} }
public void Init(Device device) public void Init(Device device)
@ -84,8 +100,6 @@ namespace MediaBrowser.Dlna.PlayTo
_device.Start(); _device.Start();
_deviceDiscovery.DeviceLeft += _deviceDiscovery_DeviceLeft; _deviceDiscovery.DeviceLeft += _deviceDiscovery_DeviceLeft;
_updateTimer = new Timer(updateTimer_Elapsed, null, 60000, 60000);
} }
void _deviceDiscovery_DeviceLeft(object sender, SsdpMessageEventArgs e) void _deviceDiscovery_DeviceLeft(object sender, SsdpMessageEventArgs e)
@ -117,22 +131,6 @@ namespace MediaBrowser.Dlna.PlayTo
} }
} }
private void updateTimer_Elapsed(object state)
{
if (DateTime.UtcNow >= _device.DateLastActivity.AddSeconds(120))
{
try
{
// Session is inactive, mark it for Disposal and don't start the elapsed timer.
_sessionManager.ReportSessionEnded(_session.Id);
}
catch (Exception ex)
{
_logger.ErrorException("Error in ReportSessionEnded", ex);
}
}
}
async void _device_MediaChanged(object sender, MediaChangedEventArgs e) async void _device_MediaChanged(object sender, MediaChangedEventArgs e)
{ {
try try
@ -634,21 +632,10 @@ namespace MediaBrowser.Dlna.PlayTo
_device.MediaChanged -= _device_MediaChanged; _device.MediaChanged -= _device_MediaChanged;
_deviceDiscovery.DeviceLeft -= _deviceDiscovery_DeviceLeft; _deviceDiscovery.DeviceLeft -= _deviceDiscovery_DeviceLeft;
DisposeUpdateTimer();
_device.Dispose(); _device.Dispose();
} }
} }
private void DisposeUpdateTimer()
{
if (_updateTimer != null)
{
_updateTimer.Dispose();
_updateTimer = null;
}
}
private readonly CultureInfo _usCulture = new CultureInfo("en-US"); private readonly CultureInfo _usCulture = new CultureInfo("en-US");
public Task SendGeneralCommand(GeneralCommand command, CancellationToken cancellationToken) public Task SendGeneralCommand(GeneralCommand command, CancellationToken cancellationToken)

View File

@ -36,7 +36,7 @@ namespace MediaBrowser.Dlna.PlayTo
private readonly IMediaSourceManager _mediaSourceManager; private readonly IMediaSourceManager _mediaSourceManager;
private readonly List<string> _nonRendererUrls = new List<string>(); private readonly List<string> _nonRendererUrls = new List<string>();
private Timer _clearNonRenderersTimer; private DateTime _lastRendererClear;
public PlayToManager(ILogger logger, ISessionManager sessionManager, ILibraryManager libraryManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost, IImageProcessor imageProcessor, IDeviceDiscovery deviceDiscovery, IHttpClient httpClient, IServerConfigurationManager config, IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager) public PlayToManager(ILogger logger, ISessionManager sessionManager, ILibraryManager libraryManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost, IImageProcessor imageProcessor, IDeviceDiscovery deviceDiscovery, IHttpClient httpClient, IServerConfigurationManager config, IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager)
{ {
@ -57,19 +57,9 @@ namespace MediaBrowser.Dlna.PlayTo
public void Start() public void Start()
{ {
_clearNonRenderersTimer = new Timer(OnClearUrlTimerCallback, null, TimeSpan.FromMinutes(10), TimeSpan.FromMinutes(10));
_deviceDiscovery.DeviceDiscovered += _deviceDiscovery_DeviceDiscovered; _deviceDiscovery.DeviceDiscovered += _deviceDiscovery_DeviceDiscovered;
} }
private void OnClearUrlTimerCallback(object state)
{
lock (_nonRendererUrls)
{
_nonRendererUrls.Clear();
}
}
async void _deviceDiscovery_DeviceDiscovered(object sender, SsdpMessageEventArgs e) async void _deviceDiscovery_DeviceDiscovered(object sender, SsdpMessageEventArgs e)
{ {
string usn; string usn;
@ -99,6 +89,12 @@ namespace MediaBrowser.Dlna.PlayTo
lock (_nonRendererUrls) lock (_nonRendererUrls)
{ {
if ((DateTime.UtcNow - _lastRendererClear).TotalMinutes >= 10)
{
_nonRendererUrls.Clear();
_lastRendererClear = DateTime.UtcNow;
}
if (_nonRendererUrls.Contains(location, StringComparer.OrdinalIgnoreCase)) if (_nonRendererUrls.Contains(location, StringComparer.OrdinalIgnoreCase))
{ {
return; return;
@ -181,12 +177,6 @@ namespace MediaBrowser.Dlna.PlayTo
public void Dispose() public void Dispose()
{ {
_deviceDiscovery.DeviceDiscovered -= _deviceDiscovery_DeviceDiscovered; _deviceDiscovery.DeviceDiscovered -= _deviceDiscovery_DeviceDiscovered;
if (_clearNonRenderersTimer != null)
{
_clearNonRenderersTimer.Dispose();
_clearNonRenderersTimer = null;
}
} }
} }
} }

View File

@ -58,7 +58,7 @@ namespace MediaBrowser.Dlna.Profiles
Container = "avi", Container = "avi",
Type = DlnaProfileType.Video, Type = DlnaProfileType.Video,
VideoCodec = "mpeg1video,mpeg2video,mpeg4,h264,vc1", VideoCodec = "mpeg1video,mpeg2video,mpeg4,h264,vc1",
AudioCodec = "ac3,dca,mp2,mp3,pcm" AudioCodec = "ac3,dca,mp2,mp3,pcm,dca"
}, },
new DirectPlayProfile new DirectPlayProfile
@ -66,7 +66,7 @@ namespace MediaBrowser.Dlna.Profiles
Container = "mpeg", Container = "mpeg",
Type = DlnaProfileType.Video, Type = DlnaProfileType.Video,
VideoCodec = "mpeg1video,mpeg2video", VideoCodec = "mpeg1video,mpeg2video",
AudioCodec = "ac3,dca,mp2,mp3,pcm" AudioCodec = "ac3,dca,mp2,mp3,pcm,dca"
}, },
new DirectPlayProfile new DirectPlayProfile
@ -74,7 +74,7 @@ namespace MediaBrowser.Dlna.Profiles
Container = "mkv", Container = "mkv",
Type = DlnaProfileType.Video, Type = DlnaProfileType.Video,
VideoCodec = "mpeg1video,mpeg2video,mpeg4,h264,vc1", VideoCodec = "mpeg1video,mpeg2video,mpeg4,h264,vc1",
AudioCodec = "ac3,dca,aac,mp2,mp3,pcm" AudioCodec = "ac3,dca,aac,mp2,mp3,pcm,dca"
}, },
new DirectPlayProfile new DirectPlayProfile
@ -82,7 +82,7 @@ namespace MediaBrowser.Dlna.Profiles
Container = "ts", Container = "ts",
Type = DlnaProfileType.Video, Type = DlnaProfileType.Video,
VideoCodec = "mpeg1video,mpeg2video,h264,vc1", VideoCodec = "mpeg1video,mpeg2video,h264,vc1",
AudioCodec = "ac3,dca,mp2,mp3,aac" AudioCodec = "ac3,dca,mp2,mp3,aac,dca"
}, },
new DirectPlayProfile new DirectPlayProfile
@ -90,7 +90,7 @@ namespace MediaBrowser.Dlna.Profiles
Container = "mp4,mov", Container = "mp4,mov",
Type = DlnaProfileType.Video, Type = DlnaProfileType.Video,
VideoCodec = "h264,mpeg4", VideoCodec = "h264,mpeg4",
AudioCodec = "ac3,aac,mp2,mp3" AudioCodec = "ac3,aac,mp2,mp3,dca"
}, },
new DirectPlayProfile new DirectPlayProfile

View File

@ -37,11 +37,11 @@
<IgnoreTranscodeByteRangeRequests>true</IgnoreTranscodeByteRangeRequests> <IgnoreTranscodeByteRangeRequests>true</IgnoreTranscodeByteRangeRequests>
<XmlRootAttributes /> <XmlRootAttributes />
<DirectPlayProfiles> <DirectPlayProfiles>
<DirectPlayProfile container="avi" audioCodec="ac3,dca,mp2,mp3,pcm" videoCodec="mpeg1video,mpeg2video,mpeg4,h264,vc1" type="Video" /> <DirectPlayProfile container="avi" audioCodec="ac3,dca,mp2,mp3,pcm,dca" videoCodec="mpeg1video,mpeg2video,mpeg4,h264,vc1" type="Video" />
<DirectPlayProfile container="mpeg" audioCodec="ac3,dca,mp2,mp3,pcm" videoCodec="mpeg1video,mpeg2video" type="Video" /> <DirectPlayProfile container="mpeg" audioCodec="ac3,dca,mp2,mp3,pcm,dca" videoCodec="mpeg1video,mpeg2video" type="Video" />
<DirectPlayProfile container="mkv" audioCodec="ac3,dca,aac,mp2,mp3,pcm" videoCodec="mpeg1video,mpeg2video,mpeg4,h264,vc1" type="Video" /> <DirectPlayProfile container="mkv" audioCodec="ac3,dca,aac,mp2,mp3,pcm,dca" videoCodec="mpeg1video,mpeg2video,mpeg4,h264,vc1" type="Video" />
<DirectPlayProfile container="ts" audioCodec="ac3,dca,mp2,mp3,aac" videoCodec="mpeg1video,mpeg2video,h264,vc1" type="Video" /> <DirectPlayProfile container="ts" audioCodec="ac3,dca,mp2,mp3,aac,dca" videoCodec="mpeg1video,mpeg2video,h264,vc1" type="Video" />
<DirectPlayProfile container="mp4,mov" audioCodec="ac3,aac,mp2,mp3" videoCodec="h264,mpeg4" type="Video" /> <DirectPlayProfile container="mp4,mov" audioCodec="ac3,aac,mp2,mp3,dca" videoCodec="h264,mpeg4" type="Video" />
<DirectPlayProfile container="asf" audioCodec="wmav2,wmapro" videoCodec="vc1" type="Video" /> <DirectPlayProfile container="asf" audioCodec="wmav2,wmapro" videoCodec="vc1" type="Video" />
<DirectPlayProfile container="asf" audioCodec="mp2,ac3" videoCodec="mpeg2video" type="Video" /> <DirectPlayProfile container="asf" audioCodec="mp2,ac3" videoCodec="mpeg2video" type="Video" />
<DirectPlayProfile container="mp3" audioCodec="mp2,mp3" type="Audio" /> <DirectPlayProfile container="mp3" audioCodec="mp2,mp3" type="Audio" />

View File

@ -33,12 +33,8 @@ namespace MediaBrowser.Dlna.Ssdp
private readonly IPAddress _ssdpIp = IPAddress.Parse(SSDPAddr); private readonly IPAddress _ssdpIp = IPAddress.Parse(SSDPAddr);
private readonly IPEndPoint _ssdpEndp = new IPEndPoint(IPAddress.Parse(SSDPAddr), SSDPPort); private readonly IPEndPoint _ssdpEndp = new IPEndPoint(IPAddress.Parse(SSDPAddr), SSDPPort);
private Timer _queueTimer;
private Timer _notificationTimer; private Timer _notificationTimer;
private readonly AutoResetEvent _datagramPosted = new AutoResetEvent(false);
private readonly ConcurrentQueue<Datagram> _messageQueue = new ConcurrentQueue<Datagram>();
private bool _isDisposed; private bool _isDisposed;
private readonly ConcurrentDictionary<Guid, List<UpnpDevice>> _devices = new ConcurrentDictionary<Guid, List<UpnpDevice>>(); private readonly ConcurrentDictionary<Guid, List<UpnpDevice>> _devices = new ConcurrentDictionary<Guid, List<UpnpDevice>>();
@ -121,9 +117,13 @@ namespace MediaBrowser.Dlna.Ssdp
public void Start() public void Start()
{ {
RestartSocketListener(); DisposeSocket();
StopAliveNotifier();
RestartSocketListener();
ReloadAliveNotifier(); ReloadAliveNotifier();
SystemEvents.PowerModeChanged -= SystemEvents_PowerModeChanged;
SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged; SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged;
} }
@ -131,7 +131,7 @@ namespace MediaBrowser.Dlna.Ssdp
{ {
if (e.Mode == PowerModes.Resume) if (e.Mode == PowerModes.Resume)
{ {
NotifyAll(); Start();
} }
} }
@ -154,7 +154,7 @@ namespace MediaBrowser.Dlna.Ssdp
SendDatagram("M-SEARCH * HTTP/1.1", values, _ssdpEndp, localIp, true, 2); SendDatagram("M-SEARCH * HTTP/1.1", values, _ssdpEndp, localIp, true, 2);
} }
public void SendDatagram(string header, public async void SendDatagram(string header,
Dictionary<string, string> values, Dictionary<string, string> values,
EndPoint endpoint, EndPoint endpoint,
EndPoint localAddress, EndPoint localAddress,
@ -162,29 +162,19 @@ namespace MediaBrowser.Dlna.Ssdp
int sendCount) int sendCount)
{ {
var msg = new SsdpMessageBuilder().BuildMessage(header, values); var msg = new SsdpMessageBuilder().BuildMessage(header, values);
var queued = false;
var enableDebugLogging = _config.GetDlnaConfiguration().EnableDebugLogging; var enableDebugLogging = _config.GetDlnaConfiguration().EnableDebugLogging;
for (var i = 0; i < sendCount; i++) for (var i = 0; i < sendCount; i++)
{ {
if (i > 0)
{
await Task.Delay(500).ConfigureAwait(false);
}
var dgram = new Datagram(endpoint, localAddress, _logger, msg, isBroadcast, enableDebugLogging); var dgram = new Datagram(endpoint, localAddress, _logger, msg, isBroadcast, enableDebugLogging);
if (_messageQueue.Count == 0)
{
dgram.Send(); dgram.Send();
} }
else
{
_messageQueue.Enqueue(dgram);
queued = true;
}
}
if (queued)
{
StartQueueTimer();
}
} }
/// <summary> /// <summary>
@ -254,47 +244,10 @@ namespace MediaBrowser.Dlna.Ssdp
} }
} }
private readonly object _queueTimerSyncLock = new object();
private void StartQueueTimer()
{
lock (_queueTimerSyncLock)
{
if (_queueTimer == null)
{
_queueTimer = new Timer(QueueTimerCallback, null, 500, Timeout.Infinite);
}
else
{
_queueTimer.Change(500, Timeout.Infinite);
}
}
}
private void QueueTimerCallback(object state)
{
Datagram msg;
while (_messageQueue.TryDequeue(out msg))
{
msg.Send();
}
_datagramPosted.Set();
if (_messageQueue.Count > 0)
{
StartQueueTimer();
}
else
{
DisposeQueueTimer();
}
}
private void RestartSocketListener() private void RestartSocketListener()
{ {
if (_isDisposed) if (_isDisposed)
{ {
StopSocketRetryTimer();
return; return;
} }
@ -304,8 +257,6 @@ namespace MediaBrowser.Dlna.Ssdp
_logger.Info("MultiCast socket created"); _logger.Info("MultiCast socket created");
StopSocketRetryTimer();
Receive(); Receive();
} }
catch (Exception ex) catch (Exception ex)
@ -315,31 +266,6 @@ namespace MediaBrowser.Dlna.Ssdp
} }
} }
private Timer _socketRetryTimer;
private readonly object _socketRetryLock = new object();
private void StartSocketRetryTimer()
{
lock (_socketRetryLock)
{
if (_socketRetryTimer == null)
{
_socketRetryTimer = new Timer(s => RestartSocketListener(), null, TimeSpan.FromSeconds(30), TimeSpan.FromSeconds(30));
}
}
}
private void StopSocketRetryTimer()
{
lock (_socketRetryLock)
{
if (_socketRetryTimer != null)
{
_socketRetryTimer.Dispose();
_socketRetryTimer = null;
}
}
}
private void Receive() private void Receive()
{ {
try try
@ -448,16 +374,9 @@ namespace MediaBrowser.Dlna.Ssdp
SystemEvents.PowerModeChanged -= SystemEvents_PowerModeChanged; SystemEvents.PowerModeChanged -= SystemEvents_PowerModeChanged;
_isDisposed = true; _isDisposed = true;
while (_messageQueue.Count != 0)
{
_datagramPosted.WaitOne();
}
DisposeSocket(); DisposeSocket();
DisposeQueueTimer(); StopAliveNotifier();
DisposeNotificationTimer();
_datagramPosted.Dispose();
} }
private void DisposeSocket() private void DisposeSocket()
@ -470,18 +389,6 @@ namespace MediaBrowser.Dlna.Ssdp
} }
} }
private void DisposeQueueTimer()
{
lock (_queueTimerSyncLock)
{
if (_queueTimer != null)
{
_queueTimer.Dispose();
_queueTimer = null;
}
}
}
private Socket CreateMulticastSocket() private Socket CreateMulticastSocket()
{ {
var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
@ -534,14 +441,7 @@ namespace MediaBrowser.Dlna.Ssdp
public void RegisterNotification(Guid uuid, Uri descriptionUri, IPAddress address, IEnumerable<string> services) public void RegisterNotification(Guid uuid, Uri descriptionUri, IPAddress address, IEnumerable<string> services)
{ {
List<UpnpDevice> list; var list = _devices.GetOrAdd(uuid, new List<UpnpDevice>());
lock (_devices)
{
if (!_devices.TryGetValue(uuid, out list))
{
_devices.TryAdd(uuid, list = new List<UpnpDevice>());
}
}
list.AddRange(services.Select(i => new UpnpDevice(uuid, i, descriptionUri, address))); list.AddRange(services.Select(i => new UpnpDevice(uuid, i, descriptionUri, address)));
@ -572,7 +472,7 @@ namespace MediaBrowser.Dlna.Ssdp
if (!config.BlastAliveMessages) if (!config.BlastAliveMessages)
{ {
DisposeNotificationTimer(); StopAliveNotifier();
return; return;
} }
@ -599,7 +499,7 @@ namespace MediaBrowser.Dlna.Ssdp
} }
} }
private void DisposeNotificationTimer() private void StopAliveNotifier()
{ {
lock (_notificationTimerSyncLock) lock (_notificationTimerSyncLock)
{ {

View File

@ -292,16 +292,25 @@ namespace MediaBrowser.MediaEncoding.Encoder
return false; return false;
} }
// If the video codec is not some form of mpeg, then take a shortcut and limit this to containers that are likely to have interlaced content
if ((videoStream.Codec ?? string.Empty).IndexOf("mpeg", StringComparison.OrdinalIgnoreCase) == -1)
{
var formats = (video.Container ?? string.Empty).Split(',').ToList(); var formats = (video.Container ?? string.Empty).Split(',').ToList();
var enableInterlacedDection = formats.Contains("vob", StringComparer.OrdinalIgnoreCase) &&
formats.Contains("m2ts", StringComparer.OrdinalIgnoreCase) &&
formats.Contains("ts", StringComparer.OrdinalIgnoreCase) &&
formats.Contains("mpegts", StringComparer.OrdinalIgnoreCase) &&
formats.Contains("wtv", StringComparer.OrdinalIgnoreCase);
if (!formats.Contains("vob", StringComparer.OrdinalIgnoreCase) && // If it's mpeg based, assume true
!formats.Contains("m2ts", StringComparer.OrdinalIgnoreCase) && if ((videoStream.Codec ?? string.Empty).IndexOf("mpeg", StringComparison.OrdinalIgnoreCase) != -1)
!formats.Contains("ts", StringComparer.OrdinalIgnoreCase) && {
!formats.Contains("mpegts", StringComparer.OrdinalIgnoreCase) && if (enableInterlacedDection)
!formats.Contains("wtv", StringComparer.OrdinalIgnoreCase)) {
return true;
}
}
else
{
// If the video codec is not some form of mpeg, then take a shortcut and limit this to containers that are likely to have interlaced content
if (!enableInterlacedDection)
{ {
return false; return false;
} }

View File

@ -164,7 +164,7 @@ namespace MediaBrowser.Model.Configuration
/// different directories and files. /// different directories and files.
/// </summary> /// </summary>
/// <value>The file watcher delay.</value> /// <value>The file watcher delay.</value>
public int RealtimeLibraryMonitorDelay { get; set; } public int LibraryMonitorDelay { get; set; }
/// <summary> /// <summary>
/// Gets or sets a value indicating whether [enable dashboard response caching]. /// Gets or sets a value indicating whether [enable dashboard response caching].
@ -181,7 +181,6 @@ namespace MediaBrowser.Model.Configuration
public string DashboardSourcePath { get; set; } public string DashboardSourcePath { get; set; }
public bool MergeMetadataAndImagesByName { get; set; } public bool MergeMetadataAndImagesByName { get; set; }
public bool EnableStandaloneMetadata { get; set; }
/// <summary> /// <summary>
/// Gets or sets the image saving convention. /// Gets or sets the image saving convention.
@ -256,7 +255,7 @@ namespace MediaBrowser.Model.Configuration
MinResumeDurationSeconds = 300; MinResumeDurationSeconds = 300;
EnableLibraryMonitor = AutoOnOff.Auto; EnableLibraryMonitor = AutoOnOff.Auto;
RealtimeLibraryMonitorDelay = 40; LibraryMonitorDelay = 60;
EnableInternetProviders = true; EnableInternetProviders = true;
FindInternetTrailers = true; FindInternetTrailers = true;

View File

@ -48,6 +48,7 @@ namespace MediaBrowser.Model.Configuration
public string[] PlainFolderViews { get; set; } public string[] PlainFolderViews { get; set; }
public bool HidePlayedInLatest { get; set; } public bool HidePlayedInLatest { get; set; }
public bool DisplayChannelsInline { get; set; }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="UserConfiguration" /> class. /// Initializes a new instance of the <see cref="UserConfiguration" /> class.

View File

@ -213,7 +213,7 @@ namespace MediaBrowser.Providers.MediaInfo
} }
var chapters = mediaInfo.Chapters ?? new List<ChapterInfo>(); var chapters = mediaInfo.Chapters ?? new List<ChapterInfo>();
if (video.VideoType == VideoType.BluRay || (video.IsoType.HasValue && video.IsoType.Value == IsoType.BluRay)) if (blurayInfo != null)
{ {
FetchBdInfo(video, chapters, mediaStreams, blurayInfo); FetchBdInfo(video, chapters, mediaStreams, blurayInfo);
} }
@ -359,9 +359,17 @@ namespace MediaBrowser.Providers.MediaInfo
/// <param name="path">The path.</param> /// <param name="path">The path.</param>
/// <returns>VideoStream.</returns> /// <returns>VideoStream.</returns>
private BlurayDiscInfo GetBDInfo(string path) private BlurayDiscInfo GetBDInfo(string path)
{
try
{ {
return _blurayExaminer.GetDiscInfo(path); return _blurayExaminer.GetDiscInfo(path);
} }
catch (Exception ex)
{
_logger.ErrorException("Error getting BDInfo", ex);
return null;
}
}
private void FetchEmbeddedInfo(Video video, Model.MediaInfo.MediaInfo data, MetadataRefreshOptions options) private void FetchEmbeddedInfo(Video video, Model.MediaInfo.MediaInfo data, MetadataRefreshOptions options)
{ {
@ -628,7 +636,7 @@ namespace MediaBrowser.Providers.MediaInfo
FetchFromDvdLib(item, mount); FetchFromDvdLib(item, mount);
} }
if (item.VideoType == VideoType.BluRay || (item.IsoType.HasValue && item.IsoType.Value == IsoType.BluRay)) if (blurayDiscInfo != null)
{ {
item.PlayableStreamFileNames = blurayDiscInfo.Files.ToList(); item.PlayableStreamFileNames = blurayDiscInfo.Files.ToList();
} }

View File

@ -38,7 +38,7 @@ namespace MediaBrowser.Providers.People
private int _requestCount; private int _requestCount;
private readonly object _requestCountLock = new object(); private readonly object _requestCountLock = new object();
private Timer _requestCountReset; private DateTime _lastRequestCountReset;
public MovieDbPersonProvider(IFileSystem fileSystem, IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IHttpClient httpClient, ILogger logger) public MovieDbPersonProvider(IFileSystem fileSystem, IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IHttpClient httpClient, ILogger logger)
{ {
@ -48,16 +48,6 @@ namespace MediaBrowser.Providers.People
_httpClient = httpClient; _httpClient = httpClient;
_logger = logger; _logger = logger;
Current = this; Current = this;
_requestCountReset = new Timer(OnRequestThrottleTimerFired, null, TimeSpan.FromHours(1), TimeSpan.FromHours(1));
}
private void OnRequestThrottleTimerFired(object state)
{
lock (_requestCountLock)
{
_requestCount = 0;
}
} }
public string Name public string Name
@ -101,6 +91,12 @@ namespace MediaBrowser.Providers.People
{ {
lock (_requestCountLock) lock (_requestCountLock)
{ {
if ((DateTime.UtcNow - _lastRequestCountReset).TotalHours >= 1)
{
_requestCount = 0;
_lastRequestCountReset = DateTime.UtcNow;
}
var requestCount = _requestCount; var requestCount = _requestCount;
if (requestCount >= 5) if (requestCount >= 5)

View File

@ -238,7 +238,7 @@ namespace MediaBrowser.Providers.TV
throw new ArgumentNullException("seriesId"); throw new ArgumentNullException("seriesId");
} }
var url = string.Format(SeriesGetZip, TVUtils.TvdbApiKey, seriesId, preferredMetadataLanguage); var url = string.Format(SeriesGetZip, TVUtils.TvdbApiKey, seriesId, NormalizeLanguage(preferredMetadataLanguage));
using (var zipStream = await _httpClient.Get(new HttpRequestOptions using (var zipStream = await _httpClient.Get(new HttpRequestOptions
{ {
@ -268,7 +268,7 @@ namespace MediaBrowser.Providers.TV
await SanitizeXmlFile(file).ConfigureAwait(false); await SanitizeXmlFile(file).ConfigureAwait(false);
} }
var downloadLangaugeXmlFile = Path.Combine(seriesDataPath, preferredMetadataLanguage + ".xml"); var downloadLangaugeXmlFile = Path.Combine(seriesDataPath, NormalizeLanguage(preferredMetadataLanguage) + ".xml");
var saveAsLanguageXmlFile = Path.Combine(seriesDataPath, saveAsMetadataLanguage + ".xml"); var saveAsLanguageXmlFile = Path.Combine(seriesDataPath, saveAsMetadataLanguage + ".xml");
if (!string.Equals(downloadLangaugeXmlFile, saveAsLanguageXmlFile, StringComparison.OrdinalIgnoreCase)) if (!string.Equals(downloadLangaugeXmlFile, saveAsLanguageXmlFile, StringComparison.OrdinalIgnoreCase))

View File

@ -29,7 +29,7 @@ using CommonIO;
namespace MediaBrowser.Server.Implementations.Channels namespace MediaBrowser.Server.Implementations.Channels
{ {
public class ChannelManager : IChannelManager, IDisposable public class ChannelManager : IChannelManager
{ {
private IChannel[] _channels; private IChannel[] _channels;
@ -47,11 +47,6 @@ namespace MediaBrowser.Server.Implementations.Channels
private readonly ILocalizationManager _localization; private readonly ILocalizationManager _localization;
private readonly ConcurrentDictionary<Guid, bool> _refreshedItems = new ConcurrentDictionary<Guid, bool>(); private readonly ConcurrentDictionary<Guid, bool> _refreshedItems = new ConcurrentDictionary<Guid, bool>();
private readonly ConcurrentDictionary<string, int> _downloadCounts = new ConcurrentDictionary<string, int>();
private Timer _refreshTimer;
private Timer _clearDownloadCountsTimer;
public ChannelManager(IUserManager userManager, IDtoService dtoService, ILibraryManager libraryManager, ILogger logger, IServerConfigurationManager config, IFileSystem fileSystem, IUserDataManager userDataManager, IJsonSerializer jsonSerializer, ILocalizationManager localization, IHttpClient httpClient, IProviderManager providerManager) public ChannelManager(IUserManager userManager, IDtoService dtoService, ILibraryManager libraryManager, ILogger logger, IServerConfigurationManager config, IFileSystem fileSystem, IUserDataManager userDataManager, IJsonSerializer jsonSerializer, ILocalizationManager localization, IHttpClient httpClient, IProviderManager providerManager)
{ {
_userManager = userManager; _userManager = userManager;
@ -65,9 +60,6 @@ namespace MediaBrowser.Server.Implementations.Channels
_localization = localization; _localization = localization;
_httpClient = httpClient; _httpClient = httpClient;
_providerManager = providerManager; _providerManager = providerManager;
_refreshTimer = new Timer(s => _refreshedItems.Clear(), null, TimeSpan.FromHours(3), TimeSpan.FromHours(3));
_clearDownloadCountsTimer = new Timer(s => _downloadCounts.Clear(), null, TimeSpan.FromHours(24), TimeSpan.FromHours(24));
} }
private TimeSpan CacheLength private TimeSpan CacheLength
@ -206,6 +198,8 @@ namespace MediaBrowser.Server.Implementations.Channels
public async Task RefreshChannels(IProgress<double> progress, CancellationToken cancellationToken) public async Task RefreshChannels(IProgress<double> progress, CancellationToken cancellationToken)
{ {
_refreshedItems.Clear();
var allChannelsList = GetAllChannels().ToList(); var allChannelsList = GetAllChannels().ToList();
var numComplete = 0; var numComplete = 0;
@ -1471,12 +1465,6 @@ namespace MediaBrowser.Server.Implementations.Channels
var limit = features.DailyDownloadLimit; var limit = features.DailyDownloadLimit;
if (!ValidateDownloadLimit(host, limit))
{
_logger.Error(string.Format("Download limit has been reached for {0}", channel.Name));
throw new ChannelDownloadException(string.Format("Download limit has been reached for {0}", channel.Name));
}
foreach (var header in source.RequiredHttpHeaders) foreach (var header in source.RequiredHttpHeaders)
{ {
options.RequestHeaders[header.Key] = header.Value; options.RequestHeaders[header.Key] = header.Value;
@ -1495,8 +1483,6 @@ namespace MediaBrowser.Server.Implementations.Channels
}; };
} }
IncrementDownloadCount(host, limit);
if (string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase) && response.ContentType.StartsWith("video/", StringComparison.OrdinalIgnoreCase)) if (string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase) && response.ContentType.StartsWith("video/", StringComparison.OrdinalIgnoreCase))
{ {
var extension = response.ContentType.Split('/') var extension = response.ContentType.Split('/')
@ -1531,46 +1517,5 @@ namespace MediaBrowser.Server.Implementations.Channels
} }
} }
private void IncrementDownloadCount(string key, int? limit)
{
if (!limit.HasValue)
{
return;
}
int current;
_downloadCounts.TryGetValue(key, out current);
current++;
_downloadCounts.AddOrUpdate(key, current, (k, v) => current);
}
private bool ValidateDownloadLimit(string key, int? limit)
{
if (!limit.HasValue)
{
return true;
}
int current;
_downloadCounts.TryGetValue(key, out current);
return current < limit.Value;
}
public void Dispose()
{
if (_clearDownloadCountsTimer != null)
{
_clearDownloadCountsTimer.Dispose();
_clearDownloadCountsTimer = null;
}
if (_refreshTimer != null)
{
_refreshTimer.Dispose();
_refreshTimer = null;
}
}
} }
} }

View File

@ -121,22 +121,14 @@ namespace MediaBrowser.Server.Implementations.Configuration
((ServerApplicationPaths)ApplicationPaths).InternalMetadataPath = metadataPath; ((ServerApplicationPaths)ApplicationPaths).InternalMetadataPath = metadataPath;
if (Configuration.MergeMetadataAndImagesByName)
{
((ServerApplicationPaths)ApplicationPaths).ItemsByNamePath = ((ServerApplicationPaths)ApplicationPaths).InternalMetadataPath; ((ServerApplicationPaths)ApplicationPaths).ItemsByNamePath = ((ServerApplicationPaths)ApplicationPaths).InternalMetadataPath;
} }
}
private string GetInternalMetadataPath() private string GetInternalMetadataPath()
{
if (Configuration.EnableStandaloneMetadata)
{ {
return Path.Combine(ApplicationPaths.ProgramDataPath, "metadata"); return Path.Combine(ApplicationPaths.ProgramDataPath, "metadata");
} }
return null;
}
/// <summary> /// <summary>
/// Updates the transcoding temporary path. /// Updates the transcoding temporary path.
/// </summary> /// </summary>

View File

@ -13,12 +13,13 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using CommonIO; using CommonIO;
using MediaBrowser.Common.IO; using MediaBrowser.Common.IO;
using MediaBrowser.Common.Threading;
namespace MediaBrowser.Server.Implementations.Connect namespace MediaBrowser.Server.Implementations.Connect
{ {
public class ConnectEntryPoint : IServerEntryPoint public class ConnectEntryPoint : IServerEntryPoint
{ {
private Timer _timer; private PeriodicTimer _timer;
private readonly IHttpClient _httpClient; private readonly IHttpClient _httpClient;
private readonly IApplicationPaths _appPaths; private readonly IApplicationPaths _appPaths;
private readonly ILogger _logger; private readonly ILogger _logger;
@ -43,7 +44,7 @@ namespace MediaBrowser.Server.Implementations.Connect
{ {
Task.Run(() => LoadCachedAddress()); Task.Run(() => LoadCachedAddress());
_timer = new Timer(TimerCallback, null, TimeSpan.FromSeconds(5), TimeSpan.FromHours(3)); _timer = new PeriodicTimer(TimerCallback, null, TimeSpan.FromSeconds(5), TimeSpan.FromHours(3));
} }
private readonly string[] _ipLookups = { "http://bot.whatismyipaddress.com", "https://connect.emby.media/service/ip" }; private readonly string[] _ipLookups = { "http://bot.whatismyipaddress.com", "https://connect.emby.media/service/ip" };

View File

@ -25,7 +25,7 @@ namespace MediaBrowser.Server.Implementations.Devices
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
private ConcurrentBag<DeviceInfo> _devices; private List<DeviceInfo> _devices;
public DeviceRepository(IApplicationPaths appPaths, IJsonSerializer json, ILogger logger, IFileSystem fileSystem) public DeviceRepository(IApplicationPaths appPaths, IJsonSerializer json, ILogger logger, IFileSystem fileSystem)
{ {
@ -92,19 +92,16 @@ namespace MediaBrowser.Server.Implementations.Devices
} }
public IEnumerable<DeviceInfo> GetDevices() public IEnumerable<DeviceInfo> GetDevices()
{
if (_devices == null)
{ {
lock (_syncLock) lock (_syncLock)
{ {
if (_devices == null) if (_devices == null)
{ {
_devices = new ConcurrentBag<DeviceInfo>(LoadDevices()); _devices = LoadDevices().ToList();
}
}
} }
return _devices.ToList(); return _devices.ToList();
} }
}
private IEnumerable<DeviceInfo> LoadDevices() private IEnumerable<DeviceInfo> LoadDevices()
{ {

View File

@ -11,6 +11,7 @@ using System.IO;
using System.Net; using System.Net;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using MediaBrowser.Common.Threading;
namespace MediaBrowser.Server.Implementations.EntryPoints namespace MediaBrowser.Server.Implementations.EntryPoints
{ {
@ -21,7 +22,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
private readonly IServerConfigurationManager _config; private readonly IServerConfigurationManager _config;
private readonly ISsdpHandler _ssdp; private readonly ISsdpHandler _ssdp;
private Timer _timer; private PeriodicTimer _timer;
private bool _isStarted; private bool _isStarted;
public ExternalPortForwarding(ILogManager logmanager, IServerApplicationHost appHost, IServerConfigurationManager config, ISsdpHandler ssdp) public ExternalPortForwarding(ILogManager logmanager, IServerApplicationHost appHost, IServerConfigurationManager config, ISsdpHandler ssdp)
@ -95,7 +96,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
NatUtility.UnhandledException += NatUtility_UnhandledException; NatUtility.UnhandledException += NatUtility_UnhandledException;
NatUtility.StartDiscovery(); NatUtility.StartDiscovery();
_timer = new Timer(s => _createdRules = new List<string>(), null, TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(5)); _timer = new PeriodicTimer(s => _createdRules = new List<string>(), null, TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(5));
_ssdp.MessageReceived += _ssdp_MessageReceived; _ssdp.MessageReceived += _ssdp_MessageReceived;

View File

@ -4,6 +4,7 @@ using MediaBrowser.Model.Logging;
using System; using System;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Common.Threading;
namespace MediaBrowser.Server.Implementations.EntryPoints namespace MediaBrowser.Server.Implementations.EntryPoints
{ {
@ -22,7 +23,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
/// </summary> /// </summary>
private readonly ILogger _logger; private readonly ILogger _logger;
private Timer _timer; private PeriodicTimer _timer;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="LoadRegistrations" /> class. /// Initializes a new instance of the <see cref="LoadRegistrations" /> class.
@ -41,7 +42,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
/// </summary> /// </summary>
public void Run() public void Run()
{ {
_timer = new Timer(s => LoadAllRegistrations(), null, TimeSpan.FromMilliseconds(100), TimeSpan.FromHours(12)); _timer = new PeriodicTimer(s => LoadAllRegistrations(), null, TimeSpan.FromMilliseconds(100), TimeSpan.FromHours(12));
} }
private async Task LoadAllRegistrations() private async Task LoadAllRegistrations()

View File

@ -9,6 +9,7 @@ using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Server.Implementations.EntryPoints namespace MediaBrowser.Server.Implementations.EntryPoints
{ {
@ -23,7 +24,6 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
private readonly ISessionManager _sessionManager; private readonly ISessionManager _sessionManager;
private readonly IUserManager _userManager; private readonly IUserManager _userManager;
private Timer _timer;
private readonly TimeSpan _frequency = TimeSpan.FromHours(24); private readonly TimeSpan _frequency = TimeSpan.FromHours(24);
private readonly ConcurrentDictionary<Guid, ClientInfo> _apps = new ConcurrentDictionary<Guid, ClientInfo>(); private readonly ConcurrentDictionary<Guid, ClientInfo> _apps = new ConcurrentDictionary<Guid, ClientInfo>();
@ -95,16 +95,16 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
return info; return info;
} }
public void Run() public async void Run()
{ {
_timer = new Timer(OnTimerFired, null, TimeSpan.FromMilliseconds(5000), _frequency); await Task.Delay(5000).ConfigureAwait(false);
OnTimerFired();
} }
/// <summary> /// <summary>
/// Called when [timer fired]. /// Called when [timer fired].
/// </summary> /// </summary>
/// <param name="state">The state.</param> private async void OnTimerFired()
private async void OnTimerFired(object state)
{ {
try try
{ {
@ -121,12 +121,6 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
public void Dispose() public void Dispose()
{ {
_sessionManager.SessionStarted -= _sessionManager_SessionStarted; _sessionManager.SessionStarted -= _sessionManager_SessionStarted;
if (_timer != null)
{
_timer.Dispose();
_timer = null;
}
} }
} }
} }

View File

@ -462,7 +462,9 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
/// <param name="seasonNumber">The season number.</param> /// <param name="seasonNumber">The season number.</param>
/// <param name="episodeNumber">The episode number.</param> /// <param name="episodeNumber">The episode number.</param>
/// <param name="endingEpisodeNumber">The ending episode number.</param> /// <param name="endingEpisodeNumber">The ending episode number.</param>
/// <param name="premiereDate">The premiere date.</param>
/// <param name="options">The options.</param> /// <param name="options">The options.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>System.String.</returns> /// <returns>System.String.</returns>
private async Task<string> GetNewPath(string sourcePath, private async Task<string> GetNewPath(string sourcePath,
Series series, Series series,
@ -492,19 +494,22 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
var episode = searchResults.FirstOrDefault(); var episode = searchResults.FirstOrDefault();
string episodeName = string.Empty;
if (episode == null) if (episode == null)
{ {
var msg = string.Format("No provider metadata found for {0} season {1} episode {2}", series.Name, seasonNumber, episodeNumber); var msg = string.Format("No provider metadata found for {0} season {1} episode {2}", series.Name, seasonNumber, episodeNumber);
_logger.Warn(msg); _logger.Warn(msg);
//throw new Exception(msg); return null;
}
else
{
episodeName = episode.Name;
} }
var episodeName = episode.Name;
//if (string.IsNullOrWhiteSpace(episodeName))
//{
// var msg = string.Format("No provider metadata found for {0} season {1} episode {2}", series.Name, seasonNumber, episodeNumber);
// _logger.Warn(msg);
// return null;
//}
seasonNumber = seasonNumber ?? episode.ParentIndexNumber; seasonNumber = seasonNumber ?? episode.ParentIndexNumber;
episodeNumber = episodeNumber ?? episode.IndexNumber; episodeNumber = episodeNumber ?? episode.IndexNumber;
@ -579,7 +584,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
{ {
seriesName = _fileSystem.GetValidFilename(seriesName).Trim(); seriesName = _fileSystem.GetValidFilename(seriesName).Trim();
if (string.IsNullOrEmpty(episodeTitle)) if (string.IsNullOrWhiteSpace(episodeTitle))
{ {
episodeTitle = string.Empty; episodeTitle = string.Empty;
} }

View File

@ -256,6 +256,25 @@ namespace MediaBrowser.Server.Implementations.HttpServer
} }
} }
private readonly Dictionary<string, int> _skipLogExtensions = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase)
{
{".js", 0},
{".css", 0},
{".woff", 0},
{".woff2", 0},
{".ttf", 0},
{".html", 0}
};
private bool EnableLogging(string url)
{
var parts = url.Split(new[] { '?' }, 2);
var extension = Path.GetExtension(parts[0]);
return string.IsNullOrWhiteSpace(extension) || !_skipLogExtensions.ContainsKey(extension);
}
/// <summary> /// <summary>
/// Overridable method that can be used to implement a custom hnandler /// Overridable method that can be used to implement a custom hnandler
/// </summary> /// </summary>
@ -271,6 +290,14 @@ namespace MediaBrowser.Server.Implementations.HttpServer
var operationName = httpReq.OperationName; var operationName = httpReq.OperationName;
var localPath = url.LocalPath; var localPath = url.LocalPath;
var urlString = url.OriginalString;
var enableLog = EnableLogging(urlString);
if (enableLog)
{
LoggerUtils.LogRequest(_logger, urlString, httpReq.HttpMethod, httpReq.UserAgent);
}
if (string.Equals(localPath, "/mediabrowser/", StringComparison.OrdinalIgnoreCase) || if (string.Equals(localPath, "/mediabrowser/", StringComparison.OrdinalIgnoreCase) ||
string.Equals(localPath, "/emby/", StringComparison.OrdinalIgnoreCase)) string.Equals(localPath, "/emby/", StringComparison.OrdinalIgnoreCase))
{ {
@ -333,15 +360,16 @@ namespace MediaBrowser.Server.Implementations.HttpServer
task.ContinueWith(x => httpRes.Close(), TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.AttachedToParent); task.ContinueWith(x => httpRes.Close(), TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.AttachedToParent);
//Matches Exceptions handled in HttpListenerBase.InitTask() //Matches Exceptions handled in HttpListenerBase.InitTask()
var urlString = url.ToString();
task.ContinueWith(x => task.ContinueWith(x =>
{ {
var statusCode = httpRes.StatusCode; var statusCode = httpRes.StatusCode;
var duration = DateTime.Now - date; var duration = DateTime.Now - date;
if (enableLog)
{
LoggerUtils.LogResponse(_logger, statusCode, urlString, remoteIp, duration); LoggerUtils.LogResponse(_logger, statusCode, urlString, remoteIp, duration);
}
}, TaskContinuationOptions.None); }, TaskContinuationOptions.None);
return task; return task;

View File

@ -1,11 +1,30 @@
using MediaBrowser.Model.Logging; using MediaBrowser.Model.Logging;
using System; using System;
using System.Globalization; using System.Globalization;
using System.IO;
using SocketHttpListener.Net;
namespace MediaBrowser.Server.Implementations.HttpServer namespace MediaBrowser.Server.Implementations.HttpServer
{ {
public static class LoggerUtils public static class LoggerUtils
{ {
/// <summary>
/// Logs the request.
/// </summary>
/// <param name="logger">The logger.</param>
/// <param name="request">The request.</param>
public static void LogRequest(ILogger logger, HttpListenerRequest request)
{
var url = request.Url.ToString();
logger.Info("{0} {1}. UserAgent: {2}", (request.IsWebSocketRequest ? "WS" : "HTTP " + request.HttpMethod), url, request.UserAgent ?? string.Empty);
}
public static void LogRequest(ILogger logger, string url, string method, string userAgent)
{
logger.Info("{0} {1}. UserAgent: {2}", ("HTTP " + method), url, userAgent ?? string.Empty);
}
/// <summary> /// <summary>
/// Logs the response. /// Logs the response.
/// </summary> /// </summary>

View File

@ -78,10 +78,10 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
{ {
var request = context.Request; var request = context.Request;
LogRequest(_logger, request);
if (request.IsWebSocketRequest) if (request.IsWebSocketRequest)
{ {
LoggerUtils.LogRequest(_logger, request);
ProcessWebSocketRequest(context); ProcessWebSocketRequest(context);
return Task.FromResult(true); return Task.FromResult(true);
} }
@ -156,44 +156,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
return req; return req;
} }
/// <summary>
/// Logs the request.
/// </summary>
/// <param name="logger">The logger.</param>
/// <param name="request">The request.</param>
private static void LogRequest(ILogger logger, HttpListenerRequest request)
{
var url = request.Url.ToString();
var extension = Path.GetExtension(url);
if (string.Equals(extension, ".js", StringComparison.OrdinalIgnoreCase))
{
return;
}
if (string.Equals(extension, ".css", StringComparison.OrdinalIgnoreCase))
{
return;
}
if (string.Equals(extension, ".woff", StringComparison.OrdinalIgnoreCase))
{
return;
}
if (string.Equals(extension, ".woff2", StringComparison.OrdinalIgnoreCase))
{
return;
}
if (string.Equals(extension, ".ttf", StringComparison.OrdinalIgnoreCase))
{
return;
}
if (string.Equals(extension, ".html", StringComparison.OrdinalIgnoreCase))
{
return;
}
logger.Info("{0} {1}. UserAgent: {2}", (request.IsWebSocketRequest ? "WS" : "HTTP " + request.HttpMethod), url, request.UserAgent ?? string.Empty);
}
private void HandleError(Exception ex, HttpListenerContext context) private void HandleError(Exception ex, HttpListenerContext context)
{ {
var httpReq = GetRequest(context); var httpReq = GetRequest(context);

View File

@ -471,11 +471,11 @@ namespace MediaBrowser.Server.Implementations.IO
{ {
if (_updateTimer == null) if (_updateTimer == null)
{ {
_updateTimer = new Timer(TimerStopped, null, TimeSpan.FromSeconds(ConfigurationManager.Configuration.RealtimeLibraryMonitorDelay), TimeSpan.FromMilliseconds(-1)); _updateTimer = new Timer(TimerStopped, null, TimeSpan.FromSeconds(ConfigurationManager.Configuration.LibraryMonitorDelay), TimeSpan.FromMilliseconds(-1));
} }
else else
{ {
_updateTimer.Change(TimeSpan.FromSeconds(ConfigurationManager.Configuration.RealtimeLibraryMonitorDelay), TimeSpan.FromMilliseconds(-1)); _updateTimer.Change(TimeSpan.FromSeconds(ConfigurationManager.Configuration.LibraryMonitorDelay), TimeSpan.FromMilliseconds(-1));
} }
} }
} }
@ -513,12 +513,18 @@ namespace MediaBrowser.Server.Implementations.IO
private bool IsFileLocked(string path) private bool IsFileLocked(string path)
{ {
if (Environment.OSVersion.Platform != PlatformID.Win32NT)
{
// Causing lockups on linux
return false;
}
try try
{ {
var data = _fileSystem.GetFileSystemInfo(path); var data = _fileSystem.GetFileSystemInfo(path);
if (!data.Exists if (!data.Exists
|| data.Attributes.HasFlag(FileAttributes.Directory) || data.IsDirectory
// Opening a writable stream will fail with readonly files // Opening a writable stream will fail with readonly files
|| data.Attributes.HasFlag(FileAttributes.ReadOnly)) || data.Attributes.HasFlag(FileAttributes.ReadOnly))

View File

@ -222,7 +222,7 @@ namespace MediaBrowser.Server.Implementations.Library
/// <summary> /// <summary>
/// The _root folder /// The _root folder
/// </summary> /// </summary>
private AggregateFolder _rootFolder; private volatile AggregateFolder _rootFolder;
/// <summary> /// <summary>
/// The _root folder sync lock /// The _root folder sync lock
/// </summary> /// </summary>
@ -743,7 +743,7 @@ namespace MediaBrowser.Server.Implementations.Library
return rootFolder; return rootFolder;
} }
private UserRootFolder _userRootFolder; private volatile UserRootFolder _userRootFolder;
private readonly object _syncLock = new object(); private readonly object _syncLock = new object();
public Folder GetUserRootFolder() public Folder GetUserRootFolder()
{ {

View File

@ -51,8 +51,17 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers
{ {
var filename = Path.GetFileNameWithoutExtension(path) ?? string.Empty; var filename = Path.GetFileNameWithoutExtension(path) ?? string.Empty;
return !IgnoreFiles.Contains(filename, StringComparer.OrdinalIgnoreCase) if (IgnoreFiles.Contains(filename, StringComparer.OrdinalIgnoreCase))
&& imageProcessor.SupportedInputFormats.Contains((Path.GetExtension(path) ?? string.Empty).TrimStart('.'), StringComparer.OrdinalIgnoreCase); {
return false;
}
if (IgnoreFiles.Any(i => filename.IndexOf("-" + i, StringComparison.OrdinalIgnoreCase) != -1))
{
return false;
}
return imageProcessor.SupportedInputFormats.Contains((Path.GetExtension(path) ?? string.Empty).TrimStart('.'), StringComparer.OrdinalIgnoreCase);
} }
} }

View File

@ -163,7 +163,14 @@ namespace MediaBrowser.Server.Implementations.Library
var channels = channelResult.Items; var channels = channelResult.Items;
if (user.Configuration.DisplayChannelsInline && channels.Length > 0)
{
list.Add(await _channelManager.GetInternalChannelFolder(cancellationToken).ConfigureAwait(false));
}
else
{
list.AddRange(channels); list.AddRange(channels);
}
if (_liveTvManager.GetEnabledUsers().Select(i => i.Id.ToString("N")).Contains(query.UserId)) if (_liveTvManager.GetEnabledUsers().Select(i => i.Id.ToString("N")).Contains(query.UserId))
{ {

View File

@ -22,12 +22,15 @@ using MediaBrowser.Server.Implementations.FileOrganization;
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using CommonIO; using CommonIO;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Power;
using Microsoft.Win32;
namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
{ {
@ -55,7 +58,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
public static EmbyTV Current; public static EmbyTV Current;
public EmbyTV(IApplicationHost appHost, ILogger logger, IJsonSerializer jsonSerializer, IHttpClient httpClient, IServerConfigurationManager config, ILiveTvManager liveTvManager, IFileSystem fileSystem, ISecurityManager security, ILibraryManager libraryManager, ILibraryMonitor libraryMonitor, IProviderManager providerManager, IFileOrganizationService organizationService, IMediaEncoder mediaEncoder) public EmbyTV(IApplicationHost appHost, ILogger logger, IJsonSerializer jsonSerializer, IHttpClient httpClient, IServerConfigurationManager config, ILiveTvManager liveTvManager, IFileSystem fileSystem, ISecurityManager security, ILibraryManager libraryManager, ILibraryMonitor libraryMonitor, IProviderManager providerManager, IFileOrganizationService organizationService, IMediaEncoder mediaEncoder, IPowerManagement powerManagement)
{ {
Current = this; Current = this;
@ -75,13 +78,26 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
_recordingProvider = new ItemDataProvider<RecordingInfo>(fileSystem, jsonSerializer, _logger, Path.Combine(DataPath, "recordings"), (r1, r2) => string.Equals(r1.Id, r2.Id, StringComparison.OrdinalIgnoreCase)); _recordingProvider = new ItemDataProvider<RecordingInfo>(fileSystem, jsonSerializer, _logger, Path.Combine(DataPath, "recordings"), (r1, r2) => string.Equals(r1.Id, r2.Id, StringComparison.OrdinalIgnoreCase));
_seriesTimerProvider = new SeriesTimerManager(fileSystem, jsonSerializer, _logger, Path.Combine(DataPath, "seriestimers")); _seriesTimerProvider = new SeriesTimerManager(fileSystem, jsonSerializer, _logger, Path.Combine(DataPath, "seriestimers"));
_timerProvider = new TimerManager(fileSystem, jsonSerializer, _logger, Path.Combine(DataPath, "timers")); _timerProvider = new TimerManager(fileSystem, jsonSerializer, _logger, Path.Combine(DataPath, "timers"), powerManagement, _logger);
_timerProvider.TimerFired += _timerProvider_TimerFired; _timerProvider.TimerFired += _timerProvider_TimerFired;
} }
public void Start() public void Start()
{ {
_timerProvider.RestartTimers(); _timerProvider.RestartTimers();
SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged;
}
void SystemEvents_PowerModeChanged(object sender, PowerModeChangedEventArgs e)
{
_logger.Info("Power mode changed to {0}", e.Mode);
if (e.Mode == PowerModes.Resume)
{
_timerProvider.RestartTimers();
}
} }
public event EventHandler DataSourceChanged; public event EventHandler DataSourceChanged;
@ -155,7 +171,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
{ {
epgData = GetEpgDataForChannel(timer.ChannelId); epgData = GetEpgDataForChannel(timer.ChannelId);
} }
await UpdateTimersForSeriesTimer(epgData, timer).ConfigureAwait(false); await UpdateTimersForSeriesTimer(epgData, timer, false).ConfigureAwait(false);
} }
var timers = await GetTimersAsync(cancellationToken).ConfigureAwait(false); var timers = await GetTimersAsync(cancellationToken).ConfigureAwait(false);
@ -223,7 +239,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
public Task CancelSeriesTimerAsync(string timerId, CancellationToken cancellationToken) public Task CancelSeriesTimerAsync(string timerId, CancellationToken cancellationToken)
{ {
var timers = _timerProvider.GetAll().Where(i => string.Equals(i.SeriesTimerId, timerId, StringComparison.OrdinalIgnoreCase)); var timers = _timerProvider
.GetAll()
.Where(i => string.Equals(i.SeriesTimerId, timerId, StringComparison.OrdinalIgnoreCase))
.ToList();
foreach (var timer in timers) foreach (var timer in timers)
{ {
CancelTimerInternal(timer.Id); CancelTimerInternal(timer.Id);
@ -332,14 +352,32 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
} }
_seriesTimerProvider.Add(info); _seriesTimerProvider.Add(info);
await UpdateTimersForSeriesTimer(epgData, info).ConfigureAwait(false); await UpdateTimersForSeriesTimer(epgData, info, false).ConfigureAwait(false);
} }
public async Task UpdateSeriesTimerAsync(SeriesTimerInfo info, CancellationToken cancellationToken) public async Task UpdateSeriesTimerAsync(SeriesTimerInfo info, CancellationToken cancellationToken)
{ {
_seriesTimerProvider.Update(info); var instance = _seriesTimerProvider.GetAll().FirstOrDefault(i => string.Equals(i.Id, info.Id, StringComparison.OrdinalIgnoreCase));
if (instance != null)
{
instance.ChannelId = info.ChannelId;
instance.Days = info.Days;
instance.EndDate = info.EndDate;
instance.IsPostPaddingRequired = info.IsPostPaddingRequired;
instance.IsPrePaddingRequired = info.IsPrePaddingRequired;
instance.PostPaddingSeconds = info.PostPaddingSeconds;
instance.PrePaddingSeconds = info.PrePaddingSeconds;
instance.Priority = info.Priority;
instance.RecordAnyChannel = info.RecordAnyChannel;
instance.RecordAnyTime = info.RecordAnyTime;
instance.RecordNewOnly = info.RecordNewOnly;
instance.StartDate = info.StartDate;
_seriesTimerProvider.Update(instance);
List<ProgramInfo> epgData; List<ProgramInfo> epgData;
if (info.RecordAnyChannel) if (instance.RecordAnyChannel)
{ {
var channels = await GetChannelsAsync(true, CancellationToken.None).ConfigureAwait(false); var channels = await GetChannelsAsync(true, CancellationToken.None).ConfigureAwait(false);
var channelIds = channels.Select(i => i.Id).ToList(); var channelIds = channels.Select(i => i.Id).ToList();
@ -347,10 +385,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
} }
else else
{ {
epgData = GetEpgDataForChannel(info.ChannelId); epgData = GetEpgDataForChannel(instance.ChannelId);
} }
await UpdateTimersForSeriesTimer(epgData, info).ConfigureAwait(false); await UpdateTimersForSeriesTimer(epgData, instance, true).ConfigureAwait(false);
}
} }
public Task UpdateTimerAsync(TimerInfo info, CancellationToken cancellationToken) public Task UpdateTimerAsync(TimerInfo info, CancellationToken cancellationToken)
@ -603,6 +642,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
{ {
await RecordStream(timer, recordingEndDate, cancellationTokenSource.Token).ConfigureAwait(false); await RecordStream(timer, recordingEndDate, cancellationTokenSource.Token).ConfigureAwait(false);
} }
else
{
_logger.Info("Skipping RecordStream because it's already in progress.");
}
} }
catch (OperationCanceledException) catch (OperationCanceledException)
{ {
@ -721,7 +764,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
recording.DateLastUpdated = DateTime.UtcNow; recording.DateLastUpdated = DateTime.UtcNow;
_recordingProvider.AddOrUpdate(recording); _recordingProvider.AddOrUpdate(recording);
_logger.Info("Beginning recording."); _logger.Info("Beginning recording. Will record for {0} minutes.", duration.TotalMinutes.ToString(CultureInfo.InvariantCulture));
httpRequestOptions.BufferContent = false; httpRequestOptions.BufferContent = false;
var durationToken = new CancellationTokenSource(duration); var durationToken = new CancellationTokenSource(duration);
@ -836,7 +879,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
return _config.GetConfiguration<LiveTvOptions>("livetv"); return _config.GetConfiguration<LiveTvOptions>("livetv");
} }
private async Task UpdateTimersForSeriesTimer(List<ProgramInfo> epgData, SeriesTimerInfo seriesTimer) private async Task UpdateTimersForSeriesTimer(List<ProgramInfo> epgData, SeriesTimerInfo seriesTimer, bool deleteInvalidTimers)
{ {
var newTimers = GetTimersForSeries(seriesTimer, epgData, _recordingProvider.GetAll()).ToList(); var newTimers = GetTimersForSeries(seriesTimer, epgData, _recordingProvider.GetAll()).ToList();
@ -849,12 +892,29 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
_timerProvider.AddOrUpdate(timer); _timerProvider.AddOrUpdate(timer);
} }
} }
if (deleteInvalidTimers)
{
var allTimers = GetTimersForSeries(seriesTimer, epgData, new List<RecordingInfo>())
.Select(i => i.Id)
.ToList();
var deletes = _timerProvider.GetAll()
.Where(i => string.Equals(i.SeriesTimerId, seriesTimer.Id, StringComparison.OrdinalIgnoreCase))
.Where(i => !allTimers.Contains(i.Id, StringComparer.OrdinalIgnoreCase) && i.StartDate > DateTime.UtcNow)
.ToList();
foreach (var timer in deletes)
{
await CancelTimerAsync(timer.Id, CancellationToken.None).ConfigureAwait(false);
}
}
} }
private IEnumerable<TimerInfo> GetTimersForSeries(SeriesTimerInfo seriesTimer, IEnumerable<ProgramInfo> allPrograms, IReadOnlyList<RecordingInfo> currentRecordings) private IEnumerable<TimerInfo> GetTimersForSeries(SeriesTimerInfo seriesTimer, IEnumerable<ProgramInfo> allPrograms, IReadOnlyList<RecordingInfo> currentRecordings)
{ {
// Exclude programs that have already ended // Exclude programs that have already ended
allPrograms = allPrograms.Where(i => i.EndDate > DateTime.UtcNow); allPrograms = allPrograms.Where(i => i.EndDate > DateTime.UtcNow && i.StartDate > DateTime.UtcNow);
allPrograms = GetProgramsForSeries(seriesTimer, allPrograms); allPrograms = GetProgramsForSeries(seriesTimer, allPrograms);

View File

@ -13,7 +13,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
where T : class where T : class
{ {
private readonly object _fileDataLock = new object(); private readonly object _fileDataLock = new object();
private List<T> _items; private volatile List<T> _items;
private readonly IJsonSerializer _jsonSerializer; private readonly IJsonSerializer _jsonSerializer;
protected readonly ILogger Logger; protected readonly ILogger Logger;
private readonly string _dataPath; private readonly string _dataPath;

View File

@ -5,22 +5,27 @@ using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Serialization;
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Globalization;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using CommonIO; using CommonIO;
using MediaBrowser.Common.IO; using MediaBrowser.Controller.Power;
namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
{ {
public class TimerManager : ItemDataProvider<TimerInfo> public class TimerManager : ItemDataProvider<TimerInfo>
{ {
private readonly ConcurrentDictionary<string, Timer> _timers = new ConcurrentDictionary<string, Timer>(StringComparer.OrdinalIgnoreCase); private readonly ConcurrentDictionary<string, Timer> _timers = new ConcurrentDictionary<string, Timer>(StringComparer.OrdinalIgnoreCase);
private readonly IPowerManagement _powerManagement;
private readonly ILogger _logger;
public event EventHandler<GenericEventArgs<TimerInfo>> TimerFired; public event EventHandler<GenericEventArgs<TimerInfo>> TimerFired;
public TimerManager(IFileSystem fileSystem, IJsonSerializer jsonSerializer, ILogger logger, string dataPath) public TimerManager(IFileSystem fileSystem, IJsonSerializer jsonSerializer, ILogger logger, string dataPath, IPowerManagement powerManagement, ILogger logger1)
: base(fileSystem, jsonSerializer, logger, dataPath, (r1, r2) => string.Equals(r1.Id, r2.Id, StringComparison.OrdinalIgnoreCase)) : base(fileSystem, jsonSerializer, logger, dataPath, (r1, r2) => string.Equals(r1.Id, r2.Id, StringComparison.OrdinalIgnoreCase))
{ {
_powerManagement = powerManagement;
_logger = logger1;
} }
public void RestartTimers() public void RestartTimers()
@ -58,6 +63,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
{ {
var timespan = RecordingHelper.GetStartTime(item) - DateTime.UtcNow; var timespan = RecordingHelper.GetStartTime(item) - DateTime.UtcNow;
timer.Change(timespan, TimeSpan.Zero); timer.Change(timespan, TimeSpan.Zero);
ScheduleWake(item);
} }
else else
{ {
@ -74,6 +80,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
base.Add(item); base.Add(item);
AddTimer(item); AddTimer(item);
ScheduleWake(item);
} }
private void AddTimer(TimerInfo item) private void AddTimer(TimerInfo item)
@ -91,15 +98,39 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
StartTimer(item, timerLength); StartTimer(item, timerLength);
} }
private void ScheduleWake(TimerInfo info)
{
var startDate = RecordingHelper.GetStartTime(info).AddMinutes(-5);
try
{
_powerManagement.ScheduleWake(startDate);
_logger.Info("Scheduled system wake timer at {0} (UTC)", startDate);
}
catch (NotImplementedException)
{
}
catch (Exception ex)
{
_logger.ErrorException("Error scheduling wake timer", ex);
}
}
public void StartTimer(TimerInfo item, TimeSpan length) public void StartTimer(TimerInfo item, TimeSpan length)
{ {
StopTimer(item); StopTimer(item);
var timer = new Timer(TimerCallback, item.Id, length, TimeSpan.Zero); var timer = new Timer(TimerCallback, item.Id, length, TimeSpan.Zero);
if (!_timers.TryAdd(item.Id, timer)) if (_timers.TryAdd(item.Id, timer))
{
_logger.Info("Creating recording timer for {0}, {1}. Timer will fire in {2} minutes", item.Id, item.Name, length.TotalMinutes.ToString(CultureInfo.InvariantCulture));
}
else
{ {
timer.Dispose(); timer.Dispose();
_logger.Warn("Timer already exists for item {0}", item.Id);
} }
} }

View File

@ -1954,6 +1954,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
await service.CreateTimerAsync(info, cancellationToken).ConfigureAwait(false); await service.CreateTimerAsync(info, cancellationToken).ConfigureAwait(false);
_lastRecordingRefreshTime = DateTime.MinValue; _lastRecordingRefreshTime = DateTime.MinValue;
_logger.Info("New recording scheduled");
} }
public async Task CreateSeriesTimer(SeriesTimerInfoDto timer, CancellationToken cancellationToken) public async Task CreateSeriesTimer(SeriesTimerInfoDto timer, CancellationToken cancellationToken)

View File

@ -1,6 +1,6 @@
{ {
"AppDeviceValues": "App: {0}, Device: {1}", "AppDeviceValues": "App: {0}, Dispositiu: {1}",
"UserDownloadingItemWithValues": "{0} is downloading {1}", "UserDownloadingItemWithValues": "{0} est\u00e0 descarregant {1}",
"FolderTypeMixed": "Mixed content", "FolderTypeMixed": "Mixed content",
"FolderTypeMovies": "Pel\u00b7l\u00edcules", "FolderTypeMovies": "Pel\u00b7l\u00edcules",
"FolderTypeMusic": "M\u00fasica", "FolderTypeMusic": "M\u00fasica",
@ -12,10 +12,10 @@
"FolderTypeBooks": "Llibres", "FolderTypeBooks": "Llibres",
"FolderTypeTvShows": "TV", "FolderTypeTvShows": "TV",
"FolderTypeInherit": "Heretat", "FolderTypeInherit": "Heretat",
"HeaderCastCrew": "Repartiment i equip", "HeaderCastCrew": "Repartiment i Equip",
"HeaderPeople": "People", "HeaderPeople": "People",
"ValueSpecialEpisodeName": "Special - {0}", "ValueSpecialEpisodeName": "Special - {0}",
"LabelChapterName": "Chapter {0}", "LabelChapterName": "Cap\u00edtol {0}",
"NameSeasonNumber": "Temporada {0}", "NameSeasonNumber": "Temporada {0}",
"LabelExit": "Sortir", "LabelExit": "Sortir",
"LabelVisitCommunity": "Visita la comunitat", "LabelVisitCommunity": "Visita la comunitat",
@ -77,7 +77,7 @@
"ViewTypeMovieMovies": "Pel\u00b7l\u00edcules", "ViewTypeMovieMovies": "Pel\u00b7l\u00edcules",
"ViewTypeMovieCollections": "Col\u00b7leccions", "ViewTypeMovieCollections": "Col\u00b7leccions",
"ViewTypeMovieFavorites": "Preferides", "ViewTypeMovieFavorites": "Preferides",
"ViewTypeMovieGenres": "Genres", "ViewTypeMovieGenres": "G\u00e8neres",
"ViewTypeMusicLatest": "Novetats", "ViewTypeMusicLatest": "Novetats",
"ViewTypeMusicPlaylists": "Llistes de reproducci\u00f3", "ViewTypeMusicPlaylists": "Llistes de reproducci\u00f3",
"ViewTypeMusicAlbums": "\u00c0lbums", "ViewTypeMusicAlbums": "\u00c0lbums",
@ -89,7 +89,7 @@
"ViewTypeMusicFavoriteArtists": "Artistes Preferits", "ViewTypeMusicFavoriteArtists": "Artistes Preferits",
"ViewTypeMusicFavoriteSongs": "Can\u00e7ons Preferides", "ViewTypeMusicFavoriteSongs": "Can\u00e7ons Preferides",
"ViewTypeFolders": "Directoris", "ViewTypeFolders": "Directoris",
"ViewTypeLiveTvRecordingGroups": "Recordings", "ViewTypeLiveTvRecordingGroups": "Enregistraments",
"ViewTypeLiveTvChannels": "Canals", "ViewTypeLiveTvChannels": "Canals",
"ScheduledTaskFailedWithName": "{0} failed", "ScheduledTaskFailedWithName": "{0} failed",
"LabelRunningTimeValue": "Running time: {0}", "LabelRunningTimeValue": "Running time: {0}",
@ -103,7 +103,7 @@
"LabelIpAddressValue": "Ip address: {0}", "LabelIpAddressValue": "Ip address: {0}",
"DeviceOnlineWithName": "{0} is connected", "DeviceOnlineWithName": "{0} is connected",
"UserOnlineFromDevice": "{0} is online from {1}", "UserOnlineFromDevice": "{0} is online from {1}",
"ProviderValue": "Provider: {0}", "ProviderValue": "Prove\u00efdor: {0}",
"SubtitlesDownloadedForItem": "Subtitles downloaded for {0}", "SubtitlesDownloadedForItem": "Subtitles downloaded for {0}",
"UserConfigurationUpdatedWithName": "User configuration has been updated for {0}", "UserConfigurationUpdatedWithName": "User configuration has been updated for {0}",
"UserCreatedWithName": "User {0} has been created", "UserCreatedWithName": "User {0} has been created",
@ -113,12 +113,12 @@
"MessageNamedServerConfigurationUpdatedWithValue": "Server configuration section {0} has been updated", "MessageNamedServerConfigurationUpdatedWithValue": "Server configuration section {0} has been updated",
"MessageApplicationUpdated": "Emby Server has been updated", "MessageApplicationUpdated": "Emby Server has been updated",
"FailedLoginAttemptWithUserName": "Failed login attempt from {0}", "FailedLoginAttemptWithUserName": "Failed login attempt from {0}",
"AuthenticationSucceededWithUserName": "{0} successfully authenticated", "AuthenticationSucceededWithUserName": "{0} autenticat correctament",
"DeviceOfflineWithName": "{0} has disconnected", "DeviceOfflineWithName": "{0} has disconnected",
"UserLockedOutWithName": "User {0} has been locked out", "UserLockedOutWithName": "User {0} has been locked out",
"UserOfflineFromDevice": "{0} has disconnected from {1}", "UserOfflineFromDevice": "{0} has disconnected from {1}",
"UserStartedPlayingItemWithValues": "{0} has started playing {1}", "UserStartedPlayingItemWithValues": "{0} ha comen\u00e7at a reproduir {1}",
"UserStoppedPlayingItemWithValues": "{0} has stopped playing {1}", "UserStoppedPlayingItemWithValues": "{0} ha parat de reproduir {1}",
"SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}", "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
"HeaderUnidentified": "Unidentified", "HeaderUnidentified": "Unidentified",
"HeaderImagePrimary": "Primary", "HeaderImagePrimary": "Primary",
@ -164,7 +164,7 @@
"HeaderTracks": "Tracks", "HeaderTracks": "Tracks",
"HeaderMusicArtist": "M\u00fasic", "HeaderMusicArtist": "M\u00fasic",
"HeaderLocked": "Locked", "HeaderLocked": "Locked",
"HeaderStudios": "Studios", "HeaderStudios": "Estudis",
"HeaderActor": "Actors", "HeaderActor": "Actors",
"HeaderComposer": "Compositors", "HeaderComposer": "Compositors",
"HeaderDirector": "Directors", "HeaderDirector": "Directors",

View File

@ -63,7 +63,7 @@
"ViewTypeLatestGames": "Nieuwste games", "ViewTypeLatestGames": "Nieuwste games",
"ViewTypeRecentlyPlayedGames": "Recent gespeelt", "ViewTypeRecentlyPlayedGames": "Recent gespeelt",
"ViewTypeGameFavorites": "Favorieten", "ViewTypeGameFavorites": "Favorieten",
"ViewTypeGameSystems": "Gam systemen", "ViewTypeGameSystems": "Game systemen",
"ViewTypeGameGenres": "Genres", "ViewTypeGameGenres": "Genres",
"ViewTypeTvResume": "Hervatten", "ViewTypeTvResume": "Hervatten",
"ViewTypeTvNextUp": "Volgende", "ViewTypeTvNextUp": "Volgende",
@ -147,7 +147,7 @@
"HeaderCommunityRating": "Gemeenschap cijfer", "HeaderCommunityRating": "Gemeenschap cijfer",
"HeaderTrailers": "Trailers", "HeaderTrailers": "Trailers",
"HeaderSpecials": "Specials", "HeaderSpecials": "Specials",
"HeaderGameSystems": "Spel systemen", "HeaderGameSystems": "Game systemen",
"HeaderPlayers": "Spelers:", "HeaderPlayers": "Spelers:",
"HeaderAlbumArtists": "Album artiesten", "HeaderAlbumArtists": "Album artiesten",
"HeaderAlbums": "Albums", "HeaderAlbums": "Albums",

View File

@ -48,9 +48,9 @@
<Reference Include="Interfaces.IO"> <Reference Include="Interfaces.IO">
<HintPath>..\packages\Interfaces.IO.1.0.0.5\lib\portable-net45+sl4+wp71+win8+wpa81\Interfaces.IO.dll</HintPath> <HintPath>..\packages\Interfaces.IO.1.0.0.5\lib\portable-net45+sl4+wp71+win8+wpa81\Interfaces.IO.dll</HintPath>
</Reference> </Reference>
<Reference Include="MediaBrowser.Naming, Version=1.0.5818.23111, Culture=neutral, processorArchitecture=MSIL"> <Reference Include="MediaBrowser.Naming, Version=1.0.5869.26812, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion> <SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\MediaBrowser.Naming.1.0.0.41\lib\portable-net45+sl4+wp71+win8+wpa81\MediaBrowser.Naming.dll</HintPath> <HintPath>..\packages\MediaBrowser.Naming.1.0.0.44\lib\portable-net45+sl4+wp71+win8+wpa81\MediaBrowser.Naming.dll</HintPath>
</Reference> </Reference>
<Reference Include="MoreLinq"> <Reference Include="MoreLinq">
<HintPath>..\packages\morelinq.1.4.0\lib\net35\MoreLinq.dll</HintPath> <HintPath>..\packages\morelinq.1.4.0\lib\net35\MoreLinq.dll</HintPath>

View File

@ -1,6 +1,5 @@
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.IO;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Notifications; using MediaBrowser.Controller.Notifications;
@ -17,12 +16,13 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Xml; using System.Xml;
using CommonIO; using CommonIO;
using MediaBrowser.Common.Threading;
namespace MediaBrowser.Server.Implementations.News namespace MediaBrowser.Server.Implementations.News
{ {
public class NewsEntryPoint : IServerEntryPoint public class NewsEntryPoint : IServerEntryPoint
{ {
private Timer _timer; private PeriodicTimer _timer;
private readonly IHttpClient _httpClient; private readonly IHttpClient _httpClient;
private readonly IApplicationPaths _appPaths; private readonly IApplicationPaths _appPaths;
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
@ -47,7 +47,7 @@ namespace MediaBrowser.Server.Implementations.News
public void Run() public void Run()
{ {
_timer = new Timer(OnTimerFired, null, TimeSpan.FromMilliseconds(500), _frequency); _timer = new PeriodicTimer(OnTimerFired, null, TimeSpan.FromMilliseconds(500), _frequency);
} }
/// <summary> /// <summary>

View File

@ -2,7 +2,7 @@
<packages> <packages>
<package id="CommonIO" version="1.0.0.7" targetFramework="net45" /> <package id="CommonIO" version="1.0.0.7" targetFramework="net45" />
<package id="Interfaces.IO" version="1.0.0.5" targetFramework="net45" /> <package id="Interfaces.IO" version="1.0.0.5" targetFramework="net45" />
<package id="MediaBrowser.Naming" version="1.0.0.41" targetFramework="net45" /> <package id="MediaBrowser.Naming" version="1.0.0.44" targetFramework="net45" />
<package id="Mono.Nat" version="1.2.24.0" targetFramework="net45" /> <package id="Mono.Nat" version="1.2.24.0" targetFramework="net45" />
<package id="morelinq" version="1.4.0" targetFramework="net45" /> <package id="morelinq" version="1.4.0" targetFramework="net45" />
<package id="Patterns.Logging" version="1.0.0.2" targetFramework="net45" /> <package id="Patterns.Logging" version="1.0.0.2" targetFramework="net45" />

View File

@ -8,6 +8,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Reflection; using System.Reflection;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using MediaBrowser.Controller.Power;
namespace MediaBrowser.Server.Mono.Native namespace MediaBrowser.Server.Mono.Native
{ {
@ -203,5 +204,18 @@ namespace MediaBrowser.Server.Mono.Native
public string sysname = string.Empty; public string sysname = string.Empty;
public string machine = string.Empty; public string machine = string.Empty;
} }
public IPowerManagement GetPowerManagement()
{
return new NullPowerManagement();
}
}
public class NullPowerManagement : IPowerManagement
{
public void ScheduleWake(DateTime utcTime)
{
throw new NotImplementedException();
}
} }
} }

View File

@ -210,7 +210,6 @@ namespace MediaBrowser.Server.Startup.Common
private readonly string _releaseAssetFilename; private readonly string _releaseAssetFilename;
internal INativeApp NativeApp { get; set; } internal INativeApp NativeApp { get; set; }
private Timer _ipAddressCacheTimer;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ApplicationHost" /> class. /// Initializes a new instance of the <see cref="ApplicationHost" /> class.
@ -234,8 +233,6 @@ namespace MediaBrowser.Server.Startup.Common
NativeApp = nativeApp; NativeApp = nativeApp;
SetBaseExceptionMessage(); SetBaseExceptionMessage();
_ipAddressCacheTimer = new Timer(OnCacheClearTimerFired, null, TimeSpan.FromMinutes(3), TimeSpan.FromMinutes(3));
} }
private Version _version; private Version _version;
@ -533,6 +530,8 @@ namespace MediaBrowser.Server.Startup.Common
EncodingManager = new EncodingManager(FileSystemManager, Logger, MediaEncoder, ChapterManager); EncodingManager = new EncodingManager(FileSystemManager, Logger, MediaEncoder, ChapterManager);
RegisterSingleInstance(EncodingManager); RegisterSingleInstance(EncodingManager);
RegisterSingleInstance(NativeApp.GetPowerManagement());
var sharingRepo = new SharingRepository(LogManager, ApplicationPaths); var sharingRepo = new SharingRepository(LogManager, ApplicationPaths);
await sharingRepo.Initialize().ConfigureAwait(false); await sharingRepo.Initialize().ConfigureAwait(false);
RegisterSingleInstance<ISharingManager>(new SharingManager(sharingRepo, ServerConfigurationManager, LibraryManager, this)); RegisterSingleInstance<ISharingManager>(new SharingManager(sharingRepo, ServerConfigurationManager, LibraryManager, this));
@ -1157,7 +1156,12 @@ namespace MediaBrowser.Server.Startup.Common
} }
private readonly ConcurrentDictionary<string, bool> _validAddressResults = new ConcurrentDictionary<string, bool>(StringComparer.OrdinalIgnoreCase); private readonly ConcurrentDictionary<string, bool> _validAddressResults = new ConcurrentDictionary<string, bool>(StringComparer.OrdinalIgnoreCase);
private DateTime _lastAddressCacheClear;
private bool IsIpAddressValid(IPAddress address) private bool IsIpAddressValid(IPAddress address)
{
return IsIpAddressValidInternal(address).Result;
}
private async Task<bool> IsIpAddressValidInternal(IPAddress address)
{ {
if (IPAddress.IsLoopback(address)) if (IPAddress.IsLoopback(address))
{ {
@ -1167,6 +1171,12 @@ namespace MediaBrowser.Server.Startup.Common
var apiUrl = GetLocalApiUrl(address.ToString()); var apiUrl = GetLocalApiUrl(address.ToString());
apiUrl += "/system/ping"; apiUrl += "/system/ping";
if ((DateTime.UtcNow - _lastAddressCacheClear).TotalMinutes >= 5)
{
_lastAddressCacheClear = DateTime.UtcNow;
_validAddressResults.Clear();
}
bool cachedResult; bool cachedResult;
if (_validAddressResults.TryGetValue(apiUrl, out cachedResult)) if (_validAddressResults.TryGetValue(apiUrl, out cachedResult))
{ {
@ -1175,14 +1185,15 @@ namespace MediaBrowser.Server.Startup.Common
try try
{ {
using (var response = HttpClient.SendAsync(new HttpRequestOptions using (var response = await HttpClient.SendAsync(new HttpRequestOptions
{ {
Url = apiUrl, Url = apiUrl,
LogErrorResponseBody = false, LogErrorResponseBody = false,
LogErrors = false, LogErrors = false,
LogRequest = false LogRequest = false,
TimeoutMs = 30000
}, "POST").Result) }, "POST").ConfigureAwait(false))
{ {
using (var reader = new StreamReader(response.Content)) using (var reader = new StreamReader(response.Content))
{ {
@ -1190,25 +1201,20 @@ namespace MediaBrowser.Server.Startup.Common
var valid = string.Equals(Name, result, StringComparison.OrdinalIgnoreCase); var valid = string.Equals(Name, result, StringComparison.OrdinalIgnoreCase);
_validAddressResults.AddOrUpdate(apiUrl, valid, (k, v) => valid); _validAddressResults.AddOrUpdate(apiUrl, valid, (k, v) => valid);
Logger.Debug("Ping test result to {0}. Success: {1}", apiUrl, valid); //Logger.Debug("Ping test result to {0}. Success: {1}", apiUrl, valid);
return valid; return valid;
} }
} }
} }
catch catch
{ {
Logger.Debug("Ping test result to {0}. Success: {1}", apiUrl, false); //Logger.Debug("Ping test result to {0}. Success: {1}", apiUrl, false);
_validAddressResults.AddOrUpdate(apiUrl, false, (k, v) => false); _validAddressResults.AddOrUpdate(apiUrl, false, (k, v) => false);
return false; return false;
} }
} }
private void OnCacheClearTimerFired(object state)
{
_validAddressResults.Clear();
}
public string FriendlyName public string FriendlyName
{ {
get get

View File

@ -4,7 +4,7 @@ using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Logging; using MediaBrowser.Model.Logging;
using System; using System;
using System.Linq; using System.Linq;
using System.Threading; using MediaBrowser.Common.Threading;
namespace MediaBrowser.Server.Startup.Common.EntryPoints namespace MediaBrowser.Server.Startup.Common.EntryPoints
{ {
@ -12,7 +12,7 @@ namespace MediaBrowser.Server.Startup.Common.EntryPoints
{ {
private readonly ISessionManager _sessionManager; private readonly ISessionManager _sessionManager;
private readonly ILogger _logger; private readonly ILogger _logger;
private Timer _timer; private PeriodicTimer _timer;
private readonly IServerApplicationHost _appHost; private readonly IServerApplicationHost _appHost;
public KeepServerAwake(ISessionManager sessionManager, ILogger logger, IServerApplicationHost appHost) public KeepServerAwake(ISessionManager sessionManager, ILogger logger, IServerApplicationHost appHost)
@ -24,7 +24,7 @@ namespace MediaBrowser.Server.Startup.Common.EntryPoints
public void Run() public void Run()
{ {
_timer = new Timer(obj => _timer = new PeriodicTimer(obj =>
{ {
var now = DateTime.UtcNow; var now = DateTime.UtcNow;
if (_sessionManager.Sessions.Any(i => (now - i.LastActivityDate).TotalMinutes < 15)) if (_sessionManager.Sessions.Any(i => (now - i.LastActivityDate).TotalMinutes < 15))

View File

@ -2,6 +2,7 @@
using MediaBrowser.Model.Logging; using MediaBrowser.Model.Logging;
using System.Collections.Generic; using System.Collections.Generic;
using System.Reflection; using System.Reflection;
using MediaBrowser.Controller.Power;
namespace MediaBrowser.Server.Startup.Common namespace MediaBrowser.Server.Startup.Common
{ {
@ -90,5 +91,11 @@ namespace MediaBrowser.Server.Startup.Common
/// Prevents the system stand by. /// Prevents the system stand by.
/// </summary> /// </summary>
void PreventSystemStandby(); void PreventSystemStandby();
/// <summary>
/// Gets the power management.
/// </summary>
/// <returns>IPowerManagement.</returns>
IPowerManagement GetPowerManagement();
} }
} }

View File

@ -218,7 +218,7 @@ namespace MediaBrowser.ServerApplication
var fileSystem = new WindowsFileSystem(new PatternsLogger(logManager.GetLogger("FileSystem"))); var fileSystem = new WindowsFileSystem(new PatternsLogger(logManager.GetLogger("FileSystem")));
fileSystem.AddShortcutHandler(new MbLinkShortcutHandler(fileSystem)); fileSystem.AddShortcutHandler(new MbLinkShortcutHandler(fileSystem));
var nativeApp = new WindowsApp(fileSystem) var nativeApp = new WindowsApp(fileSystem, _logger)
{ {
IsRunningAsService = runService IsRunningAsService = runService
}; };

View File

@ -120,6 +120,7 @@
<Compile Include="Native\Standby.cs" /> <Compile Include="Native\Standby.cs" />
<Compile Include="Native\ServerAuthorization.cs" /> <Compile Include="Native\ServerAuthorization.cs" />
<Compile Include="Native\WindowsApp.cs" /> <Compile Include="Native\WindowsApp.cs" />
<Compile Include="Native\WindowsPowerManagement.cs" />
<Compile Include="Networking\CertificateGenerator.cs" /> <Compile Include="Networking\CertificateGenerator.cs" />
<Compile Include="Networking\NativeMethods.cs" /> <Compile Include="Networking\NativeMethods.cs" />
<Compile Include="Networking\NetworkManager.cs" /> <Compile Include="Networking\NetworkManager.cs" />

View File

@ -6,16 +6,19 @@ using MediaBrowser.ServerApplication.Networking;
using System.Collections.Generic; using System.Collections.Generic;
using System.Reflection; using System.Reflection;
using CommonIO; using CommonIO;
using MediaBrowser.Controller.Power;
namespace MediaBrowser.ServerApplication.Native namespace MediaBrowser.ServerApplication.Native
{ {
public class WindowsApp : INativeApp public class WindowsApp : INativeApp
{ {
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
private readonly ILogger _logger;
public WindowsApp(IFileSystem fileSystem) public WindowsApp(IFileSystem fileSystem, ILogger logger)
{ {
_fileSystem = fileSystem; _fileSystem = fileSystem;
_logger = logger;
} }
public List<Assembly> GetAssembliesWithParts() public List<Assembly> GetAssembliesWithParts()
@ -117,5 +120,10 @@ namespace MediaBrowser.ServerApplication.Native
{ {
Standby.PreventSystemStandby(); Standby.PreventSystemStandby();
} }
public IPowerManagement GetPowerManagement()
{
return new WindowsPowerManagement(_logger);
}
} }
} }

View File

@ -0,0 +1,94 @@
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Threading;
using MediaBrowser.Controller.Power;
using MediaBrowser.Model.Logging;
using Microsoft.Win32.SafeHandles;
namespace MediaBrowser.ServerApplication.Native
{
public class WindowsPowerManagement : IPowerManagement
{
[DllImport("kernel32.dll")]
public static extern SafeWaitHandle CreateWaitableTimer(IntPtr lpTimerAttributes,
bool bManualReset,
string lpTimerName);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool SetWaitableTimer(SafeWaitHandle hTimer,
[In] ref long pDueTime,
int lPeriod,
IntPtr pfnCompletionRoutine,
IntPtr lpArgToCompletionRoutine,
bool fResume);
private BackgroundWorker _bgWorker;
private readonly ILogger _logger;
private readonly object _initLock = new object();
public WindowsPowerManagement(ILogger logger)
{
_logger = logger;
}
public void ScheduleWake(DateTime utcTime)
{
//Initialize();
//_bgWorker.RunWorkerAsync(utcTime.ToFileTime());
throw new NotImplementedException();
}
private void Initialize()
{
lock (_initLock)
{
if (_bgWorker == null)
{
_bgWorker = new BackgroundWorker();
_bgWorker.DoWork += bgWorker_DoWork;
_bgWorker.RunWorkerCompleted += bgWorker_RunWorkerCompleted;
}
}
}
void bgWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//if (Woken != null)
//{
// Woken(this, new EventArgs());
//}
}
private void bgWorker_DoWork(object sender, DoWorkEventArgs e)
{
try
{
long waketime = (long)e.Argument;
using (SafeWaitHandle handle = CreateWaitableTimer(IntPtr.Zero, true, GetType().Assembly.GetName().Name + "Timer"))
{
if (SetWaitableTimer(handle, ref waketime, 0, IntPtr.Zero, IntPtr.Zero, true))
{
using (EventWaitHandle wh = new EventWaitHandle(false,
EventResetMode.AutoReset))
{
wh.SafeWaitHandle = handle;
wh.WaitOne();
}
}
else
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
}
}
catch (Exception ex)
{
_logger.ErrorException("Error scheduling wake timer", ex);
}
}
}
}

View File

@ -354,8 +354,7 @@ namespace MediaBrowser.WebDashboard.Api
DeleteFoldersByName(Path.Combine(bowerPath, "jstree"), "src"); DeleteFoldersByName(Path.Combine(bowerPath, "jstree"), "src");
DeleteFoldersByName(Path.Combine(bowerPath, "Sortable"), "meteor"); DeleteFoldersByName(Path.Combine(bowerPath, "Sortable"), "meteor");
DeleteFoldersByName(Path.Combine(bowerPath, "Sortable"), "st"); DeleteFoldersByName(Path.Combine(bowerPath, "Sortable"), "st");
DeleteFoldersByName(Path.Combine(bowerPath, "swipebox"), "lib"); DeleteFoldersByName(Path.Combine(bowerPath, "Swiper"), "src");
DeleteFoldersByName(Path.Combine(bowerPath, "swipebox"), "scss");
if (string.Equals(mode, "cordova", StringComparison.OrdinalIgnoreCase)) if (string.Equals(mode, "cordova", StringComparison.OrdinalIgnoreCase))
{ {

View File

@ -158,9 +158,6 @@
<Content Include="dashboard-ui\components\metadataeditor\metadataeditor.template.html"> <Content Include="dashboard-ui\components\metadataeditor\metadataeditor.template.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<Content Include="dashboard-ui\components\paperdialoghelper.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\components\playlisteditor\playlisteditor.js"> <Content Include="dashboard-ui\components\playlisteditor\playlisteditor.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
@ -311,9 +308,6 @@
<Content Include="dashboard-ui\scripts\supporterkeypage.js"> <Content Include="dashboard-ui\scripts\supporterkeypage.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<Content Include="dashboard-ui\components\testermessage.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\scripts\wizardlivetvguide.js"> <Content Include="dashboard-ui\scripts\wizardlivetvguide.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>

View File

@ -215,7 +215,11 @@ namespace MediaBrowser.XbmcMetadata.Parsers
if (!string.IsNullOrWhiteSpace(val)) if (!string.IsNullOrWhiteSpace(val))
{ {
DateTime added; DateTime added;
if (DateTime.TryParse(val, out added)) if (DateTime.TryParseExact(val, BaseNfoSaver.DateAddedFormat, CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal, out added))
{
item.EndDate = added.ToUniversalTime();
}
else if (DateTime.TryParse(val, CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal, out added))
{ {
item.DateCreated = added.ToUniversalTime(); item.DateCreated = added.ToUniversalTime();
} }
@ -627,8 +631,11 @@ namespace MediaBrowser.XbmcMetadata.Parsers
{ {
var person = GetPersonFromXmlNode(subtree); var person = GetPersonFromXmlNode(subtree);
if (!string.IsNullOrWhiteSpace(person.Name))
{
itemResult.AddPerson(person); itemResult.AddPerson(person);
} }
}
break; break;
} }
@ -976,11 +983,11 @@ namespace MediaBrowser.XbmcMetadata.Parsers
if (!string.IsNullOrWhiteSpace(val) && !string.IsNullOrWhiteSpace(userDataUserId)) if (!string.IsNullOrWhiteSpace(val) && !string.IsNullOrWhiteSpace(userDataUserId))
{ {
DateTime parsedValue; DateTime parsedValue;
if (DateTime.TryParseExact(val, "yyyy-MM-dd HH:mm:ss", _usCulture, DateTimeStyles.None, out parsedValue)) if (DateTime.TryParseExact(val, "yyyy-MM-dd HH:mm:ss", _usCulture, DateTimeStyles.AssumeLocal, out parsedValue))
{ {
var userData = GetOrAdd(itemResult, userDataUserId); var userData = GetOrAdd(itemResult, userDataUserId);
userData.LastPlayedDate = parsedValue; userData.LastPlayedDate = parsedValue.ToUniversalTime();
} }
} }
break; break;

View File

@ -416,6 +416,8 @@ namespace MediaBrowser.XbmcMetadata.Savers
writer.WriteEndElement(); writer.WriteEndElement();
} }
public const string DateAddedFormat = "yyyy-MM-dd HH:mm:ss";
/// <summary> /// <summary>
/// Adds the common nodes. /// Adds the common nodes.
/// </summary> /// </summary>
@ -472,7 +474,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
writer.WriteElementString("type", item.DisplayMediaType); writer.WriteElementString("type", item.DisplayMediaType);
} }
writer.WriteElementString("dateadded", item.DateCreated.ToString("yyyy-MM-dd HH:mm:ss")); writer.WriteElementString("dateadded", item.DateCreated.ToLocalTime().ToString(DateAddedFormat));
writer.WriteElementString("title", item.Name ?? string.Empty); writer.WriteElementString("title", item.Name ?? string.Empty);
writer.WriteElementString("originaltitle", item.Name ?? string.Empty); writer.WriteElementString("originaltitle", item.Name ?? string.Empty);
@ -949,7 +951,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
if (userdata.LastPlayedDate.HasValue) if (userdata.LastPlayedDate.HasValue)
{ {
writer.WriteElementString("lastplayed", userdata.LastPlayedDate.Value.ToString("yyyy-MM-dd HH:mm:ss").ToLower()); writer.WriteElementString("lastplayed", userdata.LastPlayedDate.Value.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss").ToLower());
} }
writer.WriteStartElement("resume"); writer.WriteStartElement("resume");