diff options
Diffstat (limited to 'MediaBrowser.Server.Implementations')
15 files changed, 653 insertions, 76 deletions
diff --git a/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs b/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs index e346d6d01..9fee27db9 100644 --- a/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs +++ b/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs @@ -72,7 +72,12 @@ namespace MediaBrowser.Server.Implementations.Collections DisplayMediaType = "Collection", Path = path, IsLocked = options.IsLocked, - ProviderIds = options.ProviderIds + ProviderIds = options.ProviderIds, + Shares = options.UserIds.Select(i => new Share + { + UserId = i.ToString("N") + + }).ToList() }; await parentFolder.AddChild(collection, CancellationToken.None).ConfigureAwait(false); @@ -156,7 +161,7 @@ namespace MediaBrowser.Server.Implementations.Collections } itemList.Add(item); - + if (currentLinkedChildren.Any(i => i.Id == itemId)) { throw new ArgumentException("Item already exists in collection"); diff --git a/MediaBrowser.Server.Implementations/Devices/DeviceManager.cs b/MediaBrowser.Server.Implementations/Devices/DeviceManager.cs index e2c729b2d..8c67013ea 100644 --- a/MediaBrowser.Server.Implementations/Devices/DeviceManager.cs +++ b/MediaBrowser.Server.Implementations/Devices/DeviceManager.cs @@ -6,6 +6,7 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Model.Devices; using MediaBrowser.Model.Events; using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Querying; using MediaBrowser.Model.Session; using System; using System.Collections.Generic; @@ -28,7 +29,7 @@ namespace MediaBrowser.Server.Implementations.Devices /// Occurs when [device options updated]. /// </summary> public event EventHandler<GenericEventArgs<DeviceInfo>> DeviceOptionsUpdated; - + public DeviceManager(IDeviceRepository repo, IUserManager userManager, IFileSystem fileSystem, ILibraryMonitor libraryMonitor, IConfigurationManager config, ILogger logger) { _repo = repo; @@ -79,9 +80,30 @@ namespace MediaBrowser.Server.Implementations.Devices return _repo.GetDevice(id); } - public IEnumerable<DeviceInfo> GetDevices() + public QueryResult<DeviceInfo> GetDevices(DeviceQuery query) { - return _repo.GetDevices().OrderByDescending(i => i.DateLastModified); + IEnumerable<DeviceInfo> devices = _repo.GetDevices().OrderByDescending(i => i.DateLastModified); + + if (query.SupportsContentUploading.HasValue) + { + var val = query.SupportsContentUploading.Value; + + devices = devices.Where(i => GetCapabilities(i.Id).SupportsContentUploading == val); + } + + if (query.SupportsUniqueIdentifier.HasValue) + { + var val = query.SupportsUniqueIdentifier.Value; + + devices = devices.Where(i => GetCapabilities(i.Id).SupportsUniqueIdentifier == val); + } + + var array = devices.ToArray(); + return new QueryResult<DeviceInfo> + { + Items = array, + TotalRecordCount = array.Length + }; } public Task DeleteDevice(string id) diff --git a/MediaBrowser.Server.Implementations/Dto/DtoService.cs b/MediaBrowser.Server.Implementations/Dto/DtoService.cs index e1e8da5c5..a6f9f0675 100644 --- a/MediaBrowser.Server.Implementations/Dto/DtoService.cs +++ b/MediaBrowser.Server.Implementations/Dto/DtoService.cs @@ -1142,6 +1142,15 @@ namespace MediaBrowser.Server.Implementations.Dto dto.SeasonId = episodeSeason.Id.ToString("N"); dto.SeasonName = episodeSeason.Name; } + + if (fields.Contains(ItemFields.SeriesGenres)) + { + var episodeseries = episode.Series; + if (episodeseries != null) + { + dto.SeriesGenres = episodeseries.Genres.ToList(); + } + } } // Add SeriesInfo diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs index e06720192..bfdfc03ba 100644 --- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs +++ b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs @@ -736,16 +736,27 @@ namespace MediaBrowser.Server.Implementations.Library } private UserRootFolder _userRootFolder; + private readonly object _syncLock = new object(); public Folder GetUserRootFolder() { if (_userRootFolder == null) { - var userRootPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath; + lock (_syncLock) + { + if (_userRootFolder == null) + { + var userRootPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath; - Directory.CreateDirectory(userRootPath); + Directory.CreateDirectory(userRootPath); - _userRootFolder = GetItemById(GetNewItemId(userRootPath, typeof(UserRootFolder))) as UserRootFolder ?? - (UserRootFolder)ResolvePath(new DirectoryInfo(userRootPath)); + _userRootFolder = GetItemById(GetNewItemId(userRootPath, typeof(UserRootFolder))) as UserRootFolder; + + if (_userRootFolder == null) + { + _userRootFolder = (UserRootFolder)ResolvePath(new DirectoryInfo(userRootPath)); + } + } + } } return _userRootFolder; diff --git a/MediaBrowser.Server.Implementations/Library/MusicManager.cs b/MediaBrowser.Server.Implementations/Library/MusicManager.cs index 7ffbab860..b8c29c19b 100644 --- a/MediaBrowser.Server.Implementations/Library/MusicManager.cs +++ b/MediaBrowser.Server.Implementations/Library/MusicManager.cs @@ -1,6 +1,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Playlists; using System; using System.Collections.Generic; using System.Linq; @@ -53,6 +54,18 @@ namespace MediaBrowser.Server.Implementations.Library return GetInstantMixFromGenres(genres, user); } + public IEnumerable<Audio> GetInstantMixFromPlaylist(Playlist item, User user) + { + var genres = item + .GetRecursiveChildren(user, true) + .OfType<Audio>() + .SelectMany(i => i.Genres) + .Concat(item.Genres) + .Distinct(StringComparer.OrdinalIgnoreCase); + + return GetInstantMixFromGenres(genres, user); + } + public IEnumerable<Audio> GetInstantMixFromGenres(IEnumerable<string> genres, User user) { var inputItems = user.RootFolder.GetRecursiveChildren(user); diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs index 8fceb7f53..9f714cfc5 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs @@ -168,7 +168,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies { if (string.Equals(collectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase)) { - return FindMovie<MusicVideo>(args.Path, args.Parent, args.FileSystemChildren.ToList(), args.DirectoryService, collectionType); + return FindMovie<MusicVideo>(args.Path, args.Parent, args.FileSystemChildren.ToList(), args.DirectoryService, collectionType, false); } if (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase)) diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json index 5f2c7b2bc..aebd231f0 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json @@ -638,5 +638,14 @@ "ButtonSync": "Sync", "SyncMedia": "Sync Media", "HeaderCancelSyncJob": "Cancel Sync", - "CancelSyncJobConfirmation": "Are you sure you wish to cancel this sync job?" + "CancelSyncJobConfirmation": "Are you sure you wish to cancel this sync job?", + "TabSync": "Sync", + "MessagePleaseSelectDeviceToSyncTo": "Please select a device to sync to.", + "MessageSyncJobCreated": "Sync job created.", + "LabelSyncTo": "Sync to:", + "LabelSyncJobName": "Sync job name:", + "LabelQuality": "Quality:", + "OptionHigh": "High", + "OptionMedium": "Medium", + "OptionLow": "Low" } diff --git a/MediaBrowser.Server.Implementations/Localization/Server/server.json b/MediaBrowser.Server.Implementations/Localization/Server/server.json index 6da7adee1..6bb24be2b 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/server.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/server.json @@ -1293,5 +1293,7 @@ "LabelBlockItemsWithTags": "Block items with tags:", "LabelTag": "Tag:", "LabelEnableSingleImageInDidlLimit": "Limit to single embedded image", - "LabelEnableSingleImageInDidlLimitHelp": "Some devices will not render properly if multiple images are embedded within Didl." + "LabelEnableSingleImageInDidlLimitHelp": "Some devices will not render properly if multiple images are embedded within Didl.", + "TabActivity": "Activity", + "TitleSync": "Sync" } diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index cf029df19..514b7b1f1 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -306,6 +306,7 @@ <Compile Include="Sync\SyncJobProcessor.cs" /> <Compile Include="Sync\SyncManager.cs" /> <Compile Include="Sync\SyncRepository.cs" /> + <Compile Include="Sync\SyncScheduledTask.cs" /> <Compile Include="Themes\AppThemeManager.cs" /> <Compile Include="TV\TVSeriesManager.cs" /> <Compile Include="Udp\UdpMessageReceivedEventArgs.cs" /> diff --git a/MediaBrowser.Server.Implementations/Playlists/ManualPlaylistsFolder.cs b/MediaBrowser.Server.Implementations/Playlists/ManualPlaylistsFolder.cs index 3d0e36ead..89395a00b 100644 --- a/MediaBrowser.Server.Implementations/Playlists/ManualPlaylistsFolder.cs +++ b/MediaBrowser.Server.Implementations/Playlists/ManualPlaylistsFolder.cs @@ -18,7 +18,7 @@ namespace MediaBrowser.Server.Implementations.Playlists { return base.IsVisible(user) && GetRecursiveChildren(user, false) .OfType<Playlist>() - .Any(i => string.Equals(i.OwnerUserId, user.Id.ToString("N"))); + .Any(i => i.IsVisible(user)); } protected override IEnumerable<BaseItem> GetEligibleChildrenForRecursiveChildren(User user) diff --git a/MediaBrowser.Server.Implementations/Playlists/PlaylistManager.cs b/MediaBrowser.Server.Implementations/Playlists/PlaylistManager.cs index 852959312..8da9ba222 100644 --- a/MediaBrowser.Server.Implementations/Playlists/PlaylistManager.cs +++ b/MediaBrowser.Server.Implementations/Playlists/PlaylistManager.cs @@ -115,10 +115,15 @@ namespace MediaBrowser.Server.Implementations.Playlists { Name = name, Parent = parentFolder, - Path = path, - OwnerUserId = options.UserId + Path = path }; + playlist.Shares.Add(new Share + { + UserId = options.UserId, + CanEdit = true + }); + playlist.SetMediaType(options.MediaType); await parentFolder.AddChild(playlist, CancellationToken.None).ConfigureAwait(false); diff --git a/MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs b/MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs index c7f02b3dd..1976c0540 100644 --- a/MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs +++ b/MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs @@ -1,11 +1,18 @@ using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Sync; +using MediaBrowser.Model.Dlna; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.MediaInfo; +using MediaBrowser.Model.Session; using MediaBrowser.Model.Sync; using MoreLinq; using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; namespace MediaBrowser.Server.Implementations.Sync @@ -14,11 +21,17 @@ namespace MediaBrowser.Server.Implementations.Sync { private readonly ILibraryManager _libraryManager; private readonly ISyncRepository _syncRepo; + private readonly ISyncManager _syncManager; + private readonly ILogger _logger; + private readonly IUserManager _userManager; - public SyncJobProcessor(ILibraryManager libraryManager, ISyncRepository syncRepo) + public SyncJobProcessor(ILibraryManager libraryManager, ISyncRepository syncRepo, ISyncManager syncManager, ILogger logger, IUserManager userManager) { _libraryManager = libraryManager; _syncRepo = syncRepo; + _syncManager = syncManager; + _logger = logger; + _userManager = userManager; } public void ProcessJobItem(SyncJob job, SyncJobItem jobItem, SyncTarget target) @@ -28,13 +41,21 @@ namespace MediaBrowser.Server.Implementations.Sync public async Task EnsureJobItems(SyncJob job) { - var items = GetItemsForSync(job.RequestedItemIds) - .ToList(); + var user = _userManager.GetUserById(job.UserId); - var jobItems = _syncRepo.GetJobItems(job.Id) + if (user == null) + { + throw new InvalidOperationException("Cannot proceed with sync because user no longer exists."); + } + + var items = GetItemsForSync(job.RequestedItemIds, user) .ToList(); - var created = 0; + var jobItems = _syncRepo.GetJobItems(new SyncJobItemQuery + { + JobId = job.Id + + }).Items.ToList(); foreach (var item in items) { @@ -52,24 +73,97 @@ namespace MediaBrowser.Server.Implementations.Sync Id = Guid.NewGuid().ToString("N"), ItemId = itemId, JobId = job.Id, - TargetId = job.TargetId + TargetId = job.TargetId, + DateCreated = DateTime.UtcNow }; await _syncRepo.Create(jobItem).ConfigureAwait(false); - created++; + jobItems.Add(jobItem); + } + + jobItems = jobItems + .OrderBy(i => i.DateCreated) + .ToList(); + + await UpdateJobStatus(job, jobItems).ConfigureAwait(false); + } + + private Task UpdateJobStatus(SyncJob job) + { + if (job == null) + { + throw new ArgumentNullException("job"); + } + + var result = _syncRepo.GetJobItems(new SyncJobItemQuery + { + JobId = job.Id + }); + + return UpdateJobStatus(job, result.Items.ToList()); + } + + private Task UpdateJobStatus(SyncJob job, List<SyncJobItem> jobItems) + { + job.ItemCount = jobItems.Count; + + double pct = 0; + + foreach (var item in jobItems) + { + if (item.Status == SyncJobItemStatus.Failed || item.Status == SyncJobItemStatus.Completed) + { + pct += 100; + } + else + { + pct += item.Progress ?? 0; + } + } + + if (job.ItemCount > 0) + { + pct /= job.ItemCount; + job.Progress = pct; + } + else + { + job.Progress = null; + } + + if (pct >= 100) + { + if (jobItems.Any(i => i.Status == SyncJobItemStatus.Failed)) + { + job.Status = SyncJobStatus.CompletedWithError; + } + else + { + job.Status = SyncJobStatus.Completed; + } + } + else if (pct.Equals(0)) + { + job.Status = SyncJobStatus.Queued; + } + else + { + job.Status = SyncJobStatus.InProgress; } - job.ItemCount = jobItems.Count + created; - await _syncRepo.Update(job).ConfigureAwait(false); + return _syncRepo.Update(job); } - public IEnumerable<BaseItem> GetItemsForSync(IEnumerable<string> itemIds) + public IEnumerable<BaseItem> GetItemsForSync(IEnumerable<string> itemIds, User user) { - return itemIds.SelectMany(GetItemsForSync).DistinctBy(i => i.Id); + return itemIds + .SelectMany(i => GetItemsForSync(i, user)) + .Where(_syncManager.SupportsSync) + .DistinctBy(i => i.Id); } - private IEnumerable<BaseItem> GetItemsForSync(string id) + private IEnumerable<BaseItem> GetItemsForSync(string id, User user) { var item = _libraryManager.GetItemById(id); @@ -78,12 +172,224 @@ namespace MediaBrowser.Server.Implementations.Sync return new List<BaseItem>(); } - return GetItemsForSync(item); + return GetItemsForSync(item, user); } - private IEnumerable<BaseItem> GetItemsForSync(BaseItem item) + private IEnumerable<BaseItem> GetItemsForSync(BaseItem item, User user) { + var itemByName = item as IItemByName; + if (itemByName != null) + { + var items = user.RootFolder + .GetRecursiveChildren(user); + + return itemByName.GetTaggedItems(items); + } + + if (item.IsFolder) + { + var folder = (Folder)item; + var items = folder.GetRecursiveChildren(user); + + items = items.Where(i => !i.IsFolder); + + if (!folder.IsPreSorted) + { + items = items.OrderBy(i => i.SortName); + } + + return items; + } + return new[] { item }; } + + public async Task EnsureSyncJobs(CancellationToken cancellationToken) + { + var jobResult = _syncRepo.GetJobs(new SyncJobQuery + { + IsCompleted = false + }); + + foreach (var job in jobResult.Items) + { + cancellationToken.ThrowIfCancellationRequested(); + + if (job.SyncNewContent) + { + await EnsureJobItems(job).ConfigureAwait(false); + } + } + } + + public async Task Sync(IProgress<double> progress, CancellationToken cancellationToken) + { + await EnsureSyncJobs(cancellationToken).ConfigureAwait(false); + + var result = _syncRepo.GetJobItems(new SyncJobItemQuery + { + IsCompleted = false + }); + + var jobItems = result.Items; + var index = 0; + + foreach (var item in jobItems) + { + double percent = index; + percent /= result.TotalRecordCount; + + progress.Report(100 * percent); + + cancellationToken.ThrowIfCancellationRequested(); + + if (item.Status == SyncJobItemStatus.Queued) + { + await ProcessJobItem(item, cancellationToken).ConfigureAwait(false); + } + + var job = _syncRepo.GetJob(item.JobId); + await UpdateJobStatus(job).ConfigureAwait(false); + + index++; + } + } + + private async Task ProcessJobItem(SyncJobItem jobItem, CancellationToken cancellationToken) + { + var item = _libraryManager.GetItemById(jobItem.ItemId); + if (item == null) + { + jobItem.Status = SyncJobItemStatus.Failed; + _logger.Error("Unable to locate library item for JobItem {0}, ItemId {1}", jobItem.Id, jobItem.ItemId); + await _syncRepo.Update(jobItem).ConfigureAwait(false); + return; + } + + var deviceProfile = _syncManager.GetDeviceProfile(jobItem.TargetId); + if (deviceProfile == null) + { + jobItem.Status = SyncJobItemStatus.Failed; + _logger.Error("Unable to locate SyncTarget for JobItem {0}, SyncTargetId {1}", jobItem.Id, jobItem.TargetId); + await _syncRepo.Update(jobItem).ConfigureAwait(false); + return; + } + + jobItem.Progress = 0; + jobItem.Status = SyncJobItemStatus.Converting; + + var video = item as Video; + if (video != null) + { + jobItem.OutputPath = await Sync(jobItem, video, deviceProfile, cancellationToken).ConfigureAwait(false); + } + + else if (item is Audio) + { + jobItem.OutputPath = await Sync(jobItem, (Audio)item, deviceProfile, cancellationToken).ConfigureAwait(false); + } + + else if (item is Photo) + { + jobItem.OutputPath = await Sync(jobItem, (Photo)item, deviceProfile, cancellationToken).ConfigureAwait(false); + } + + else if (item is Game) + { + jobItem.OutputPath = await Sync(jobItem, (Game)item, deviceProfile, cancellationToken).ConfigureAwait(false); + } + + else if (item is Book) + { + jobItem.OutputPath = await Sync(jobItem, (Book)item, deviceProfile, cancellationToken).ConfigureAwait(false); + } + + jobItem.Progress = 50; + jobItem.Status = SyncJobItemStatus.Transferring; + await _syncRepo.Update(jobItem).ConfigureAwait(false); + } + + private async Task<string> Sync(SyncJobItem jobItem, Video item, DeviceProfile profile, CancellationToken cancellationToken) + { + var options = new VideoOptions + { + Context = EncodingContext.Streaming, + ItemId = item.Id.ToString("N"), + DeviceId = jobItem.TargetId, + Profile = profile, + MediaSources = item.GetMediaSources(false).ToList() + }; + + var streamInfo = new StreamBuilder().BuildVideoItem(options); + var mediaSource = streamInfo.MediaSource; + + if (streamInfo.PlayMethod != PlayMethod.Transcode) + { + if (mediaSource.Protocol == MediaProtocol.File) + { + return mediaSource.Path; + } + if (mediaSource.Protocol == MediaProtocol.Http) + { + return await DownloadFile(jobItem, mediaSource, cancellationToken).ConfigureAwait(false); + } + throw new InvalidOperationException(string.Format("Cannot direct stream {0} protocol", mediaSource.Protocol)); + } + + // TODO: Transcode + return mediaSource.Path; + } + + private async Task<string> Sync(SyncJobItem jobItem, Audio item, DeviceProfile profile, CancellationToken cancellationToken) + { + var options = new AudioOptions + { + Context = EncodingContext.Streaming, + ItemId = item.Id.ToString("N"), + DeviceId = jobItem.TargetId, + Profile = profile, + MediaSources = item.GetMediaSources(false).ToList() + }; + + var streamInfo = new StreamBuilder().BuildAudioItem(options); + var mediaSource = streamInfo.MediaSource; + + if (streamInfo.PlayMethod != PlayMethod.Transcode) + { + if (mediaSource.Protocol == MediaProtocol.File) + { + return mediaSource.Path; + } + if (mediaSource.Protocol == MediaProtocol.Http) + { + return await DownloadFile(jobItem, mediaSource, cancellationToken).ConfigureAwait(false); + } + throw new InvalidOperationException(string.Format("Cannot direct stream {0} protocol", mediaSource.Protocol)); + } + + // TODO: Transcode + return mediaSource.Path; + } + + private async Task<string> Sync(SyncJobItem jobItem, Photo item, DeviceProfile profile, CancellationToken cancellationToken) + { + return item.Path; + } + + private async Task<string> Sync(SyncJobItem jobItem, Game item, DeviceProfile profile, CancellationToken cancellationToken) + { + return item.Path; + } + + private async Task<string> Sync(SyncJobItem jobItem, Book item, DeviceProfile profile, CancellationToken cancellationToken) + { + return item.Path; + } + + private async Task<string> DownloadFile(SyncJobItem jobItem, MediaSourceInfo mediaSource, CancellationToken cancellationToken) + { + // TODO: Download + return mediaSource.Path; + } } } diff --git a/MediaBrowser.Server.Implementations/Sync/SyncManager.cs b/MediaBrowser.Server.Implementations/Sync/SyncManager.cs index 0c7b5c2b9..b3c7e6202 100644 --- a/MediaBrowser.Server.Implementations/Sync/SyncManager.cs +++ b/MediaBrowser.Server.Implementations/Sync/SyncManager.cs @@ -5,6 +5,7 @@ using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Sync; +using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Querying; @@ -23,15 +24,17 @@ namespace MediaBrowser.Server.Implementations.Sync private readonly ISyncRepository _repo; private readonly IImageProcessor _imageProcessor; private readonly ILogger _logger; + private readonly IUserManager _userManager; private ISyncProvider[] _providers = { }; - public SyncManager(ILibraryManager libraryManager, ISyncRepository repo, IImageProcessor imageProcessor, ILogger logger) + public SyncManager(ILibraryManager libraryManager, ISyncRepository repo, IImageProcessor imageProcessor, ILogger logger, IUserManager userManager) { _libraryManager = libraryManager; _repo = repo; _imageProcessor = imageProcessor; _logger = logger; + _userManager = userManager; } public void AddParts(IEnumerable<ISyncProvider> providers) @@ -41,8 +44,12 @@ namespace MediaBrowser.Server.Implementations.Sync public async Task<SyncJobCreationResult> CreateJob(SyncJobRequest request) { - var items = new SyncJobProcessor(_libraryManager, _repo) - .GetItemsForSync(request.ItemIds) + var processor = new SyncJobProcessor(_libraryManager, _repo, this, _logger, _userManager); + + var user = _userManager.GetUserById(request.UserId); + + var items = processor + .GetItemsForSync(request.ItemIds, user) .ToList(); if (items.Any(i => !SupportsSync(i))) @@ -50,9 +57,9 @@ namespace MediaBrowser.Server.Implementations.Sync throw new ArgumentException("Item does not support sync."); } - if (items.Count == 1) + if (string.IsNullOrWhiteSpace(request.Name) && request.ItemIds.Count == 1) { - request.Name = GetDefaultName(items[0]); + request.Name = GetDefaultName(_libraryManager.GetItemById(request.ItemIds[0])); } if (string.IsNullOrWhiteSpace(request.Name)) @@ -82,8 +89,16 @@ namespace MediaBrowser.Server.Implementations.Sync Quality = request.Quality }; + // It's just a static list + if (!items.Any(i => i.IsFolder || i is IItemByName)) + { + job.SyncNewContent = false; + } + await _repo.Create(job).ConfigureAwait(false); + await processor.EnsureJobItems(job).ConfigureAwait(false); + return new SyncJobCreationResult { Job = GetJob(jobId) @@ -101,9 +116,9 @@ namespace MediaBrowser.Server.Implementations.Sync private void FillMetadata(SyncJob job) { - var item = new SyncJobProcessor(_libraryManager, _repo) - .GetItemsForSync(job.RequestedItemIds) - .FirstOrDefault(); + var item = job.RequestedItemIds + .Select(_libraryManager.GetItemById) + .FirstOrDefault(i => i != null); if (item != null) { @@ -139,10 +154,6 @@ namespace MediaBrowser.Server.Implementations.Sync public Task CancelJob(string id) { - var job = GetJob(id); - - job.Status = SyncJobStatus.Cancelled; - return _repo.DeleteJob(id); } @@ -165,10 +176,15 @@ namespace MediaBrowser.Server.Implementations.Sync return provider.GetSyncTargets().Select(i => new SyncTarget { Name = i.Name, - Id = providerId + "-" + i.Id + Id = GetSyncTargetId(providerId, i) }); } + private string GetSyncTargetId(string providerId, SyncTarget target) + { + return (providerId + "-" + target.Id).GetMD5().ToString("N"); + } + private ISyncProvider GetSyncProvider(SyncTarget target) { var providerId = target.Id.Split(new[] { '-' }, 2).First(); @@ -183,35 +199,46 @@ namespace MediaBrowser.Server.Implementations.Sync public bool SupportsSync(BaseItem item) { - if (string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) || - string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase)) + if (string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase) || + string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) || + string.Equals(item.MediaType, MediaType.Photo, StringComparison.OrdinalIgnoreCase) || + string.Equals(item.MediaType, MediaType.Game, StringComparison.OrdinalIgnoreCase) || + string.Equals(item.MediaType, MediaType.Book, StringComparison.OrdinalIgnoreCase)) { if (item.LocationType == LocationType.Virtual) { return false; } - if (item.RunTimeTicks.HasValue) + if (!item.RunTimeTicks.HasValue) { - var video = item as Video; + return false; + } + + var video = item as Video; + if (video != null) + { + if (video.VideoType == VideoType.Iso) + { + return false; + } - if (video != null) + if (video.IsStacked) { - if (video.VideoType == VideoType.Iso) - { - return false; - } - - if (video.IsStacked) - { - return false; - } + return false; } + } - return true; + var game = item as Game; + if (game != null) + { + if (game.IsMultiPart) + { + return false; + } } - return false; + return true; } return item.LocationType == LocationType.FileSystem || item is Season; @@ -221,5 +248,21 @@ namespace MediaBrowser.Server.Implementations.Sync { return item.Name; } + + public DeviceProfile GetDeviceProfile(string targetId) + { + foreach (var provider in _providers) + { + foreach (var target in GetSyncTargets(provider, null)) + { + if (string.Equals(target.Id, targetId, StringComparison.OrdinalIgnoreCase)) + { + return provider.GetDeviceProfile(target); + } + } + } + + return null; + } } } diff --git a/MediaBrowser.Server.Implementations/Sync/SyncRepository.cs b/MediaBrowser.Server.Implementations/Sync/SyncRepository.cs index 338529043..c7ffbb27f 100644 --- a/MediaBrowser.Server.Implementations/Sync/SyncRepository.cs +++ b/MediaBrowser.Server.Implementations/Sync/SyncRepository.cs @@ -35,7 +35,7 @@ namespace MediaBrowser.Server.Implementations.Sync public async Task Initialize() { - var dbFile = Path.Combine(_appPaths.DataPath, "sync2.db"); + var dbFile = Path.Combine(_appPaths.DataPath, "sync4.db"); _connection = await SqliteExtensions.ConnectToDb(dbFile, _logger).ConfigureAwait(false); @@ -44,7 +44,7 @@ namespace MediaBrowser.Server.Implementations.Sync "create table if not exists SyncJobs (Id GUID PRIMARY KEY, TargetId TEXT NOT NULL, Name TEXT NOT NULL, Quality TEXT NOT NULL, Status TEXT NOT NULL, Progress FLOAT, UserId TEXT NOT NULL, ItemIds TEXT NOT NULL, UnwatchedOnly BIT, ItemLimit INT, RemoveWhenWatched BIT, SyncNewContent BIT, DateCreated DateTime, DateLastModified DateTime, ItemCount int)", "create index if not exists idx_SyncJobs on SyncJobs(Id)", - "create table if not exists SyncJobItems (Id GUID PRIMARY KEY, ItemId TEXT, JobId TEXT, OutputPath TEXT, Status TEXT, TargetId TEXT)", + "create table if not exists SyncJobItems (Id GUID PRIMARY KEY, ItemId TEXT, JobId TEXT, OutputPath TEXT, Status TEXT, TargetId TEXT, DateCreated DateTime, Progress FLOAT)", "create index if not exists idx_SyncJobItems on SyncJobs(Id)", //pragmas @@ -84,17 +84,20 @@ namespace MediaBrowser.Server.Implementations.Sync _saveJobCommand.Parameters.Add(_saveJobCommand, "@ItemCount"); _saveJobItemCommand = _connection.CreateCommand(); - _saveJobItemCommand.CommandText = "replace into SyncJobItems (Id, ItemId, JobId, OutputPath, Status, TargetId) values (@Id, @ItemId, @JobId, @OutputPath, @Status, @TargetId)"; + _saveJobItemCommand.CommandText = "replace into SyncJobItems (Id, ItemId, JobId, OutputPath, Status, TargetId, DateCreated, Progress) values (@Id, @ItemId, @JobId, @OutputPath, @Status, @TargetId, @DateCreated, @Progress)"; _saveJobItemCommand.Parameters.Add(_saveJobCommand, "@Id"); _saveJobItemCommand.Parameters.Add(_saveJobCommand, "@ItemId"); _saveJobItemCommand.Parameters.Add(_saveJobCommand, "@JobId"); _saveJobItemCommand.Parameters.Add(_saveJobCommand, "@OutputPath"); _saveJobItemCommand.Parameters.Add(_saveJobCommand, "@Status"); + _saveJobItemCommand.Parameters.Add(_saveJobCommand, "@TargetId"); + _saveJobItemCommand.Parameters.Add(_saveJobCommand, "@DateCreated"); + _saveJobItemCommand.Parameters.Add(_saveJobCommand, "@Progress"); } private const string BaseJobSelectText = "select Id, TargetId, Name, Quality, Status, Progress, UserId, ItemIds, UnwatchedOnly, ItemLimit, RemoveWhenWatched, SyncNewContent, DateCreated, DateLastModified, ItemCount from SyncJobs"; - private const string BaseJobItemSelectText = "select Id, ItemId, JobId, OutputPath, Status, TargetId from SyncJobItems"; + private const string BaseJobItemSelectText = "select Id, ItemId, JobId, OutputPath, Status, TargetId, DateCreated, Progress from SyncJobItems"; public SyncJob GetJob(string id) { @@ -105,6 +108,11 @@ namespace MediaBrowser.Server.Implementations.Sync var guid = new Guid(id); + if (guid == Guid.Empty) + { + throw new ArgumentNullException("id"); + } + using (var cmd = _connection.CreateCommand()) { cmd.CommandText = BaseJobSelectText + " where Id=@Id"; @@ -321,8 +329,24 @@ namespace MediaBrowser.Server.Implementations.Sync var whereClauses = new List<string>(); - var startIndex = query.StartIndex ?? 0; + if (query.IsCompleted.HasValue) + { + if (query.IsCompleted.Value) + { + whereClauses.Add("Status=@Status"); + } + else + { + whereClauses.Add("Status<>@Status"); + } + cmd.Parameters.Add(cmd, "@Status", DbType.String).Value = SyncJobStatus.Completed.ToString(); + } + var whereTextWithoutPaging = whereClauses.Count == 0 ? + string.Empty : + " where " + string.Join(" AND ", whereClauses.ToArray()); + + var startIndex = query.StartIndex ?? 0; if (startIndex > 0) { whereClauses.Add(string.Format("Id NOT IN (SELECT Id FROM SyncJobs ORDER BY DateLastModified DESC LIMIT {0})", @@ -341,7 +365,7 @@ namespace MediaBrowser.Server.Implementations.Sync cmd.CommandText += " LIMIT " + query.Limit.Value.ToString(_usCulture); } - cmd.CommandText += "; select count (Id) from SyncJobs"; + cmd.CommandText += "; select count (Id) from SyncJobs" + whereTextWithoutPaging; var list = new List<SyncJob>(); var count = 0; @@ -386,7 +410,7 @@ namespace MediaBrowser.Server.Implementations.Sync { if (reader.Read()) { - return GetSyncJobItem(reader); + return GetJobItem(reader); } } } @@ -394,28 +418,84 @@ namespace MediaBrowser.Server.Implementations.Sync return null; } - public IEnumerable<SyncJobItem> GetJobItems(string jobId) + public QueryResult<SyncJobItem> GetJobItems(SyncJobItemQuery query) { - if (string.IsNullOrEmpty(jobId)) + if (query == null) { - throw new ArgumentNullException("jobId"); + throw new ArgumentNullException("query"); } - var guid = new Guid(jobId); - using (var cmd = _connection.CreateCommand()) { - cmd.CommandText = BaseJobItemSelectText + " where JobId=@Id"; + cmd.CommandText = BaseJobItemSelectText; - cmd.Parameters.Add(cmd, "@Id", DbType.Guid).Value = guid; + var whereClauses = new List<string>(); + + if (!string.IsNullOrWhiteSpace(query.JobId)) + { + whereClauses.Add("JobId=@JobId"); + cmd.Parameters.Add(cmd, "@JobId", DbType.String).Value = query.JobId; + } + + if (query.IsCompleted.HasValue) + { + if (query.IsCompleted.Value) + { + whereClauses.Add("Status=@Status"); + } + else + { + whereClauses.Add("Status<>@Status"); + } + cmd.Parameters.Add(cmd, "@Status", DbType.String).Value = SyncJobStatus.Completed.ToString(); + } + + var whereTextWithoutPaging = whereClauses.Count == 0 ? + string.Empty : + " where " + string.Join(" AND ", whereClauses.ToArray()); + + var startIndex = query.StartIndex ?? 0; + if (startIndex > 0) + { + whereClauses.Add(string.Format("Id NOT IN (SELECT Id FROM SyncJobItems ORDER BY DateCreated LIMIT {0})", + startIndex.ToString(_usCulture))); + } + + if (whereClauses.Count > 0) + { + cmd.CommandText += " where " + string.Join(" AND ", whereClauses.ToArray()); + } + + cmd.CommandText += " ORDER BY DateCreated"; + + if (query.Limit.HasValue) + { + cmd.CommandText += " LIMIT " + query.Limit.Value.ToString(_usCulture); + } + + cmd.CommandText += "; select count (Id) from SyncJobItems" + whereTextWithoutPaging; + + var list = new List<SyncJobItem>(); + var count = 0; - using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult)) + using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess)) { while (reader.Read()) { - yield return GetSyncJobItem(reader); + list.Add(GetJobItem(reader)); + } + + if (reader.NextResult() && reader.Read()) + { + count = reader.GetInt32(0); } } + + return new QueryResult<SyncJobItem>() + { + Items = list.ToArray(), + TotalRecordCount = count + }; } } @@ -447,6 +527,8 @@ namespace MediaBrowser.Server.Implementations.Sync _saveJobItemCommand.GetParameter(index++).Value = jobItem.OutputPath; _saveJobItemCommand.GetParameter(index++).Value = jobItem.Status; _saveJobItemCommand.GetParameter(index++).Value = jobItem.TargetId; + _saveJobItemCommand.GetParameter(index++).Value = jobItem.DateCreated; + _saveJobItemCommand.GetParameter(index++).Value = jobItem.Progress; _saveJobItemCommand.Transaction = transaction; @@ -485,7 +567,7 @@ namespace MediaBrowser.Server.Implementations.Sync } } - private SyncJobItem GetSyncJobItem(IDataReader reader) + private SyncJobItem GetJobItem(IDataReader reader) { var info = new SyncJobItem { @@ -501,11 +583,18 @@ namespace MediaBrowser.Server.Implementations.Sync if (!reader.IsDBNull(4)) { - info.Status = (SyncJobStatus)Enum.Parse(typeof(SyncJobStatus), reader.GetString(4), true); + info.Status = (SyncJobItemStatus)Enum.Parse(typeof(SyncJobItemStatus), reader.GetString(4), true); } info.TargetId = reader.GetString(5); + info.DateCreated = reader.GetDateTime(6); + + if (!reader.IsDBNull(7)) + { + info.Progress = reader.GetDouble(7); + } + return info; } diff --git a/MediaBrowser.Server.Implementations/Sync/SyncScheduledTask.cs b/MediaBrowser.Server.Implementations/Sync/SyncScheduledTask.cs new file mode 100644 index 000000000..019951680 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Sync/SyncScheduledTask.cs @@ -0,0 +1,62 @@ +using MediaBrowser.Common.ScheduledTasks; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Sync; +using MediaBrowser.Model.Logging; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Server.Implementations.Sync +{ + public class SyncScheduledTask : IScheduledTask + { + private readonly ILibraryManager _libraryManager; + private readonly ISyncRepository _syncRepo; + private readonly ISyncManager _syncManager; + private readonly ILogger _logger; + private readonly IUserManager _userManager; + + public SyncScheduledTask(ILibraryManager libraryManager, ISyncRepository syncRepo, ISyncManager syncManager, ILogger logger, IUserManager userManager) + { + _libraryManager = libraryManager; + _syncRepo = syncRepo; + _syncManager = syncManager; + _logger = logger; + _userManager = userManager; + } + + public string Name + { + get { return "Sync"; } + } + + public string Description + { + get { return "Runs scheduled sync jobs"; } + } + + public string Category + { + get + { + return "Library"; + } + } + + public Task Execute(CancellationToken cancellationToken, IProgress<double> progress) + { + return new SyncJobProcessor(_libraryManager, _syncRepo, _syncManager, _logger, _userManager).Sync(progress, + cancellationToken); + } + + public IEnumerable<ITaskTrigger> GetDefaultTriggers() + { + return new ITaskTrigger[] + { + new IntervalTrigger { Interval = TimeSpan.FromHours(3) }, + new StartupTrigger{ DelayMs = Convert.ToInt32(TimeSpan.FromMinutes(5).TotalMilliseconds)} + }; + } + } +} |
