浏览代码

Merge pull request #1 from MediaBrowser/dev

Dev
jluce50 10 年之前
父节点
当前提交
6c2e01830c
共有 96 个文件被更改,包括 1616 次插入467 次删除
  1. 1 1
      MediaBrowser.Api/Images/ImageService.cs
  2. 0 3
      MediaBrowser.Api/Library/LibraryService.cs
  3. 1 0
      MediaBrowser.Api/MediaBrowser.Api.csproj
  4. 30 26
      MediaBrowser.Api/Playback/BaseStreamingService.cs
  5. 2 1
      MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
  6. 4 1
      MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
  7. 2 1
      MediaBrowser.Api/Playback/Hls/MpegDashService.cs
  8. 2 1
      MediaBrowser.Api/Playback/Hls/VideoHlsService.cs
  9. 1 0
      MediaBrowser.Api/Playback/MediaInfoService.cs
  10. 2 1
      MediaBrowser.Api/Playback/Progressive/AudioService.cs
  11. 3 100
      MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs
  12. 2 1
      MediaBrowser.Api/Playback/Progressive/VideoService.cs
  13. 0 1
      MediaBrowser.Api/Playback/StreamRequest.cs
  14. 21 3
      MediaBrowser.Api/Playback/StreamState.cs
  15. 164 0
      MediaBrowser.Api/Playback/TranscodingThrottler.cs
  16. 28 0
      MediaBrowser.Controller/Diagnostics/IProcessManager.cs
  17. 2 4
      MediaBrowser.Controller/Drawing/IImageProcessor.cs
  18. 1 1
      MediaBrowser.Controller/Entities/Audio/Audio.cs
  19. 10 5
      MediaBrowser.Controller/Entities/Video.cs
  20. 18 0
      MediaBrowser.Controller/Library/IMediaSourceManager.cs
  21. 1 0
      MediaBrowser.Controller/Library/IUserManager.cs
  22. 2 1
      MediaBrowser.Controller/MediaBrowser.Controller.csproj
  23. 0 5
      MediaBrowser.Controller/Net/StaticResultOptions.cs
  24. 0 35
      MediaBrowser.Controller/Sync/ICloudSyncProvider.cs
  25. 46 15
      MediaBrowser.Controller/Sync/IServerSyncProvider.cs
  26. 41 0
      MediaBrowser.Controller/Sync/ISyncDataProvider.cs
  27. 5 7
      MediaBrowser.Controller/Sync/ISyncProvider.cs
  28. 1 1
      MediaBrowser.Dlna/Didl/DidlBuilder.cs
  29. 0 18
      MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs
  30. 3 3
      MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
  31. 6 0
      MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj
  32. 6 0
      MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj
  33. 6 0
      MediaBrowser.Model/ApiClient/ConnectionOptions.cs
  34. 0 12
      MediaBrowser.Model/Configuration/UserConfiguration.cs
  35. 10 0
      MediaBrowser.Model/Dlna/PlaybackErrorCode.cs
  36. 9 0
      MediaBrowser.Model/Dlna/PlaybackException.cs
  37. 40 9
      MediaBrowser.Model/Dlna/StreamBuilder.cs
  38. 1 1
      MediaBrowser.Model/Dlna/StreamInfo.cs
  39. 2 0
      MediaBrowser.Model/Dto/MediaSourceInfo.cs
  40. 2 0
      MediaBrowser.Model/MediaBrowser.Model.csproj
  41. 2 1
      MediaBrowser.Model/Notifications/NotificationType.cs
  42. 2 0
      MediaBrowser.Model/Users/UserPolicy.cs
  43. 9 1
      MediaBrowser.Providers/Manager/MetadataService.cs
  44. 17 0
      MediaBrowser.Providers/Movies/MovieMetadataService.cs
  45. 1 1
      MediaBrowser.Providers/Photos/PhotoProvider.cs
  46. 17 0
      MediaBrowser.Providers/TV/SeriesMetadataService.cs
  47. 6 0
      MediaBrowser.Server.Implementations/Devices/CameraUploadsFolder.cs
  48. 2 1
      MediaBrowser.Server.Implementations/Drawing/ImageHeader.cs
  49. 10 37
      MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs
  50. 1 4
      MediaBrowser.Server.Implementations/Dto/DtoService.cs
  51. 12 0
      MediaBrowser.Server.Implementations/EntryPoints/ActivityLogEntryPoint.cs
  52. 17 1
      MediaBrowser.Server.Implementations/EntryPoints/Notifications/Notifications.cs
  53. 0 8
      MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs
  54. 0 12
      MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs
  55. 0 12
      MediaBrowser.Server.Implementations/HttpServer/StreamWriter.cs
  56. 55 0
      MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs
  57. 5 6
      MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs
  58. 38 9
      MediaBrowser.Server.Implementations/Library/UserManager.cs
  59. 1 1
      MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs
  60. 15 2
      MediaBrowser.Server.Implementations/Localization/Server/server.json
  61. 7 2
      MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
  62. 11 0
      MediaBrowser.Server.Implementations/Notifications/CoreNotificationTypes.cs
  63. 12 2
      MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs
  64. 13 2
      MediaBrowser.Server.Implementations/Photos/DynamicImageHelpers.cs
  65. 18 9
      MediaBrowser.Server.Implementations/Session/SessionManager.cs
  66. 70 0
      MediaBrowser.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs
  67. 0 1
      MediaBrowser.Server.Implementations/Sorting/DatePlayedComparer.cs
  68. 14 1
      MediaBrowser.Server.Implementations/Sync/AppSyncProvider.cs
  69. 118 0
      MediaBrowser.Server.Implementations/Sync/CloudSyncProfile.cs
  70. 0 59
      MediaBrowser.Server.Implementations/Sync/CloudSyncProvider.cs
  71. 31 0
      MediaBrowser.Server.Implementations/Sync/FolderSync/FolderSyncDataProvider.cs
  72. 143 0
      MediaBrowser.Server.Implementations/Sync/FolderSync/FolderSyncProvider.cs
  73. 15 0
      MediaBrowser.Server.Implementations/Sync/IHasSyncProfile.cs
  74. 206 19
      MediaBrowser.Server.Implementations/Sync/MediaSync.cs
  75. 69 0
      MediaBrowser.Server.Implementations/Sync/MultiProviderSync.cs
  76. 23 9
      MediaBrowser.Server.Implementations/Sync/SyncManager.cs
  77. 1 1
      MediaBrowser.Server.Implementations/packages.config
  78. 25 0
      MediaBrowser.Server.Mono/Diagnostics/LinuxProcessManager.cs
  79. 1 0
      MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj
  80. 13 0
      MediaBrowser.Server.Mono/Native/BaseMonoApp.cs
  81. 2 0
      MediaBrowser.Server.Startup.Common/ApplicationHost.cs
  82. 23 0
      MediaBrowser.Server.Startup.Common/Diagnostics/ProcessManager.cs
  83. 7 0
      MediaBrowser.Server.Startup.Common/INativeApp.cs
  84. 1 0
      MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj
  85. 2 1
      MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj
  86. 7 0
      MediaBrowser.ServerApplication/Native/WindowsApp.cs
  87. 78 0
      MediaBrowser.ServerApplication/Native/WindowsProcessManager.cs
  88. 1 1
      MediaBrowser.ServerApplication/packages.config
  89. 1 0
      MediaBrowser.WebDashboard/Api/PackageCreator.cs
  90. 9 0
      MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj
  91. 1 1
      MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs
  92. 3 0
      MediaBrowser.sln
  93. 2 2
      Nuget/MediaBrowser.Common.Internal.nuspec
  94. 1 1
      Nuget/MediaBrowser.Common.nuspec
  95. 1 1
      Nuget/MediaBrowser.Model.Signed.nuspec
  96. 2 2
      Nuget/MediaBrowser.Server.Core.nuspec

+ 1 - 1
MediaBrowser.Api/Images/ImageService.cs

@@ -318,7 +318,7 @@ namespace MediaBrowser.Api.Images
 
                 try
                 {
-                    var size = _imageProcessor.GetImageSize(info.Path, info.DateModified);
+                    var size = _imageProcessor.GetImageSize(info);
 
                     width = Convert.ToInt32(size.Width);
                     height = Convert.ToInt32(size.Height);

+ 0 - 3
MediaBrowser.Api/Library/LibraryService.cs

@@ -1,5 +1,4 @@
 using MediaBrowser.Controller.Activity;
-using MediaBrowser.Controller.Channels;
 using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Audio;
@@ -9,11 +8,9 @@ using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Localization;
 using MediaBrowser.Controller.Net;
 using MediaBrowser.Controller.Persistence;
-using MediaBrowser.Controller.Session;
 using MediaBrowser.Model.Activity;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Querying;
 using ServiceStack;
 using System;

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

@@ -81,6 +81,7 @@
     <Compile Include="Library\ChapterService.cs" />
     <Compile Include="Playback\Hls\MpegDashService.cs" />
     <Compile Include="Playback\MediaInfoService.cs" />
+    <Compile Include="Playback\TranscodingThrottler.cs" />
     <Compile Include="PlaylistService.cs" />
     <Compile Include="Reports\ReportFieldType.cs" />
     <Compile Include="Reports\ReportResult.cs" />

+ 30 - 26
MediaBrowser.Api/Playback/BaseStreamingService.cs

@@ -1,4 +1,5 @@
 using MediaBrowser.Controller.Devices;
+using MediaBrowser.Controller.Diagnostics;
 using MediaBrowser.Model.Extensions;
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.IO;
@@ -70,12 +71,14 @@ namespace MediaBrowser.Api.Playback
         protected IDeviceManager DeviceManager { get; private set; }
         protected IChannelManager ChannelManager { get; private set; }
         protected ISubtitleEncoder SubtitleEncoder { get; private set; }
+        protected IProcessManager ProcessManager { get; private set; }
 
         /// <summary>
         /// Initializes a new instance of the <see cref="BaseStreamingService" /> class.
         /// </summary>
-        protected BaseStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager)
+        protected BaseStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IProcessManager processManager)
         {
+            ProcessManager = processManager;
             DeviceManager = deviceManager;
             SubtitleEncoder = subtitleEncoder;
             ChannelManager = channelManager;
@@ -877,14 +880,6 @@ namespace MediaBrowser.Api.Playback
             return "copy";
         }
 
-        private bool SupportsThrottleWithStream
-        {
-            get
-            {
-                return false;
-            }
-        }
-
         /// <summary>
         /// Gets the input argument.
         /// </summary>
@@ -908,23 +903,15 @@ namespace MediaBrowser.Api.Playback
 
         private string GetInputPathArgument(string transcodingJobId, StreamState state)
         {
-            if (state.InputProtocol == MediaProtocol.File &&
-               state.RunTimeTicks.HasValue &&
-               state.VideoType == VideoType.VideoFile &&
-               !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
-            {
-                if (state.RunTimeTicks.Value >= TimeSpan.FromMinutes(5).Ticks && state.IsInputVideo)
-                {
-                    if (SupportsThrottleWithStream)
-                    {
-                        var url = "http://localhost:" + ServerConfigurationManager.Configuration.HttpServerPortNumber.ToString(UsCulture) + "/videos/" + state.Request.Id + "/stream?static=true&Throttle=true&mediaSourceId=" + state.Request.MediaSourceId;
-
-                        url += "&transcodingJobId=" + transcodingJobId;
-
-                        return string.Format("\"{0}\"", url);
-                    }
-                }
-            }
+            //if (state.InputProtocol == MediaProtocol.File &&
+            //   state.RunTimeTicks.HasValue &&
+            //   state.VideoType == VideoType.VideoFile &&
+            //   !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
+            //{
+            //    if (state.RunTimeTicks.Value >= TimeSpan.FromMinutes(5).Ticks && state.IsInputVideo)
+            //    {
+            //    }
+            //}
 
             var protocol = state.InputProtocol;
 
@@ -1109,9 +1096,26 @@ namespace MediaBrowser.Api.Playback
                 }
             }
 
+            StartThrottler(state, transcodingJob);
+
             return transcodingJob;
         }
 
+        private void StartThrottler(StreamState state, TranscodingJob transcodingJob)
+        {
+            if (state.InputProtocol == MediaProtocol.File &&
+                           state.RunTimeTicks.HasValue &&
+                           state.VideoType == VideoType.VideoFile &&
+                           !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
+            {
+                if (state.RunTimeTicks.Value >= TimeSpan.FromMinutes(5).Ticks && state.IsInputVideo)
+                {
+                    state.TranscodingThrottler = new TranscodingThrottler(transcodingJob, Logger, ProcessManager);
+                    state.TranscodingThrottler.Start();
+                }
+            }
+        }
+
         private async void StartStreamingLog(TranscodingJob transcodingJob, StreamState state, Stream source, Stream target)
         {
             try

+ 2 - 1
MediaBrowser.Api/Playback/Hls/BaseHlsService.cs

@@ -2,6 +2,7 @@
 using MediaBrowser.Controller.Channels;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Devices;
+using MediaBrowser.Controller.Diagnostics;
 using MediaBrowser.Controller.Dlna;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.LiveTv;
@@ -23,7 +24,7 @@ namespace MediaBrowser.Api.Playback.Hls
     /// </summary>
     public abstract class BaseHlsService : BaseStreamingService
     {
-        protected BaseHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder, deviceManager)
+        protected BaseHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IProcessManager processManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder, deviceManager, processManager)
         {
         }
 

+ 4 - 1
MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs

@@ -1,4 +1,5 @@
 using MediaBrowser.Controller.Devices;
+using MediaBrowser.Controller.Diagnostics;
 using MediaBrowser.Model.Extensions;
 using MediaBrowser.Common.IO;
 using MediaBrowser.Common.Net;
@@ -63,7 +64,7 @@ namespace MediaBrowser.Api.Playback.Hls
 
     public class DynamicHlsService : BaseHlsService
     {
-        public DynamicHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, INetworkManager networkManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder, deviceManager)
+        public DynamicHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IProcessManager processManager, INetworkManager networkManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder, deviceManager, processManager)
         {
             NetworkManager = networkManager;
         }
@@ -115,6 +116,7 @@ namespace MediaBrowser.Api.Playback.Hls
 
             if (File.Exists(segmentPath))
             {
+                job = ApiEntryPoint.Instance.GetTranscodingJob(playlistPath, TranscodingJobType);
                 return await GetSegmentResult(playlistPath, segmentPath, index, segmentLength, job, cancellationToken).ConfigureAwait(false);
             }
 
@@ -123,6 +125,7 @@ namespace MediaBrowser.Api.Playback.Hls
             {
                 if (File.Exists(segmentPath))
                 {
+                    job = ApiEntryPoint.Instance.GetTranscodingJob(playlistPath, TranscodingJobType);
                     return await GetSegmentResult(playlistPath, segmentPath, index, segmentLength, job, cancellationToken).ConfigureAwait(false);
                 }
                 else

+ 2 - 1
MediaBrowser.Api/Playback/Hls/MpegDashService.cs

@@ -3,6 +3,7 @@ using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Channels;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Devices;
+using MediaBrowser.Controller.Diagnostics;
 using MediaBrowser.Controller.Dlna;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.LiveTv;
@@ -51,7 +52,7 @@ namespace MediaBrowser.Api.Playback.Hls
 
     public class MpegDashService : BaseHlsService
     {
-        public MpegDashService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, INetworkManager networkManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder, deviceManager)
+        public MpegDashService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IProcessManager processManager, INetworkManager networkManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder, deviceManager, processManager)
         {
             NetworkManager = networkManager;
         }

+ 2 - 1
MediaBrowser.Api/Playback/Hls/VideoHlsService.cs

@@ -2,6 +2,7 @@ using MediaBrowser.Common.IO;
 using MediaBrowser.Controller.Channels;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Devices;
+using MediaBrowser.Controller.Diagnostics;
 using MediaBrowser.Controller.Dlna;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.LiveTv;
@@ -57,7 +58,7 @@ namespace MediaBrowser.Api.Playback.Hls
     /// </summary>
     public class VideoHlsService : BaseHlsService
     {
-        public VideoHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder, deviceManager)
+        public VideoHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IProcessManager processManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder, deviceManager, processManager)
         {
         }
 

+ 1 - 0
MediaBrowser.Api/Playback/MediaInfoService.cs

@@ -13,6 +13,7 @@ using System.Threading.Tasks;
 namespace MediaBrowser.Api.Playback
 {
     [Route("/Items/{Id}/MediaInfo", "GET", Summary = "Gets live playback media info for an item")]
+    [Route("/Items/{Id}/PlaybackInfo", "GET", Summary = "Gets live playback media info for an item")]
     public class GetLiveMediaInfo : IReturn<LiveMediaInfoResult>
     {
         [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]

+ 2 - 1
MediaBrowser.Api/Playback/Progressive/AudioService.cs

@@ -3,6 +3,7 @@ using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Channels;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Devices;
+using MediaBrowser.Controller.Diagnostics;
 using MediaBrowser.Controller.Dlna;
 using MediaBrowser.Controller.Drawing;
 using MediaBrowser.Controller.Library;
@@ -32,7 +33,7 @@ namespace MediaBrowser.Api.Playback.Progressive
     /// </summary>
     public class AudioService : BaseProgressiveStreamingService
     {
-        public AudioService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder, deviceManager, imageProcessor, httpClient)
+        public AudioService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IProcessManager processManager, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder, deviceManager, processManager, imageProcessor, httpClient)
         {
         }
 

+ 3 - 100
MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs

@@ -3,6 +3,7 @@ using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Channels;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Devices;
+using MediaBrowser.Controller.Diagnostics;
 using MediaBrowser.Controller.Dlna;
 using MediaBrowser.Controller.Drawing;
 using MediaBrowser.Controller.Library;
@@ -29,7 +30,7 @@ namespace MediaBrowser.Api.Playback.Progressive
         protected readonly IImageProcessor ImageProcessor;
         protected readonly IHttpClient HttpClient;
 
-        protected BaseProgressiveStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder, deviceManager)
+        protected BaseProgressiveStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IProcessManager processManager, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder, deviceManager, processManager)
         {
             ImageProcessor = imageProcessor;
             HttpClient = httpClient;
@@ -153,49 +154,12 @@ namespace MediaBrowser.Api.Playback.Progressive
 
                 using (state)
                 {
-                    var job = string.IsNullOrEmpty(request.TranscodingJobId) ?
-                        null :
-                        ApiEntryPoint.Instance.GetTranscodingJob(request.TranscodingJobId);
-
-                    var limits = new List<long>();
-                    if (state.InputBitrate.HasValue)
-                    {
-                        // Bytes per second
-                        limits.Add((state.InputBitrate.Value / 8));
-                    }
-                    if (state.InputFileSize.HasValue && state.RunTimeTicks.HasValue)
-                    {
-                        var totalSeconds = TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalSeconds;
-
-                        if (totalSeconds > 1)
-                        {
-                            var timeBasedLimit = state.InputFileSize.Value / totalSeconds;
-                            limits.Add(Convert.ToInt64(timeBasedLimit));
-                        }
-                    }
-
-                    // Take the greater of the above to methods, just to be safe
-                    var throttleLimit = limits.Count > 0 ? limits.First() : 0;
-
-                    // Pad to play it safe
-                    var bytesPerSecond = Convert.ToInt64(1.05 * throttleLimit);
-
-                    // Don't even start evaluating this until at least two minutes have content have been consumed
-                    var targetGap = throttleLimit * 120;
-
                     return ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
                     {
                         ResponseHeaders = responseHeaders,
                         ContentType = contentType,
                         IsHeadRequest = isHeadRequest,
-                        Path = state.MediaPath,
-                        Throttle = request.Throttle,
-
-                        ThrottleLimit = bytesPerSecond,
-
-                        MinThrottlePosition = targetGap,
-
-                        ThrottleCallback = (l1, l2) => ThrottleCallack(l1, l2, bytesPerSecond, job)
+                        Path = state.MediaPath
                     });
                 }
             }
@@ -234,67 +198,6 @@ namespace MediaBrowser.Api.Playback.Progressive
             }
         }
 
-        private readonly long _gapLengthInTicks = TimeSpan.FromMinutes(3).Ticks;
-
-        private long ThrottleCallack(long currentBytesPerSecond, long bytesWritten, long originalBytesPerSecond, TranscodingJob job)
-        {
-            var bytesDownloaded = job.BytesDownloaded ?? 0;
-            var transcodingPositionTicks = job.TranscodingPositionTicks ?? 0;
-            var downloadPositionTicks = job.DownloadPositionTicks ?? 0;
-
-            var path = job.Path;
-
-            if (bytesDownloaded > 0 && transcodingPositionTicks > 0)
-            {
-                // Progressive Streaming - byte-based consideration
-
-                try
-                {
-                    var bytesTranscoded = job.BytesTranscoded ?? new FileInfo(path).Length;
-
-                    // Estimate the bytes the transcoder should be ahead
-                    double gapFactor = _gapLengthInTicks;
-                    gapFactor /= transcodingPositionTicks;
-                    var targetGap = bytesTranscoded * gapFactor;
-
-                    var gap = bytesTranscoded - bytesDownloaded;
-
-                    if (gap < targetGap)
-                    {
-                        //Logger.Debug("Not throttling transcoder gap {0} target gap {1} bytes downloaded {2}", gap, targetGap, bytesDownloaded);
-                        return 0;
-                    }
-
-                    //Logger.Debug("Throttling transcoder gap {0} target gap {1} bytes downloaded {2}", gap, targetGap, bytesDownloaded);
-                }
-                catch
-                {
-                    //Logger.Error("Error getting output size");
-                }
-            }
-            else if (downloadPositionTicks > 0 && transcodingPositionTicks > 0)
-            {
-                // HLS - time-based consideration
-
-                var targetGap = _gapLengthInTicks;
-                var gap = transcodingPositionTicks - downloadPositionTicks;
-
-                if (gap < targetGap)
-                {
-                    //Logger.Debug("Not throttling transcoder gap {0} target gap {1}", gap, targetGap);
-                    return 0;
-                }
-
-                //Logger.Debug("Throttling transcoder gap {0} target gap {1}", gap, targetGap);
-            }
-            else
-            {
-                //Logger.Debug("No throttle data for " + path);
-            }
-
-            return originalBytesPerSecond;
-        }
-
         /// <summary>
         /// Gets the static remote stream result.
         /// </summary>

+ 2 - 1
MediaBrowser.Api/Playback/Progressive/VideoService.cs

@@ -3,6 +3,7 @@ using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Channels;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Devices;
+using MediaBrowser.Controller.Diagnostics;
 using MediaBrowser.Controller.Dlna;
 using MediaBrowser.Controller.Drawing;
 using MediaBrowser.Controller.Library;
@@ -63,7 +64,7 @@ namespace MediaBrowser.Api.Playback.Progressive
     /// </summary>
     public class VideoService : BaseProgressiveStreamingService
     {
-        public VideoService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder, deviceManager, imageProcessor, httpClient)
+        public VideoService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IProcessManager processManager, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder, deviceManager, processManager, imageProcessor, httpClient)
         {
         }
 

+ 0 - 1
MediaBrowser.Api/Playback/StreamRequest.cs

@@ -72,7 +72,6 @@ namespace MediaBrowser.Api.Playback
         public string Params { get; set; }
         public string ClientTime { get; set; }
 
-        public bool Throttle { get; set; }
         public string TranscodingJobId { get; set; }
     }
 

+ 21 - 3
MediaBrowser.Api/Playback/StreamState.cs

@@ -1,17 +1,16 @@
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Controller.LiveTv;
 using MediaBrowser.Model.Dlna;
 using MediaBrowser.Model.Drawing;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.MediaInfo;
+using MediaBrowser.Model.Net;
 using System;
 using System.Collections.Generic;
 using System.Globalization;
 using System.IO;
 using System.Threading;
-using MediaBrowser.Model.Net;
 
 namespace MediaBrowser.Api.Playback
 {
@@ -23,6 +22,7 @@ namespace MediaBrowser.Api.Playback
         public string RequestedUrl { get; set; }
 
         public StreamRequest Request { get; set; }
+        public TranscodingThrottler TranscodingThrottler { get; set; }
 
         public VideoStreamRequest VideoRequest
         {
@@ -125,6 +125,7 @@ namespace MediaBrowser.Api.Playback
 
         public void Dispose()
         {
+            DisposeTranscodingThrottler();
             DisposeLiveStream();
             DisposeLogStream();
             DisposeIsoMount();
@@ -147,6 +148,23 @@ namespace MediaBrowser.Api.Playback
             }
         }
 
+        private void DisposeTranscodingThrottler()
+        {
+            if (TranscodingThrottler != null)
+            {
+                try
+                {
+                    TranscodingThrottler.Dispose();
+                }
+                catch (Exception ex)
+                {
+                    _logger.ErrorException("Error disposing TranscodingThrottler", ex);
+                }
+
+                TranscodingThrottler = null;
+            }
+        }
+
         private void DisposeIsoMount()
         {
             if (IsoMount != null)

+ 164 - 0
MediaBrowser.Api/Playback/TranscodingThrottler.cs

@@ -0,0 +1,164 @@
+using MediaBrowser.Controller.Diagnostics;
+using MediaBrowser.Model.Logging;
+using System;
+using System.IO;
+using System.Threading;
+
+namespace MediaBrowser.Api.Playback
+{
+    public class TranscodingThrottler : IDisposable
+    {
+        private readonly TranscodingJob _job;
+        private readonly ILogger _logger;
+        private readonly IProcessManager _processManager;
+        private Timer _timer;
+        private bool _isPaused;
+
+        public void Start()
+        {
+            if (_processManager.SupportsSuspension)
+            {
+                _timer = new Timer(TimerCallback, null, 5000, 5000);
+            }
+        }
+
+        private void TimerCallback(object state)
+        {
+            if (_job.HasExited)
+            {
+                DisposeTimer();
+                return;
+            }
+
+            if (IsThrottleAllowed(_job))
+            {
+                PauseTranscoding();
+            }
+            else
+            {
+                UnpauseTranscoding();
+            }
+        }
+
+        private void PauseTranscoding()
+        {
+            if (!_isPaused)
+            {
+                _logger.Debug("Sending pause command to ffmpeg");
+            }
+
+            try
+            {
+                //_job.Process.StandardInput.WriteLine("p");
+                _processManager.SuspendProcess(_job.Process);
+                _isPaused = true;
+            }
+            catch (Exception ex)
+            {
+                _logger.ErrorException("Error pausing transcoding", ex);
+            }
+        }
+
+        private void UnpauseTranscoding()
+        {
+            if (_isPaused)
+            {
+                _logger.Debug("Sending unpause command to ffmpeg");
+            }
+
+            try
+            {
+                //_job.Process.StandardInput.WriteLine("u");
+                _processManager.ResumeProcess(_job.Process);
+                _isPaused = false;
+            }
+            catch (Exception ex)
+            {
+                _logger.ErrorException("Error unpausing transcoding", ex);
+            }
+        }
+
+        private readonly long _gapLengthInTicks = TimeSpan.FromMinutes(2).Ticks;
+
+        public TranscodingThrottler(TranscodingJob job, ILogger logger, IProcessManager processManager)
+        {
+            _job = job;
+            _logger = logger;
+            _processManager = processManager;
+        }
+
+        private bool IsThrottleAllowed(TranscodingJob job)
+        {
+            var bytesDownloaded = job.BytesDownloaded ?? 0;
+            var transcodingPositionTicks = job.TranscodingPositionTicks ?? 0;
+            var downloadPositionTicks = job.DownloadPositionTicks ?? 0;
+
+            var path = job.Path;
+
+            if (downloadPositionTicks > 0 && transcodingPositionTicks > 0)
+            {
+                // HLS - time-based consideration
+
+                var targetGap = _gapLengthInTicks;
+                var gap = transcodingPositionTicks - downloadPositionTicks;
+
+                if (gap < targetGap)
+                {
+                    //_logger.Debug("Not throttling transcoder gap {0} target gap {1}", gap, targetGap);
+                    return false;
+                }
+
+                //_logger.Debug("Throttling transcoder gap {0} target gap {1}", gap, targetGap);
+                return true;
+            }
+
+            if (bytesDownloaded > 0 && transcodingPositionTicks > 0)
+            {
+                // Progressive Streaming - byte-based consideration
+
+                try
+                {
+                    var bytesTranscoded = job.BytesTranscoded ?? new FileInfo(path).Length;
+
+                    // Estimate the bytes the transcoder should be ahead
+                    double gapFactor = _gapLengthInTicks;
+                    gapFactor /= transcodingPositionTicks;
+                    var targetGap = bytesTranscoded * gapFactor;
+
+                    var gap = bytesTranscoded - bytesDownloaded;
+
+                    if (gap < targetGap)
+                    {
+                        //_logger.Debug("Not throttling transcoder gap {0} target gap {1} bytes downloaded {2}", gap, targetGap, bytesDownloaded);
+                        return false;
+                    }
+
+                    //_logger.Debug("Throttling transcoder gap {0} target gap {1} bytes downloaded {2}", gap, targetGap, bytesDownloaded);
+                    return true;
+                }
+                catch
+                {
+                    //_logger.Error("Error getting output size");
+                    return false;
+                }
+            }
+
+            //_logger.Debug("No throttle data for " + path);
+            return false;
+        }
+
+        public void Dispose()
+        {
+            DisposeTimer();
+        }
+
+        private void DisposeTimer()
+        {
+            if (_timer != null)
+            {
+                _timer.Dispose();
+                _timer = null;
+            }
+        }
+    }
+}

+ 28 - 0
MediaBrowser.Controller/Diagnostics/IProcessManager.cs

@@ -0,0 +1,28 @@
+using System.Diagnostics;
+
+namespace MediaBrowser.Controller.Diagnostics
+{
+    /// <summary>
+    /// Interface IProcessManager
+    /// </summary>
+    public interface IProcessManager
+    {
+        /// <summary>
+        /// Gets a value indicating whether [supports suspension].
+        /// </summary>
+        /// <value><c>true</c> if [supports suspension]; otherwise, <c>false</c>.</value>
+        bool SupportsSuspension { get; }
+
+        /// <summary>
+        /// Suspends the process.
+        /// </summary>
+        /// <param name="process">The process.</param>
+        void SuspendProcess(Process process);
+
+        /// <summary>
+        /// Resumes the process.
+        /// </summary>
+        /// <param name="process">The process.</param>
+        void ResumeProcess(Process process);
+    }
+}

+ 2 - 4
MediaBrowser.Controller/Drawing/IImageProcessor.cs

@@ -2,7 +2,6 @@
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Drawing;
 using MediaBrowser.Model.Entities;
-using System;
 using System.Collections.Generic;
 using System.IO;
 using System.Threading.Tasks;
@@ -30,10 +29,9 @@ namespace MediaBrowser.Controller.Drawing
         /// <summary>
         /// Gets the size of the image.
         /// </summary>
-        /// <param name="path">The path.</param>
-        /// <param name="imageDateModified">The image date modified.</param>
+        /// <param name="info">The information.</param>
         /// <returns>ImageSize.</returns>
-        ImageSize GetImageSize(string path, DateTime imageDateModified);
+        ImageSize GetImageSize(ItemImageInfo info);
 
         /// <summary>
         /// Adds the parts.

+ 1 - 1
MediaBrowser.Controller/Entities/Audio/Audio.cs

@@ -239,7 +239,7 @@ namespace MediaBrowser.Controller.Entities.Audio
             {
                 Id = i.Id.ToString("N"),
                 Protocol = locationType == LocationType.Remote ? MediaProtocol.Http : MediaProtocol.File,
-                MediaStreams = MediaSourceManager.GetMediaStreams(new MediaStreamQuery { ItemId = i.Id }).ToList(),
+                MediaStreams = MediaSourceManager.GetMediaStreams(i.Id).ToList(),
                 Name = i.Name,
                 Path = enablePathSubstituion ? GetMappedPath(i.Path, locationType) : i.Path,
                 RunTimeTicks = i.RunTimeTicks,

+ 10 - 5
MediaBrowser.Controller/Entities/Video.cs

@@ -420,12 +420,17 @@ namespace MediaBrowser.Controller.Entities
             return base.GetDeletePaths();
         }
 
-        public virtual IEnumerable<MediaStream> GetMediaStreams()
+        public IEnumerable<MediaStream> GetMediaStreams()
         {
-            return MediaSourceManager.GetMediaStreams(new MediaStreamQuery
+            var mediaSource = GetMediaSources(false)
+                .FirstOrDefault();
+
+            if (mediaSource == null)
             {
-                ItemId = Id
-            });
+                return new List<MediaStream>();
+            }
+
+            return mediaSource.MediaStreams;
         }
 
         public virtual MediaStream GetDefaultVideoStream()
@@ -474,7 +479,7 @@ namespace MediaBrowser.Controller.Entities
 
         private static MediaSourceInfo GetVersionInfo(bool enablePathSubstitution, Video i, MediaSourceType type)
         {
-            var mediaStreams = MediaSourceManager.GetMediaStreams(new MediaStreamQuery { ItemId = i.Id })
+            var mediaStreams = MediaSourceManager.GetMediaStreams(i.Id)
                 .ToList();
 
             var locationType = i.LocationType;

+ 18 - 0
MediaBrowser.Controller/Library/IMediaSourceManager.cs

@@ -1,11 +1,29 @@
 using MediaBrowser.Controller.Persistence;
 using MediaBrowser.Model.Entities;
+using System;
 using System.Collections.Generic;
 
 namespace MediaBrowser.Controller.Library
 {
     public interface IMediaSourceManager
     {
+        /// <summary>
+        /// Gets the media streams.
+        /// </summary>
+        /// <param name="itemId">The item identifier.</param>
+        /// <returns>IEnumerable&lt;MediaStream&gt;.</returns>
+        IEnumerable<MediaStream> GetMediaStreams(Guid itemId);
+        /// <summary>
+        /// Gets the media streams.
+        /// </summary>
+        /// <param name="mediaSourceId">The media source identifier.</param>
+        /// <returns>IEnumerable&lt;MediaStream&gt;.</returns>
+        IEnumerable<MediaStream> GetMediaStreams(string mediaSourceId);
+        /// <summary>
+        /// Gets the media streams.
+        /// </summary>
+        /// <param name="query">The query.</param>
+        /// <returns>IEnumerable&lt;MediaStream&gt;.</returns>
         IEnumerable<MediaStream> GetMediaStreams(MediaStreamQuery query);
     }
 }

+ 1 - 0
MediaBrowser.Controller/Library/IUserManager.cs

@@ -34,6 +34,7 @@ namespace MediaBrowser.Controller.Library
         event EventHandler<GenericEventArgs<User>> UserCreated;
         event EventHandler<GenericEventArgs<User>> UserConfigurationUpdated;
         event EventHandler<GenericEventArgs<User>> UserPasswordChanged;
+        event EventHandler<GenericEventArgs<User>> UserLockedOut;
 
         /// <summary>
         /// Gets a User by Id

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

@@ -104,6 +104,7 @@
     <Compile Include="Devices\CameraImageUploadInfo.cs" />
     <Compile Include="Devices\IDeviceManager.cs" />
     <Compile Include="Devices\IDeviceRepository.cs" />
+    <Compile Include="Diagnostics\IProcessManager.cs" />
     <Compile Include="Dlna\ControlRequest.cs" />
     <Compile Include="Dlna\ControlResponse.cs" />
     <Compile Include="Dlna\EventSubscriptionResponse.cs" />
@@ -341,8 +342,8 @@
     <Compile Include="Subtitles\SubtitleDownloadEventArgs.cs" />
     <Compile Include="Subtitles\SubtitleResponse.cs" />
     <Compile Include="Subtitles\SubtitleSearchRequest.cs" />
-    <Compile Include="Sync\ICloudSyncProvider.cs" />
     <Compile Include="Sync\IServerSyncProvider.cs" />
+    <Compile Include="Sync\ISyncDataProvider.cs" />
     <Compile Include="Sync\ISyncManager.cs" />
     <Compile Include="Sync\ISyncProvider.cs" />
     <Compile Include="Sync\ISyncRepository.cs" />

+ 0 - 5
MediaBrowser.Controller/Net/StaticResultOptions.cs

@@ -18,11 +18,6 @@ namespace MediaBrowser.Controller.Net
 
         public IDictionary<string, string> ResponseHeaders { get; set; }
 
-        public bool Throttle { get; set; }
-        public long ThrottleLimit { get; set; }
-        public long MinThrottlePosition { get; set; }
-        public Func<long, long, long> ThrottleCallback { get; set; }
-
         public Action OnComplete { get; set; }
 
         public StaticResultOptions()

+ 0 - 35
MediaBrowser.Controller/Sync/ICloudSyncProvider.cs

@@ -1,35 +0,0 @@
-using MediaBrowser.Model.Sync;
-using System.Collections.Generic;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace MediaBrowser.Controller.Sync
-{
-    public interface ICloudSyncProvider
-    {
-        /// <summary>
-        /// Gets the name.
-        /// </summary>
-        /// <value>The name.</value>
-        string Name { get; }
-
-        /// <summary>
-        /// Gets the synchronize targets.
-        /// </summary>
-        /// <param name="userId">The user identifier.</param>
-        /// <returns>IEnumerable&lt;SyncTarget&gt;.</returns>
-        IEnumerable<SyncTarget> GetSyncTargets(string userId);
-
-        /// <summary>
-        /// Transfers the item file.
-        /// </summary>
-        /// <param name="serverId">The server identifier.</param>
-        /// <param name="itemId">The item identifier.</param>
-        /// <param name="inputFile">The input file.</param>
-        /// <param name="pathParts">The path parts.</param>
-        /// <param name="target">The target.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task.</returns>
-        Task TransferItemFile(string serverId, string itemId, string inputFile, string[] pathParts, SyncTarget target, CancellationToken cancellationToken);
-    }
-}

+ 46 - 15
MediaBrowser.Controller/Sync/IServerSyncProvider.cs

@@ -1,5 +1,7 @@
 using MediaBrowser.Model.Sync;
+using System;
 using System.Collections.Generic;
+using System.IO;
 using System.Threading;
 using System.Threading.Tasks;
 
@@ -8,34 +10,63 @@ namespace MediaBrowser.Controller.Sync
     public interface IServerSyncProvider : ISyncProvider
     {
         /// <summary>
-        /// Gets the server item ids.
+        /// Transfers the file.
         /// </summary>
-        /// <param name="serverId">The server identifier.</param>
+        /// <param name="inputFile">The input file.</param>
+        /// <param name="path">The path.</param>
         /// <param name="target">The target.</param>
+        /// <param name="progress">The progress.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task&lt;List&lt;System.String&gt;&gt;.</returns>
-        Task<List<string>> GetServerItemIds(string serverId, SyncTarget target, CancellationToken cancellationToken);
+        /// <returns>Task.</returns>
+        Task SendFile(string inputFile, string path, SyncTarget target, IProgress<double> progress, CancellationToken cancellationToken);
 
         /// <summary>
-        /// Removes the item.
+        /// Deletes the file.
         /// </summary>
-        /// <param name="serverId">The server identifier.</param>
-        /// <param name="itemId">The item identifier.</param>
+        /// <param name="path">The path.</param>
         /// <param name="target">The target.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task.</returns>
-        Task DeleteItem(string serverId, string itemId, SyncTarget target, CancellationToken cancellationToken);
+        Task DeleteFile(string path, SyncTarget target, CancellationToken cancellationToken);
 
         /// <summary>
-        /// Transfers the file.
+        /// Gets the file.
         /// </summary>
-        /// <param name="serverId">The server identifier.</param>
-        /// <param name="itemId">The item identifier.</param>
-        /// <param name="inputFile">The input file.</param>
-        /// <param name="pathParts">The path parts.</param>
+        /// <param name="path">The path.</param>
         /// <param name="target">The target.</param>
+        /// <param name="progress">The progress.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task.</returns>
-        Task TransferItemFile(string serverId, string itemId, string inputFile, string[] pathParts, SyncTarget target, CancellationToken cancellationToken);
+        /// <returns>Task&lt;Stream&gt;.</returns>
+        Task<Stream> GetFile(string path, SyncTarget target, IProgress<double> progress, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// Gets the full path.
+        /// </summary>
+        /// <param name="path">The path.</param>
+        /// <param name="target">The target.</param>
+        /// <returns>System.String.</returns>
+        string GetFullPath(IEnumerable<string> path, SyncTarget target);
+
+        /// <summary>
+        /// Gets the parent directory path.
+        /// </summary>
+        /// <param name="path">The path.</param>
+        /// <param name="target">The target.</param>
+        /// <returns>System.String.</returns>
+        string GetParentDirectoryPath(string path, SyncTarget target);
+
+        /// <summary>
+        /// Gets the file system entries.
+        /// </summary>
+        /// <param name="path">The path.</param>
+        /// <param name="target">The target.</param>
+        /// <returns>Task&lt;List&lt;DeviceFileInfo&gt;&gt;.</returns>
+        Task<List<DeviceFileInfo>> GetFileSystemEntries(string path, SyncTarget target);
+
+        /// <summary>
+        /// Gets the data provider.
+        /// </summary>
+        /// <returns>ISyncDataProvider.</returns>
+        ISyncDataProvider GetDataProvider();
     }
 }

+ 41 - 0
MediaBrowser.Controller/Sync/ISyncDataProvider.cs

@@ -0,0 +1,41 @@
+using MediaBrowser.Model.Sync;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Controller.Sync
+{
+    public interface ISyncDataProvider
+    {
+        /// <summary>
+        /// Gets the server item ids.
+        /// </summary>
+        /// <param name="target">The target.</param>
+        /// <param name="serverId">The server identifier.</param>
+        /// <returns>Task&lt;List&lt;System.String&gt;&gt;.</returns>
+        Task<List<string>> GetServerItemIds(SyncTarget target, string serverId);
+
+        /// <summary>
+        /// Adds the or update.
+        /// </summary>
+        /// <param name="target">The target.</param>
+        /// <param name="item">The item.</param>
+        /// <returns>Task.</returns>
+        Task AddOrUpdate(SyncTarget target, LocalItem item);
+
+        /// <summary>
+        /// Deletes the specified identifier.
+        /// </summary>
+        /// <param name="target">The target.</param>
+        /// <param name="id">The identifier.</param>
+        /// <returns>Task.</returns>
+        Task Delete(SyncTarget target, string id);
+
+        /// <summary>
+        /// Gets the specified identifier.
+        /// </summary>
+        /// <param name="target">The target.</param>
+        /// <param name="id">The identifier.</param>
+        /// <returns>Task&lt;LocalItem&gt;.</returns>
+        Task<LocalItem> Get(SyncTarget target, string id);
+    }
+}

+ 5 - 7
MediaBrowser.Controller/Sync/ISyncProvider.cs

@@ -1,5 +1,4 @@
-using MediaBrowser.Model.Dlna;
-using MediaBrowser.Model.Sync;
+using MediaBrowser.Model.Sync;
 using System.Collections.Generic;
 
 namespace MediaBrowser.Controller.Sync
@@ -18,13 +17,12 @@ namespace MediaBrowser.Controller.Sync
         /// <param name="userId">The user identifier.</param>
         /// <returns>IEnumerable&lt;SyncTarget&gt;.</returns>
         IEnumerable<SyncTarget> GetSyncTargets(string userId);
-        
+
         /// <summary>
-        /// Gets the device profile.
+        /// Gets all synchronize targets.
         /// </summary>
-        /// <param name="target">The target.</param>
-        /// <returns>DeviceProfile.</returns>
-        DeviceProfile GetDeviceProfile(SyncTarget target);
+        /// <returns>IEnumerable&lt;SyncTarget&gt;.</returns>
+        IEnumerable<SyncTarget> GetAllSyncTargets();
     }
 
     public interface IHasUniqueTargetIds

+ 1 - 1
MediaBrowser.Dlna/Didl/DidlBuilder.cs

@@ -930,7 +930,7 @@ namespace MediaBrowser.Dlna.Didl
 
             try
             {
-                var size = _imageProcessor.GetImageSize(imageInfo.Path, imageInfo.DateModified);
+                var size = _imageProcessor.GetImageSize(imageInfo);
 
                 width = Convert.ToInt32(size.Width);
                 height = Convert.ToInt32(size.Height);

+ 0 - 18
MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs

@@ -452,24 +452,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
 
         private string GetInputPathArgument(EncodingJob job)
         {
-            //if (job.InputProtocol == MediaProtocol.File &&
-            //   job.RunTimeTicks.HasValue &&
-            //   job.VideoType == VideoType.VideoFile &&
-            //   !string.Equals(job.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
-            //{
-            //    if (job.RunTimeTicks.Value >= TimeSpan.FromMinutes(5).Ticks && job.IsInputVideo)
-            //    {
-            //        if (SupportsThrottleWithStream)
-            //        {
-            //            var url = "http://localhost:" + ServerConfigurationManager.Configuration.HttpServerPortNumber.ToString(UsCulture) + "/videos/" + job.Request.Id + "/stream?static=true&Throttle=true&mediaSourceId=" + job.Request.MediaSourceId;
-
-            //            url += "&transcodingJobId=" + transcodingJobId;
-
-            //            return string.Format("\"{0}\"", url);
-            //        }
-            //    }
-            //}
-
             var protocol = job.InputProtocol;
 
             var inputPath = new[] { job.MediaPath };

+ 3 - 3
MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs

@@ -773,7 +773,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
         {
             try
             {
-                using (var file = new FileStream(path, FileMode.Open))
+                using (var file = _fileSystem.GetFileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
                 {
                     var detector = new CharsetDetector();
                     detector.Feed(file);
@@ -797,12 +797,12 @@ namespace MediaBrowser.MediaEncoding.Subtitles
             return null;
         }
 
-        private static Encoding GetFileEncoding(string srcFile)
+        private Encoding GetFileEncoding(string srcFile)
         {
             // *** Detect byte order mark if any - otherwise assume default
             var buffer = new byte[5];
 
-            using (var file = new FileStream(srcFile, FileMode.Open))
+            using (var file = _fileSystem.GetFileStream(srcFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
             {
                 file.Read(buffer, 0, 5);
             }

+ 6 - 0
MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj

@@ -362,6 +362,12 @@
     <Compile Include="..\MediaBrowser.Model\Dlna\MediaFormatProfileResolver.cs">
       <Link>Dlna\MediaFormatProfileResolver.cs</Link>
     </Compile>
+    <Compile Include="..\MediaBrowser.Model\Dlna\PlaybackErrorCode.cs">
+      <Link>Dlna\PlaybackErrorCode.cs</Link>
+    </Compile>
+    <Compile Include="..\MediaBrowser.Model\Dlna\PlaybackException.cs">
+      <Link>Dlna\PlaybackException.cs</Link>
+    </Compile>
     <Compile Include="..\MediaBrowser.Model\Dlna\ProfileCondition.cs">
       <Link>Dlna\ProfileCondition.cs</Link>
     </Compile>

+ 6 - 0
MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj

@@ -327,6 +327,12 @@
     <Compile Include="..\MediaBrowser.Model\Dlna\MediaFormatProfileResolver.cs">
       <Link>Dlna\MediaFormatProfileResolver.cs</Link>
     </Compile>
+    <Compile Include="..\MediaBrowser.Model\Dlna\PlaybackErrorCode.cs">
+      <Link>Dlna\PlaybackErrorCode.cs</Link>
+    </Compile>
+    <Compile Include="..\MediaBrowser.Model\Dlna\PlaybackException.cs">
+      <Link>Dlna\PlaybackException.cs</Link>
+    </Compile>
     <Compile Include="..\MediaBrowser.Model\Dlna\ProfileCondition.cs">
       <Link>Dlna\ProfileCondition.cs</Link>
     </Compile>

+ 6 - 0
MediaBrowser.Model/ApiClient/ConnectionOptions.cs

@@ -13,11 +13,17 @@ namespace MediaBrowser.Model.ApiClient
         /// </summary>
         /// <value><c>true</c> if [report capabilities]; otherwise, <c>false</c>.</value>
         public bool ReportCapabilities { get; set; }
+        /// <summary>
+        /// Gets or sets a value indicating whether [update date last accessed].
+        /// </summary>
+        /// <value><c>true</c> if [update date last accessed]; otherwise, <c>false</c>.</value>
+        public bool UpdateDateLastAccessed { get; set; }
 
         public ConnectionOptions()
         {
             EnableWebSocket = true;
             ReportCapabilities = true;
+            UpdateDateLastAccessed = true;
         }
     }
 }

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

@@ -33,20 +33,12 @@ namespace MediaBrowser.Model.Configuration
         public bool DisplayMissingEpisodes { get; set; }
         public bool DisplayUnairedEpisodes { get; set; }
 
-        public bool EnableLiveTvManagement { get; set; }
-        public bool EnableLiveTvAccess { get; set; }
-
-        public bool EnableMediaPlayback { get; set; }
-        public bool EnableContentDeletion { get; set; }
-
         public bool GroupMoviesIntoBoxSets { get; set; }
 
         public string[] DisplayChannelsWithinViews { get; set; }
 
         public string[] ExcludeFoldersFromGrouping { get; set; }
 
-        public UnratedItem[] BlockUnratedItems { get; set; }
-
         public SubtitlePlaybackMode SubtitleMode { get; set; }
         public bool DisplayCollectionsView { get; set; }
         public bool DisplayFoldersView { get; set; }
@@ -69,14 +61,10 @@ namespace MediaBrowser.Model.Configuration
         public UserConfiguration()
         {
             PlayDefaultAudioTrack = true;
-            EnableLiveTvManagement = true;
-            EnableMediaPlayback = true;
-            EnableLiveTvAccess = true;
 
             LatestItemsExcludes = new string[] { };
             OrderedViews = new string[] { };
             DisplayChannelsWithinViews = new string[] { };
-            BlockUnratedItems = new UnratedItem[] { };
 
             ExcludeFoldersFromGrouping = new string[] { };
             DisplayCollectionsView = true;

+ 10 - 0
MediaBrowser.Model/Dlna/PlaybackErrorCode.cs

@@ -0,0 +1,10 @@
+
+namespace MediaBrowser.Model.Dlna
+{
+    public enum PlaybackErrorCode
+    {
+        NotAllowed = 0,
+        NoCompatibleStream = 1,
+        RateLimitExceeded = 2
+    }
+}

+ 9 - 0
MediaBrowser.Model/Dlna/PlaybackException.cs

@@ -0,0 +1,9 @@
+using System;
+
+namespace MediaBrowser.Model.Dlna
+{
+    public class PlaybackException : Exception
+    {
+        public PlaybackErrorCode ErrorCode { get; set;}
+    }
+}

+ 40 - 9
MediaBrowser.Model/Dlna/StreamBuilder.cs

@@ -31,7 +31,13 @@ namespace MediaBrowser.Model.Dlna
 
             List<StreamInfo> streams = new List<StreamInfo>();
             foreach (MediaSourceInfo i in mediaSources)
-                streams.Add(BuildAudioItem(i, options));
+            {
+                StreamInfo streamInfo = BuildAudioItem(i, options);
+                if (streamInfo != null)
+                {
+                    streams.Add(streamInfo);
+                }
+            }
 
             foreach (StreamInfo stream in streams)
             {
@@ -63,7 +69,13 @@ namespace MediaBrowser.Model.Dlna
 
             List<StreamInfo> streams = new List<StreamInfo>();
             foreach (MediaSourceInfo i in mediaSources)
-                streams.Add(BuildVideoItem(i, options));
+            {
+                StreamInfo streamInfo = BuildVideoItem(i, options);
+                if (streamInfo != null)
+                {
+                streams.Add(streamInfo);
+                }
+            }
 
             foreach (StreamInfo stream in streams)
             {
@@ -97,7 +109,10 @@ namespace MediaBrowser.Model.Dlna
             {
                 return stream;
             }
-            return null;
+
+            PlaybackException error = new PlaybackException();
+            error.ErrorCode = PlaybackErrorCode.NoCompatibleStream;
+            throw error;
         }
 
         private StreamInfo BuildAudioItem(MediaSourceInfo item, AudioOptions options)
@@ -186,6 +201,11 @@ namespace MediaBrowser.Model.Dlna
 
             if (transcodingProfile != null)
             {
+                if (!item.SupportsTranscoding)
+                {
+                    return null;
+                }
+
                 playlistItem.PlayMethod = PlayMethod.Transcode;
                 playlistItem.TranscodeSeekInfo = transcodingProfile.TranscodeSeekInfo;
                 playlistItem.EstimateContentLength = transcodingProfile.EstimateContentLength;
@@ -267,7 +287,7 @@ namespace MediaBrowser.Model.Dlna
 
                     if (subtitleStream != null)
                     {
-                        SubtitleProfile subtitleProfile = GetSubtitleProfile(subtitleStream, options.Profile);
+                        SubtitleProfile subtitleProfile = GetSubtitleProfile(subtitleStream, options.Profile, options.Context);
 
                         playlistItem.SubtitleDeliveryMethod = subtitleProfile.Method;
                         playlistItem.SubtitleFormat = subtitleProfile.Format;
@@ -290,9 +310,14 @@ namespace MediaBrowser.Model.Dlna
 
             if (transcodingProfile != null)
             {
+                if (!item.SupportsTranscoding)
+                {
+                    return null;
+                }
+
                 if (subtitleStream != null)
                 {
-                    SubtitleProfile subtitleProfile = GetSubtitleProfile(subtitleStream, options.Profile);
+                    SubtitleProfile subtitleProfile = GetSubtitleProfile(subtitleStream, options.Profile, options.Context);
 
                     playlistItem.SubtitleDeliveryMethod = subtitleProfile.Method;
                     playlistItem.SubtitleFormat = subtitleProfile.Format;
@@ -527,7 +552,7 @@ namespace MediaBrowser.Model.Dlna
         {
             if (subtitleStream != null)
             {
-                SubtitleProfile subtitleProfile = GetSubtitleProfile(subtitleStream, options.Profile);
+                SubtitleProfile subtitleProfile = GetSubtitleProfile(subtitleStream, options.Profile, options.Context);
 
                 if (subtitleProfile.Method != SubtitleDeliveryMethod.External && subtitleProfile.Method != SubtitleDeliveryMethod.Embed)
                 {
@@ -538,14 +563,20 @@ namespace MediaBrowser.Model.Dlna
             return IsAudioEligibleForDirectPlay(item, maxBitrate);
         }
 
-        public static SubtitleProfile GetSubtitleProfile(MediaStream subtitleStream, DeviceProfile deviceProfile)
+        public static SubtitleProfile GetSubtitleProfile(MediaStream subtitleStream, DeviceProfile deviceProfile, EncodingContext context)
         {
             // Look for an external profile that matches the stream type (text/graphical)
             foreach (SubtitleProfile profile in deviceProfile.SubtitleProfiles)
             {
-                if (subtitleStream.SupportsExternalStream)
+                if (profile.Method == SubtitleDeliveryMethod.External && subtitleStream.IsTextSubtitleStream == MediaStream.IsTextFormat(profile.Format))
                 {
-                    if (profile.Method == SubtitleDeliveryMethod.External && subtitleStream.IsTextSubtitleStream == MediaStream.IsTextFormat(profile.Format))
+                    if (subtitleStream.SupportsExternalStream)
+                    {
+                        return profile;
+                    }
+                    
+                    // For sync we can handle the longer extraction times
+                    if (context == EncodingContext.Static && subtitleStream.IsTextSubtitleStream)
                     {
                         return profile;
                     }

+ 1 - 1
MediaBrowser.Model/Dlna/StreamInfo.cs

@@ -262,7 +262,7 @@ namespace MediaBrowser.Model.Dlna
 
         private SubtitleStreamInfo GetSubtitleStreamInfo(MediaStream stream)
         {
-            SubtitleProfile subtitleProfile = StreamBuilder.GetSubtitleProfile(stream, DeviceProfile);
+            SubtitleProfile subtitleProfile = StreamBuilder.GetSubtitleProfile(stream, DeviceProfile, Context);
 
             if (subtitleProfile.Method != SubtitleDeliveryMethod.External)
             {

+ 2 - 0
MediaBrowser.Model/Dto/MediaSourceInfo.cs

@@ -22,6 +22,7 @@ namespace MediaBrowser.Model.Dto
 
         public long? RunTimeTicks { get; set; }
         public bool ReadAtNativeFramerate { get; set; }
+        public bool SupportsTranscoding { get; set; }
 
         public VideoType? VideoType { get; set; }
 
@@ -45,6 +46,7 @@ namespace MediaBrowser.Model.Dto
             MediaStreams = new List<MediaStream>();
             RequiredHttpHeaders = new Dictionary<string, string>();
             PlayableStreamFileNames = new List<string>();
+            SupportsTranscoding = true;
         }
 
         public int? DefaultAudioStreamIndex { get; set; }

+ 2 - 0
MediaBrowser.Model/MediaBrowser.Model.csproj

@@ -125,6 +125,8 @@
     <Compile Include="Devices\DeviceInfo.cs" />
     <Compile Include="Devices\DevicesOptions.cs" />
     <Compile Include="Dlna\EncodingContext.cs" />
+    <Compile Include="Dlna\PlaybackErrorCode.cs" />
+    <Compile Include="Dlna\PlaybackException.cs" />
     <Compile Include="Dlna\Profiles\DefaultProfile.cs" />
     <Compile Include="Dlna\ResolutionConfiguration.cs" />
     <Compile Include="Dlna\ResolutionNormalizer.cs" />

+ 2 - 1
MediaBrowser.Model/Notifications/NotificationType.cs

@@ -19,6 +19,7 @@ namespace MediaBrowser.Model.Notifications
         NewLibraryContentMultiple,
         ServerRestartRequired,
         TaskFailed,
-        CameraImageUploaded
+        CameraImageUploaded,
+        UserLockedOut
     }
 }

+ 2 - 0
MediaBrowser.Model/Users/UserPolicy.cs

@@ -59,6 +59,8 @@ namespace MediaBrowser.Model.Users
         public string[] EnabledFolders { get; set; }
         public bool EnableAllFolders { get; set; }
 
+        public int InvalidLoginAttemptCount { get; set; }
+        
         public UserPolicy()
         {
             EnableLiveTvManagement = true;

+ 9 - 1
MediaBrowser.Providers/Manager/MetadataService.cs

@@ -397,7 +397,10 @@ namespace MediaBrowser.Providers.Manager
                         refreshResult.UpdateType = refreshResult.UpdateType | ItemUpdateType.MetadataImport;
 
                         // Only one local provider allowed per item
-                        hasLocalMetadata = true;
+                        if (IsFullLocalMetadata(localItem.Item))
+                        {
+                            hasLocalMetadata = true;
+                        }
                         successfulProviderCount++;
                         break;
                     }
@@ -473,6 +476,11 @@ namespace MediaBrowser.Providers.Manager
             return refreshResult;
         }
 
+        protected virtual bool IsFullLocalMetadata(TItemType item)
+        {
+            return true;
+        }
+
         private async Task ImportUserData(TItemType item, List<UserItemData> userDataList, CancellationToken cancellationToken)
         {
             var hasUserData = item as IHasUserData;

+ 17 - 0
MediaBrowser.Providers/Movies/MovieMetadataService.cs

@@ -33,5 +33,22 @@ namespace MediaBrowser.Providers.Movies
                 target.TmdbCollectionName = source.TmdbCollectionName;
             }
         }
+
+        protected override bool IsFullLocalMetadata(Movie item)
+        {
+            if (string.IsNullOrWhiteSpace(item.Name))
+            {
+                return false;
+            }
+            if (string.IsNullOrWhiteSpace(item.Overview))
+            {
+                return false;
+            }
+            if (!item.ProductionYear.HasValue)
+            {
+                return false;
+            }
+            return base.IsFullLocalMetadata(item);
+        }
     }
 }

+ 1 - 1
MediaBrowser.Providers/Photos/PhotoProvider.cs

@@ -146,7 +146,7 @@ namespace MediaBrowser.Providers.Photos
             
             try
             {
-                var size = _imageProcessor.GetImageSize(imageInfo.Path, imageInfo.DateModified);
+                var size = _imageProcessor.GetImageSize(imageInfo);
 
                 item.Width = Convert.ToInt32(size.Width);
                 item.Height = Convert.ToInt32(size.Height);

+ 17 - 0
MediaBrowser.Providers/TV/SeriesMetadataService.cs

@@ -74,5 +74,22 @@ namespace MediaBrowser.Providers.TV
                 await provider.Run(item, CancellationToken.None).ConfigureAwait(false);
             }
         }
+
+        protected override bool IsFullLocalMetadata(Series item)
+        {
+            if (string.IsNullOrWhiteSpace(item.Name))
+            {
+                return false;
+            }
+            if (string.IsNullOrWhiteSpace(item.Overview))
+            {
+                return false;
+            }
+            if (!item.ProductionYear.HasValue)
+            {
+                return false;
+            }
+            return base.IsFullLocalMetadata(item);
+        }
     }
 }

+ 6 - 0
MediaBrowser.Server.Implementations/Devices/CameraUploadsFolder.cs

@@ -1,5 +1,6 @@
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Controller.Entities;
+using System;
 using System.IO;
 using System.Linq;
 
@@ -14,6 +15,11 @@ namespace MediaBrowser.Server.Implementations.Devices
 
         public override bool IsVisible(User user)
         {
+            if (!user.Policy.EnableAllFolders && !user.Policy.EnabledFolders.Contains(Id.ToString("N"), StringComparer.OrdinalIgnoreCase))
+            {
+                return false;
+            }
+            
             return GetChildren(user, true).Any() &&
                 base.IsVisible(user);
         }

+ 2 - 1
MediaBrowser.Server.Implementations/Drawing/ImageHeader.cs

@@ -62,8 +62,9 @@ namespace MediaBrowser.Server.Implementations.Drawing
                 logger.Info("Failed to read image header for {0}. Doing it the slow way.", path);
             }
 
-            using (var wand = new MagickWand(path))
+            using (var wand = new MagickWand())
             {
+                wand.PingImage(path);
                 var img = wand.CurrentImage;
 
                 return new ImageSize

+ 10 - 37
MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs

@@ -350,9 +350,9 @@ namespace MediaBrowser.Server.Implementations.Drawing
         }
 
         /// <summary>
-        /// Increment this when indicator drawings change
+        /// Increment this when there's a change requiring caches to be invalidated
         /// </summary>
-        private const string IndicatorVersion = "2";
+        private const string Version = "3";
 
         /// <summary>
         /// Gets the cache file path based on a set of parameters
@@ -371,29 +371,19 @@ namespace MediaBrowser.Server.Implementations.Drawing
 
             filename += "f=" + format;
 
-            var hasIndicator = false;
-
             if (addPlayedIndicator)
             {
                 filename += "pl=true";
-                hasIndicator = true;
             }
 
             if (percentPlayed > 0)
             {
                 filename += "p=" + percentPlayed;
-                hasIndicator = true;
             }
 
             if (unwatchedCount.HasValue)
             {
                 filename += "p=" + unwatchedCount.Value;
-                hasIndicator = true;
-            }
-
-            if (hasIndicator)
-            {
-                filename += "iv=" + IndicatorVersion;
             }
 
             if (!string.IsNullOrEmpty(backgroundColor))
@@ -401,6 +391,8 @@ namespace MediaBrowser.Server.Implementations.Drawing
                 filename += "b=" + backgroundColor;
             }
 
+            filename += "v=" + Version;
+
             return GetCachePath(ResizedImageCachePath, filename, "." + format.ToString().ToLower());
         }
 
@@ -414,6 +406,11 @@ namespace MediaBrowser.Server.Implementations.Drawing
             return GetImageSize(path, File.GetLastWriteTimeUtc(path));
         }
 
+        public ImageSize GetImageSize(ItemImageInfo info)
+        {
+            return GetImageSize(info.Path, info.DateModified);
+        }
+
         /// <summary>
         /// Gets the size of the image.
         /// </summary>
@@ -421,7 +418,7 @@ namespace MediaBrowser.Server.Implementations.Drawing
         /// <param name="imageDateModified">The image date modified.</param>
         /// <returns>ImageSize.</returns>
         /// <exception cref="System.ArgumentNullException">path</exception>
-        public ImageSize GetImageSize(string path, DateTime imageDateModified)
+        private ImageSize GetImageSize(string path, DateTime imageDateModified)
         {
             if (string.IsNullOrEmpty(path))
             {
@@ -666,30 +663,6 @@ namespace MediaBrowser.Server.Implementations.Drawing
             return enhancedImagePath;
         }
 
-        private ImageFormat GetFormat(string path)
-        {
-            var extension = Path.GetExtension(path);
-
-            if (string.Equals(extension, ".png", StringComparison.OrdinalIgnoreCase))
-            {
-                return ImageFormat.Png;
-            }
-            if (string.Equals(extension, ".gif", StringComparison.OrdinalIgnoreCase))
-            {
-                return ImageFormat.Gif;
-            }
-            if (string.Equals(extension, ".webp", StringComparison.OrdinalIgnoreCase))
-            {
-                return ImageFormat.Webp;
-            }
-            if (string.Equals(extension, ".bmp", StringComparison.OrdinalIgnoreCase))
-            {
-                return ImageFormat.Bmp;
-            }
-
-            return ImageFormat.Jpg;
-        }
-
         /// <summary>
         /// Executes the image enhancers.
         /// </summary>

+ 1 - 4
MediaBrowser.Server.Implementations/Dto/DtoService.cs

@@ -1598,14 +1598,11 @@ namespace MediaBrowser.Server.Implementations.Dto
 
             var path = imageInfo.Path;
 
-            // See if we can avoid a file system lookup by looking for the file in ResolveArgs
-            var dateModified = imageInfo.DateModified;
-
             ImageSize size;
 
             try
             {
-                size = _imageProcessor.GetImageSize(path, dateModified);
+                size = _imageProcessor.GetImageSize(imageInfo);
             }
             catch (FileNotFoundException)
             {

+ 12 - 0
MediaBrowser.Server.Implementations/EntryPoints/ActivityLogEntryPoint.cs

@@ -86,6 +86,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
             _userManager.UserPasswordChanged += _userManager_UserPasswordChanged;
             _userManager.UserDeleted += _userManager_UserDeleted;
             _userManager.UserConfigurationUpdated += _userManager_UserConfigurationUpdated;
+            _userManager.UserLockedOut += _userManager_UserLockedOut;
 
             //_config.ConfigurationUpdated += _config_ConfigurationUpdated;
             //_config.NamedConfigurationUpdated += _config_NamedConfigurationUpdated;
@@ -95,6 +96,16 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
             _appHost.ApplicationUpdated += _appHost_ApplicationUpdated;
         }
 
+        void _userManager_UserLockedOut(object sender, GenericEventArgs<User> e)
+        {
+            CreateLogEntry(new ActivityLogEntry
+            {
+                Name = string.Format(_localization.GetLocalizedString("UserLockedOutWithName"), e.Argument.Name),
+                Type = "UserLockedOut",
+                UserId = e.Argument.Id.ToString("N")
+            });
+        }
+
         void _subManager_SubtitleDownloadFailure(object sender, SubtitleDownloadFailureEventArgs e)
         {
             CreateLogEntry(new ActivityLogEntry
@@ -482,6 +493,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
             _userManager.UserPasswordChanged -= _userManager_UserPasswordChanged;
             _userManager.UserDeleted -= _userManager_UserDeleted;
             _userManager.UserConfigurationUpdated -= _userManager_UserConfigurationUpdated;
+            _userManager.UserLockedOut -= _userManager_UserLockedOut;
 
             _config.ConfigurationUpdated -= _config_ConfigurationUpdated;
             _config.NamedConfigurationUpdated -= _config_NamedConfigurationUpdated;

+ 17 - 1
MediaBrowser.Server.Implementations/EntryPoints/Notifications/Notifications.cs

@@ -78,6 +78,22 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications
             _appHost.HasUpdateAvailableChanged += _appHost_HasUpdateAvailableChanged;
             _appHost.ApplicationUpdated += _appHost_ApplicationUpdated;
             _deviceManager.CameraImageUploaded +=_deviceManager_CameraImageUploaded;
+
+            _userManager.UserLockedOut += _userManager_UserLockedOut;    
+        }
+
+        async void _userManager_UserLockedOut(object sender, GenericEventArgs<User> e)
+        {
+            var type = NotificationType.UserLockedOut.ToString();
+
+            var notification = new NotificationRequest
+            {
+                NotificationType = type
+            };
+
+            notification.Variables["UserName"] = e.Argument.Name;
+
+            await SendNotification(notification).ConfigureAwait(false);
         }
 
         async void _deviceManager_CameraImageUploaded(object sender, GenericEventArgs<CameraImageUploadInfo> e)
@@ -235,7 +251,6 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications
                 return;
             }
 
-
             var notification = new NotificationRequest
             {
                 NotificationType = type
@@ -471,6 +486,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications
             _appHost.ApplicationUpdated -= _appHost_ApplicationUpdated;
 
             _deviceManager.CameraImageUploaded -= _deviceManager_CameraImageUploaded;
+            _userManager.UserLockedOut -= _userManager_UserLockedOut;
         }
 
         private void DisposeLibraryUpdateTimer()

+ 0 - 8
MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs

@@ -461,10 +461,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer
                 {
                     return new RangeRequestWriter(rangeHeader, stream, contentType, isHeadRequest)
                     {
-                        Throttle = options.Throttle,
-                        ThrottleLimit = options.ThrottleLimit,
-                        MinThrottlePosition = options.MinThrottlePosition,
-                        ThrottleCallback = options.ThrottleCallback,
                         OnComplete = options.OnComplete
                     };
                 }
@@ -480,10 +476,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer
 
                 return new StreamWriter(stream, contentType, _logger)
                 {
-                    Throttle = options.Throttle,
-                    ThrottleLimit = options.ThrottleLimit,
-                    MinThrottlePosition = options.MinThrottlePosition,
-                    ThrottleCallback = options.ThrottleCallback,
                     OnComplete = options.OnComplete
                 };
             }

+ 0 - 12
MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs

@@ -24,10 +24,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer
         private long RangeLength { get; set; }
         private long TotalContentLength { get; set; }
 
-        public bool Throttle { get; set; }
-        public long ThrottleLimit { get; set; }
-        public long MinThrottlePosition;
-        public Func<long, long, long> ThrottleCallback { get; set; }
         public Action OnComplete { get; set; }
 
         /// <summary>
@@ -165,14 +161,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer
         /// <param name="responseStream">The response stream.</param>
         public void WriteTo(Stream responseStream)
         {
-            if (Throttle)
-            {
-                responseStream = new ThrottledStream(responseStream, ThrottleLimit)
-                {
-                    MinThrottlePosition = MinThrottlePosition,
-                    ThrottleCallback = ThrottleCallback
-                };
-            }
             WriteToInternal(responseStream);
         }
 

+ 0 - 12
MediaBrowser.Server.Implementations/HttpServer/StreamWriter.cs

@@ -35,10 +35,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer
             get { return _options; }
         }
 
-        public bool Throttle { get; set; }
-        public long ThrottleLimit { get; set; }
-        public long MinThrottlePosition;
-        public Func<long, long, long> ThrottleCallback { get; set; }
         public Action OnComplete { get; set; }
 
         /// <summary>
@@ -82,14 +78,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer
         /// <param name="responseStream">The response stream.</param>
         public void WriteTo(Stream responseStream)
         {
-            if (Throttle)
-            {
-                responseStream = new ThrottledStream(responseStream, ThrottleLimit)
-                {
-                    MinThrottlePosition = MinThrottlePosition,
-                    ThrottleCallback = ThrottleCallback
-                };
-            }
             WriteToInternal(responseStream);
         }
 

+ 55 - 0
MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs

@@ -1,6 +1,7 @@
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Persistence;
 using MediaBrowser.Model.Entities;
+using System;
 using System.Collections.Generic;
 using System.Linq;
 
@@ -47,5 +48,59 @@ namespace MediaBrowser.Server.Implementations.Library
         {
             return true;
         }
+
+        public IEnumerable<MediaStream> GetMediaStreams(string mediaSourceId)
+        {
+            var list = GetMediaStreams(new MediaStreamQuery
+            {
+                ItemId = new Guid(mediaSourceId)
+            });
+
+            return GetMediaStreamsForItem(list);
+        }
+
+        public IEnumerable<MediaStream> GetMediaStreams(Guid itemId)
+        {
+            var list = GetMediaStreams(new MediaStreamQuery
+            {
+                ItemId = itemId
+            });
+
+            return GetMediaStreamsForItem(list);
+        }
+
+        private IEnumerable<MediaStream> GetMediaStreamsForItem(IEnumerable<MediaStream> streams)
+        {
+            var list = streams.ToList();
+
+            var subtitleStreams = list
+                .Where(i => i.Type == MediaStreamType.Subtitle)
+                .ToList();
+
+            if (subtitleStreams.Count > 0)
+            {
+                var videoStream = list.FirstOrDefault(i => i.Type == MediaStreamType.Video);
+
+                // This is abitrary but at some point it becomes too slow to extract subtitles on the fly
+                // We need to learn more about when this is the case vs. when it isn't
+                const int maxAllowedBitrateForExternalSubtitleStream = 10000000;
+
+                var videoBitrate = videoStream == null ? maxAllowedBitrateForExternalSubtitleStream : videoStream.BitRate ?? maxAllowedBitrateForExternalSubtitleStream;
+
+                foreach (var subStream in subtitleStreams)
+                {
+                    var supportsExternalStream = StreamSupportsExternalStream(subStream);
+
+                    if (supportsExternalStream && videoBitrate >= maxAllowedBitrateForExternalSubtitleStream)
+                    {
+                        supportsExternalStream = false;
+                    }
+
+                    subStream.SupportsExternalStream = supportsExternalStream;
+                }
+            }
+
+            return list;
+        }
     }
 }

+ 5 - 6
MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs

@@ -1,19 +1,18 @@
-using System.Collections.Generic;
-using System.Linq;
-using MediaBrowser.Common.IO;
+using MediaBrowser.Common.IO;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Controller.Resolvers;
 using MediaBrowser.Model.Entities;
-using System;
-using System.IO;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Naming.Common;
 using MediaBrowser.Naming.IO;
 using MediaBrowser.Naming.TV;
 using MediaBrowser.Server.Implementations.Logging;
-using EpisodeInfo = MediaBrowser.Controller.Providers.EpisodeInfo;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
 
 namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV
 {

+ 38 - 9
MediaBrowser.Server.Implementations/Library/UserManager.cs

@@ -97,6 +97,7 @@ namespace MediaBrowser.Server.Implementations.Library
         /// </summary>
         public event EventHandler<GenericEventArgs<User>> UserUpdated;
         public event EventHandler<GenericEventArgs<User>> UserConfigurationUpdated;
+        public event EventHandler<GenericEventArgs<User>> UserLockedOut;
 
         /// <summary>
         /// Called when [user updated].
@@ -259,6 +260,11 @@ namespace MediaBrowser.Server.Implementations.Library
             {
                 user.LastActivityDate = user.LastLoginDate = DateTime.UtcNow;
                 await UpdateUser(user).ConfigureAwait(false);
+                await UpdateInvalidLoginAttemptCount(user, 0).ConfigureAwait(false);
+            }
+            else
+            {
+                await UpdateInvalidLoginAttemptCount(user, user.Policy.InvalidLoginAttemptCount + 1).ConfigureAwait(false);
             }
 
             _logger.Info("Authentication request for {0} {1}.", user.Name, (success ? "has succeeded" : "has been denied"));
@@ -266,6 +272,38 @@ namespace MediaBrowser.Server.Implementations.Library
             return success;
         }
 
+        private async Task UpdateInvalidLoginAttemptCount(User user, int newValue)
+        {
+            if (user.Policy.InvalidLoginAttemptCount != newValue || newValue > 0)
+            {
+                user.Policy.InvalidLoginAttemptCount = newValue;
+
+                var maxCount = user.Policy.IsAdministrator ? 
+                    3 : 
+                    5;
+
+                var fireLockout = false;
+
+                if (newValue >= maxCount)
+                {
+                    _logger.Debug("Disabling user {0} due to {1} unsuccessful login attempts.", user.Name, newValue.ToString(CultureInfo.InvariantCulture));
+                    user.Policy.IsDisabled = true;
+
+                    fireLockout = true;
+                }
+
+                await UpdateUserPolicy(user, user.Policy, false).ConfigureAwait(false);
+
+                if (fireLockout)
+                {
+                    if (UserLockedOut != null)
+                    {
+                        EventHelper.FireEventIfNotNull(UserLockedOut, this, new GenericEventArgs<User>(user), _logger);
+                    }
+                }
+            }
+        }
+
         private string GetPasswordHash(User user)
         {
             return string.IsNullOrEmpty(user.Password)
@@ -332,11 +370,6 @@ namespace MediaBrowser.Server.Implementations.Library
         {
             if (!user.Configuration.HasMigratedToPolicy)
             {
-                user.Policy.BlockUnratedItems = user.Configuration.BlockUnratedItems;
-                user.Policy.EnableContentDeletion = user.Configuration.EnableContentDeletion;
-                user.Policy.EnableLiveTvAccess = user.Configuration.EnableLiveTvAccess;
-                user.Policy.EnableLiveTvManagement = user.Configuration.EnableLiveTvManagement;
-                user.Policy.EnableMediaPlayback = user.Configuration.EnableMediaPlayback;
                 user.Policy.IsAdministrator = user.Configuration.IsAdministrator;
 
                 await UpdateUserPolicy(user, user.Policy, false);
@@ -915,10 +948,6 @@ namespace MediaBrowser.Server.Implementations.Library
             }
 
             user.Configuration.IsAdministrator = user.Policy.IsAdministrator;
-            user.Configuration.EnableLiveTvManagement = user.Policy.EnableLiveTvManagement;
-            user.Configuration.EnableLiveTvAccess = user.Policy.EnableLiveTvAccess;
-            user.Configuration.EnableMediaPlayback = user.Policy.EnableMediaPlayback;
-            user.Configuration.EnableContentDeletion = user.Policy.EnableContentDeletion;
 
             await UpdateConfiguration(user, user.Configuration, true).ConfigureAwait(false);
         }

+ 1 - 1
MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs

@@ -442,7 +442,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
             return null;
         }
 
-        private const string InternalVersionNumber = "3";
+        private const string InternalVersionNumber = "4";
 
         public Guid GetInternalChannelId(string serviceName, string externalId)
         {

+ 15 - 2
MediaBrowser.Server.Implementations/Localization/Server/server.json

@@ -48,8 +48,10 @@
     "LabelDashboardSourcePathHelp": "If running the server from source, specify the path to the dashboard-ui folder. All web client files will be served from this location.",
     "ButtonConvertMedia": "Convert media",
     "ButtonOrganize": "Organize",
+    "LabelPinCode": "Pin code:",
     "ButtonOk": "Ok",
     "ButtonCancel": "Cancel",
+    "ButtonExit": "Exit",
     "ButtonNew": "New",
     "HeaderTV": "TV",
     "HeaderAudio": "Audio",
@@ -57,6 +59,12 @@
     "HeaderPaths": "Paths",
     "CategorySync": "Sync",
     "HeaderEasyPinCode": "Easy Pin Code",
+    "HeaderGrownupsOnly": "Grown-ups Only!",
+    "DividerOr": "-- or --",
+    "HeaderToAccessPleaseEnterEasyPinCode": "To access, please enter your easy pin code",
+    "KidsModeAdultInstruction": "Click the lock icon in the bottom right to configure or leave kids mode. Your pin code will be required.",
+    "ButtonConfigurePinCode": "Configure pin code",
+    "HeaderAdultsReadHere": "Adults Read Here!",
     "RegisterWithPayPal": "Register with PayPal",
     "HeaderSyncRequiresSupporterMembership": "Sync Requires a Supporter Membership",
     "HeaderEnjoyDayTrial": "Enjoy a 14 Day Free Trial",
@@ -670,6 +678,7 @@
     "NotificationOptionNewLibraryContent": "New content added",
     "NotificationOptionNewLibraryContentMultiple": "New content added (multiple)",
     "NotificationOptionCameraImageUploaded": "Camera image uploaded",
+    "NotificationOptionUserLockedOut": "User locked out",
     "SendNotificationHelp": "By default, notifications are delivered to the dashboard inbox. Browse the plugin catalog to install additional notification options.",
     "NotificationOptionServerRestartRequired": "Server restart required",
     "LabelNotificationEnabled": "Enable this notification",
@@ -1061,6 +1070,7 @@
     "OptionBox": "Box",
     "OptionBoxRear": "Box rear",
     "OptionDisc": "Disc",
+    "OptionIcon": "Icon",
     "OptionLogo": "Logo",
     "OptionMenu": "Menu",
     "OptionScreenshot": "Screenshot",
@@ -1105,6 +1115,7 @@
     "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
     "LabelRunningTimeValue": "Running time: {0}",
     "LabelIpAddressValue": "Ip address: {0}",
+    "UserLockedOutWithName": "User {0} has been locked out",
     "UserConfigurationUpdatedWithName": "User configuration has been updated for {0}",
     "UserCreatedWithName": "User {0} has been created",
     "UserPasswordChangedWithName": "Password has been changed for user {0}",
@@ -1114,7 +1125,7 @@
     "MessageApplicationUpdated": "Media Browser Server has been updated",
     "AuthenticationSucceededWithUserName": "{0} successfully authenticated",
     "FailedLoginAttemptWithUserName": "Failed login attempt from {0}",
-    "UserDownloadingItemWithValues":  "{0} is downloading {1}",
+    "UserDownloadingItemWithValues": "{0} is downloading {1}",
     "UserStartedPlayingItemWithValues": "{0} has started playing {1}",
     "UserStoppedPlayingItemWithValues": "{0} has stopped playing {1}",
     "AppDeviceValues": "App: {0}, Device: {1}",
@@ -1369,5 +1380,7 @@
     "TabJobs": "Jobs",
     "TabSyncJobs": "Sync Jobs",
     "LabelTagFilterMode": "Mode:",
-    "LabelTagFilterAllowModeHelp": "If allowed tags are used as part of a deeply nested folder structure, content that is tagged will require parent folders to be tagged as well."
+    "LabelTagFilterAllowModeHelp": "If allowed tags are used as part of a deeply nested folder structure, content that is tagged will require parent folders to be tagged as well.",
+    "HeaderThisUserIsCurrentlyDisabled": "This user is currently disabled",
+    "MessageReenableUser": "See below to reenable"
 }

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

@@ -47,7 +47,7 @@
   <ItemGroup>
     <Reference Include="ImageMagickSharp, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
       <SpecificVersion>False</SpecificVersion>
-      <HintPath>..\packages\ImageMagickSharp.1.0.0.2\lib\net45\ImageMagickSharp.dll</HintPath>
+      <HintPath>..\packages\ImageMagickSharp.1.0.0.6\lib\net45\ImageMagickSharp.dll</HintPath>
     </Reference>
     <Reference Include="MediaBrowser.Naming, Version=1.0.5509.27636, Culture=neutral, processorArchitecture=MSIL">
       <SpecificVersion>False</SpecificVersion>
@@ -278,6 +278,7 @@
     <Compile Include="Sorting\CommunityRatingComparer.cs" />
     <Compile Include="Sorting\CriticRatingComparer.cs" />
     <Compile Include="Sorting\DateCreatedComparer.cs" />
+    <Compile Include="Sorting\DateLastMediaAddedComparer.cs" />
     <Compile Include="Sorting\DatePlayedComparer.cs" />
     <Compile Include="Sorting\GameSystemComparer.cs" />
     <Compile Include="Sorting\IsFavoriteOrLikeComparer.cs" />
@@ -303,8 +304,12 @@
     <Compile Include="Sorting\StudioComparer.cs" />
     <Compile Include="Sorting\VideoBitRateComparer.cs" />
     <Compile Include="Sync\AppSyncProvider.cs" />
-    <Compile Include="Sync\CloudSyncProvider.cs" />
+    <Compile Include="Sync\FolderSync\FolderSyncDataProvider.cs" />
+    <Compile Include="Sync\FolderSync\FolderSyncProvider.cs" />
+    <Compile Include="Sync\CloudSyncProfile.cs" />
+    <Compile Include="Sync\IHasSyncProfile.cs" />
     <Compile Include="Sync\MediaSync.cs" />
+    <Compile Include="Sync\MultiProviderSync.cs" />
     <Compile Include="Sync\SyncRegistrationInfo.cs" />
     <Compile Include="Sync\SyncConfig.cs" />
     <Compile Include="Sync\SyncJobProcessor.cs" />

+ 11 - 0
MediaBrowser.Server.Implementations/Notifications/CoreNotificationTypes.cs

@@ -143,6 +143,13 @@ namespace MediaBrowser.Server.Implementations.Notifications
                      Type = NotificationType.CameraImageUploaded.ToString(),
                      DefaultTitle = "A new camera image has been uploaded from {DeviceName}.",
                      Variables = new List<string>{"DeviceName"}
+                },
+
+                new NotificationTypeInfo
+                {
+                     Type = NotificationType.UserLockedOut.ToString(),
+                     DefaultTitle = "{UserName} has been locked out.",
+                     Variables = new List<string>{"UserName"}
                 }
             };
 
@@ -185,6 +192,10 @@ namespace MediaBrowser.Server.Implementations.Notifications
             {
                 note.Category = _localization.GetLocalizedString("CategorySync");
             }
+            else if (note.Type.IndexOf("UserLockedOut", StringComparison.OrdinalIgnoreCase) != -1)
+            {
+                note.Category = _localization.GetLocalizedString("CategoryUser");
+            }
             else
             {
                 note.Category = _localization.GetLocalizedString("CategorySystem");

+ 12 - 2
MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs

@@ -108,7 +108,12 @@ namespace MediaBrowser.Server.Implementations.Photos
 
         protected Task<Stream> GetThumbCollage(List<BaseItem> items)
         {
-            return DynamicImageHelpers.GetThumbCollage(items.Select(i => i.GetImagePath(ImageType.Primary) ?? i.GetImagePath(ImageType.Thumb)).ToList(),
+            var files = items
+                .Select(i => i.GetImagePath(ImageType.Primary) ?? i.GetImagePath(ImageType.Thumb))
+                .Where(i => !string.IsNullOrWhiteSpace(i))
+                .ToList();
+
+            return DynamicImageHelpers.GetThumbCollage(files,
                 FileSystem,
                 1600,
                 900,
@@ -117,7 +122,12 @@ namespace MediaBrowser.Server.Implementations.Photos
 
         protected Task<Stream> GetSquareCollage(List<BaseItem> items)
         {
-            return DynamicImageHelpers.GetSquareCollage(items.Select(i => i.GetImagePath(ImageType.Primary) ?? i.GetImagePath(ImageType.Thumb)).ToList(),
+            var files = items
+                .Select(i => i.GetImagePath(ImageType.Primary) ?? i.GetImagePath(ImageType.Thumb))
+                .Where(i => !string.IsNullOrWhiteSpace(i))
+                .ToList();
+
+            return DynamicImageHelpers.GetSquareCollage(files,
                 FileSystem,
                 800, ApplicationPaths);
         }

+ 13 - 2
MediaBrowser.Server.Implementations/Photos/DynamicImageHelpers.cs

@@ -4,6 +4,7 @@ using MediaBrowser.Common.IO;
 using System;
 using System.Collections.Generic;
 using System.IO;
+using System.Linq;
 using System.Threading.Tasks;
 
 namespace MediaBrowser.Server.Implementations.Photos
@@ -15,6 +16,11 @@ namespace MediaBrowser.Server.Implementations.Photos
             int width,
             int height, IApplicationPaths appPaths)
         {
+            if (files.Any(string.IsNullOrWhiteSpace))
+            {
+                throw new ArgumentException("Empty file found in files list");
+            }
+
             if (files.Count < 3)
             {
                 return await GetSingleImage(files, fileSystem).ConfigureAwait(false);
@@ -27,7 +33,7 @@ namespace MediaBrowser.Server.Implementations.Photos
             int cellHeight = height;
             var index = 0;
 
-            using (var wand = new MagickWand(width, height, "transparent"))
+            using (var wand = new MagickWand(width, height, new PixelWand(ColorName.None, 1)))
             {
                 for (var row = 0; row < rows; row++)
                 {
@@ -57,6 +63,11 @@ namespace MediaBrowser.Server.Implementations.Photos
             IFileSystem fileSystem,
             int size, IApplicationPaths appPaths)
         {
+            if (files.Any(string.IsNullOrWhiteSpace))
+            {
+                throw new ArgumentException("Empty file found in files list");
+            }
+
             if (files.Count < 4)
             {
                 return await GetSingleImage(files, fileSystem).ConfigureAwait(false);
@@ -68,7 +79,7 @@ namespace MediaBrowser.Server.Implementations.Photos
             int singleSize = size / 2;
             var index = 0;
 
-            using (var wand = new MagickWand(size, size, "transparent"))
+            using (var wand = new MagickWand(size, size, new PixelWand(ColorName.None, 1)))
             {
                 for (var row = 0; row < rows; row++)
                 {

+ 18 - 9
MediaBrowser.Server.Implementations/Session/SessionManager.cs

@@ -399,7 +399,7 @@ namespace MediaBrowser.Server.Implementations.Session
                         Client = clientType,
                         DeviceId = deviceId,
                         ApplicationVersion = appVersion,
-                        Id = Guid.NewGuid().ToString("N")
+                        Id = key.GetMD5().ToString("N")
                     };
 
                     sessionInfo.DeviceName = deviceName;
@@ -798,6 +798,19 @@ namespace MediaBrowser.Server.Implementations.Session
             return session;
         }
 
+        private SessionInfo GetSessionToRemoteControl(string sessionId)
+        {
+            // Accept either device id or session id
+            var session = Sessions.FirstOrDefault(i => string.Equals(i.Id, sessionId));
+
+            if (session == null)
+            {
+                throw new ResourceNotFoundException(string.Format("Session {0} not found.", sessionId));
+            }
+
+            return session;
+        }
+
         public Task SendMessageCommand(string controllingSessionId, string sessionId, MessageCommand command, CancellationToken cancellationToken)
         {
             var generalCommand = new GeneralCommand
@@ -818,7 +831,7 @@ namespace MediaBrowser.Server.Implementations.Session
 
         public Task SendGeneralCommand(string controllingSessionId, string sessionId, GeneralCommand command, CancellationToken cancellationToken)
         {
-            var session = GetSession(sessionId);
+            var session = GetSessionToRemoteControl(sessionId);
 
             var controllingSession = GetSession(controllingSessionId);
             AssertCanControl(session, controllingSession);
@@ -828,7 +841,7 @@ namespace MediaBrowser.Server.Implementations.Session
 
         public Task SendPlayCommand(string controllingSessionId, string sessionId, PlayRequest command, CancellationToken cancellationToken)
         {
-            var session = GetSession(sessionId);
+            var session = GetSessionToRemoteControl(sessionId);
 
             var user = session.UserId.HasValue ? _userManager.GetUserById(session.UserId.Value) : null;
 
@@ -955,7 +968,7 @@ namespace MediaBrowser.Server.Implementations.Session
 
         public Task SendPlaystateCommand(string controllingSessionId, string sessionId, PlaystateRequest command, CancellationToken cancellationToken)
         {
-            var session = GetSession(sessionId);
+            var session = GetSessionToRemoteControl(sessionId);
 
             var controllingSession = GetSession(controllingSessionId);
             AssertCanControl(session, controllingSession);
@@ -1566,11 +1579,7 @@ namespace MediaBrowser.Server.Implementations.Session
 
             if (!string.IsNullOrWhiteSpace(mediaSourceId))
             {
-                info.MediaStreams = _mediaSourceManager.GetMediaStreams(new MediaStreamQuery
-                {
-                    ItemId = new Guid(mediaSourceId)
-
-                }).ToList();
+                info.MediaStreams = _mediaSourceManager.GetMediaStreams(mediaSourceId).ToList();
             }
 
             return info;

+ 70 - 0
MediaBrowser.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs

@@ -0,0 +1,70 @@
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Sorting;
+using MediaBrowser.Model.Querying;
+using System;
+using System.Linq;
+
+namespace MediaBrowser.Server.Implementations.Sorting
+{
+    public class DateLastMediaAddedComparer : IUserBaseItemComparer
+    {
+        /// <summary>
+        /// Gets or sets the user.
+        /// </summary>
+        /// <value>The user.</value>
+        public User User { get; set; }
+
+        /// <summary>
+        /// Gets or sets the user manager.
+        /// </summary>
+        /// <value>The user manager.</value>
+        public IUserManager UserManager { get; set; }
+
+        /// <summary>
+        /// Gets or sets the user data repository.
+        /// </summary>
+        /// <value>The user data repository.</value>
+        public IUserDataManager UserDataRepository { get; set; }
+
+        /// <summary>
+        /// Compares the specified x.
+        /// </summary>
+        /// <param name="x">The x.</param>
+        /// <param name="y">The y.</param>
+        /// <returns>System.Int32.</returns>
+        public int Compare(BaseItem x, BaseItem y)
+        {
+            return GetDate(x).CompareTo(GetDate(y));
+        }
+
+        /// <summary>
+        /// Gets the date.
+        /// </summary>
+        /// <param name="x">The x.</param>
+        /// <returns>DateTime.</returns>
+        private DateTime GetDate(BaseItem x)
+        {
+            var folder = x as Folder;
+
+            if (folder != null)
+            {
+                return folder.GetRecursiveChildren(User, i => !i.IsFolder)
+                    .Select(i => i.DateCreated)
+                    .OrderByDescending(i => i)
+                    .FirstOrDefault();
+            }
+
+            return x.DateCreated;
+        }
+
+        /// <summary>
+        /// Gets the name.
+        /// </summary>
+        /// <value>The name.</value>
+        public string Name
+        {
+            get { return ItemSortBy.DateLastContentAdded; }
+        }
+    }
+}

+ 0 - 1
MediaBrowser.Server.Implementations/Sorting/DatePlayedComparer.cs

@@ -1,6 +1,5 @@
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Persistence;
 using MediaBrowser.Controller.Sorting;
 using MediaBrowser.Model.Querying;
 using System;

+ 14 - 1
MediaBrowser.Server.Implementations/Sync/AppSyncProvider.cs

@@ -8,7 +8,7 @@ using System.Linq;
 
 namespace MediaBrowser.Server.Implementations.Sync
 {
-    public class AppSyncProvider : ISyncProvider, IHasUniqueTargetIds
+    public class AppSyncProvider : ISyncProvider, IHasUniqueTargetIds, IHasSyncProfile
     {
         private readonly IDeviceManager _deviceManager;
 
@@ -42,5 +42,18 @@ namespace MediaBrowser.Server.Implementations.Sync
         {
             get { return "App Sync"; }
         }
+
+        public IEnumerable<SyncTarget> GetAllSyncTargets()
+        {
+            return _deviceManager.GetDevices(new DeviceQuery
+            {
+                SupportsSync = true
+
+            }).Items.Select(i => new SyncTarget
+            {
+                Id = i.Id,
+                Name = i.Name
+            });
+        }
     }
 }

+ 118 - 0
MediaBrowser.Server.Implementations/Sync/CloudSyncProfile.cs

@@ -0,0 +1,118 @@
+using MediaBrowser.Model.Dlna;
+
+namespace MediaBrowser.Server.Implementations.Sync
+{
+    public class CloudSyncProfile : DeviceProfile
+    {
+        public CloudSyncProfile(bool supportsAc3, bool supportsDca)
+        {
+            Name = "Cloud Sync";
+
+            MaxStreamingBitrate = 20000000;
+            MaxStaticBitrate = 20000000;
+
+            var mkvAudio = "aac,mp3";
+            var mp4Audio = "aac";
+
+            if (supportsAc3)
+            {
+                mkvAudio += ",ac3";
+                mp4Audio += ",ac3";
+            }
+
+            if (supportsDca)
+            {
+                mkvAudio += ",dca";
+            }
+
+            DirectPlayProfiles = new[]
+            {
+                new DirectPlayProfile
+                {
+                    Container = "mkv",
+                    VideoCodec = "h264,mpeg4",
+                    AudioCodec = mkvAudio,
+                    Type = DlnaProfileType.Video
+                },
+                new DirectPlayProfile
+                {
+                    Container = "mp4,mov,m4v",
+                    VideoCodec = "h264,mpeg4",
+                    AudioCodec = mp4Audio,
+                    Type = DlnaProfileType.Video
+                }
+            };
+
+            ContainerProfiles = new ContainerProfile[] { };
+
+            CodecProfiles = new[]
+            {
+                new CodecProfile
+                {
+                    Type = CodecType.Video,
+                    Conditions = new []
+                    {
+                        new ProfileCondition
+                        {
+                            Condition = ProfileConditionType.LessThanEqual,
+                            Property = ProfileConditionValue.VideoBitDepth,
+                            Value = "8",
+                            IsRequired = false
+                        },
+                        new ProfileCondition
+                        {
+                            Condition = ProfileConditionType.LessThanEqual,
+                            Property = ProfileConditionValue.Height,
+                            Value = "1080",
+                            IsRequired = false
+                        },
+                        new ProfileCondition
+                        {
+                            Condition = ProfileConditionType.LessThanEqual,
+                            Property = ProfileConditionValue.RefFrames,
+                            Value = "12",
+                            IsRequired = false
+                        }
+                    }
+                }
+            };
+
+            SubtitleProfiles = new[]
+            {
+                new SubtitleProfile
+                {
+                    Format = "srt",
+                    Method = SubtitleDeliveryMethod.External
+                }
+            };
+
+            TranscodingProfiles = new[]
+            {
+                new TranscodingProfile
+                {
+                    Container = "mp3",
+                    AudioCodec = "mp3",
+                    Type = DlnaProfileType.Audio,
+                    Context = EncodingContext.Static
+                },
+
+                new TranscodingProfile
+                {
+                    Container = "mp4",
+                    Type = DlnaProfileType.Video,
+                    AudioCodec = "aac",
+                    VideoCodec = "h264",
+                    Context = EncodingContext.Static
+                },
+
+                new TranscodingProfile
+                {
+                    Container = "jpeg",
+                    Type = DlnaProfileType.Photo,
+                    Context = EncodingContext.Static
+                }
+            };
+
+        }
+    }
+}

+ 0 - 59
MediaBrowser.Server.Implementations/Sync/CloudSyncProvider.cs

@@ -1,59 +0,0 @@
-using MediaBrowser.Common;
-using MediaBrowser.Controller.Sync;
-using MediaBrowser.Model.Dlna;
-using MediaBrowser.Model.Sync;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace MediaBrowser.Server.Implementations.Sync
-{
-    public class CloudSyncProvider : IServerSyncProvider
-    {
-        private readonly ICloudSyncProvider[] _providers = {};
-
-        public CloudSyncProvider(IApplicationHost appHost)
-        {
-            _providers = appHost.GetExports<ICloudSyncProvider>().ToArray();
-        }
-
-        public IEnumerable<SyncTarget> GetSyncTargets(string userId)
-        {
-            return _providers.SelectMany(i => i.GetSyncTargets(userId));
-        }
-
-        public DeviceProfile GetDeviceProfile(SyncTarget target)
-        {
-            return new DeviceProfile();
-        }
-
-        public string Name
-        {
-            get { return "Cloud Sync"; }
-        }
-
-        private ICloudSyncProvider GetProvider(SyncTarget target)
-        {
-            return null;
-        }
-
-        public Task<List<string>> GetServerItemIds(string serverId, SyncTarget target, CancellationToken cancellationToken)
-        {
-            throw new NotImplementedException();
-        }
-
-        public Task DeleteItem(string serverId, string itemId, SyncTarget target, CancellationToken cancellationToken)
-        {
-            throw new NotImplementedException();
-        }
-
-        public Task TransferItemFile(string serverId, string itemId, string inputFile, string[] pathParts, SyncTarget target, CancellationToken cancellationToken)
-        {
-            var provider = GetProvider(target);
-
-            return provider.TransferItemFile(serverId, itemId, inputFile, pathParts, target, cancellationToken);
-        }
-    }
-}

+ 31 - 0
MediaBrowser.Server.Implementations/Sync/FolderSync/FolderSyncDataProvider.cs

@@ -0,0 +1,31 @@
+using MediaBrowser.Controller.Sync;
+using MediaBrowser.Model.Sync;
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Server.Implementations.Sync.FolderSync
+{
+    public class FolderSyncDataProvider : ISyncDataProvider
+    {
+        public Task<List<string>> GetServerItemIds(SyncTarget target, string serverId)
+        {
+            throw new NotImplementedException();
+        }
+
+        public Task AddOrUpdate(SyncTarget target, LocalItem item)
+        {
+            throw new NotImplementedException();
+        }
+
+        public Task Delete(SyncTarget target, string id)
+        {
+            throw new NotImplementedException();
+        }
+
+        public Task<LocalItem> Get(SyncTarget target, string id)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

+ 143 - 0
MediaBrowser.Server.Implementations/Sync/FolderSync/FolderSyncProvider.cs

@@ -0,0 +1,143 @@
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Sync;
+using MediaBrowser.Model.Sync;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Server.Implementations.Sync.FolderSync
+{
+    public class FolderSyncProvider : IServerSyncProvider
+    {
+        private readonly IApplicationPaths _appPaths;
+        private readonly IUserManager _userManager;
+
+        public FolderSyncProvider(IApplicationPaths appPaths, IUserManager userManager)
+        {
+            _appPaths = appPaths;
+            _userManager = userManager;
+        }
+
+        public Task SendFile(string inputFile, string path, SyncTarget target, IProgress<double> progress, CancellationToken cancellationToken)
+        {
+            return Task.Run(() => File.Copy(inputFile, path, true), cancellationToken);
+        }
+
+        public Task DeleteFile(string path, SyncTarget target, CancellationToken cancellationToken)
+        {
+            return Task.Run(() => File.Delete(path), cancellationToken);
+        }
+
+        public Task<Stream> GetFile(string path, SyncTarget target, IProgress<double> progress, CancellationToken cancellationToken)
+        {
+            return Task.FromResult((Stream)File.OpenRead(path));
+        }
+
+        public string GetFullPath(IEnumerable<string> paths, SyncTarget target)
+        {
+            var account = GetSyncAccounts()
+                .FirstOrDefault(i => string.Equals(i.Id, target.Id, StringComparison.OrdinalIgnoreCase));
+
+            if (account == null)
+            {
+                throw new ArgumentException("Invalid SyncTarget supplied.");
+            }
+
+            var list = paths.ToList();
+            list.Insert(0, account.Path);
+
+            return Path.Combine(list.ToArray());
+        }
+
+        public string GetParentDirectoryPath(string path, SyncTarget target)
+        {
+            return Path.GetDirectoryName(path);
+        }
+
+        public Task<List<DeviceFileInfo>> GetFileSystemEntries(string path, SyncTarget target)
+        {
+            List<FileInfo> files;
+
+            try
+            {
+                files = new DirectoryInfo(path).EnumerateFiles("*", SearchOption.TopDirectoryOnly).ToList();
+            }
+            catch (DirectoryNotFoundException)
+            {
+                files = new List<FileInfo>();
+            }
+
+            return Task.FromResult(files.Select(i => new DeviceFileInfo
+            {
+                Name = i.Name,
+                Path = i.FullName
+
+            }).ToList());
+        }
+
+        public ISyncDataProvider GetDataProvider()
+        {
+            // If single instances are needed, manage them here
+            return new FolderSyncDataProvider();
+        }
+
+        public string Name
+        {
+            get { return "Folder Sync"; }
+        }
+
+        public IEnumerable<SyncTarget> GetSyncTargets(string userId)
+        {
+            return GetSyncAccounts()
+                .Where(i => i.UserIds.Contains(userId, StringComparer.OrdinalIgnoreCase))
+                .Select(GetSyncTarget);
+        }
+
+        public IEnumerable<SyncTarget> GetAllSyncTargets()
+        {
+            return GetSyncAccounts().Select(GetSyncTarget);
+        }
+
+        private SyncTarget GetSyncTarget(SyncAccount account)
+        {
+            return new SyncTarget
+            {
+                Id = account.Id,
+                Name = account.Name
+            };
+        }
+
+        private IEnumerable<SyncAccount> GetSyncAccounts()
+        {
+            return new List<SyncAccount>();
+            // Dummy this up
+            return _userManager
+                .Users
+                .Select(i => new SyncAccount
+                {
+                    Id = i.Id.ToString("N"),
+                    UserIds = new List<string> { i.Id.ToString("N") },
+                    Path = Path.Combine(_appPaths.DataPath, "foldersync", i.Id.ToString("N")),
+                    Name = i.Name + "'s Folder Sync"
+                });
+        }
+
+        // An internal class to manage all configured Folder Sync accounts for differnet users
+        class SyncAccount
+        {
+            public string Id { get; set; }
+            public string Name { get; set; }
+            public string Path { get; set; }
+            public List<string> UserIds { get; set; }
+
+            public SyncAccount()
+            {
+                UserIds = new List<string>();
+            }
+        }
+    }
+}

+ 15 - 0
MediaBrowser.Server.Implementations/Sync/IHasSyncProfile.cs

@@ -0,0 +1,15 @@
+using MediaBrowser.Model.Dlna;
+using MediaBrowser.Model.Sync;
+
+namespace MediaBrowser.Server.Implementations.Sync
+{
+    public interface IHasSyncProfile
+    {
+        /// <summary>
+        /// Gets the device profile.
+        /// </summary>
+        /// <param name="target">The target.</param>
+        /// <returns>DeviceProfile.</returns>
+        DeviceProfile GetDeviceProfile(SyncTarget target);
+    }
+}

+ 206 - 19
MediaBrowser.Server.Implementations/Sync/MediaSync.cs

@@ -1,10 +1,18 @@
-using MediaBrowser.Common.Progress;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Common.Progress;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller.Sync;
 using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.MediaInfo;
 using MediaBrowser.Model.Sync;
 using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Security.Cryptography;
+using System.Text;
 using System.Threading;
 using System.Threading.Tasks;
 
@@ -15,22 +23,25 @@ namespace MediaBrowser.Server.Implementations.Sync
         private readonly ISyncManager _syncManager;
         private readonly IServerApplicationHost _appHost;
         private readonly ILogger _logger;
+        private readonly IFileSystem _fileSystem;
 
-        public MediaSync(ILogger logger, ISyncManager syncManager, IServerApplicationHost appHost)
+        public MediaSync(ILogger logger, ISyncManager syncManager, IServerApplicationHost appHost, IFileSystem fileSystem)
         {
             _logger = logger;
             _syncManager = syncManager;
             _appHost = appHost;
+            _fileSystem = fileSystem;
         }
 
-        public async Task Sync(IServerSyncProvider provider, 
+        public async Task Sync(IServerSyncProvider provider,
+            ISyncDataProvider dataProvider,
             SyncTarget target,
             IProgress<double> progress,
             CancellationToken cancellationToken)
         {
             var serverId = _appHost.SystemId;
 
-            await SyncData(provider, serverId, target, cancellationToken).ConfigureAwait(false);
+            await SyncData(provider, dataProvider, serverId, target, cancellationToken).ConfigureAwait(false);
             progress.Report(3);
 
             var innerProgress = new ActionableProgress<double>();
@@ -40,20 +51,21 @@ namespace MediaBrowser.Server.Implementations.Sync
                 totalProgress += 1;
                 progress.Report(totalProgress);
             });
-            await GetNewMedia(provider, target, serverId, innerProgress, cancellationToken);
+            await GetNewMedia(provider, dataProvider, target, serverId, innerProgress, cancellationToken);
 
             // Do the data sync twice so the server knows what was removed from the device
-            await SyncData(provider, serverId, target, cancellationToken).ConfigureAwait(false);
+            await SyncData(provider, dataProvider, serverId, target, cancellationToken).ConfigureAwait(false);
 
             progress.Report(100);
         }
 
         private async Task SyncData(IServerSyncProvider provider,
+            ISyncDataProvider dataProvider,
             string serverId,
             SyncTarget target,
             CancellationToken cancellationToken)
         {
-            var localIds = await provider.GetServerItemIds(serverId, target, cancellationToken).ConfigureAwait(false);
+            var localIds = await dataProvider.GetServerItemIds(target, serverId).ConfigureAwait(false);
 
             var result = await _syncManager.SyncData(new SyncDataRequest
             {
@@ -68,23 +80,24 @@ namespace MediaBrowser.Server.Implementations.Sync
             {
                 try
                 {
-                    await RemoveItem(provider, serverId, itemIdToRemove, target, cancellationToken).ConfigureAwait(false);
+                    await RemoveItem(provider, dataProvider, serverId, itemIdToRemove, target, cancellationToken).ConfigureAwait(false);
                 }
                 catch (Exception ex)
                 {
-                    _logger.ErrorException("Error deleting item from sync target. Id: {0}", ex, itemIdToRemove);
+                    _logger.ErrorException("Error deleting item from device. Id: {0}", ex, itemIdToRemove);
                 }
             }
         }
 
         private async Task GetNewMedia(IServerSyncProvider provider,
+            ISyncDataProvider dataProvider,
             SyncTarget target,
             string serverId,
             IProgress<double> progress,
             CancellationToken cancellationToken)
         {
-            var jobItems =  await _syncManager.GetReadySyncItems(target.Id).ConfigureAwait(false);
-            
+            var jobItems = await _syncManager.GetReadySyncItems(target.Id).ConfigureAwait(false);
+
             var numComplete = 0;
             double startingPercent = 0;
             double percentPerItem = 1;
@@ -106,7 +119,7 @@ namespace MediaBrowser.Server.Implementations.Sync
                     progress.Report(totalProgress);
                 });
 
-                await GetItem(provider, target, serverId, jobItem, innerProgress, cancellationToken).ConfigureAwait(false);
+                await GetItem(provider, dataProvider, target, serverId, jobItem, innerProgress, cancellationToken).ConfigureAwait(false);
 
                 numComplete++;
                 startingPercent = numComplete;
@@ -117,6 +130,7 @@ namespace MediaBrowser.Server.Implementations.Sync
         }
 
         private async Task GetItem(IServerSyncProvider provider,
+            ISyncDataProvider dataProvider,
             SyncTarget target,
             string serverId,
             SyncedItem jobItem,
@@ -129,6 +143,8 @@ namespace MediaBrowser.Server.Implementations.Sync
             var fileTransferProgress = new ActionableProgress<double>();
             fileTransferProgress.RegisterAction(pct => progress.Report(pct * .92));
 
+            var localItem = CreateLocalItem(provider, target, libraryItem, serverId, jobItem.OriginalFileName);
+
             await _syncManager.ReportSyncJobItemTransferBeginning(internalSyncJobItem.Id);
 
             var transferSuccess = false;
@@ -136,10 +152,10 @@ namespace MediaBrowser.Server.Implementations.Sync
 
             try
             {
-                string[] pathParts = GetPathParts(serverId, libraryItem);
+                await SendFile(provider, internalSyncJobItem.OutputPath, localItem, target, cancellationToken).ConfigureAwait(false);
 
-                await provider.TransferItemFile(serverId, libraryItem.Id, internalSyncJobItem.OutputPath, pathParts, target, cancellationToken)
-                        .ConfigureAwait(false);
+                // Create db record
+                await dataProvider.AddOrUpdate(target, localItem).ConfigureAwait(false);
 
                 progress.Report(92);
 
@@ -165,18 +181,189 @@ namespace MediaBrowser.Server.Implementations.Sync
             }
         }
 
-        private Task RemoveItem(IServerSyncProvider provider,
+        private async Task RemoveItem(IServerSyncProvider provider,
+            ISyncDataProvider dataProvider,
             string serverId,
             string itemId,
             SyncTarget target,
             CancellationToken cancellationToken)
         {
-            return provider.DeleteItem(serverId, itemId, target, cancellationToken);
+            var localId = GetLocalId(serverId, itemId);
+            var localItem = await dataProvider.Get(target, localId);
+
+            if (localItem == null)
+            {
+                return;
+            }
+
+            var files = await GetFiles(provider, localItem, target);
+
+            foreach (var file in files)
+            {
+                await provider.DeleteFile(file.Path, target, cancellationToken).ConfigureAwait(false);
+            }
+
+            await dataProvider.Delete(target, localId).ConfigureAwait(false);
+        }
+
+        private Task SendFile(IServerSyncProvider provider, string inputPath, LocalItem item, SyncTarget target, CancellationToken cancellationToken)
+        {
+            return provider.SendFile(inputPath, item.LocalPath, target, new Progress<double>(), cancellationToken);
+        }
+
+        private string GetLocalId(string serverId, string itemId)
+        {
+            var bytes = Encoding.UTF8.GetBytes(serverId + itemId);
+            bytes = CreateMD5(bytes);
+            return BitConverter.ToString(bytes, 0, bytes.Length).Replace("-", string.Empty);
+        }
+
+        private byte[] CreateMD5(byte[] value)
+        {
+            using (var provider = MD5.Create())
+            {
+                return provider.ComputeHash(value);
+            }
+        }
+
+        public LocalItem CreateLocalItem(IServerSyncProvider provider, SyncTarget target, BaseItemDto libraryItem, string serverId, string originalFileName)
+        {
+            var path = GetDirectoryPath(provider, libraryItem, serverId);
+            path.Add(GetLocalFileName(provider, libraryItem, originalFileName));
+
+            var localPath = provider.GetFullPath(path, target);
+
+            foreach (var mediaSource in libraryItem.MediaSources)
+            {
+                mediaSource.Path = localPath;
+                mediaSource.Protocol = MediaProtocol.File;
+            }
+
+            return new LocalItem
+            {
+                Item = libraryItem,
+                ItemId = libraryItem.Id,
+                ServerId = serverId,
+                LocalPath = localPath,
+                Id = GetLocalId(serverId, libraryItem.Id)
+            };
+        }
+
+        private List<string> GetDirectoryPath(IServerSyncProvider provider, BaseItemDto item, string serverId)
+        {
+            var parts = new List<string>
+            {
+                serverId
+            };
+
+            if (item.IsType("episode"))
+            {
+                parts.Add("TV");
+                parts.Add(item.SeriesName);
+
+                if (!string.IsNullOrWhiteSpace(item.SeasonName))
+                {
+                    parts.Add(item.SeasonName);
+                }
+            }
+            else if (item.IsVideo)
+            {
+                parts.Add("Videos");
+                parts.Add(item.Name);
+            }
+            else if (item.IsAudio)
+            {
+                parts.Add("Music");
+
+                if (!string.IsNullOrWhiteSpace(item.AlbumArtist))
+                {
+                    parts.Add(item.AlbumArtist);
+                }
+
+                if (!string.IsNullOrWhiteSpace(item.Album))
+                {
+                    parts.Add(item.Album);
+                }
+            }
+            else if (string.Equals(item.MediaType, MediaType.Photo, StringComparison.OrdinalIgnoreCase))
+            {
+                parts.Add("Photos");
+
+                if (!string.IsNullOrWhiteSpace(item.Album))
+                {
+                    parts.Add(item.Album);
+                }
+            }
+
+            return parts.Select(i => GetValidFilename(provider, i)).ToList();
+        }
+
+        private string GetLocalFileName(IServerSyncProvider provider, BaseItemDto item, string originalFileName)
+        {
+            var filename = originalFileName;
+
+            if (string.IsNullOrEmpty(filename))
+            {
+                filename = item.Name;
+            }
+
+            return GetValidFilename(provider, filename);
+        }
+
+        private string GetValidFilename(IServerSyncProvider provider, string filename)
+        {
+            // We can always add this method to the sync provider if it's really needed
+            return _fileSystem.GetValidFilename(filename);
         }
 
-        private string[] GetPathParts(string serverId, BaseItemDto item)
+        private async Task<List<ItemFileInfo>> GetFiles(IServerSyncProvider provider, LocalItem item, SyncTarget target)
         {
-            return null;
+            var path = item.LocalPath;
+            path = provider.GetParentDirectoryPath(path, target);
+
+            var list = await provider.GetFileSystemEntries(path, target).ConfigureAwait(false);
+
+            var itemFiles = new List<ItemFileInfo>();
+
+            var name = Path.GetFileNameWithoutExtension(item.LocalPath);
+
+            foreach (var file in list.Where(f => f.Name.Contains(name)))
+            {
+                var itemFile = new ItemFileInfo
+                {
+                    Path = file.Path,
+                    Name = file.Name
+                };
+
+                if (IsSubtitleFile(file.Name))
+                {
+                    itemFile.Type = ItemFileType.Subtitles;
+                }
+                else if (!IsImageFile(file.Name))
+                {
+                    itemFile.Type = ItemFileType.Media;
+                }
+
+                itemFiles.Add(itemFile);
+            }
+
+            return itemFiles;
+        }
+
+        private static readonly string[] SupportedImageExtensions = { ".png", ".jpg", ".jpeg", ".webp" };
+        private bool IsImageFile(string path)
+        {
+            var ext = Path.GetExtension(path) ?? string.Empty;
+
+            return SupportedImageExtensions.Contains(ext, StringComparer.OrdinalIgnoreCase);
+        }
+
+        private static readonly string[] SupportedSubtitleExtensions = { ".srt", ".vtt" };
+        private bool IsSubtitleFile(string path)
+        {
+            var ext = Path.GetExtension(path) ?? string.Empty;
+
+            return SupportedSubtitleExtensions.Contains(ext, StringComparer.OrdinalIgnoreCase);
         }
     }
 }

+ 69 - 0
MediaBrowser.Server.Implementations/Sync/MultiProviderSync.cs

@@ -0,0 +1,69 @@
+using MediaBrowser.Common.IO;
+using MediaBrowser.Common.Progress;
+using MediaBrowser.Controller;
+using MediaBrowser.Controller.Sync;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.Sync;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Server.Implementations.Sync
+{
+    public class MultiProviderSync
+    {
+        private readonly ISyncManager _syncManager;
+        private readonly IServerApplicationHost _appHost;
+        private readonly ILogger _logger;
+        private readonly IFileSystem _fileSystem;
+
+        public MultiProviderSync(ISyncManager syncManager, IServerApplicationHost appHost, ILogger logger, IFileSystem fileSystem)
+        {
+            _syncManager = syncManager;
+            _appHost = appHost;
+            _logger = logger;
+            _fileSystem = fileSystem;
+        }
+
+        public async Task Sync(IEnumerable<IServerSyncProvider> providers, IProgress<double> progress, CancellationToken cancellationToken)
+        {
+            var targets = providers
+                .SelectMany(i => i.GetAllSyncTargets().Select(t => new Tuple<IServerSyncProvider, SyncTarget>(i, t)))
+                .ToList();
+
+            var numComplete = 0;
+            double startingPercent = 0;
+            double percentPerItem = 1;
+            if (targets.Count > 0)
+            {
+                percentPerItem /= targets.Count;
+            }
+
+            foreach (var target in targets)
+            {
+                cancellationToken.ThrowIfCancellationRequested();
+
+                var currentPercent = startingPercent;
+                var innerProgress = new ActionableProgress<double>();
+                innerProgress.RegisterAction(pct =>
+                {
+                    var totalProgress = pct * percentPerItem;
+                    totalProgress += currentPercent;
+                    progress.Report(totalProgress);
+                });
+
+                await new MediaSync(_logger, _syncManager, _appHost, _fileSystem)
+                    .Sync(target.Item1, target.Item1.GetDataProvider(), target.Item2, innerProgress, cancellationToken)
+                    .ConfigureAwait(false);
+
+                numComplete++;
+                startingPercent = numComplete;
+                startingPercent /= targets.Count;
+                startingPercent *= 100;
+                progress.Report(startingPercent);
+            }
+        }
+    }
+}

+ 23 - 9
MediaBrowser.Server.Implementations/Sync/SyncManager.cs

@@ -407,6 +407,15 @@ namespace MediaBrowser.Server.Implementations.Sync
                 .OrderBy(i => i.Name);
         }
 
+        private IEnumerable<SyncTarget> GetSyncTargets(ISyncProvider provider)
+        {
+            return provider.GetAllSyncTargets().Select(i => new SyncTarget
+            {
+                Name = i.Name,
+                Id = GetSyncTargetId(provider, i)
+            });
+        }
+
         private IEnumerable<SyncTarget> GetSyncTargets(ISyncProvider provider, string userId)
         {
             return provider.GetSyncTargets(userId).Select(i => new SyncTarget
@@ -429,13 +438,6 @@ namespace MediaBrowser.Server.Implementations.Sync
             return (providerId + "-" + target.Id).GetMD5().ToString("N");
         }
 
-        private ISyncProvider GetSyncProvider(SyncTarget target)
-        {
-            var providerId = target.Id.Split(new[] { '-' }, 2).First();
-
-            return _providers.First(i => string.Equals(providerId, GetSyncProviderId(i)));
-        }
-
         private string GetSyncProviderId(ISyncProvider provider)
         {
             return (provider.GetType().Name).GetMD5().ToString("N");
@@ -543,11 +545,11 @@ namespace MediaBrowser.Server.Implementations.Sync
         {
             foreach (var provider in _providers)
             {
-                foreach (var target in GetSyncTargets(provider, null))
+                foreach (var target in GetSyncTargets(provider))
                 {
                     if (string.Equals(target.Id, targetId, StringComparison.OrdinalIgnoreCase))
                     {
-                        return provider.GetDeviceProfile(target);
+                        return GetDeviceProfile(provider, target);
                     }
                 }
             }
@@ -555,6 +557,18 @@ namespace MediaBrowser.Server.Implementations.Sync
             return null;
         }
 
+        public DeviceProfile GetDeviceProfile(ISyncProvider provider, SyncTarget target)
+        {
+            var hasProfile = provider as IHasSyncProfile;
+
+            if (hasProfile != null)
+            {
+                return hasProfile.GetDeviceProfile(target);
+            }
+
+            return new CloudSyncProfile(true, false);
+        }
+
         public async Task ReportSyncJobItemTransferred(string id)
         {
             var jobItem = _repo.GetJobItem(id);

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

@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <packages>
-  <package id="ImageMagickSharp" version="1.0.0.2" targetFramework="net45" />
+  <package id="ImageMagickSharp" version="1.0.0.6" targetFramework="net45" />
   <package id="MediaBrowser.Naming" version="1.0.0.32" targetFramework="net45" />
   <package id="Mono.Nat" version="1.2.21.0" targetFramework="net45" />
   <package id="morelinq" version="1.1.0" targetFramework="net45" />

+ 25 - 0
MediaBrowser.Server.Mono/Diagnostics/LinuxProcessManager.cs

@@ -0,0 +1,25 @@
+using MediaBrowser.Controller.Diagnostics;
+using System.Diagnostics;
+
+namespace MediaBrowser.Server.Mono.Diagnostics
+{
+    public class LinuxProcessManager : IProcessManager
+    {
+        public bool SupportsSuspension
+        {
+            get { return true; }
+        }
+
+        public void SuspendProcess(Process process)
+        {
+            // http://jumptuck.com/2011/11/23/quick-tip-pause-process-linux/
+            process.StandardInput.WriteLine("^Z");
+        }
+
+        public void ResumeProcess(Process process)
+        {
+            // http://jumptuck.com/2011/11/23/quick-tip-pause-process-linux/
+            process.StandardInput.WriteLine("fg");
+        }
+    }
+}

+ 1 - 0
MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj

@@ -74,6 +74,7 @@
     <Compile Include="..\SharedVersion.cs">
       <Link>Properties\SharedVersion.cs</Link>
     </Compile>
+    <Compile Include="Diagnostics\LinuxProcessManager.cs" />
     <Compile Include="Native\BaseMonoApp.cs" />
     <Compile Include="Networking\CertificateGenerator.cs" />
     <Compile Include="Program.cs" />

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

@@ -1,6 +1,8 @@
 using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Diagnostics;
 using MediaBrowser.IsoMounter;
 using MediaBrowser.Model.Logging;
+using MediaBrowser.Server.Mono.Diagnostics;
 using MediaBrowser.Server.Mono.Networking;
 using MediaBrowser.Server.Startup.Common;
 using Mono.Unix.Native;
@@ -189,5 +191,16 @@ namespace MediaBrowser.Server.Mono.Native
             public string sysname = string.Empty;
             public string machine = string.Empty;
         }
+
+
+        public IProcessManager GetProcessManager()
+        {
+            if (Environment.OperatingSystem == Startup.Common.OperatingSystem.Linux)
+            {
+                return new LinuxProcessManager();
+            }
+
+            return new ProcessManager();
+        }
     }
 }

+ 2 - 0
MediaBrowser.Server.Startup.Common/ApplicationHost.cs

@@ -380,6 +380,8 @@ namespace MediaBrowser.Server.Startup.Common
 
             RegisterSingleInstance(ServerConfigurationManager);
 
+            RegisterSingleInstance(NativeApp.GetProcessManager());
+
             LocalizationManager = new LocalizationManager(ServerConfigurationManager, FileSystemManager, JsonSerializer);
             RegisterSingleInstance(LocalizationManager);
 

+ 23 - 0
MediaBrowser.Server.Startup.Common/Diagnostics/ProcessManager.cs

@@ -0,0 +1,23 @@
+using MediaBrowser.Controller.Diagnostics;
+using System.Diagnostics;
+
+namespace MediaBrowser.Server.Mono.Diagnostics
+{
+    public class ProcessManager : IProcessManager
+    {
+        public void SuspendProcess(Process process)
+        {
+            process.PriorityClass = ProcessPriorityClass.Idle;
+        }
+
+        public void ResumeProcess(Process process)
+        {
+            process.PriorityClass = ProcessPriorityClass.Normal;
+        }
+
+        public bool SupportsSuspension
+        {
+            get { return true; }
+        }
+    }
+}

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

@@ -1,4 +1,5 @@
 using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Diagnostics;
 using MediaBrowser.Model.Logging;
 using System.Collections.Generic;
 using System.Reflection;
@@ -84,5 +85,11 @@ namespace MediaBrowser.Server.Startup.Common
         /// Prevents the system stand by.
         /// </summary>
         void PreventSystemStandby();
+
+        /// <summary>
+        /// Gets the process manager.
+        /// </summary>
+        /// <returns>IProcessManager.</returns>
+        IProcessManager GetProcessManager();
     }
 }

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

@@ -56,6 +56,7 @@
     <Compile Include="ApplicationHost.cs" />
     <Compile Include="ApplicationPathHelper.cs" />
     <Compile Include="Browser\BrowserLauncher.cs" />
+    <Compile Include="Diagnostics\ProcessManager.cs" />
     <Compile Include="EntryPoints\KeepServerAwake.cs" />
     <Compile Include="EntryPoints\StartupWizard.cs" />
     <Compile Include="FFMpeg\FFMpegDownloader.cs" />

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

@@ -62,7 +62,7 @@
   <ItemGroup>
     <Reference Include="ImageMagickSharp, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
       <SpecificVersion>False</SpecificVersion>
-      <HintPath>..\packages\ImageMagickSharp.1.0.0.2\lib\net45\ImageMagickSharp.dll</HintPath>
+      <HintPath>..\packages\ImageMagickSharp.1.0.0.6\lib\net45\ImageMagickSharp.dll</HintPath>
     </Reference>
     <Reference Include="MediaBrowser.IsoMounter">
       <HintPath>..\packages\MediaBrowser.IsoMounting.3.0.69\lib\net45\MediaBrowser.IsoMounter.dll</HintPath>
@@ -113,6 +113,7 @@
     <Compile Include="Native\Standby.cs" />
     <Compile Include="Native\ServerAuthorization.cs" />
     <Compile Include="Native\WindowsApp.cs" />
+    <Compile Include="Native\WindowsProcessManager.cs" />
     <Compile Include="Networking\CertificateGenerator.cs" />
     <Compile Include="Networking\NativeMethods.cs" />
     <Compile Include="Networking\NetworkManager.cs" />

+ 7 - 0
MediaBrowser.ServerApplication/Native/WindowsApp.cs

@@ -1,7 +1,9 @@
 using MediaBrowser.Common.IO;
 using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Diagnostics;
 using MediaBrowser.IsoMounter;
 using MediaBrowser.Model.Logging;
+using MediaBrowser.Server.Mono.Diagnostics;
 using MediaBrowser.Server.Startup.Common;
 using MediaBrowser.ServerApplication.Networking;
 using System.Collections.Generic;
@@ -109,5 +111,10 @@ namespace MediaBrowser.ServerApplication.Native
         {
             Standby.PreventSystemStandby();
         }
+
+        public IProcessManager GetProcessManager()
+        {
+            return new WindowsProcessManager();
+        }
     }
 }

+ 78 - 0
MediaBrowser.ServerApplication/Native/WindowsProcessManager.cs

@@ -0,0 +1,78 @@
+using MediaBrowser.Controller.Diagnostics;
+using System;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+
+namespace MediaBrowser.ServerApplication.Native
+{
+    public class WindowsProcessManager : IProcessManager
+    {
+        public void SuspendProcess(Process process)
+        {
+            process.Suspend();
+        }
+
+        public void ResumeProcess(Process process)
+        {
+            process.Resume();
+        }
+
+        public bool SupportsSuspension
+        {
+            get { return true; }
+        }
+    }
+
+    public static class ProcessExtension
+    {
+        [DllImport("kernel32.dll")]
+        static extern IntPtr OpenThread(ThreadAccess dwDesiredAccess, bool bInheritHandle, uint dwThreadId);
+        [DllImport("kernel32.dll")]
+        static extern uint SuspendThread(IntPtr hThread);
+        [DllImport("kernel32.dll")]
+        static extern int ResumeThread(IntPtr hThread);
+
+        public static void Suspend(this Process process)
+        {
+            foreach (ProcessThread thread in process.Threads)
+            {
+                var pOpenThread = OpenThread(ThreadAccess.SUSPEND_RESUME, false, (uint)thread.Id);
+                if (pOpenThread == IntPtr.Zero)
+                {
+                    break;
+                }
+                SuspendThread(pOpenThread);
+            }
+        }
+        public static void Resume(this Process process)
+        {
+            foreach (ProcessThread thread in process.Threads)
+            {
+                var pOpenThread = OpenThread(ThreadAccess.SUSPEND_RESUME, false, (uint)thread.Id);
+                if (pOpenThread == IntPtr.Zero)
+                {
+                    break;
+                }
+                ResumeThread(pOpenThread);
+            }
+        }
+        public static void Print(this Process process)
+        {
+            Console.WriteLine("{0,8}    {1}", process.Id, process.ProcessName);
+        }
+    }
+
+    [Flags]
+    public enum ThreadAccess : int
+    {
+        TERMINATE = (0x0001),
+        SUSPEND_RESUME = (0x0002),
+        GET_CONTEXT = (0x0008),
+        SET_CONTEXT = (0x0010),
+        SET_INFORMATION = (0x0020),
+        QUERY_INFORMATION = (0x0040),
+        SET_THREAD_TOKEN = (0x0080),
+        IMPERSONATE = (0x0100),
+        DIRECT_IMPERSONATION = (0x0200)
+    }
+}

+ 1 - 1
MediaBrowser.ServerApplication/packages.config

@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <packages>
-  <package id="ImageMagickSharp" version="1.0.0.2" targetFramework="net45" />
+  <package id="ImageMagickSharp" version="1.0.0.6" targetFramework="net45" />
   <package id="MediaBrowser.IsoMounting" version="3.0.69" targetFramework="net45" />
   <package id="System.Data.SQLite.Core" version="1.0.94.0" targetFramework="net45" />
 </packages>

+ 1 - 0
MediaBrowser.WebDashboard/Api/PackageCreator.cs

@@ -421,6 +421,7 @@ namespace MediaBrowser.WebDashboard.Api
                                 "itembynamedetailpage.js",
                                 "itemdetailpage.js",
                                 "itemlistpage.js",
+                                "kids.js",
                                 "librarypathmapping.js",
                                 "reports.js",
                                 "librarysettings.js",

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

@@ -87,6 +87,9 @@
     </ProjectReference>
   </ItemGroup>
   <ItemGroup>
+    <Content Include="dashboard-ui\css\images\kids\bg.jpg">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
     <Content Include="dashboard-ui\css\images\server.png">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
@@ -114,6 +117,9 @@
     <Content Include="dashboard-ui\forgotpasswordpin.html">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
+    <Content Include="dashboard-ui\kids.html">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
     <Content Include="dashboard-ui\mysync.html">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
@@ -129,6 +135,9 @@
     <Content Include="dashboard-ui\scripts\forgotpasswordpin.js">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
+    <Content Include="dashboard-ui\scripts\kids.js">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
     <Content Include="dashboard-ui\scripts\selectserver.js">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>

+ 1 - 1
MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs

@@ -214,7 +214,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
                 }
             }
 
-            using (var filestream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read))
+            using (var filestream = FileSystem.GetFileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read))
             {
                 stream.CopyTo(filestream);
             }

+ 3 - 0
MediaBrowser.sln

@@ -520,4 +520,7 @@ Global
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
 	EndGlobalSection
+	GlobalSection(Performance) = preSolution
+		HasPerformanceSessions = true
+	EndGlobalSection
 EndGlobal

+ 2 - 2
Nuget/MediaBrowser.Common.Internal.nuspec

@@ -2,7 +2,7 @@
 <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
     <metadata>
         <id>MediaBrowser.Common.Internal</id>
-        <version>3.0.576</version>
+        <version>3.0.579</version>
         <title>MediaBrowser.Common.Internal</title>
         <authors>Luke</authors>
         <owners>ebr,Luke,scottisafool</owners>
@@ -12,7 +12,7 @@
         <description>Contains common components shared by Media Browser Theater and Media Browser Server. Not intended for plugin developer consumption.</description>
         <copyright>Copyright © Media Browser 2013</copyright>
         <dependencies>
-            <dependency id="MediaBrowser.Common" version="3.0.576" />
+            <dependency id="MediaBrowser.Common" version="3.0.579" />
             <dependency id="NLog" version="3.2.0.0" />
             <dependency id="SimpleInjector" version="2.7.0" />
         </dependencies>

+ 1 - 1
Nuget/MediaBrowser.Common.nuspec

@@ -2,7 +2,7 @@
 <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
     <metadata>
         <id>MediaBrowser.Common</id>
-        <version>3.0.576</version>
+        <version>3.0.579</version>
         <title>MediaBrowser.Common</title>
         <authors>Media Browser Team</authors>
         <owners>ebr,Luke,scottisafool</owners>

+ 1 - 1
Nuget/MediaBrowser.Model.Signed.nuspec

@@ -2,7 +2,7 @@
 <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
     <metadata>
         <id>MediaBrowser.Model.Signed</id>
-        <version>3.0.576</version>
+        <version>3.0.579</version>
         <title>MediaBrowser.Model - Signed Edition</title>
         <authors>Media Browser Team</authors>
         <owners>ebr,Luke,scottisafool</owners>

+ 2 - 2
Nuget/MediaBrowser.Server.Core.nuspec

@@ -2,7 +2,7 @@
 <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
     <metadata>
         <id>MediaBrowser.Server.Core</id>
-        <version>3.0.576</version>
+        <version>3.0.579</version>
         <title>Media Browser.Server.Core</title>
         <authors>Media Browser Team</authors>
         <owners>ebr,Luke,scottisafool</owners>
@@ -12,7 +12,7 @@
         <description>Contains core components required to build plugins for Media Browser Server.</description>
         <copyright>Copyright © Media Browser 2013</copyright>
         <dependencies>
-            <dependency id="MediaBrowser.Common" version="3.0.576" />
+            <dependency id="MediaBrowser.Common" version="3.0.579" />
         </dependencies>
     </metadata>
     <files>