diff --git a/MediaBrowser.Api/Sync/SyncService.cs b/MediaBrowser.Api/Sync/SyncService.cs index bcadb4c08..b9dbf5946 100644 --- a/MediaBrowser.Api/Sync/SyncService.cs +++ b/MediaBrowser.Api/Sync/SyncService.cs @@ -65,6 +65,16 @@ namespace MediaBrowser.Api.Sync public string Id { get; set; } } + [Route("/Sync/{TargetId}/Items", "DELETE", Summary = "Cancels items from a sync target")] + public class CancelItems : IReturnVoid + { + [ApiMember(Name = "TargetId", Description = "TargetId", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "Items")] + public string TargetId { get; set; } + + [ApiMember(Name = "ItemIds", Description = "ItemIds", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "Items")] + public string ItemIds { get; set; } + } + [Route("/Sync/Jobs", "GET", Summary = "Gets sync jobs.")] public class GetSyncJobs : SyncJobQuery, IReturn> { @@ -200,6 +210,15 @@ namespace MediaBrowser.Api.Sync return ToOptimizedResult(result); } + public void Delete(CancelItems request) + { + var itemIds = request.ItemIds.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + + var task = _syncManager.CancelItems(request.TargetId, itemIds); + + Task.WaitAll(task); + } + public void Post(ReportSyncJobItemTransferred request) { var task = _syncManager.ReportSyncJobItemTransferred(request.Id); @@ -261,7 +280,7 @@ namespace MediaBrowser.Api.Sync var auth = AuthorizationContext.GetAuthorizationInfo(Request); var authenticatedUser = _userManager.GetUserById(auth.UserId); - + var items = request.ItemIds.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) .Select(_libraryManager.GetItemById) .Where(i => i != null); diff --git a/MediaBrowser.Controller/Sync/ISyncDataProvider.cs b/MediaBrowser.Controller/Sync/ISyncDataProvider.cs index 04101fd46..dc3edc4e4 100644 --- a/MediaBrowser.Controller/Sync/ISyncDataProvider.cs +++ b/MediaBrowser.Controller/Sync/ISyncDataProvider.cs @@ -21,7 +21,7 @@ namespace MediaBrowser.Controller.Sync /// The server identifier. /// Task<List<System.String>>. Task> GetSyncJobItemIds(SyncTarget target, string serverId); - + /// /// Adds the or update. /// @@ -53,7 +53,7 @@ namespace MediaBrowser.Controller.Sync /// The server identifier. /// The item identifier. /// Task<LocalItem>. - Task> GetCachedItems(SyncTarget target, string serverId, string itemId); + Task> GetItems(SyncTarget target, string serverId, string itemId); /// /// Gets the cached items by synchronize job item identifier. /// @@ -61,6 +61,6 @@ namespace MediaBrowser.Controller.Sync /// The server identifier. /// The synchronize job item identifier. /// Task<List<LocalItem>>. - Task> GetCachedItemsBySyncJobItemId(SyncTarget target, string serverId, string syncJobItemId); + Task> GetItemsBySyncJobItemId(SyncTarget target, string serverId, string syncJobItemId); } } diff --git a/MediaBrowser.Controller/Sync/ISyncManager.cs b/MediaBrowser.Controller/Sync/ISyncManager.cs index c51c8c1ba..3b6e20edc 100644 --- a/MediaBrowser.Controller/Sync/ISyncManager.cs +++ b/MediaBrowser.Controller/Sync/ISyncManager.cs @@ -73,6 +73,14 @@ namespace MediaBrowser.Controller.Sync /// Task. Task CancelJob(string id); + /// + /// Cancels the items. + /// + /// The target identifier. + /// The item ids. + /// Task. + Task CancelItems(string targetId, IEnumerable itemIds); + /// /// Adds the parts. /// diff --git a/MediaBrowser.Model/ApiClient/IApiClient.cs b/MediaBrowser.Model/ApiClient/IApiClient.cs index 9675de38b..8c7ca0ad9 100644 --- a/MediaBrowser.Model/ApiClient/IApiClient.cs +++ b/MediaBrowser.Model/ApiClient/IApiClient.cs @@ -1359,7 +1359,7 @@ namespace MediaBrowser.Model.ApiClient /// The job. /// Task. Task UpdateSyncJob(SyncJob job); - + /// /// Gets the synchronize jobs. /// @@ -1492,5 +1492,12 @@ namespace MediaBrowser.Model.ApiClient /// The cancellation token. /// Task<LiveStreamResponse>. Task OpenLiveStream(LiveStreamRequest request, CancellationToken cancellationToken); + /// + /// Cancels the synchronize library items. + /// + /// The target identifier. + /// The item ids. + /// Task. + Task CancelSyncLibraryItems(string targetId, IEnumerable itemIds); } } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Sync/MediaSync.cs b/MediaBrowser.Server.Implementations/Sync/MediaSync.cs index 5bc8b8088..26413c033 100644 --- a/MediaBrowser.Server.Implementations/Sync/MediaSync.cs +++ b/MediaBrowser.Server.Implementations/Sync/MediaSync.cs @@ -289,7 +289,7 @@ namespace MediaBrowser.Server.Implementations.Sync SyncTarget target, CancellationToken cancellationToken) { - var localItems = await dataProvider.GetCachedItemsBySyncJobItemId(target, serverId, syncJobItemId); + var localItems = await dataProvider.GetItemsBySyncJobItemId(target, serverId, syncJobItemId); foreach (var localItem in localItems) { diff --git a/MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs b/MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs index 271b2bb39..c0435a81e 100644 --- a/MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs +++ b/MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs @@ -67,9 +67,10 @@ namespace MediaBrowser.Server.Implementations.Sync var items = (await GetItemsForSync(job.Category, job.ParentId, job.RequestedItemIds, user, job.UnwatchedOnly).ConfigureAwait(false)) .ToList(); - var jobItems = _syncRepo.GetJobItems(new SyncJobItemQuery + var jobItems = _syncManager.GetJobItems(new SyncJobItemQuery { - JobId = job.Id + JobId = job.Id, + AddMetadata = false }).Items.ToList(); @@ -140,9 +141,10 @@ namespace MediaBrowser.Server.Implementations.Sync throw new ArgumentNullException("job"); } - var result = _syncRepo.GetJobItems(new SyncJobItemQuery + var result = _syncManager.GetJobItems(new SyncJobItemQuery { - JobId = job.Id + JobId = job.Id, + AddMetadata = false }); return UpdateJobStatus(job, result.Items.ToList()); @@ -362,9 +364,10 @@ namespace MediaBrowser.Server.Implementations.Sync await EnsureSyncJobItems(null, cancellationToken).ConfigureAwait(false); // If it already has a converting status then is must have been aborted during conversion - var result = _syncRepo.GetJobItems(new SyncJobItemQuery + var result = _syncManager.GetJobItems(new SyncJobItemQuery { - Statuses = new SyncJobItemStatus[] { SyncJobItemStatus.Queued, SyncJobItemStatus.Converting } + Statuses = new[] { SyncJobItemStatus.Queued, SyncJobItemStatus.Converting }, + AddMetadata = false }); await SyncJobItems(result.Items, true, progress, cancellationToken).ConfigureAwait(false); @@ -384,10 +387,11 @@ namespace MediaBrowser.Server.Implementations.Sync await EnsureSyncJobItems(targetId, cancellationToken).ConfigureAwait(false); // If it already has a converting status then is must have been aborted during conversion - var result = _syncRepo.GetJobItems(new SyncJobItemQuery + var result = _syncManager.GetJobItems(new SyncJobItemQuery { - Statuses = new SyncJobItemStatus[] { SyncJobItemStatus.Queued, SyncJobItemStatus.Converting }, - TargetId = targetId + Statuses = new[] { SyncJobItemStatus.Queued, SyncJobItemStatus.Converting }, + TargetId = targetId, + AddMetadata = false }); await SyncJobItems(result.Items, true, progress, cancellationToken).ConfigureAwait(false); diff --git a/MediaBrowser.Server.Implementations/Sync/SyncManager.cs b/MediaBrowser.Server.Implementations/Sync/SyncManager.cs index 102218979..a39a2b1cd 100644 --- a/MediaBrowser.Server.Implementations/Sync/SyncManager.cs +++ b/MediaBrowser.Server.Implementations/Sync/SyncManager.cs @@ -182,19 +182,21 @@ namespace MediaBrowser.Server.Implementations.Sync await processor.EnsureJobItems(job).ConfigureAwait(false); // If it already has a converting status then is must have been aborted during conversion - var jobItemsResult = _repo.GetJobItems(new SyncJobItemQuery + var jobItemsResult = GetJobItems(new SyncJobItemQuery { - Statuses = new SyncJobItemStatus[] { SyncJobItemStatus.Queued, SyncJobItemStatus.Converting }, - JobId = jobId + Statuses = new[] { SyncJobItemStatus.Queued, SyncJobItemStatus.Converting }, + JobId = jobId, + AddMetadata = false }); await processor.SyncJobItems(jobItemsResult.Items, false, new Progress(), CancellationToken.None) .ConfigureAwait(false); - jobItemsResult = _repo.GetJobItems(new SyncJobItemQuery + jobItemsResult = GetJobItems(new SyncJobItemQuery { - Statuses = new SyncJobItemStatus[] { SyncJobItemStatus.Queued, SyncJobItemStatus.Converting }, - JobId = jobId + Statuses = new[] { SyncJobItemStatus.Queued, SyncJobItemStatus.Converting }, + JobId = jobId, + AddMetadata = false }); var returnResult = new SyncJobCreationResult @@ -761,11 +763,13 @@ namespace MediaBrowser.Server.Implementations.Sync if (jobItem.IsMarkedForRemoval) { // Tell the device to remove it since it has been marked for removal + _logger.Debug("Adding ItemIdsToRemove {0} because IsMarkedForRemoval is set.", jobItem.ItemId); response.ItemIdsToRemove.Add(jobItem.ItemId); } else if (user == null) { // Tell the device to remove it since the user is gone now + _logger.Debug("Adding ItemIdsToRemove {0} because the user is no longer valid.", jobItem.ItemId); response.ItemIdsToRemove.Add(jobItem.ItemId); } else if (job.UnwatchedOnly) @@ -777,18 +781,22 @@ namespace MediaBrowser.Server.Implementations.Sync if (libraryItem.IsPlayed(user) && libraryItem is Video) { // Tell the device to remove it since it has been played + _logger.Debug("Adding ItemIdsToRemove {0} because it has been marked played.", jobItem.ItemId); response.ItemIdsToRemove.Add(jobItem.ItemId); } } else { // Tell the device to remove it since it's no longer available + _logger.Debug("Adding ItemIdsToRemove {0} because it is no longer available.", jobItem.ItemId); response.ItemIdsToRemove.Add(jobItem.ItemId); } } } else { + _logger.Debug("Setting status to RemovedFromDevice for {0} because it is no longer on the device.", jobItem.ItemId); + // Content is no longer on the device jobItem.Status = SyncJobItemStatus.RemovedFromDevice; await UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false); @@ -842,11 +850,13 @@ namespace MediaBrowser.Server.Implementations.Sync if (jobItem.IsMarkedForRemoval) { // Tell the device to remove it since it has been marked for removal + _logger.Debug("Adding ItemIdsToRemove {0} because IsMarkedForRemoval is set.", jobItem.Id); response.ItemIdsToRemove.Add(jobItem.Id); } else if (user == null) { // Tell the device to remove it since the user is gone now + _logger.Debug("Adding ItemIdsToRemove {0} because the user is no longer valid.", jobItem.Id); response.ItemIdsToRemove.Add(jobItem.Id); } else if (job.UnwatchedOnly) @@ -858,18 +868,22 @@ namespace MediaBrowser.Server.Implementations.Sync if (libraryItem.IsPlayed(user) && libraryItem is Video) { // Tell the device to remove it since it has been played + _logger.Debug("Adding ItemIdsToRemove {0} because it has been marked played.", jobItem.Id); response.ItemIdsToRemove.Add(jobItem.Id); } } else { // Tell the device to remove it since it's no longer available + _logger.Debug("Adding ItemIdsToRemove {0} because it is no longer available.", jobItem.Id); response.ItemIdsToRemove.Add(jobItem.Id); } } } else { + _logger.Debug("Setting status to RemovedFromDevice for {0} because it is no longer on the device.", jobItem.Id); + // Content is no longer on the device jobItem.Status = SyncJobItemStatus.RemovedFromDevice; await UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false); @@ -894,12 +908,6 @@ namespace MediaBrowser.Server.Implementations.Sync response.ItemIdsToRemove = response.ItemIdsToRemove.Distinct(StringComparer.OrdinalIgnoreCase).ToList(); - var itemsOnDevice = request.LocalItemIds - .Except(response.ItemIdsToRemove) - .ToList(); - - SetUserAccess(request, response, itemsOnDevice); - return response; } @@ -962,16 +970,39 @@ namespace MediaBrowser.Server.Implementations.Sync await processor.UpdateJobStatus(jobItem.JobId).ConfigureAwait(false); } + public async Task CancelItems(string targetId, IEnumerable itemIds) + { + foreach (var item in itemIds) + { + var syncJobItemResult = GetJobItems(new SyncJobItemQuery + { + AddMetadata = false, + ItemId = item, + TargetId = targetId, + Statuses = new[] { SyncJobItemStatus.Queued, SyncJobItemStatus.ReadyToTransfer, SyncJobItemStatus.Converting, SyncJobItemStatus.Synced, SyncJobItemStatus.Failed } + }); + + foreach (var jobItem in syncJobItemResult.Items) + { + await CancelJobItem(jobItem.Id).ConfigureAwait(false); + } + } + } + public async Task CancelJobItem(string id) { var jobItem = _repo.GetJobItem(id); - if (jobItem.Status != SyncJobItemStatus.Queued && jobItem.Status != SyncJobItemStatus.ReadyToTransfer && jobItem.Status != SyncJobItemStatus.Converting) + if (jobItem.Status != SyncJobItemStatus.Queued && jobItem.Status != SyncJobItemStatus.ReadyToTransfer && jobItem.Status != SyncJobItemStatus.Converting && jobItem.Status != SyncJobItemStatus.Failed && jobItem.Status != SyncJobItemStatus.Synced) { throw new ArgumentException("Operation is not valid for this job item"); } - jobItem.Status = SyncJobItemStatus.Cancelled; + if (jobItem.Status != SyncJobItemStatus.Synced) + { + jobItem.Status = SyncJobItemStatus.Cancelled; + } + jobItem.Progress = 0; jobItem.IsMarkedForRemoval = true; @@ -995,24 +1026,24 @@ namespace MediaBrowser.Server.Implementations.Sync { _logger.ErrorException("Error deleting directory {0}", ex, path); } + + //var jobItemsResult = GetJobItems(new SyncJobItemQuery + //{ + // AddMetadata = false, + // JobId = jobItem.JobId, + // Limit = 0, + // Statuses = new[] { SyncJobItemStatus.Converting, SyncJobItemStatus.Failed, SyncJobItemStatus.Queued, SyncJobItemStatus.ReadyToTransfer, SyncJobItemStatus.Synced, SyncJobItemStatus.Transferring } + //}); + + //if (jobItemsResult.TotalRecordCount == 0) + //{ + // await CancelJob(jobItem.JobId).ConfigureAwait(false); + //} } - public async Task MarkJobItemForRemoval(string id) + public Task MarkJobItemForRemoval(string id) { - var jobItem = _repo.GetJobItem(id); - - if (jobItem.Status != SyncJobItemStatus.Synced) - { - throw new ArgumentException("Operation is not valid for this job item"); - } - - jobItem.IsMarkedForRemoval = true; - - await UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false); - - var processor = GetSyncJobProcessor(); - - await processor.UpdateJobStatus(jobItem.JobId).ConfigureAwait(false); + return CancelJobItem(id); } public async Task UnmarkJobItemForRemoval(string id) diff --git a/MediaBrowser.Server.Implementations/Sync/SyncedMediaSourceProvider.cs b/MediaBrowser.Server.Implementations/Sync/SyncedMediaSourceProvider.cs index d1ef523e1..40d40d415 100644 --- a/MediaBrowser.Server.Implementations/Sync/SyncedMediaSourceProvider.cs +++ b/MediaBrowser.Server.Implementations/Sync/SyncedMediaSourceProvider.cs @@ -56,7 +56,7 @@ namespace MediaBrowser.Server.Implementations.Sync var syncProvider = targetTuple.Item1; var dataProvider = _syncManager.GetDataProvider(targetTuple.Item1, syncTarget); - var localItems = await dataProvider.GetCachedItems(syncTarget, serverId, item.Id.ToString("N")).ConfigureAwait(false); + var localItems = await dataProvider.GetItems(syncTarget, serverId, item.Id.ToString("N")).ConfigureAwait(false); foreach (var localItem in localItems) { diff --git a/MediaBrowser.Server.Implementations/Sync/TargetDataProvider.cs b/MediaBrowser.Server.Implementations/Sync/TargetDataProvider.cs index dea868848..3323ae148 100644 --- a/MediaBrowser.Server.Implementations/Sync/TargetDataProvider.cs +++ b/MediaBrowser.Server.Implementations/Sync/TargetDataProvider.cs @@ -42,11 +42,6 @@ namespace MediaBrowser.Server.Implementations.Sync _appHost = appHost; } - private string GetCachePath() - { - return Path.Combine(_appPaths.DataPath, "sync", _target.Id.GetMD5().ToString("N") + ".json"); - } - private string GetRemotePath() { var parts = new List @@ -66,37 +61,17 @@ namespace MediaBrowser.Server.Implementations.Sync return _fileSystem.GetValidFilename(filename); } - private async Task CacheData(Stream stream) - { - var cachePath = GetCachePath(); - - await _cacheFileLock.WaitAsync().ConfigureAwait(false); - - try - { - Directory.CreateDirectory(Path.GetDirectoryName(cachePath)); - using (var fileStream = _fileSystem.GetFileStream(cachePath, FileMode.Create, FileAccess.Write, FileShare.Read, true)) - { - await stream.CopyToAsync(fileStream).ConfigureAwait(false); - } - } - catch (Exception ex) - { - _logger.ErrorException("Error saving sync data to {0}", ex, cachePath); - } - finally - { - _cacheFileLock.Release(); - } - } - private async Task EnsureData(CancellationToken cancellationToken) { if (_items == null) { try { - using (var stream = await _provider.GetFile(GetRemotePath(), _target, new Progress(), cancellationToken)) + var path = GetRemotePath(); + + _logger.Debug("Getting {0} from {1}", path, _provider.Name); + + using (var stream = await _provider.GetFile(path, _target, new Progress(), cancellationToken)) { _items = _json.DeserializeFromStream>(stream); } @@ -109,15 +84,6 @@ namespace MediaBrowser.Server.Implementations.Sync { _items = new List(); } - - using (var memoryStream = new MemoryStream()) - { - _json.SerializeToStream(_items, memoryStream); - - // Now cache it - memoryStream.Position = 0; - await CacheData(memoryStream).ConfigureAwait(false); - } } } @@ -130,10 +96,6 @@ namespace MediaBrowser.Server.Implementations.Sync // Save to sync provider stream.Position = 0; await _provider.SendFile(stream, GetRemotePath(), _target, new Progress(), cancellationToken).ConfigureAwait(false); - - // Now cache it - stream.Position = 0; - await CacheData(stream).ConfigureAwait(false); } } @@ -204,62 +166,14 @@ namespace MediaBrowser.Server.Implementations.Sync return GetData(items => items.FirstOrDefault(i => string.Equals(i.Id, id, StringComparison.OrdinalIgnoreCase))); } - private async Task> GetCachedData() + public Task> GetItems(SyncTarget target, string serverId, string itemId) { - if (_items == null) - { - await _cacheFileLock.WaitAsync().ConfigureAwait(false); - - try - { - if (_items == null) - { - try - { - _items = _json.DeserializeFromFile>(GetCachePath()); - } - catch (FileNotFoundException) - { - _items = new List(); - } - catch (DirectoryNotFoundException) - { - _items = new List(); - } - } - } - finally - { - _cacheFileLock.Release(); - } - } - - return _items.ToList(); + return GetData(items => items.Where(i => string.Equals(i.ServerId, serverId, StringComparison.OrdinalIgnoreCase) && string.Equals(i.ItemId, itemId, StringComparison.OrdinalIgnoreCase)).ToList()); } - public async Task> GetCachedServerItemIds(SyncTarget target, string serverId) + public Task> GetItemsBySyncJobItemId(SyncTarget target, string serverId, string syncJobItemId) { - var items = await GetCachedData().ConfigureAwait(false); - - return items.Where(i => string.Equals(i.ServerId, serverId, StringComparison.OrdinalIgnoreCase)) - .Select(i => i.ItemId) - .ToList(); - } - - public async Task> GetCachedItems(SyncTarget target, string serverId, string itemId) - { - var items = await GetCachedData().ConfigureAwait(false); - - return items.Where(i => string.Equals(i.ServerId, serverId, StringComparison.OrdinalIgnoreCase) && string.Equals(i.ItemId, itemId, StringComparison.OrdinalIgnoreCase)) - .ToList(); - } - - public async Task> GetCachedItemsBySyncJobItemId(SyncTarget target, string serverId, string syncJobItemId) - { - var items = await GetCachedData().ConfigureAwait(false); - - return items.Where(i => string.Equals(i.ServerId, serverId, StringComparison.OrdinalIgnoreCase) && string.Equals(i.SyncJobItemId, syncJobItemId, StringComparison.OrdinalIgnoreCase)) - .ToList(); + return GetData(items => items.Where(i => string.Equals(i.ServerId, serverId, StringComparison.OrdinalIgnoreCase) && string.Equals(i.SyncJobItemId, syncJobItemId, StringComparison.OrdinalIgnoreCase)).ToList()); } } }