浏览代码

cloud sync updates

Luke Pulverenti 10 年之前
父节点
当前提交
2bf2d5fd76
共有 24 个文件被更改,包括 732 次插入210 次删除
  1. 1 1
      MediaBrowser.Api/Images/ImageService.cs
  2. 13 37
      MediaBrowser.Api/Playback/TranscodingThrottler.cs
  3. 2 4
      MediaBrowser.Controller/Drawing/IImageProcessor.cs
  4. 1 1
      MediaBrowser.Controller/MediaBrowser.Controller.csproj
  5. 0 46
      MediaBrowser.Controller/Sync/ICloudSyncProvider.cs
  6. 50 4
      MediaBrowser.Controller/Sync/IServerSyncProvider.cs
  7. 41 0
      MediaBrowser.Controller/Sync/ISyncDataProvider.cs
  8. 0 7
      MediaBrowser.Controller/Sync/ISyncProvider.cs
  9. 1 1
      MediaBrowser.Dlna/Didl/DidlBuilder.cs
  10. 2 0
      MediaBrowser.Model/Users/UserPolicy.cs
  11. 1 1
      MediaBrowser.Providers/Photos/PhotoProvider.cs
  12. 6 1
      MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs
  13. 1 4
      MediaBrowser.Server.Implementations/Dto/DtoService.cs
  14. 4 2
      MediaBrowser.Server.Implementations/Localization/Server/server.json
  15. 5 1
      MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
  16. 1 1
      MediaBrowser.Server.Implementations/Sync/AppSyncProvider.cs
  17. 118 0
      MediaBrowser.Server.Implementations/Sync/CloudSyncProfile.cs
  18. 0 57
      MediaBrowser.Server.Implementations/Sync/CloudSyncProvider.cs
  19. 31 0
      MediaBrowser.Server.Implementations/Sync/FolderSync/FolderSyncDataProvider.cs
  20. 142 0
      MediaBrowser.Server.Implementations/Sync/FolderSync/FolderSyncProvider.cs
  21. 15 0
      MediaBrowser.Server.Implementations/Sync/IHasSyncProfile.cs
  22. 215 34
      MediaBrowser.Server.Implementations/Sync/MediaSync.cs
  23. 69 0
      MediaBrowser.Server.Implementations/Sync/MultiProviderSync.cs
  24. 13 8
      MediaBrowser.Server.Implementations/Sync/SyncManager.cs

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

+ 13 - 37
MediaBrowser.Api/Playback/TranscodingThrottler.cs

@@ -13,11 +13,17 @@ namespace MediaBrowser.Api.Playback
 
         public void Start()
         {
-            _timer = new Timer(TimerCallback, null, 1000, 1000);
+            _timer = new Timer(TimerCallback, null, 5000, 5000);
         }
 
         private void TimerCallback(object state)
         {
+            if (_job.HasExited)
+            {
+                DisposeTimer();
+                return;
+            }
+
             if (IsThrottleAllowed(_job))
             {
                 PauseTranscoding();
@@ -50,36 +56,6 @@ namespace MediaBrowser.Api.Playback
 
         private bool IsThrottleAllowed(TranscodingJob job)
         {
-            //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;
-
             var bytesDownloaded = job.BytesDownloaded ?? 0;
             var transcodingPositionTicks = job.TranscodingPositionTicks ?? 0;
             var downloadPositionTicks = job.DownloadPositionTicks ?? 0;
@@ -95,11 +71,11 @@ namespace MediaBrowser.Api.Playback
 
                 if (gap < targetGap)
                 {
-                    //Logger.Debug("Not throttling transcoder gap {0} target gap {1}", 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);
+                //_logger.Debug("Throttling transcoder gap {0} target gap {1}", gap, targetGap);
                 return true;
             }
 
@@ -120,21 +96,21 @@ namespace MediaBrowser.Api.Playback
 
                     if (gap < targetGap)
                     {
-                        //Logger.Debug("Not throttling transcoder gap {0} target gap {1} bytes downloaded {2}", gap, targetGap, bytesDownloaded);
+                        //_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);
+                    //_logger.Debug("Throttling transcoder gap {0} target gap {1} bytes downloaded {2}", gap, targetGap, bytesDownloaded);
                     return true;
                 }
                 catch
                 {
-                    //Logger.Error("Error getting output size");
+                    //_logger.Error("Error getting output size");
                 }
             }
             else
             {
-                //Logger.Debug("No throttle data for " + path);
+                //_logger.Debug("No throttle data for " + path);
             }
 
             return false;

+ 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/MediaBrowser.Controller.csproj

@@ -341,8 +341,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 - 46
MediaBrowser.Controller/Sync/ICloudSyncProvider.cs

@@ -1,46 +0,0 @@
-using MediaBrowser.Model.Sync;
-using System;
-using System.Collections.Generic;
-using System.IO;
-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="inputFile">The input file.</param>
-        /// <param name="pathParts">The path parts.</param>
-        /// <param name="target">The target.</param>
-        /// <param name="progress">The progress.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task.</returns>
-        Task SendFile(string inputFile, string[] pathParts, SyncTarget target, IProgress<double> progress, CancellationToken cancellationToken);
-
-        /// <summary>
-        /// Gets the file.
-        /// </summary>
-        /// <param name="pathParts">The path parts.</param>
-        /// <param name="target">The target.</param>
-        /// <param name="progress">The progress.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task&lt;Stream&gt;.</returns>
-        Task<Stream> GetFile(string[] pathParts, SyncTarget target, IProgress<double> progress, CancellationToken cancellationToken);
-    }
-}

+ 50 - 4
MediaBrowser.Controller/Sync/IServerSyncProvider.cs

@@ -1,5 +1,6 @@
 using MediaBrowser.Model.Sync;
 using System;
+using System.Collections.Generic;
 using System.IO;
 using System.Threading;
 using System.Threading.Tasks;
@@ -12,21 +13,66 @@ namespace MediaBrowser.Controller.Sync
         /// Transfers the file.
         /// </summary>
         /// <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 SendFile(string inputFile, string[] pathParts, SyncTarget target, IProgress<double> progress, CancellationToken cancellationToken);
+        Task SendFile(string inputFile, string path, SyncTarget target, IProgress<double> progress, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// Deletes the file.
+        /// </summary>
+        /// <param name="path">The path.</param>
+        /// <param name="target">The target.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task.</returns>
+        Task DeleteFile(string path, SyncTarget target, CancellationToken cancellationToken);
 
         /// <summary>
         /// Gets the file.
         /// </summary>
-        /// <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&lt;Stream&gt;.</returns>
-        Task<Stream> GetFile(string[] pathParts, SyncTarget target, IProgress<double> progress, CancellationToken cancellationToken);
+        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();
+
+        /// <summary>
+        /// Gets all synchronize targets.
+        /// </summary>
+        /// <returns>IEnumerable&lt;SyncTarget&gt;.</returns>
+        IEnumerable<SyncTarget> GetAllSyncTargets();
     }
 }

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

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

@@ -18,13 +18,6 @@ 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.
-        /// </summary>
-        /// <param name="target">The target.</param>
-        /// <returns>DeviceProfile.</returns>
-        DeviceProfile GetDeviceProfile(SyncTarget target);
     }
 
     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);

+ 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;

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

+ 6 - 1
MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs

@@ -414,6 +414,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 +426,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))
             {

+ 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)
             {

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

@@ -1114,7 +1114,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 +1369,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"
 }

+ 5 - 1
MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj

@@ -303,8 +303,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" />

+ 1 - 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;
 

+ 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 - 57
MediaBrowser.Server.Implementations/Sync/CloudSyncProvider.cs

@@ -1,57 +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.IO;
-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 SendFile(string inputFile, string[] pathParts, SyncTarget target, IProgress<double> progress, CancellationToken cancellationToken)
-        {
-            var provider = GetProvider(target);
-
-            return provider.SendFile(inputFile, pathParts, target, progress, cancellationToken);
-        }
-
-        public Task<Stream> GetFile(string[] pathParts, SyncTarget target, IProgress<double> progress, CancellationToken cancellationToken)
-        {
-            var provider = GetProvider(target);
-
-            return provider.GetFile(pathParts, target, progress, 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();
+        }
+    }
+}

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

@@ -0,0 +1,142 @@
+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()
+        {
+            // 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);
+    }
+}

+ 215 - 34
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,
+            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,44 +51,46 @@ 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
-            //{
-            //    TargetId = target.Id,
-            //    LocalItemIds = localIds
+            var result = await _syncManager.SyncData(new SyncDataRequest
+            {
+                TargetId = target.Id,
+                LocalItemIds = localIds
 
-            //}).ConfigureAwait(false);
+            }).ConfigureAwait(false);
 
-            //cancellationToken.ThrowIfCancellationRequested();
+            cancellationToken.ThrowIfCancellationRequested();
 
-            //foreach (var itemIdToRemove in result.ItemIdsToRemove)
-            //{
-            //    try
-            //    {
-            //        await RemoveItem(provider, serverId, itemIdToRemove, target, cancellationToken).ConfigureAwait(false);
-            //    }
-            //    catch (Exception ex)
-            //    {
-            //        _logger.ErrorException("Error deleting item from sync target. Id: {0}", ex, itemIdToRemove);
-            //    }
-            //}
+            foreach (var itemIdToRemove in result.ItemIdsToRemove)
+            {
+                try
+                {
+                    await RemoveItem(provider, dataProvider, serverId, itemIdToRemove, target, cancellationToken).ConfigureAwait(false);
+                }
+                catch (Exception ex)
+                {
+                    _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,
@@ -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,9 +152,10 @@ namespace MediaBrowser.Server.Implementations.Sync
 
             try
             {
-                string[] pathParts = GetPathParts(serverId, libraryItem);
+                await SendFile(provider, internalSyncJobItem.OutputPath, localItem, target, cancellationToken).ConfigureAwait(false);
 
-                await SendFile(provider, internalSyncJobItem.OutputPath, pathParts, target, cancellationToken).ConfigureAwait(false);
+                // Create db record
+                await dataProvider.AddOrUpdate(target, localItem).ConfigureAwait(false);
 
                 progress.Report(92);
 
@@ -164,25 +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 Task.FromResult(true);
-            //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);
+            }
         }
 
-        private string[] GetPathParts(string serverId, BaseItemDto item)
+        public LocalItem CreateLocalItem(IServerSyncProvider provider, SyncTarget target, BaseItemDto libraryItem, string serverId, string originalFileName)
         {
-            return null;
+            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 async Task SendFile(IServerSyncProvider provider, string inputPath, string[] path, SyncTarget target, CancellationToken cancellationToken)
+        private List<string> GetDirectoryPath(IServerSyncProvider provider, BaseItemDto item, string serverId)
         {
-            await provider.SendFile(inputPath, path, target, new Progress<double>(), cancellationToken)
-                    .ConfigureAwait(false);
+            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 async Task<List<ItemFileInfo>> GetFiles(IServerSyncProvider provider, LocalItem item, SyncTarget target)
+        {
+            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);
+            }
+        }
+    }
+}

+ 13 - 8
MediaBrowser.Server.Implementations/Sync/SyncManager.cs

@@ -429,13 +429,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");
@@ -547,7 +540,7 @@ namespace MediaBrowser.Server.Implementations.Sync
                 {
                     if (string.Equals(target.Id, targetId, StringComparison.OrdinalIgnoreCase))
                     {
-                        return provider.GetDeviceProfile(target);
+                        return GetDeviceProfile(provider, target);
                     }
                 }
             }
@@ -555,6 +548,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);