Ver código fonte

Merge branch 'beta'

Luke Pulverenti 9 anos atrás
pai
commit
37352785ac
61 arquivos alterados com 835 adições e 606 exclusões
  1. 6 2
      MediaBrowser.Api/EnvironmentService.cs
  2. 1 1
      MediaBrowser.Api/Playback/BaseStreamingService.cs
  3. 3 3
      MediaBrowser.Api/Playback/MediaInfoService.cs
  4. 1 2
      MediaBrowser.Api/StartupWizardService.cs
  5. 2 0
      MediaBrowser.Api/UserLibrary/PlaystateService.cs
  6. 9 15
      MediaBrowser.Common.Implementations/Networking/BaseNetworkManager.cs
  7. 4 4
      MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs
  8. 1 0
      MediaBrowser.Common/MediaBrowser.Common.csproj
  9. 72 0
      MediaBrowser.Common/Threading/PeriodicTimer.cs
  10. 59 2
      MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs
  11. 2 2
      MediaBrowser.Controller/Entities/User.cs
  12. 1 0
      MediaBrowser.Controller/MediaBrowser.Controller.csproj
  13. 13 0
      MediaBrowser.Controller/Power/IPowerManagement.cs
  14. 42 37
      MediaBrowser.Dlna/PlayTo/Device.cs
  15. 18 31
      MediaBrowser.Dlna/PlayTo/PlayToController.cs
  16. 7 17
      MediaBrowser.Dlna/PlayTo/PlayToManager.cs
  17. 5 5
      MediaBrowser.Dlna/Profiles/WdtvLiveProfile.cs
  18. 5 5
      MediaBrowser.Dlna/Profiles/Xml/WDTV Live.xml
  19. 15 115
      MediaBrowser.Dlna/Ssdp/SsdpHandler.cs
  20. 18 9
      MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
  21. 2 3
      MediaBrowser.Model/Configuration/ServerConfiguration.cs
  22. 1 0
      MediaBrowser.Model/Configuration/UserConfiguration.cs
  23. 11 3
      MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
  24. 7 11
      MediaBrowser.Providers/People/MovieDbPersonProvider.cs
  25. 2 2
      MediaBrowser.Providers/TV/TvdbSeriesProvider.cs
  26. 3 58
      MediaBrowser.Server.Implementations/Channels/ChannelManager.cs
  27. 2 10
      MediaBrowser.Server.Implementations/Configuration/ServerConfigurationManager.cs
  28. 3 2
      MediaBrowser.Server.Implementations/Connect/ConnectEntryPoint.cs
  29. 5 8
      MediaBrowser.Server.Implementations/Devices/DeviceRepository.cs
  30. 3 2
      MediaBrowser.Server.Implementations/EntryPoints/ExternalPortForwarding.cs
  31. 3 2
      MediaBrowser.Server.Implementations/EntryPoints/LoadRegistrations.cs
  32. 5 11
      MediaBrowser.Server.Implementations/EntryPoints/UsageEntryPoint.cs
  33. 119 114
      MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs
  34. 31 3
      MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs
  35. 19 0
      MediaBrowser.Server.Implementations/HttpServer/LoggerUtils.cs
  36. 2 40
      MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs
  37. 9 3
      MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs
  38. 2 2
      MediaBrowser.Server.Implementations/Library/LibraryManager.cs
  39. 11 2
      MediaBrowser.Server.Implementations/Library/Resolvers/PhotoResolver.cs
  40. 8 1
      MediaBrowser.Server.Implementations/Library/UserViewManager.cs
  41. 80 20
      MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
  42. 1 1
      MediaBrowser.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs
  43. 34 3
      MediaBrowser.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs
  44. 1 0
      MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs
  45. 11 11
      MediaBrowser.Server.Implementations/Localization/Core/ca.json
  46. 2 2
      MediaBrowser.Server.Implementations/Localization/Core/nl.json
  47. 2 2
      MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
  48. 3 3
      MediaBrowser.Server.Implementations/News/NewsEntryPoint.cs
  49. 1 1
      MediaBrowser.Server.Implementations/packages.config
  50. 14 0
      MediaBrowser.Server.Mono/Native/BaseMonoApp.cs
  51. 23 17
      MediaBrowser.Server.Startup.Common/ApplicationHost.cs
  52. 3 3
      MediaBrowser.Server.Startup.Common/EntryPoints/KeepServerAwake.cs
  53. 7 0
      MediaBrowser.Server.Startup.Common/INativeApp.cs
  54. 1 1
      MediaBrowser.ServerApplication/MainStartup.cs
  55. 1 0
      MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj
  56. 9 1
      MediaBrowser.ServerApplication/Native/WindowsApp.cs
  57. 94 0
      MediaBrowser.ServerApplication/Native/WindowsPowerManagement.cs
  58. 1 2
      MediaBrowser.WebDashboard/Api/DashboardService.cs
  59. 0 6
      MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj
  60. 11 4
      MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs
  61. 4 2
      MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs

+ 6 - 2
MediaBrowser.Api/EnvironmentService.cs

@@ -1,5 +1,4 @@
-using MediaBrowser.Common.IO;
-using MediaBrowser.Common.Net;
+using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Net;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.Net;
@@ -46,6 +45,11 @@ namespace MediaBrowser.Api
         /// <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")]
         public bool IncludeHidden { get; set; }
+
+        public GetDirectoryContents()
+        {
+            IncludeHidden = true;
+        }
     }
 
     [Route("/Environment/NetworkShares", "GET", Summary = "Gets shares from a network device")]

+ 1 - 1
MediaBrowser.Api/Playback/BaseStreamingService.cs

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

+ 3 - 3
MediaBrowser.Api/Playback/MediaInfoService.cs

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

+ 1 - 2
MediaBrowser.Api/StartupWizardService.cs

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

+ 2 - 0
MediaBrowser.Api/UserLibrary/PlaystateService.cs

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

+ 9 - 15
MediaBrowser.Common.Implementations/Networking/BaseNetworkManager.cs

@@ -6,7 +6,6 @@ using System.Linq;
 using System.Net;
 using System.Net.NetworkInformation;
 using System.Net.Sockets;
-using System.Threading;
 using MoreLinq;
 
 namespace MediaBrowser.Common.Implementations.Networking
@@ -14,22 +13,11 @@ namespace MediaBrowser.Common.Implementations.Networking
     public abstract class BaseNetworkManager
     {
         protected ILogger Logger { get; private set; }
-        private Timer _clearCacheTimer;
+        private DateTime _lastRefresh;
 
         protected BaseNetworkManager(ILogger 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;
@@ -41,15 +29,21 @@ namespace MediaBrowser.Common.Implementations.Networking
         /// <returns>IPAddress.</returns>
 		public IEnumerable<IPAddress> GetLocalIpAddresses()
         {
-            if (_localIpAddresses == null)
+            const int cacheMinutes = 3;
+            var forceRefresh = (DateTime.UtcNow - _lastRefresh).TotalMinutes >= cacheMinutes;
+
+            if (_localIpAddresses == null || forceRefresh)
             {
                 lock (_localIpAddressSyncLock)
                 {
-                    if (_localIpAddresses == null)
+                    forceRefresh = (DateTime.UtcNow - _lastRefresh).TotalMinutes >= cacheMinutes;
+
+                    if (_localIpAddresses == null || forceRefresh)
                     {
                         var addresses = GetLocalIpAddressesInternal().ToList();
 
                         _localIpAddresses = addresses;
+                        _lastRefresh = DateTime.UtcNow;
 
                         return addresses;
                     }

+ 4 - 4
MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs

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

+ 1 - 0
MediaBrowser.Common/MediaBrowser.Common.csproj

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

+ 72 - 0
MediaBrowser.Common/Threading/PeriodicTimer.cs

@@ -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();
+        }
+    }
+}

+ 59 - 2
MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs

@@ -1,17 +1,21 @@
-using MediaBrowser.Controller.Providers;
+using System;
+using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Users;
 using System.Collections.Generic;
 using System.Linq;
 using System.Runtime.Serialization;
+using System.Threading;
+using System.Threading.Tasks;
+using MediaBrowser.Controller.Library;
 
 namespace MediaBrowser.Controller.Entities.Audio
 {
     /// <summary>
     /// Class MusicAlbum
     /// </summary>
-    public class MusicAlbum : Folder, IHasAlbumArtist, IHasArtist, IHasMusicGenres, IHasLookupInfo<AlbumInfo>
+    public class MusicAlbum : Folder, IHasAlbumArtist, IHasArtist, IHasMusicGenres, IHasLookupInfo<AlbumInfo>, IMetadataContainer
     {
         public MusicAlbum()
         {
@@ -139,5 +143,58 @@ namespace MediaBrowser.Controller.Entities.Audio
 
             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);
+        }
     }
 }

+ 2 - 2
MediaBrowser.Controller/Entities/User.cs

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

+ 1 - 0
MediaBrowser.Controller/MediaBrowser.Controller.csproj

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

+ 13 - 0
MediaBrowser.Controller/Power/IPowerManagement.cs

@@ -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);
+    }
+}

+ 42 - 37
MediaBrowser.Dlna/PlayTo/Device.cs

@@ -22,14 +22,26 @@ namespace MediaBrowser.Dlna.PlayTo
         #region Fields & Properties
 
         private Timer _timer;
-        private Timer _volumeTimer;
 
         public DeviceInfo Properties { get; set; }
 
         private int _muteVol;
         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; }
 
@@ -93,11 +105,6 @@ namespace MediaBrowser.Dlna.PlayTo
             return 1000;
         }
 
-        private int GetVolumeTimerIntervalMs()
-        {
-            return 5000;
-        }
-
         private int GetInactiveTimerIntervalMs()
         {
             return 20000;
@@ -107,11 +114,37 @@ namespace MediaBrowser.Dlna.PlayTo
         {
             _timer = new Timer(TimerCallback, null, GetPlaybackTimerIntervalMs(), GetInactiveTimerIntervalMs());
 
-            _volumeTimer = new Timer(VolumeTimerCallback, null, Timeout.Infinite, Timeout.Infinite);
-
             _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 bool _timerActive;
         private void RestartTimer()
@@ -124,7 +157,6 @@ namespace MediaBrowser.Dlna.PlayTo
                     {
                         _logger.Debug("RestartTimer");
                         _timer.Change(10, GetPlaybackTimerIntervalMs());
-                        _volumeTimer.Change(100, GetVolumeTimerIntervalMs());
                     }
 
                     _timerActive = true;
@@ -150,10 +182,6 @@ namespace MediaBrowser.Dlna.PlayTo
                         {
                             _timer.Change(interval, interval);
                         }
-                        if (_volumeTimer != null)
-                        {
-                            _volumeTimer.Change(Timeout.Infinite, Timeout.Infinite);
-                        }
                     }
 
                     _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()
         {
             var command = RendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetVolume");
@@ -1012,7 +1027,6 @@ namespace MediaBrowser.Dlna.PlayTo
                 _disposed = true;
 
                 DisposeTimer();
-                DisposeVolumeTimer();
             }
         }
 
@@ -1025,15 +1039,6 @@ namespace MediaBrowser.Dlna.PlayTo
             }
         }
 
-        private void DisposeVolumeTimer()
-        {
-            if (_volumeTimer != null)
-            {
-                _volumeTimer.Dispose();
-                _volumeTimer = null;
-            }
-        }
-
         #endregion
 
         public override string ToString()

+ 18 - 31
MediaBrowser.Dlna/PlayTo/PlayToController.cs

@@ -37,11 +37,28 @@ namespace MediaBrowser.Dlna.PlayTo
         private readonly IDeviceDiscovery _deviceDiscovery;
         private readonly string _serverAddress;
         private readonly string _accessToken;
+        private readonly DateTime _creationTime;
 
         public bool IsSessionActive
         {
             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;
             }
         }
@@ -55,8 +72,6 @@ namespace MediaBrowser.Dlna.PlayTo
             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)
         {
             _session = session;
@@ -72,6 +87,7 @@ namespace MediaBrowser.Dlna.PlayTo
             _mediaSourceManager = mediaSourceManager;
             _accessToken = accessToken;
             _logger = logger;
+            _creationTime = DateTime.UtcNow;
         }
 
         public void Init(Device device)
@@ -84,8 +100,6 @@ namespace MediaBrowser.Dlna.PlayTo
             _device.Start();
 
             _deviceDiscovery.DeviceLeft += _deviceDiscovery_DeviceLeft;
-
-            _updateTimer = new Timer(updateTimer_Elapsed, null, 60000, 60000);
         }
 
         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)
         {
             try
@@ -634,21 +632,10 @@ namespace MediaBrowser.Dlna.PlayTo
                 _device.MediaChanged -= _device_MediaChanged;
                 _deviceDiscovery.DeviceLeft -= _deviceDiscovery_DeviceLeft;
 
-                DisposeUpdateTimer();
-
                 _device.Dispose();
             }
         }
 
-        private void DisposeUpdateTimer()
-        {
-            if (_updateTimer != null)
-            {
-                _updateTimer.Dispose();
-                _updateTimer = null;
-            }
-        }
-
         private readonly CultureInfo _usCulture = new CultureInfo("en-US");
 
         public Task SendGeneralCommand(GeneralCommand command, CancellationToken cancellationToken)

+ 7 - 17
MediaBrowser.Dlna/PlayTo/PlayToManager.cs

@@ -36,7 +36,7 @@ namespace MediaBrowser.Dlna.PlayTo
         private readonly IMediaSourceManager _mediaSourceManager;
 
         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)
         {
@@ -57,19 +57,9 @@ namespace MediaBrowser.Dlna.PlayTo
 
         public void Start()
         {
-            _clearNonRenderersTimer = new Timer(OnClearUrlTimerCallback, null, TimeSpan.FromMinutes(10), TimeSpan.FromMinutes(10));
-
             _deviceDiscovery.DeviceDiscovered += _deviceDiscovery_DeviceDiscovered;
         }
 
-        private void OnClearUrlTimerCallback(object state)
-        {
-            lock (_nonRendererUrls)
-            {
-                _nonRendererUrls.Clear();
-            }
-        }
-
         async void _deviceDiscovery_DeviceDiscovered(object sender, SsdpMessageEventArgs e)
         {
             string usn;
@@ -99,6 +89,12 @@ namespace MediaBrowser.Dlna.PlayTo
 
                 lock (_nonRendererUrls)
                 {
+                    if ((DateTime.UtcNow - _lastRendererClear).TotalMinutes >= 10)
+                    {
+                        _nonRendererUrls.Clear();
+                        _lastRendererClear = DateTime.UtcNow;
+                    }
+
                     if (_nonRendererUrls.Contains(location, StringComparer.OrdinalIgnoreCase))
                     {
                         return;
@@ -181,12 +177,6 @@ namespace MediaBrowser.Dlna.PlayTo
         public void Dispose()
         {
             _deviceDiscovery.DeviceDiscovered -= _deviceDiscovery_DeviceDiscovered;
-
-            if (_clearNonRenderersTimer != null)
-            {
-                _clearNonRenderersTimer.Dispose();
-                _clearNonRenderersTimer = null;
-            }
         }
     }
 }

+ 5 - 5
MediaBrowser.Dlna/Profiles/WdtvLiveProfile.cs

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

+ 5 - 5
MediaBrowser.Dlna/Profiles/Xml/WDTV Live.xml

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

+ 15 - 115
MediaBrowser.Dlna/Ssdp/SsdpHandler.cs

@@ -33,12 +33,8 @@ namespace MediaBrowser.Dlna.Ssdp
         private readonly IPAddress _ssdpIp = IPAddress.Parse(SSDPAddr);
         private readonly IPEndPoint _ssdpEndp = new IPEndPoint(IPAddress.Parse(SSDPAddr), SSDPPort);
 
-        private Timer _queueTimer;
         private Timer _notificationTimer;
 
-        private readonly AutoResetEvent _datagramPosted = new AutoResetEvent(false);
-        private readonly ConcurrentQueue<Datagram> _messageQueue = new ConcurrentQueue<Datagram>();
-
         private bool _isDisposed;
         private readonly ConcurrentDictionary<Guid, List<UpnpDevice>> _devices = new ConcurrentDictionary<Guid, List<UpnpDevice>>();
 
@@ -121,9 +117,13 @@ namespace MediaBrowser.Dlna.Ssdp
 
         public void Start()
         {
-            RestartSocketListener();
+            DisposeSocket();
+            StopAliveNotifier();
 
+            RestartSocketListener();
             ReloadAliveNotifier();
+
+            SystemEvents.PowerModeChanged -= SystemEvents_PowerModeChanged;
             SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged;
         }
 
@@ -131,7 +131,7 @@ namespace MediaBrowser.Dlna.Ssdp
         {
             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);
         }
 
-        public void SendDatagram(string header,
+        public async void SendDatagram(string header,
             Dictionary<string, string> values,
             EndPoint endpoint,
             EndPoint localAddress,
@@ -162,28 +162,18 @@ namespace MediaBrowser.Dlna.Ssdp
             int sendCount)
         {
             var msg = new SsdpMessageBuilder().BuildMessage(header, values);
-            var queued = false;
 
             var enableDebugLogging = _config.GetDlnaConfiguration().EnableDebugLogging;
 
             for (var i = 0; i < sendCount; i++)
             {
-                var dgram = new Datagram(endpoint, localAddress, _logger, msg, isBroadcast, enableDebugLogging);
-
-                if (_messageQueue.Count == 0)
+                if (i > 0)
                 {
-                    dgram.Send();
+                    await Task.Delay(500).ConfigureAwait(false);
                 }
-                else
-                {
-                    _messageQueue.Enqueue(dgram);
-                    queued = true;
-                }
-            }
 
-            if (queued)
-            {
-                StartQueueTimer();
+                var dgram = new Datagram(endpoint, localAddress, _logger, msg, isBroadcast, enableDebugLogging);
+                dgram.Send();
             }
         }
 
@@ -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()
         {
             if (_isDisposed)
             {
-                StopSocketRetryTimer();
                 return;
             }
 
@@ -304,8 +257,6 @@ namespace MediaBrowser.Dlna.Ssdp
 
                 _logger.Info("MultiCast socket created");
 
-                StopSocketRetryTimer();
-
                 Receive();
             }
             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()
         {
             try
@@ -448,16 +374,9 @@ namespace MediaBrowser.Dlna.Ssdp
             SystemEvents.PowerModeChanged -= SystemEvents_PowerModeChanged;
 
             _isDisposed = true;
-            while (_messageQueue.Count != 0)
-            {
-                _datagramPosted.WaitOne();
-            }
 
             DisposeSocket();
-            DisposeQueueTimer();
-            DisposeNotificationTimer();
-
-            _datagramPosted.Dispose();
+            StopAliveNotifier();
         }
 
         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()
         {
             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)
         {
-            List<UpnpDevice> list;
-            lock (_devices)
-            {
-                if (!_devices.TryGetValue(uuid, out list))
-                {
-                    _devices.TryAdd(uuid, list = new List<UpnpDevice>());
-                }
-            }
+            var list = _devices.GetOrAdd(uuid, new List<UpnpDevice>());
 
             list.AddRange(services.Select(i => new UpnpDevice(uuid, i, descriptionUri, address)));
 
@@ -572,7 +472,7 @@ namespace MediaBrowser.Dlna.Ssdp
 
             if (!config.BlastAliveMessages)
             {
-                DisposeNotificationTimer();
+                StopAliveNotifier();
                 return;
             }
 
@@ -599,7 +499,7 @@ namespace MediaBrowser.Dlna.Ssdp
             }
         }
 
-        private void DisposeNotificationTimer()
+        private void StopAliveNotifier()
         {
             lock (_notificationTimerSyncLock)
             {

+ 18 - 9
MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs

@@ -292,16 +292,25 @@ namespace MediaBrowser.MediaEncoding.Encoder
                 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 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 it's mpeg based, assume true
+            if ((videoStream.Codec ?? string.Empty).IndexOf("mpeg", StringComparison.OrdinalIgnoreCase) != -1)
             {
-                var formats = (video.Container ?? string.Empty).Split(',').ToList();
-
-                if (!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 (enableInterlacedDection)
+                {
+                    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;
                 }

+ 2 - 3
MediaBrowser.Model/Configuration/ServerConfiguration.cs

@@ -164,7 +164,7 @@ namespace MediaBrowser.Model.Configuration
         /// different directories and files.
         /// </summary>
         /// <value>The file watcher delay.</value>
-        public int RealtimeLibraryMonitorDelay { get; set; }
+        public int LibraryMonitorDelay { get; set; }
 
         /// <summary>
         /// 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 bool MergeMetadataAndImagesByName { get; set; }
-        public bool EnableStandaloneMetadata { get; set; }
 
         /// <summary>
         /// Gets or sets the image saving convention.
@@ -256,7 +255,7 @@ namespace MediaBrowser.Model.Configuration
             MinResumeDurationSeconds = 300;
 
             EnableLibraryMonitor = AutoOnOff.Auto;
-            RealtimeLibraryMonitorDelay = 40;
+            LibraryMonitorDelay = 60;
 
             EnableInternetProviders = true;
             FindInternetTrailers = true;

+ 1 - 0
MediaBrowser.Model/Configuration/UserConfiguration.cs

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

+ 11 - 3
MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs

@@ -213,7 +213,7 @@ namespace MediaBrowser.Providers.MediaInfo
             }
 
             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);
             }
@@ -360,7 +360,15 @@ namespace MediaBrowser.Providers.MediaInfo
         /// <returns>VideoStream.</returns>
         private BlurayDiscInfo GetBDInfo(string path)
         {
-            return _blurayExaminer.GetDiscInfo(path);
+            try
+            {
+                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)
@@ -628,7 +636,7 @@ namespace MediaBrowser.Providers.MediaInfo
                 FetchFromDvdLib(item, mount);
             }
 
-            if (item.VideoType == VideoType.BluRay || (item.IsoType.HasValue && item.IsoType.Value == IsoType.BluRay))
+            if (blurayDiscInfo != null)
             {
                 item.PlayableStreamFileNames = blurayDiscInfo.Files.ToList();
             }

+ 7 - 11
MediaBrowser.Providers/People/MovieDbPersonProvider.cs

@@ -38,7 +38,7 @@ namespace MediaBrowser.Providers.People
 
         private int _requestCount;
         private readonly object _requestCountLock = new object();
-        private Timer _requestCountReset;
+        private DateTime _lastRequestCountReset;
 
         public MovieDbPersonProvider(IFileSystem fileSystem, IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IHttpClient httpClient, ILogger logger)
         {
@@ -48,16 +48,6 @@ namespace MediaBrowser.Providers.People
             _httpClient = httpClient;
             _logger = logger;
             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
@@ -101,6 +91,12 @@ namespace MediaBrowser.Providers.People
             {
                 lock (_requestCountLock)
                 {
+                    if ((DateTime.UtcNow - _lastRequestCountReset).TotalHours >= 1)
+                    {
+                        _requestCount = 0;
+                        _lastRequestCountReset = DateTime.UtcNow;
+                    }
+
                     var requestCount = _requestCount;
 
                     if (requestCount >= 5)

+ 2 - 2
MediaBrowser.Providers/TV/TvdbSeriesProvider.cs

@@ -238,7 +238,7 @@ namespace MediaBrowser.Providers.TV
                 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
             {
@@ -268,7 +268,7 @@ namespace MediaBrowser.Providers.TV
                 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");
 
             if (!string.Equals(downloadLangaugeXmlFile, saveAsLanguageXmlFile, StringComparison.OrdinalIgnoreCase))

+ 3 - 58
MediaBrowser.Server.Implementations/Channels/ChannelManager.cs

@@ -29,7 +29,7 @@ using CommonIO;
 
 namespace MediaBrowser.Server.Implementations.Channels
 {
-    public class ChannelManager : IChannelManager, IDisposable
+    public class ChannelManager : IChannelManager
     {
         private IChannel[] _channels;
 
@@ -47,11 +47,6 @@ namespace MediaBrowser.Server.Implementations.Channels
         private readonly ILocalizationManager _localization;
         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)
         {
             _userManager = userManager;
@@ -65,9 +60,6 @@ namespace MediaBrowser.Server.Implementations.Channels
             _localization = localization;
             _httpClient = httpClient;
             _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
@@ -206,6 +198,8 @@ namespace MediaBrowser.Server.Implementations.Channels
 
         public async Task RefreshChannels(IProgress<double> progress, CancellationToken cancellationToken)
         {
+            _refreshedItems.Clear();
+
             var allChannelsList = GetAllChannels().ToList();
 
             var numComplete = 0;
@@ -1471,12 +1465,6 @@ namespace MediaBrowser.Server.Implementations.Channels
 
             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)
             {
                 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))
             {
                 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;
-            }
-        }
     }
 }

+ 2 - 10
MediaBrowser.Server.Implementations/Configuration/ServerConfigurationManager.cs

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

+ 3 - 2
MediaBrowser.Server.Implementations/Connect/ConnectEntryPoint.cs

@@ -13,12 +13,13 @@ using System.Threading;
 using System.Threading.Tasks;
 using CommonIO;
 using MediaBrowser.Common.IO;
+using MediaBrowser.Common.Threading;
 
 namespace MediaBrowser.Server.Implementations.Connect
 {
     public class ConnectEntryPoint : IServerEntryPoint
     {
-        private Timer _timer;
+        private PeriodicTimer _timer;
         private readonly IHttpClient _httpClient;
         private readonly IApplicationPaths _appPaths;
         private readonly ILogger _logger;
@@ -43,7 +44,7 @@ namespace MediaBrowser.Server.Implementations.Connect
         {
             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" };

+ 5 - 8
MediaBrowser.Server.Implementations/Devices/DeviceRepository.cs

@@ -25,7 +25,7 @@ namespace MediaBrowser.Server.Implementations.Devices
         private readonly ILogger _logger;
         private readonly IFileSystem _fileSystem;
 
-        private ConcurrentBag<DeviceInfo> _devices;
+        private List<DeviceInfo> _devices;
 
         public DeviceRepository(IApplicationPaths appPaths, IJsonSerializer json, ILogger logger, IFileSystem fileSystem)
         {
@@ -93,17 +93,14 @@ namespace MediaBrowser.Server.Implementations.Devices
 
         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()

+ 3 - 2
MediaBrowser.Server.Implementations/EntryPoints/ExternalPortForwarding.cs

@@ -11,6 +11,7 @@ using System.IO;
 using System.Net;
 using System.Text;
 using System.Threading;
+using MediaBrowser.Common.Threading;
 
 namespace MediaBrowser.Server.Implementations.EntryPoints
 {
@@ -21,7 +22,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
         private readonly IServerConfigurationManager _config;
         private readonly ISsdpHandler _ssdp;
 
-        private Timer _timer;
+        private PeriodicTimer _timer;
         private bool _isStarted;
 
         public ExternalPortForwarding(ILogManager logmanager, IServerApplicationHost appHost, IServerConfigurationManager config, ISsdpHandler ssdp)
@@ -95,7 +96,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
             NatUtility.UnhandledException += NatUtility_UnhandledException;
             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;
 

+ 3 - 2
MediaBrowser.Server.Implementations/EntryPoints/LoadRegistrations.cs

@@ -4,6 +4,7 @@ using MediaBrowser.Model.Logging;
 using System;
 using System.Threading;
 using System.Threading.Tasks;
+using MediaBrowser.Common.Threading;
 
 namespace MediaBrowser.Server.Implementations.EntryPoints
 {
@@ -22,7 +23,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
         /// </summary>
         private readonly ILogger _logger;
 
-        private Timer _timer;
+        private PeriodicTimer _timer;
 
         /// <summary>
         /// Initializes a new instance of the <see cref="LoadRegistrations" /> class.
@@ -41,7 +42,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
         /// </summary>
         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()

+ 5 - 11
MediaBrowser.Server.Implementations/EntryPoints/UsageEntryPoint.cs

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

+ 119 - 114
MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs

@@ -76,50 +76,50 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
             {
                 var seasonNumber = episodeInfo.SeasonNumber;
 
-				result.ExtractedSeasonNumber = seasonNumber;
-
-				// Passing in true will include a few extra regex's
-				var episodeNumber = episodeInfo.EpisodeNumber;
-
-				result.ExtractedEpisodeNumber = episodeNumber;
-
-				var premiereDate = episodeInfo.IsByDate ? 
-					new DateTime(episodeInfo.Year.Value, episodeInfo.Month.Value, episodeInfo.Day.Value) : 
-					(DateTime?)null;
-
-				if (episodeInfo.IsByDate || (seasonNumber.HasValue && episodeNumber.HasValue))
-				{
-					if (episodeInfo.IsByDate) 
-					{
-						_logger.Debug("Extracted information from {0}. Series name {1}, Date {2}", path, seriesName, premiereDate.Value);
-					} 
-					else 
-					{
-						_logger.Debug("Extracted information from {0}. Series name {1}, Season {2}, Episode {3}", path, seriesName, seasonNumber, episodeNumber);
-					}
-
-					var endingEpisodeNumber = episodeInfo.EndingEpsiodeNumber;
-
-					result.ExtractedEndingEpisodeNumber = endingEpisodeNumber;
-
-					await OrganizeEpisode(path, 
-						seriesName, 
-						seasonNumber, 
-						episodeNumber, 
-						endingEpisodeNumber, 
-						premiereDate,
-						options, 
-						overwriteExisting, 
-						result, 
-						cancellationToken).ConfigureAwait(false);
-				}
-				else
-				{
-					var msg = string.Format("Unable to determine episode number from {0}", path);
-					result.Status = FileSortingStatus.Failure;
-					result.StatusMessage = msg;
-					_logger.Warn(msg);
-				}
+                result.ExtractedSeasonNumber = seasonNumber;
+
+                // Passing in true will include a few extra regex's
+                var episodeNumber = episodeInfo.EpisodeNumber;
+
+                result.ExtractedEpisodeNumber = episodeNumber;
+
+                var premiereDate = episodeInfo.IsByDate ?
+                    new DateTime(episodeInfo.Year.Value, episodeInfo.Month.Value, episodeInfo.Day.Value) :
+                    (DateTime?)null;
+
+                if (episodeInfo.IsByDate || (seasonNumber.HasValue && episodeNumber.HasValue))
+                {
+                    if (episodeInfo.IsByDate)
+                    {
+                        _logger.Debug("Extracted information from {0}. Series name {1}, Date {2}", path, seriesName, premiereDate.Value);
+                    }
+                    else
+                    {
+                        _logger.Debug("Extracted information from {0}. Series name {1}, Season {2}, Episode {3}", path, seriesName, seasonNumber, episodeNumber);
+                    }
+
+                    var endingEpisodeNumber = episodeInfo.EndingEpsiodeNumber;
+
+                    result.ExtractedEndingEpisodeNumber = endingEpisodeNumber;
+
+                    await OrganizeEpisode(path,
+                        seriesName,
+                        seasonNumber,
+                        episodeNumber,
+                        endingEpisodeNumber,
+                        premiereDate,
+                        options,
+                        overwriteExisting,
+                        result,
+                        cancellationToken).ConfigureAwait(false);
+                }
+                else
+                {
+                    var msg = string.Format("Unable to determine episode number from {0}", path);
+                    result.Status = FileSortingStatus.Failure;
+                    result.StatusMessage = msg;
+                    _logger.Warn(msg);
+                }
             }
             else
             {
@@ -151,32 +151,32 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
 
             var series = (Series)_libraryManager.GetItemById(new Guid(request.SeriesId));
 
-            await OrganizeEpisode(result.OriginalPath, 
-				series, 
-				request.SeasonNumber, 
-				request.EpisodeNumber, 
-				request.EndingEpisodeNumber, 
-				null,
-				options, 
-				true, 
-				result, 
-				cancellationToken).ConfigureAwait(false);
+            await OrganizeEpisode(result.OriginalPath,
+                series,
+                request.SeasonNumber,
+                request.EpisodeNumber,
+                request.EndingEpisodeNumber,
+                null,
+                options,
+                true,
+                result,
+                cancellationToken).ConfigureAwait(false);
 
             await _organizationService.SaveResult(result, CancellationToken.None).ConfigureAwait(false);
 
             return result;
         }
 
-        private Task OrganizeEpisode(string sourcePath, 
-			string seriesName, 
-			int? seasonNumber, 
-			int? episodeNumber, 
-			int? endingEpiosdeNumber, 
-			DateTime? premiereDate,
-			TvFileOrganizationOptions options, 
-			bool overwriteExisting, 
-			FileOrganizationResult result, 
-			CancellationToken cancellationToken)
+        private Task OrganizeEpisode(string sourcePath,
+            string seriesName,
+            int? seasonNumber,
+            int? episodeNumber,
+            int? endingEpiosdeNumber,
+            DateTime? premiereDate,
+            TvFileOrganizationOptions options,
+            bool overwriteExisting,
+            FileOrganizationResult result,
+            CancellationToken cancellationToken)
         {
             var series = GetMatchingSeries(seriesName, result);
 
@@ -189,33 +189,33 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
                 return Task.FromResult(true);
             }
 
-            return OrganizeEpisode(sourcePath, 
-				series, 
-				seasonNumber, 
-				episodeNumber, 
-				endingEpiosdeNumber,
-				premiereDate,
-				options, 
-				overwriteExisting, 
-				result, 
-				cancellationToken);
+            return OrganizeEpisode(sourcePath,
+                series,
+                seasonNumber,
+                episodeNumber,
+                endingEpiosdeNumber,
+                premiereDate,
+                options,
+                overwriteExisting,
+                result,
+                cancellationToken);
         }
 
-        private async Task OrganizeEpisode(string sourcePath, 
-			Series series, 
-			int? seasonNumber, 
-			int? episodeNumber, 
-			int? endingEpiosdeNumber, 
-			DateTime? premiereDate,
-			TvFileOrganizationOptions options, 
-			bool overwriteExisting, 
-			FileOrganizationResult result, 
-			CancellationToken cancellationToken)
+        private async Task OrganizeEpisode(string sourcePath,
+            Series series,
+            int? seasonNumber,
+            int? episodeNumber,
+            int? endingEpiosdeNumber,
+            DateTime? premiereDate,
+            TvFileOrganizationOptions options,
+            bool overwriteExisting,
+            FileOrganizationResult result,
+            CancellationToken cancellationToken)
         {
             _logger.Info("Sorting file {0} into series {1}", sourcePath, series.Path);
 
             // Proceed to sort the file
-			var newPath = await GetNewPath(sourcePath, series, seasonNumber, episodeNumber, endingEpiosdeNumber, premiereDate, options, cancellationToken).ConfigureAwait(false);
+            var newPath = await GetNewPath(sourcePath, series, seasonNumber, episodeNumber, endingEpiosdeNumber, premiereDate, options, cancellationToken).ConfigureAwait(false);
 
             if (string.IsNullOrEmpty(newPath))
             {
@@ -324,17 +324,17 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
             }
         }
 
-        private List<string> GetOtherDuplicatePaths(string targetPath, 
-			Series series, 
-			int? seasonNumber, 
-			int? episodeNumber, 
-			int? endingEpisodeNumber)
+        private List<string> GetOtherDuplicatePaths(string targetPath,
+            Series series,
+            int? seasonNumber,
+            int? episodeNumber,
+            int? endingEpisodeNumber)
         {
-			// TODO: Support date-naming?
-			if (!seasonNumber.HasValue || episodeNumber.HasValue) 
-			{
-				return new List<string> ();
-			}
+            // TODO: Support date-naming?
+            if (!seasonNumber.HasValue || episodeNumber.HasValue)
+            {
+                return new List<string>();
+            }
 
             var episodePaths = series.GetRecursiveChildren()
                 .OfType<Episode>()
@@ -462,16 +462,18 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
         /// <param name="seasonNumber">The season number.</param>
         /// <param name="episodeNumber">The 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="cancellationToken">The cancellation token.</param>
         /// <returns>System.String.</returns>
-        private async Task<string> GetNewPath(string sourcePath, 
-			Series series, 
-			int? seasonNumber, 
-			int? episodeNumber, 
-			int? endingEpisodeNumber, 
-			DateTime? premiereDate,
-			TvFileOrganizationOptions options, 
-			CancellationToken cancellationToken)
+        private async Task<string> GetNewPath(string sourcePath,
+            Series series,
+            int? seasonNumber,
+            int? episodeNumber,
+            int? endingEpisodeNumber,
+            DateTime? premiereDate,
+            TvFileOrganizationOptions options,
+            CancellationToken cancellationToken)
         {
             var episodeInfo = new EpisodeInfo
             {
@@ -481,7 +483,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
                 MetadataLanguage = series.GetPreferredMetadataLanguage(),
                 ParentIndexNumber = seasonNumber,
                 SeriesProviderIds = series.ProviderIds,
-				PremiereDate = premiereDate
+                PremiereDate = premiereDate
             };
 
             var searchResults = await _providerManager.GetRemoteSearchResults<Episode, EpisodeInfo>(new RemoteSearchQuery<EpisodeInfo>
@@ -491,22 +493,25 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
             }, cancellationToken).ConfigureAwait(false);
 
             var episode = searchResults.FirstOrDefault();
-            
-            string episodeName = string.Empty;
 
             if (episode == null)
             {
                 var msg = string.Format("No provider metadata found for {0} season {1} episode {2}", series.Name, seasonNumber, episodeNumber);
                 _logger.Warn(msg);
-                //throw new Exception(msg);
+                return null;
             }
-            else
-            {
-                episodeName = episode.Name;
-			}
 
-			seasonNumber = seasonNumber ?? episode.ParentIndexNumber;
-			episodeNumber = episodeNumber ?? episode.IndexNumber;
+            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;
+            episodeNumber = episodeNumber ?? episode.IndexNumber;
 
             var newPath = GetSeasonFolderPath(series, seasonNumber.Value, options);
 
@@ -579,7 +584,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
         {
             seriesName = _fileSystem.GetValidFilename(seriesName).Trim();
 
-            if (string.IsNullOrEmpty(episodeTitle))
+            if (string.IsNullOrWhiteSpace(episodeTitle))
             {
                 episodeTitle = string.Empty;
             }

+ 31 - 3
MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs

@@ -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>
         /// Overridable method that can be used to implement a custom hnandler
         /// </summary>
@@ -271,6 +290,14 @@ namespace MediaBrowser.Server.Implementations.HttpServer
             var operationName = httpReq.OperationName;
             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) ||
                 string.Equals(localPath, "/emby/", StringComparison.OrdinalIgnoreCase))
             {
@@ -333,15 +360,16 @@ namespace MediaBrowser.Server.Implementations.HttpServer
                 task.ContinueWith(x => httpRes.Close(), TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.AttachedToParent);
                 //Matches Exceptions handled in HttpListenerBase.InitTask()
 
-                var urlString = url.ToString();
-
                 task.ContinueWith(x =>
                 {
                     var statusCode = httpRes.StatusCode;
 
                     var duration = DateTime.Now - date;
 
-                    LoggerUtils.LogResponse(_logger, statusCode, urlString, remoteIp, duration);
+                    if (enableLog)
+                    {
+                        LoggerUtils.LogResponse(_logger, statusCode, urlString, remoteIp, duration);
+                    }
 
                 }, TaskContinuationOptions.None);
                 return task;

+ 19 - 0
MediaBrowser.Server.Implementations/HttpServer/LoggerUtils.cs

@@ -1,11 +1,30 @@
 using MediaBrowser.Model.Logging;
 using System;
 using System.Globalization;
+using System.IO;
+using SocketHttpListener.Net;
 
 namespace MediaBrowser.Server.Implementations.HttpServer
 {
     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>
         /// Logs the response.
         /// </summary>

+ 2 - 40
MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs

@@ -78,10 +78,10 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
         {
             var request = context.Request;
 
-            LogRequest(_logger, request);
-
             if (request.IsWebSocketRequest)
             {
+                LoggerUtils.LogRequest(_logger, request);
+
                 ProcessWebSocketRequest(context);
                 return Task.FromResult(true);
             }
@@ -156,44 +156,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
             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)
         {
             var httpReq = GetRequest(context);

+ 9 - 3
MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs

@@ -471,11 +471,11 @@ namespace MediaBrowser.Server.Implementations.IO
             {
                 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
                 {
-                    _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)
         {
+            if (Environment.OSVersion.Platform != PlatformID.Win32NT)
+            {
+                // Causing lockups on linux
+                return false;
+            }
+
             try
             {
                 var data = _fileSystem.GetFileSystemInfo(path);
 
                 if (!data.Exists
-                    || data.Attributes.HasFlag(FileAttributes.Directory)
+                    || data.IsDirectory
 
                     // Opening a writable stream will fail with readonly files
                     || data.Attributes.HasFlag(FileAttributes.ReadOnly))

+ 2 - 2
MediaBrowser.Server.Implementations/Library/LibraryManager.cs

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

+ 11 - 2
MediaBrowser.Server.Implementations/Library/Resolvers/PhotoResolver.cs

@@ -51,8 +51,17 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers
         {
             var filename = Path.GetFileNameWithoutExtension(path) ?? string.Empty;
 
-            return !IgnoreFiles.Contains(filename, StringComparer.OrdinalIgnoreCase)
-                && imageProcessor.SupportedInputFormats.Contains((Path.GetExtension(path) ?? string.Empty).TrimStart('.'), StringComparer.OrdinalIgnoreCase);
+            if (IgnoreFiles.Contains(filename, 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);
         }
 
     }

+ 8 - 1
MediaBrowser.Server.Implementations/Library/UserViewManager.cs

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

+ 80 - 20
MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs

@@ -22,12 +22,15 @@ using MediaBrowser.Server.Implementations.FileOrganization;
 using System;
 using System.Collections.Concurrent;
 using System.Collections.Generic;
+using System.Globalization;
 using System.IO;
 using System.Linq;
 using System.Threading;
 using System.Threading.Tasks;
 using CommonIO;
 using MediaBrowser.Common.Extensions;
+using MediaBrowser.Controller.Power;
+using Microsoft.Win32;
 
 namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
 {
@@ -55,7 +58,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
 
         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;
 
@@ -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));
             _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;
         }
 
         public void Start()
         {
             _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;
@@ -155,7 +171,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
                 {
                     epgData = GetEpgDataForChannel(timer.ChannelId);
                 }
-                await UpdateTimersForSeriesTimer(epgData, timer).ConfigureAwait(false);
+                await UpdateTimersForSeriesTimer(epgData, timer, false).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)
         {
-            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)
             {
                 CancelTimerInternal(timer.Id);
@@ -332,25 +352,44 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
             }
 
             _seriesTimerProvider.Add(info);
-            await UpdateTimersForSeriesTimer(epgData, info).ConfigureAwait(false);
+            await UpdateTimersForSeriesTimer(epgData, info, false).ConfigureAwait(false);
         }
 
         public async Task UpdateSeriesTimerAsync(SeriesTimerInfo info, CancellationToken cancellationToken)
         {
-            _seriesTimerProvider.Update(info);
-            List<ProgramInfo> epgData;
-            if (info.RecordAnyChannel)
-            {
-                var channels = await GetChannelsAsync(true, CancellationToken.None).ConfigureAwait(false);
-                var channelIds = channels.Select(i => i.Id).ToList();
-                epgData = GetEpgDataForChannels(channelIds);
-            }
-            else
+            var instance = _seriesTimerProvider.GetAll().FirstOrDefault(i => string.Equals(i.Id, info.Id, StringComparison.OrdinalIgnoreCase));
+
+            if (instance != null)
             {
-                epgData = GetEpgDataForChannel(info.ChannelId);
-            }
+                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;
 
-            await UpdateTimersForSeriesTimer(epgData, info).ConfigureAwait(false);
+                _seriesTimerProvider.Update(instance);
+
+                List<ProgramInfo> epgData;
+                if (instance.RecordAnyChannel)
+                {
+                    var channels = await GetChannelsAsync(true, CancellationToken.None).ConfigureAwait(false);
+                    var channelIds = channels.Select(i => i.Id).ToList();
+                    epgData = GetEpgDataForChannels(channelIds);
+                }
+                else
+                {
+                    epgData = GetEpgDataForChannel(instance.ChannelId);
+                }
+
+                await UpdateTimersForSeriesTimer(epgData, instance, true).ConfigureAwait(false);
+            }
         }
 
         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);
                 }
+                else
+                {
+                    _logger.Info("Skipping RecordStream because it's already in progress.");
+                }
             }
             catch (OperationCanceledException)
             {
@@ -721,7 +764,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
                     recording.DateLastUpdated = DateTime.UtcNow;
                     _recordingProvider.AddOrUpdate(recording);
 
-                    _logger.Info("Beginning recording.");
+                    _logger.Info("Beginning recording. Will record for {0} minutes.", duration.TotalMinutes.ToString(CultureInfo.InvariantCulture));
 
                     httpRequestOptions.BufferContent = false;
                     var durationToken = new CancellationTokenSource(duration);
@@ -836,7 +879,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
             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();
 
@@ -849,12 +892,29 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
                     _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)
         {
             // 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);
 

+ 1 - 1
MediaBrowser.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs

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

+ 34 - 3
MediaBrowser.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs

@@ -5,22 +5,27 @@ using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Serialization;
 using System;
 using System.Collections.Concurrent;
+using System.Globalization;
 using System.Linq;
 using System.Threading;
 using CommonIO;
-using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.Power;
 
 namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
 {
     public class TimerManager : ItemDataProvider<TimerInfo>
     {
         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 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))
         {
+            _powerManagement = powerManagement;
+            _logger = logger1;
         }
 
         public void RestartTimers()
@@ -58,6 +63,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
             {
                 var timespan = RecordingHelper.GetStartTime(item) - DateTime.UtcNow;
                 timer.Change(timespan, TimeSpan.Zero);
+                ScheduleWake(item);
             }
             else
             {
@@ -74,6 +80,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
 
             base.Add(item);
             AddTimer(item);
+            ScheduleWake(item);
         }
 
         private void AddTimer(TimerInfo item)
@@ -91,15 +98,39 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
             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)
         {
             StopTimer(item);
 
             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();
+                _logger.Warn("Timer already exists for item {0}", item.Id);
             }
         }
 

+ 1 - 0
MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs

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

+ 11 - 11
MediaBrowser.Server.Implementations/Localization/Core/ca.json

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

+ 2 - 2
MediaBrowser.Server.Implementations/Localization/Core/nl.json

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

+ 2 - 2
MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj

@@ -48,9 +48,9 @@
     <Reference Include="Interfaces.IO">
       <HintPath>..\packages\Interfaces.IO.1.0.0.5\lib\portable-net45+sl4+wp71+win8+wpa81\Interfaces.IO.dll</HintPath>
     </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>
-      <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 Include="MoreLinq">
       <HintPath>..\packages\morelinq.1.4.0\lib\net35\MoreLinq.dll</HintPath>

+ 3 - 3
MediaBrowser.Server.Implementations/News/NewsEntryPoint.cs

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

+ 1 - 1
MediaBrowser.Server.Implementations/packages.config

@@ -2,7 +2,7 @@
 <packages>
   <package id="CommonIO" version="1.0.0.7" 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="morelinq" version="1.4.0" targetFramework="net45" />
   <package id="Patterns.Logging" version="1.0.0.2" targetFramework="net45" />

+ 14 - 0
MediaBrowser.Server.Mono/Native/BaseMonoApp.cs

@@ -8,6 +8,7 @@ using System;
 using System.Collections.Generic;
 using System.Reflection;
 using System.Text.RegularExpressions;
+using MediaBrowser.Controller.Power;
 
 namespace MediaBrowser.Server.Mono.Native
 {
@@ -203,5 +204,18 @@ namespace MediaBrowser.Server.Mono.Native
             public string sysname = 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();
+        }
     }
 }

+ 23 - 17
MediaBrowser.Server.Startup.Common/ApplicationHost.cs

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

+ 3 - 3
MediaBrowser.Server.Startup.Common/EntryPoints/KeepServerAwake.cs

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

+ 7 - 0
MediaBrowser.Server.Startup.Common/INativeApp.cs

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

+ 1 - 1
MediaBrowser.ServerApplication/MainStartup.cs

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

+ 1 - 0
MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj

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

+ 9 - 1
MediaBrowser.ServerApplication/Native/WindowsApp.cs

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

+ 94 - 0
MediaBrowser.ServerApplication/Native/WindowsPowerManagement.cs

@@ -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);
+            }
+        }
+    }
+}

+ 1 - 2
MediaBrowser.WebDashboard/Api/DashboardService.cs

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

+ 0 - 6
MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj

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

+ 11 - 4
MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs

@@ -215,7 +215,11 @@ namespace MediaBrowser.XbmcMetadata.Parsers
                         if (!string.IsNullOrWhiteSpace(val))
                         {
                             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();
                             }
@@ -627,7 +631,10 @@ namespace MediaBrowser.XbmcMetadata.Parsers
                         {
                             var person = GetPersonFromXmlNode(subtree);
 
-                            itemResult.AddPerson(person);
+                            if (!string.IsNullOrWhiteSpace(person.Name))
+                            {
+                                itemResult.AddPerson(person);
+                            }
                         }
                         break;
                     }
@@ -976,11 +983,11 @@ namespace MediaBrowser.XbmcMetadata.Parsers
                         if (!string.IsNullOrWhiteSpace(val) && !string.IsNullOrWhiteSpace(userDataUserId))
                         {
                             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);
 
-                                userData.LastPlayedDate = parsedValue;
+                                userData.LastPlayedDate = parsedValue.ToUniversalTime();
                             }
                         }
                         break;

+ 4 - 2
MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs

@@ -416,6 +416,8 @@ namespace MediaBrowser.XbmcMetadata.Savers
             writer.WriteEndElement();
         }
 
+        public const string DateAddedFormat = "yyyy-MM-dd HH:mm:ss";
+
         /// <summary>
         /// Adds the common nodes.
         /// </summary>
@@ -472,7 +474,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
                 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("originaltitle", item.Name ?? string.Empty);
@@ -949,7 +951,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
 
                 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");