|  | @@ -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<double>(), 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<string> 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)
 |