SyncManager.cs 48 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365
  1. using MediaBrowser.Common.Configuration;
  2. using MediaBrowser.Common.Events;
  3. using MediaBrowser.Common.Extensions;
  4. using MediaBrowser.Common.ScheduledTasks;
  5. using MediaBrowser.Controller;
  6. using MediaBrowser.Controller.Drawing;
  7. using MediaBrowser.Controller.Dto;
  8. using MediaBrowser.Controller.Entities;
  9. using MediaBrowser.Controller.Entities.Audio;
  10. using MediaBrowser.Controller.Entities.TV;
  11. using MediaBrowser.Controller.Library;
  12. using MediaBrowser.Controller.MediaEncoding;
  13. using MediaBrowser.Controller.Playlists;
  14. using MediaBrowser.Controller.Sync;
  15. using MediaBrowser.Controller.TV;
  16. using MediaBrowser.Model.Dto;
  17. using MediaBrowser.Model.Entities;
  18. using MediaBrowser.Model.Events;
  19. using MediaBrowser.Model.Logging;
  20. using MediaBrowser.Model.Querying;
  21. using MediaBrowser.Model.Serialization;
  22. using MediaBrowser.Model.Sync;
  23. using MediaBrowser.Model.Users;
  24. using System;
  25. using System.Collections.Concurrent;
  26. using System.Collections.Generic;
  27. using System.IO;
  28. using System.Linq;
  29. using System.Threading;
  30. using System.Threading.Tasks;
  31. using MediaBrowser.Model.IO;
  32. using MediaBrowser.Common.IO;
  33. using MediaBrowser.Controller.IO;
  34. using MediaBrowser.Model.IO;
  35. using MediaBrowser.Model.Tasks;
  36. namespace MediaBrowser.Server.Implementations.Sync
  37. {
  38. public class SyncManager : ISyncManager
  39. {
  40. private readonly ILibraryManager _libraryManager;
  41. private readonly ISyncRepository _repo;
  42. private readonly IImageProcessor _imageProcessor;
  43. private readonly ILogger _logger;
  44. private readonly IUserManager _userManager;
  45. private readonly Func<IDtoService> _dtoService;
  46. private readonly IServerApplicationHost _appHost;
  47. private readonly ITVSeriesManager _tvSeriesManager;
  48. private readonly Func<IMediaEncoder> _mediaEncoder;
  49. private readonly IFileSystem _fileSystem;
  50. private readonly Func<ISubtitleEncoder> _subtitleEncoder;
  51. private readonly IConfigurationManager _config;
  52. private readonly IUserDataManager _userDataManager;
  53. private readonly Func<IMediaSourceManager> _mediaSourceManager;
  54. private readonly IJsonSerializer _json;
  55. private readonly ITaskManager _taskManager;
  56. private readonly IMemoryStreamProvider _memoryStreamProvider;
  57. private ISyncProvider[] _providers = { };
  58. public event EventHandler<GenericEventArgs<SyncJobCreationResult>> SyncJobCreated;
  59. public event EventHandler<GenericEventArgs<SyncJob>> SyncJobCancelled;
  60. public event EventHandler<GenericEventArgs<SyncJob>> SyncJobUpdated;
  61. public event EventHandler<GenericEventArgs<SyncJobItem>> SyncJobItemUpdated;
  62. public event EventHandler<GenericEventArgs<SyncJobItem>> SyncJobItemCreated;
  63. public SyncManager(ILibraryManager libraryManager, ISyncRepository repo, IImageProcessor imageProcessor, ILogger logger, IUserManager userManager, Func<IDtoService> dtoService, IServerApplicationHost appHost, ITVSeriesManager tvSeriesManager, Func<IMediaEncoder> mediaEncoder, IFileSystem fileSystem, Func<ISubtitleEncoder> subtitleEncoder, IConfigurationManager config, IUserDataManager userDataManager, Func<IMediaSourceManager> mediaSourceManager, IJsonSerializer json, ITaskManager taskManager, IMemoryStreamProvider memoryStreamProvider)
  64. {
  65. _libraryManager = libraryManager;
  66. _repo = repo;
  67. _imageProcessor = imageProcessor;
  68. _logger = logger;
  69. _userManager = userManager;
  70. _dtoService = dtoService;
  71. _appHost = appHost;
  72. _tvSeriesManager = tvSeriesManager;
  73. _mediaEncoder = mediaEncoder;
  74. _fileSystem = fileSystem;
  75. _subtitleEncoder = subtitleEncoder;
  76. _config = config;
  77. _userDataManager = userDataManager;
  78. _mediaSourceManager = mediaSourceManager;
  79. _json = json;
  80. _taskManager = taskManager;
  81. _memoryStreamProvider = memoryStreamProvider;
  82. }
  83. public void AddParts(IEnumerable<ISyncProvider> providers)
  84. {
  85. _providers = providers.ToArray();
  86. }
  87. public IEnumerable<IServerSyncProvider> ServerSyncProviders
  88. {
  89. get { return _providers.OfType<IServerSyncProvider>(); }
  90. }
  91. private readonly ConcurrentDictionary<string, ISyncDataProvider> _dataProviders =
  92. new ConcurrentDictionary<string, ISyncDataProvider>(StringComparer.OrdinalIgnoreCase);
  93. public ISyncDataProvider GetDataProvider(IServerSyncProvider provider, SyncTarget target)
  94. {
  95. return _dataProviders.GetOrAdd(target.Id, key => new TargetDataProvider(provider, target, _appHost, _logger, _json, _fileSystem, _config.CommonApplicationPaths, _memoryStreamProvider));
  96. }
  97. public async Task<SyncJobCreationResult> CreateJob(SyncJobRequest request)
  98. {
  99. var processor = GetSyncJobProcessor();
  100. var user = _userManager.GetUserById(request.UserId);
  101. var items = (await processor
  102. .GetItemsForSync(request.Category, request.ParentId, request.ItemIds, user, request.UnwatchedOnly).ConfigureAwait(false))
  103. .ToList();
  104. if (items.Any(i => !SupportsSync(i)))
  105. {
  106. throw new ArgumentException("Item does not support sync.");
  107. }
  108. if (string.IsNullOrWhiteSpace(request.Name))
  109. {
  110. if (request.ItemIds.Count == 1)
  111. {
  112. request.Name = GetDefaultName(_libraryManager.GetItemById(request.ItemIds[0]));
  113. }
  114. }
  115. if (string.IsNullOrWhiteSpace(request.Name))
  116. {
  117. request.Name = DateTime.Now.ToShortDateString() + " " + DateTime.Now.ToShortTimeString();
  118. }
  119. var target = GetSyncTargets(request.UserId)
  120. .FirstOrDefault(i => string.Equals(request.TargetId, i.Id));
  121. if (target == null)
  122. {
  123. throw new ArgumentException("Sync target not found.");
  124. }
  125. var jobId = Guid.NewGuid().ToString("N");
  126. if (string.IsNullOrWhiteSpace(request.Quality))
  127. {
  128. request.Quality = GetQualityOptions(request.TargetId)
  129. .Where(i => i.IsDefault)
  130. .Select(i => i.Id)
  131. .FirstOrDefault(i => !string.IsNullOrWhiteSpace(i));
  132. }
  133. var job = new SyncJob
  134. {
  135. Id = jobId,
  136. Name = request.Name,
  137. TargetId = target.Id,
  138. UserId = request.UserId,
  139. UnwatchedOnly = request.UnwatchedOnly,
  140. ItemLimit = request.ItemLimit,
  141. RequestedItemIds = request.ItemIds ?? new List<string>(),
  142. DateCreated = DateTime.UtcNow,
  143. DateLastModified = DateTime.UtcNow,
  144. SyncNewContent = request.SyncNewContent,
  145. ItemCount = items.Count,
  146. Category = request.Category,
  147. ParentId = request.ParentId,
  148. Quality = request.Quality,
  149. Profile = request.Profile,
  150. Bitrate = request.Bitrate
  151. };
  152. if (!request.Category.HasValue && request.ItemIds != null)
  153. {
  154. var requestedItems = request.ItemIds
  155. .Select(_libraryManager.GetItemById)
  156. .Where(i => i != null);
  157. // It's just a static list
  158. if (!requestedItems.Any(i => i.IsFolder || i is IItemByName))
  159. {
  160. job.SyncNewContent = false;
  161. }
  162. }
  163. await _repo.Create(job).ConfigureAwait(false);
  164. await processor.EnsureJobItems(job).ConfigureAwait(false);
  165. // If it already has a converting status then is must have been aborted during conversion
  166. var jobItemsResult = GetJobItems(new SyncJobItemQuery
  167. {
  168. Statuses = new[] { SyncJobItemStatus.Queued, SyncJobItemStatus.Converting },
  169. JobId = jobId,
  170. AddMetadata = false
  171. });
  172. await processor.SyncJobItems(jobItemsResult.Items, false, new Progress<double>(), CancellationToken.None)
  173. .ConfigureAwait(false);
  174. jobItemsResult = GetJobItems(new SyncJobItemQuery
  175. {
  176. Statuses = new[] { SyncJobItemStatus.Queued, SyncJobItemStatus.Converting },
  177. JobId = jobId,
  178. AddMetadata = false
  179. });
  180. var returnResult = new SyncJobCreationResult
  181. {
  182. Job = GetJob(jobId),
  183. JobItems = jobItemsResult.Items.ToList()
  184. };
  185. if (SyncJobCreated != null)
  186. {
  187. EventHelper.FireEventIfNotNull(SyncJobCreated, this, new GenericEventArgs<SyncJobCreationResult>
  188. {
  189. Argument = returnResult
  190. }, _logger);
  191. }
  192. if (returnResult.JobItems.Any(i => i.Status == SyncJobItemStatus.Queued || i.Status == SyncJobItemStatus.Converting))
  193. {
  194. _taskManager.QueueScheduledTask<SyncConvertScheduledTask>();
  195. }
  196. return returnResult;
  197. }
  198. public async Task UpdateJob(SyncJob job)
  199. {
  200. // Get fresh from the db and only update the fields that are supported to be changed.
  201. var instance = _repo.GetJob(job.Id);
  202. instance.Name = job.Name;
  203. instance.Quality = job.Quality;
  204. instance.Profile = job.Profile;
  205. instance.UnwatchedOnly = job.UnwatchedOnly;
  206. instance.SyncNewContent = job.SyncNewContent;
  207. instance.ItemLimit = job.ItemLimit;
  208. await _repo.Update(instance).ConfigureAwait(false);
  209. OnSyncJobUpdated(instance);
  210. }
  211. internal void OnSyncJobUpdated(SyncJob job)
  212. {
  213. if (SyncJobUpdated != null)
  214. {
  215. EventHelper.FireEventIfNotNull(SyncJobUpdated, this, new GenericEventArgs<SyncJob>
  216. {
  217. Argument = job
  218. }, _logger);
  219. }
  220. }
  221. internal async Task UpdateSyncJobItemInternal(SyncJobItem jobItem)
  222. {
  223. await _repo.Update(jobItem).ConfigureAwait(false);
  224. if (SyncJobUpdated != null)
  225. {
  226. EventHelper.FireEventIfNotNull(SyncJobItemUpdated, this, new GenericEventArgs<SyncJobItem>
  227. {
  228. Argument = jobItem
  229. }, _logger);
  230. }
  231. }
  232. internal void OnSyncJobItemCreated(SyncJobItem job)
  233. {
  234. if (SyncJobUpdated != null)
  235. {
  236. EventHelper.FireEventIfNotNull(SyncJobItemCreated, this, new GenericEventArgs<SyncJobItem>
  237. {
  238. Argument = job
  239. }, _logger);
  240. }
  241. }
  242. public async Task<QueryResult<SyncJob>> GetJobs(SyncJobQuery query)
  243. {
  244. var result = _repo.GetJobs(query);
  245. foreach (var item in result.Items)
  246. {
  247. await FillMetadata(item).ConfigureAwait(false);
  248. }
  249. return result;
  250. }
  251. private async Task FillMetadata(SyncJob job)
  252. {
  253. var user = _userManager.GetUserById(job.UserId);
  254. if (user == null)
  255. {
  256. return;
  257. }
  258. var target = GetSyncTargets(job.UserId)
  259. .FirstOrDefault(i => string.Equals(i.Id, job.TargetId, StringComparison.OrdinalIgnoreCase));
  260. if (target != null)
  261. {
  262. job.TargetName = target.Name;
  263. }
  264. var item = job.RequestedItemIds
  265. .Select(_libraryManager.GetItemById)
  266. .FirstOrDefault(i => i != null);
  267. if (item == null)
  268. {
  269. var processor = GetSyncJobProcessor();
  270. item = (await processor
  271. .GetItemsForSync(job.Category, job.ParentId, job.RequestedItemIds, user, job.UnwatchedOnly).ConfigureAwait(false))
  272. .FirstOrDefault();
  273. }
  274. if (item != null)
  275. {
  276. var hasSeries = item as IHasSeries;
  277. if (hasSeries != null)
  278. {
  279. job.ParentName = hasSeries.SeriesName;
  280. }
  281. var hasAlbumArtist = item as IHasAlbumArtist;
  282. if (hasAlbumArtist != null)
  283. {
  284. job.ParentName = hasAlbumArtist.AlbumArtists.FirstOrDefault();
  285. }
  286. var primaryImage = item.GetImageInfo(ImageType.Primary, 0);
  287. var itemWithImage = item;
  288. if (primaryImage == null)
  289. {
  290. var parentWithImage = item.GetParents().FirstOrDefault(i => i.HasImage(ImageType.Primary));
  291. if (parentWithImage != null)
  292. {
  293. itemWithImage = parentWithImage;
  294. primaryImage = parentWithImage.GetImageInfo(ImageType.Primary, 0);
  295. }
  296. }
  297. if (primaryImage != null)
  298. {
  299. try
  300. {
  301. job.PrimaryImageTag = _imageProcessor.GetImageCacheTag(itemWithImage, ImageType.Primary);
  302. job.PrimaryImageItemId = itemWithImage.Id.ToString("N");
  303. }
  304. catch (Exception ex)
  305. {
  306. _logger.ErrorException("Error getting image info", ex);
  307. }
  308. }
  309. }
  310. }
  311. private void FillMetadata(SyncJobItem jobItem)
  312. {
  313. var item = _libraryManager.GetItemById(jobItem.ItemId);
  314. if (item == null)
  315. {
  316. return;
  317. }
  318. var primaryImage = item.GetImageInfo(ImageType.Primary, 0);
  319. var itemWithImage = item;
  320. if (primaryImage == null)
  321. {
  322. var parentWithImage = item.GetParents().FirstOrDefault(i => i.HasImage(ImageType.Primary));
  323. if (parentWithImage != null)
  324. {
  325. itemWithImage = parentWithImage;
  326. primaryImage = parentWithImage.GetImageInfo(ImageType.Primary, 0);
  327. }
  328. }
  329. if (primaryImage != null)
  330. {
  331. try
  332. {
  333. jobItem.PrimaryImageTag = _imageProcessor.GetImageCacheTag(itemWithImage, ImageType.Primary);
  334. jobItem.PrimaryImageItemId = itemWithImage.Id.ToString("N");
  335. }
  336. catch (Exception ex)
  337. {
  338. _logger.ErrorException("Error getting image info", ex);
  339. }
  340. }
  341. }
  342. public async Task CancelJob(string id)
  343. {
  344. var job = GetJob(id);
  345. if (job == null)
  346. {
  347. throw new ArgumentException("Job not found.");
  348. }
  349. await _repo.DeleteJob(id).ConfigureAwait(false);
  350. var path = GetSyncJobProcessor().GetTemporaryPath(id);
  351. try
  352. {
  353. _fileSystem.DeleteDirectory(path, true);
  354. }
  355. catch (DirectoryNotFoundException)
  356. {
  357. }
  358. catch (Exception ex)
  359. {
  360. _logger.ErrorException("Error deleting directory {0}", ex, path);
  361. }
  362. if (SyncJobCancelled != null)
  363. {
  364. EventHelper.FireEventIfNotNull(SyncJobCancelled, this, new GenericEventArgs<SyncJob>
  365. {
  366. Argument = job
  367. }, _logger);
  368. }
  369. }
  370. public SyncJob GetJob(string id)
  371. {
  372. return _repo.GetJob(id);
  373. }
  374. public IEnumerable<SyncTarget> GetSyncTargets(string userId)
  375. {
  376. return _providers
  377. .SelectMany(i => GetSyncTargets(i, userId))
  378. .OrderBy(i => i.Name);
  379. }
  380. private IEnumerable<SyncTarget> GetSyncTargets(ISyncProvider provider)
  381. {
  382. return provider.GetAllSyncTargets().Select(i => new SyncTarget
  383. {
  384. Name = i.Name,
  385. Id = GetSyncTargetId(provider, i)
  386. });
  387. }
  388. private IEnumerable<SyncTarget> GetSyncTargets(ISyncProvider provider, string userId)
  389. {
  390. return provider.GetSyncTargets(userId).Select(i => new SyncTarget
  391. {
  392. Name = i.Name,
  393. Id = GetSyncTargetId(provider, i)
  394. });
  395. }
  396. private string GetSyncTargetId(ISyncProvider provider, SyncTarget target)
  397. {
  398. var hasUniqueId = provider as IHasUniqueTargetIds;
  399. if (hasUniqueId != null)
  400. {
  401. return target.Id;
  402. }
  403. return target.Id;
  404. //var providerId = GetSyncProviderId(provider);
  405. //return (providerId + "-" + target.Id).GetMD5().ToString("N");
  406. }
  407. private string GetSyncProviderId(ISyncProvider provider)
  408. {
  409. return provider.GetType().Name.GetMD5().ToString("N");
  410. }
  411. public bool SupportsSync(BaseItem item)
  412. {
  413. if (item == null)
  414. {
  415. throw new ArgumentNullException("item");
  416. }
  417. if (item is Playlist)
  418. {
  419. return true;
  420. }
  421. if (item is Person)
  422. {
  423. return false;
  424. }
  425. if (item is Year)
  426. {
  427. return false;
  428. }
  429. if (string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase) ||
  430. string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) ||
  431. string.Equals(item.MediaType, MediaType.Photo, StringComparison.OrdinalIgnoreCase) ||
  432. string.Equals(item.MediaType, MediaType.Game, StringComparison.OrdinalIgnoreCase) ||
  433. string.Equals(item.MediaType, MediaType.Book, StringComparison.OrdinalIgnoreCase))
  434. {
  435. if (item.LocationType == LocationType.Virtual)
  436. {
  437. return false;
  438. }
  439. var video = item as Video;
  440. if (video != null)
  441. {
  442. if (video.IsPlaceHolder)
  443. {
  444. return false;
  445. }
  446. if (video.IsShortcut)
  447. {
  448. return false;
  449. }
  450. }
  451. if (item.SourceType != SourceType.Library)
  452. {
  453. return false;
  454. }
  455. return true;
  456. }
  457. if (item.SourceType == SourceType.Channel)
  458. {
  459. return BaseItem.ChannelManager.SupportsSync(item.ChannelId);
  460. }
  461. return item.LocationType == LocationType.FileSystem || item is Season;
  462. }
  463. private string GetDefaultName(BaseItem item)
  464. {
  465. return item.Name;
  466. }
  467. public async Task ReportSyncJobItemTransferred(string id)
  468. {
  469. var jobItem = _repo.GetJobItem(id);
  470. jobItem.Status = SyncJobItemStatus.Synced;
  471. jobItem.Progress = 100;
  472. await UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false);
  473. var processor = GetSyncJobProcessor();
  474. await processor.UpdateJobStatus(jobItem.JobId).ConfigureAwait(false);
  475. if (!string.IsNullOrWhiteSpace(jobItem.TemporaryPath))
  476. {
  477. try
  478. {
  479. _fileSystem.DeleteDirectory(jobItem.TemporaryPath, true);
  480. }
  481. catch (DirectoryNotFoundException)
  482. {
  483. }
  484. catch (Exception ex)
  485. {
  486. _logger.ErrorException("Error deleting temporary job file: {0}", ex, jobItem.OutputPath);
  487. }
  488. }
  489. }
  490. private SyncJobProcessor GetSyncJobProcessor()
  491. {
  492. return new SyncJobProcessor(_libraryManager, _repo, this, _logger, _userManager, _tvSeriesManager, _mediaEncoder(), _subtitleEncoder(), _config, _fileSystem, _mediaSourceManager());
  493. }
  494. public SyncJobItem GetJobItem(string id)
  495. {
  496. return _repo.GetJobItem(id);
  497. }
  498. public QueryResult<SyncJobItem> GetJobItems(SyncJobItemQuery query)
  499. {
  500. var result = _repo.GetJobItems(query);
  501. if (query.AddMetadata)
  502. {
  503. foreach (var item in result.Items)
  504. {
  505. FillMetadata(item);
  506. }
  507. }
  508. return result;
  509. }
  510. private SyncedItem GetJobItemInfo(SyncJobItem jobItem)
  511. {
  512. var job = _repo.GetJob(jobItem.JobId);
  513. if (job == null)
  514. {
  515. _logger.Error("GetJobItemInfo job id {0} no longer exists", jobItem.JobId);
  516. return null;
  517. }
  518. var libraryItem = _libraryManager.GetItemById(jobItem.ItemId);
  519. if (libraryItem == null)
  520. {
  521. _logger.Error("GetJobItemInfo library item with id {0} no longer exists", jobItem.ItemId);
  522. return null;
  523. }
  524. var syncedItem = new SyncedItem
  525. {
  526. SyncJobId = jobItem.JobId,
  527. SyncJobItemId = jobItem.Id,
  528. ServerId = _appHost.SystemId,
  529. UserId = job.UserId,
  530. SyncJobName = job.Name,
  531. SyncJobDateCreated = job.DateCreated,
  532. AdditionalFiles = jobItem.AdditionalFiles.Select(i => new ItemFileInfo
  533. {
  534. ImageType = i.ImageType,
  535. Name = i.Name,
  536. Type = i.Type,
  537. Index = i.Index
  538. }).ToList()
  539. };
  540. var dtoOptions = new DtoOptions();
  541. // Remove some bloat
  542. dtoOptions.Fields.Remove(ItemFields.MediaStreams);
  543. dtoOptions.Fields.Remove(ItemFields.IndexOptions);
  544. dtoOptions.Fields.Remove(ItemFields.MediaSourceCount);
  545. dtoOptions.Fields.Remove(ItemFields.Path);
  546. dtoOptions.Fields.Remove(ItemFields.SeriesGenres);
  547. dtoOptions.Fields.Remove(ItemFields.Settings);
  548. dtoOptions.Fields.Remove(ItemFields.SyncInfo);
  549. dtoOptions.Fields.Remove(ItemFields.BasicSyncInfo);
  550. syncedItem.Item = _dtoService().GetBaseItemDto(libraryItem, dtoOptions);
  551. var mediaSource = jobItem.MediaSource;
  552. syncedItem.Item.MediaSources = new List<MediaSourceInfo>();
  553. syncedItem.OriginalFileName = Path.GetFileName(libraryItem.Path);
  554. if (string.IsNullOrWhiteSpace(syncedItem.OriginalFileName))
  555. {
  556. syncedItem.OriginalFileName = Path.GetFileName(mediaSource.Path);
  557. }
  558. // This will be null for items that are not audio/video
  559. if (mediaSource != null)
  560. {
  561. syncedItem.OriginalFileName = Path.ChangeExtension(syncedItem.OriginalFileName, Path.GetExtension(mediaSource.Path));
  562. syncedItem.Item.MediaSources.Add(mediaSource);
  563. }
  564. if (string.IsNullOrWhiteSpace(syncedItem.OriginalFileName))
  565. {
  566. syncedItem.OriginalFileName = libraryItem.Name;
  567. }
  568. return syncedItem;
  569. }
  570. public Task ReportOfflineAction(UserAction action)
  571. {
  572. switch (action.Type)
  573. {
  574. case UserActionType.PlayedItem:
  575. return ReportOfflinePlayedItem(action);
  576. default:
  577. throw new ArgumentException("Unexpected action type");
  578. }
  579. }
  580. private Task ReportOfflinePlayedItem(UserAction action)
  581. {
  582. var item = _libraryManager.GetItemById(action.ItemId);
  583. var userData = _userDataManager.GetUserData(action.UserId, item);
  584. userData.LastPlayedDate = action.Date;
  585. _userDataManager.UpdatePlayState(item, userData, action.PositionTicks);
  586. return _userDataManager.SaveUserData(new Guid(action.UserId), item, userData, UserDataSaveReason.Import, CancellationToken.None);
  587. }
  588. public async Task<List<SyncedItem>> GetReadySyncItems(string targetId)
  589. {
  590. var processor = GetSyncJobProcessor();
  591. await processor.SyncJobItems(targetId, false, new Progress<double>(), CancellationToken.None).ConfigureAwait(false);
  592. var jobItemResult = GetJobItems(new SyncJobItemQuery
  593. {
  594. TargetId = targetId,
  595. Statuses = new[]
  596. {
  597. SyncJobItemStatus.ReadyToTransfer,
  598. SyncJobItemStatus.Transferring
  599. }
  600. });
  601. var readyItems = jobItemResult.Items
  602. .Select(GetJobItemInfo)
  603. .Where(i => i != null)
  604. .ToList();
  605. _logger.Debug("Returning {0} ready sync items for targetId {1}", readyItems.Count, targetId);
  606. return readyItems;
  607. }
  608. public async Task<SyncDataResponse> SyncData(SyncDataRequest request)
  609. {
  610. if (request.SyncJobItemIds != null)
  611. {
  612. return await SyncDataUsingSyncJobItemIds(request).ConfigureAwait(false);
  613. }
  614. var jobItemResult = GetJobItems(new SyncJobItemQuery
  615. {
  616. TargetId = request.TargetId,
  617. Statuses = new[] { SyncJobItemStatus.Synced }
  618. });
  619. var response = new SyncDataResponse();
  620. foreach (var jobItem in jobItemResult.Items)
  621. {
  622. var requiresSaving = false;
  623. var removeFromDevice = false;
  624. if (request.LocalItemIds.Contains(jobItem.ItemId, StringComparer.OrdinalIgnoreCase))
  625. {
  626. var libraryItem = _libraryManager.GetItemById(jobItem.ItemId);
  627. var job = _repo.GetJob(jobItem.JobId);
  628. var user = _userManager.GetUserById(job.UserId);
  629. if (jobItem.IsMarkedForRemoval)
  630. {
  631. // Tell the device to remove it since it has been marked for removal
  632. _logger.Info("Adding ItemIdsToRemove {0} because IsMarkedForRemoval is set.", jobItem.ItemId);
  633. removeFromDevice = true;
  634. }
  635. else if (user == null)
  636. {
  637. // Tell the device to remove it since the user is gone now
  638. _logger.Info("Adding ItemIdsToRemove {0} because the user is no longer valid.", jobItem.ItemId);
  639. removeFromDevice = true;
  640. }
  641. else if (!IsLibraryItemAvailable(libraryItem))
  642. {
  643. // Tell the device to remove it since it's no longer available
  644. _logger.Info("Adding ItemIdsToRemove {0} because it is no longer available.", jobItem.ItemId);
  645. removeFromDevice = true;
  646. }
  647. else if (job.UnwatchedOnly)
  648. {
  649. if (libraryItem is Video && libraryItem.IsPlayed(user))
  650. {
  651. // Tell the device to remove it since it has been played
  652. _logger.Info("Adding ItemIdsToRemove {0} because it has been marked played.", jobItem.ItemId);
  653. removeFromDevice = true;
  654. }
  655. }
  656. else if (libraryItem != null && libraryItem.DateModified.Ticks != jobItem.ItemDateModifiedTicks && jobItem.ItemDateModifiedTicks > 0)
  657. {
  658. _logger.Info("Setting status to Queued for {0} because the media has been modified since the original sync.", jobItem.ItemId);
  659. jobItem.Status = SyncJobItemStatus.Queued;
  660. jobItem.Progress = 0;
  661. requiresSaving = true;
  662. }
  663. }
  664. else
  665. {
  666. // Content is no longer on the device
  667. if (jobItem.IsMarkedForRemoval)
  668. {
  669. jobItem.Status = SyncJobItemStatus.RemovedFromDevice;
  670. }
  671. else
  672. {
  673. _logger.Info("Setting status to Queued for {0} because it is no longer on the device.", jobItem.ItemId);
  674. jobItem.Status = SyncJobItemStatus.Queued;
  675. jobItem.Progress = 0;
  676. }
  677. requiresSaving = true;
  678. }
  679. if (removeFromDevice)
  680. {
  681. response.ItemIdsToRemove.Add(jobItem.ItemId);
  682. jobItem.IsMarkedForRemoval = true;
  683. requiresSaving = true;
  684. }
  685. if (requiresSaving)
  686. {
  687. await UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false);
  688. }
  689. }
  690. // Now check each item that's on the device
  691. foreach (var itemId in request.LocalItemIds)
  692. {
  693. // See if it's already marked for removal
  694. if (response.ItemIdsToRemove.Contains(itemId, StringComparer.OrdinalIgnoreCase))
  695. {
  696. continue;
  697. }
  698. // If there isn't a sync job for this item, mark it for removal
  699. if (!jobItemResult.Items.Any(i => string.Equals(itemId, i.ItemId, StringComparison.OrdinalIgnoreCase)))
  700. {
  701. response.ItemIdsToRemove.Add(itemId);
  702. }
  703. }
  704. response.ItemIdsToRemove = response.ItemIdsToRemove.Distinct(StringComparer.OrdinalIgnoreCase).ToList();
  705. var itemsOnDevice = request.LocalItemIds
  706. .Except(response.ItemIdsToRemove)
  707. .ToList();
  708. SetUserAccess(request, response, itemsOnDevice);
  709. return response;
  710. }
  711. private async Task<SyncDataResponse> SyncDataUsingSyncJobItemIds(SyncDataRequest request)
  712. {
  713. var jobItemResult = GetJobItems(new SyncJobItemQuery
  714. {
  715. TargetId = request.TargetId,
  716. Statuses = new[] { SyncJobItemStatus.Synced }
  717. });
  718. var response = new SyncDataResponse();
  719. foreach (var jobItem in jobItemResult.Items)
  720. {
  721. var requiresSaving = false;
  722. var removeFromDevice = false;
  723. if (request.SyncJobItemIds.Contains(jobItem.Id, StringComparer.OrdinalIgnoreCase))
  724. {
  725. var libraryItem = _libraryManager.GetItemById(jobItem.ItemId);
  726. var job = _repo.GetJob(jobItem.JobId);
  727. var user = _userManager.GetUserById(job.UserId);
  728. if (jobItem.IsMarkedForRemoval)
  729. {
  730. // Tell the device to remove it since it has been marked for removal
  731. _logger.Info("Adding ItemIdsToRemove {0} because IsMarkedForRemoval is set.", jobItem.Id);
  732. removeFromDevice = true;
  733. }
  734. else if (user == null)
  735. {
  736. // Tell the device to remove it since the user is gone now
  737. _logger.Info("Adding ItemIdsToRemove {0} because the user is no longer valid.", jobItem.Id);
  738. removeFromDevice = true;
  739. }
  740. else if (!IsLibraryItemAvailable(libraryItem))
  741. {
  742. // Tell the device to remove it since it's no longer available
  743. _logger.Info("Adding ItemIdsToRemove {0} because it is no longer available.", jobItem.Id);
  744. removeFromDevice = true;
  745. }
  746. else if (job.UnwatchedOnly)
  747. {
  748. if (libraryItem is Video && libraryItem.IsPlayed(user))
  749. {
  750. // Tell the device to remove it since it has been played
  751. _logger.Info("Adding ItemIdsToRemove {0} because it has been marked played.", jobItem.Id);
  752. removeFromDevice = true;
  753. }
  754. }
  755. else if (libraryItem != null && libraryItem.DateModified.Ticks != jobItem.ItemDateModifiedTicks && jobItem.ItemDateModifiedTicks > 0)
  756. {
  757. _logger.Info("Setting status to Queued for {0} because the media has been modified since the original sync.", jobItem.ItemId);
  758. jobItem.Status = SyncJobItemStatus.Queued;
  759. jobItem.Progress = 0;
  760. requiresSaving = true;
  761. }
  762. }
  763. else
  764. {
  765. // Content is no longer on the device
  766. if (jobItem.IsMarkedForRemoval)
  767. {
  768. jobItem.Status = SyncJobItemStatus.RemovedFromDevice;
  769. }
  770. else
  771. {
  772. _logger.Info("Setting status to Queued for {0} because it is no longer on the device.", jobItem.Id);
  773. jobItem.Status = SyncJobItemStatus.Queued;
  774. jobItem.Progress = 0;
  775. }
  776. requiresSaving = true;
  777. }
  778. if (removeFromDevice)
  779. {
  780. response.ItemIdsToRemove.Add(jobItem.Id);
  781. jobItem.IsMarkedForRemoval = true;
  782. requiresSaving = true;
  783. }
  784. if (requiresSaving)
  785. {
  786. await UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false);
  787. }
  788. }
  789. // Now check each item that's on the device
  790. foreach (var syncJobItemId in request.SyncJobItemIds)
  791. {
  792. // See if it's already marked for removal
  793. if (response.ItemIdsToRemove.Contains(syncJobItemId, StringComparer.OrdinalIgnoreCase))
  794. {
  795. continue;
  796. }
  797. // If there isn't a sync job for this item, mark it for removal
  798. if (!jobItemResult.Items.Any(i => string.Equals(syncJobItemId, i.Id, StringComparison.OrdinalIgnoreCase)))
  799. {
  800. response.ItemIdsToRemove.Add(syncJobItemId);
  801. }
  802. }
  803. response.ItemIdsToRemove = response.ItemIdsToRemove.Distinct(StringComparer.OrdinalIgnoreCase).ToList();
  804. return response;
  805. }
  806. private void SetUserAccess(SyncDataRequest request, SyncDataResponse response, List<string> itemIds)
  807. {
  808. var users = request.OfflineUserIds
  809. .Select(_userManager.GetUserById)
  810. .Where(i => i != null)
  811. .ToList();
  812. foreach (var itemId in itemIds)
  813. {
  814. var item = _libraryManager.GetItemById(itemId);
  815. if (item != null)
  816. {
  817. response.ItemUserAccess[itemId] = users
  818. .Where(i => IsUserVisible(item, i))
  819. .Select(i => i.Id.ToString("N"))
  820. .OrderBy(i => i)
  821. .ToList();
  822. }
  823. }
  824. }
  825. private bool IsUserVisible(BaseItem item, User user)
  826. {
  827. return item.IsVisibleStandalone(user);
  828. }
  829. private bool IsLibraryItemAvailable(BaseItem item)
  830. {
  831. if (item == null)
  832. {
  833. return false;
  834. }
  835. return true;
  836. }
  837. public async Task ReEnableJobItem(string id)
  838. {
  839. var jobItem = _repo.GetJobItem(id);
  840. if (jobItem.Status != SyncJobItemStatus.Failed && jobItem.Status != SyncJobItemStatus.Cancelled)
  841. {
  842. throw new ArgumentException("Operation is not valid for this job item");
  843. }
  844. jobItem.Status = SyncJobItemStatus.Queued;
  845. jobItem.Progress = 0;
  846. jobItem.IsMarkedForRemoval = false;
  847. await UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false);
  848. var processor = GetSyncJobProcessor();
  849. await processor.UpdateJobStatus(jobItem.JobId).ConfigureAwait(false);
  850. }
  851. public async Task CancelItems(string targetId, IEnumerable<string> itemIds)
  852. {
  853. foreach (var item in itemIds)
  854. {
  855. var syncJobItemResult = GetJobItems(new SyncJobItemQuery
  856. {
  857. AddMetadata = false,
  858. ItemId = item,
  859. TargetId = targetId,
  860. Statuses = new[] { SyncJobItemStatus.Queued, SyncJobItemStatus.ReadyToTransfer, SyncJobItemStatus.Converting, SyncJobItemStatus.Synced, SyncJobItemStatus.Failed }
  861. });
  862. foreach (var jobItem in syncJobItemResult.Items)
  863. {
  864. await CancelJobItem(jobItem.Id).ConfigureAwait(false);
  865. }
  866. }
  867. }
  868. public async Task CancelJobItem(string id)
  869. {
  870. var jobItem = _repo.GetJobItem(id);
  871. if (jobItem.Status != SyncJobItemStatus.Queued && jobItem.Status != SyncJobItemStatus.ReadyToTransfer && jobItem.Status != SyncJobItemStatus.Converting && jobItem.Status != SyncJobItemStatus.Failed && jobItem.Status != SyncJobItemStatus.Synced && jobItem.Status != SyncJobItemStatus.Transferring)
  872. {
  873. throw new ArgumentException("Operation is not valid for this job item");
  874. }
  875. if (jobItem.Status != SyncJobItemStatus.Synced)
  876. {
  877. jobItem.Status = SyncJobItemStatus.Cancelled;
  878. }
  879. jobItem.Progress = 0;
  880. jobItem.IsMarkedForRemoval = true;
  881. await UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false);
  882. var processor = GetSyncJobProcessor();
  883. await processor.UpdateJobStatus(jobItem.JobId).ConfigureAwait(false);
  884. var path = processor.GetTemporaryPath(jobItem);
  885. try
  886. {
  887. _fileSystem.DeleteDirectory(path, true);
  888. }
  889. catch (DirectoryNotFoundException)
  890. {
  891. }
  892. catch (Exception ex)
  893. {
  894. _logger.ErrorException("Error deleting directory {0}", ex, path);
  895. }
  896. //var jobItemsResult = GetJobItems(new SyncJobItemQuery
  897. //{
  898. // AddMetadata = false,
  899. // JobId = jobItem.JobId,
  900. // Limit = 0,
  901. // Statuses = new[] { SyncJobItemStatus.Converting, SyncJobItemStatus.Failed, SyncJobItemStatus.Queued, SyncJobItemStatus.ReadyToTransfer, SyncJobItemStatus.Synced, SyncJobItemStatus.Transferring }
  902. //});
  903. //if (jobItemsResult.TotalRecordCount == 0)
  904. //{
  905. // await CancelJob(jobItem.JobId).ConfigureAwait(false);
  906. //}
  907. }
  908. public Task MarkJobItemForRemoval(string id)
  909. {
  910. return CancelJobItem(id);
  911. }
  912. public async Task UnmarkJobItemForRemoval(string id)
  913. {
  914. var jobItem = _repo.GetJobItem(id);
  915. if (jobItem.Status != SyncJobItemStatus.Synced)
  916. {
  917. throw new ArgumentException("Operation is not valid for this job item");
  918. }
  919. jobItem.IsMarkedForRemoval = false;
  920. await UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false);
  921. var processor = GetSyncJobProcessor();
  922. await processor.UpdateJobStatus(jobItem.JobId).ConfigureAwait(false);
  923. }
  924. public async Task ReportSyncJobItemTransferBeginning(string id)
  925. {
  926. var jobItem = _repo.GetJobItem(id);
  927. jobItem.Status = SyncJobItemStatus.Transferring;
  928. await UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false);
  929. var processor = GetSyncJobProcessor();
  930. await processor.UpdateJobStatus(jobItem.JobId).ConfigureAwait(false);
  931. }
  932. public async Task ReportSyncJobItemTransferFailed(string id)
  933. {
  934. var jobItem = _repo.GetJobItem(id);
  935. jobItem.Status = SyncJobItemStatus.ReadyToTransfer;
  936. await UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false);
  937. var processor = GetSyncJobProcessor();
  938. await processor.UpdateJobStatus(jobItem.JobId).ConfigureAwait(false);
  939. }
  940. public Dictionary<string, SyncedItemProgress> GetSyncedItemProgresses(SyncJobItemQuery query)
  941. {
  942. return _repo.GetSyncedItemProgresses(query);
  943. }
  944. public SyncJobOptions GetAudioOptions(SyncJobItem jobItem, SyncJob job)
  945. {
  946. var options = GetSyncJobOptions(jobItem.TargetId, null, null);
  947. if (job.Bitrate.HasValue)
  948. {
  949. options.DeviceProfile.MaxStaticBitrate = job.Bitrate.Value;
  950. }
  951. return options;
  952. }
  953. public ISyncProvider GetSyncProvider(SyncJobItem jobItem)
  954. {
  955. foreach (var provider in _providers)
  956. {
  957. foreach (var target in GetSyncTargets(provider))
  958. {
  959. if (string.Equals(target.Id, jobItem.TargetId, StringComparison.OrdinalIgnoreCase))
  960. {
  961. return provider;
  962. }
  963. }
  964. }
  965. return null;
  966. }
  967. public SyncJobOptions GetVideoOptions(SyncJobItem jobItem, SyncJob job)
  968. {
  969. var options = GetSyncJobOptions(jobItem.TargetId, job.Profile, job.Quality);
  970. if (job.Bitrate.HasValue)
  971. {
  972. options.DeviceProfile.MaxStaticBitrate = job.Bitrate.Value;
  973. }
  974. return options;
  975. }
  976. private SyncJobOptions GetSyncJobOptions(string targetId, string profile, string quality)
  977. {
  978. foreach (var provider in _providers)
  979. {
  980. foreach (var target in GetSyncTargets(provider))
  981. {
  982. if (string.Equals(target.Id, targetId, StringComparison.OrdinalIgnoreCase))
  983. {
  984. return GetSyncJobOptions(provider, target, profile, quality);
  985. }
  986. }
  987. }
  988. return GetDefaultSyncJobOptions(profile, quality);
  989. }
  990. private SyncJobOptions GetSyncJobOptions(ISyncProvider provider, SyncTarget target, string profile, string quality)
  991. {
  992. var hasProfile = provider as IHasSyncQuality;
  993. if (hasProfile != null)
  994. {
  995. return hasProfile.GetSyncJobOptions(target, profile, quality);
  996. }
  997. return GetDefaultSyncJobOptions(profile, quality);
  998. }
  999. private SyncJobOptions GetDefaultSyncJobOptions(string profile, string quality)
  1000. {
  1001. var supportsAc3 = string.Equals(profile, "general", StringComparison.OrdinalIgnoreCase);
  1002. var deviceProfile = new CloudSyncProfile(supportsAc3, false);
  1003. deviceProfile.MaxStaticBitrate = SyncHelper.AdjustBitrate(deviceProfile.MaxStaticBitrate, quality);
  1004. return new SyncJobOptions
  1005. {
  1006. DeviceProfile = deviceProfile,
  1007. IsConverting = IsConverting(profile, quality)
  1008. };
  1009. }
  1010. private bool IsConverting(string profile, string quality)
  1011. {
  1012. return !string.Equals(profile, "original", StringComparison.OrdinalIgnoreCase);
  1013. }
  1014. public IEnumerable<SyncQualityOption> GetQualityOptions(string targetId)
  1015. {
  1016. return GetQualityOptions(targetId, null);
  1017. }
  1018. public IEnumerable<SyncQualityOption> GetQualityOptions(string targetId, User user)
  1019. {
  1020. foreach (var provider in _providers)
  1021. {
  1022. foreach (var target in GetSyncTargets(provider))
  1023. {
  1024. if (string.Equals(target.Id, targetId, StringComparison.OrdinalIgnoreCase))
  1025. {
  1026. return GetQualityOptions(provider, target, user);
  1027. }
  1028. }
  1029. }
  1030. return new List<SyncQualityOption>();
  1031. }
  1032. private IEnumerable<SyncQualityOption> GetQualityOptions(ISyncProvider provider, SyncTarget target, User user)
  1033. {
  1034. var hasQuality = provider as IHasSyncQuality;
  1035. if (hasQuality != null)
  1036. {
  1037. var options = hasQuality.GetQualityOptions(target);
  1038. if (user != null && !user.Policy.EnableSyncTranscoding)
  1039. {
  1040. options = options.Where(i => i.IsOriginalQuality);
  1041. }
  1042. return options;
  1043. }
  1044. // Default options for providers that don't override
  1045. return new List<SyncQualityOption>
  1046. {
  1047. new SyncQualityOption
  1048. {
  1049. Name = "High",
  1050. Id = "high",
  1051. IsDefault = true
  1052. },
  1053. new SyncQualityOption
  1054. {
  1055. Name = "Medium",
  1056. Id = "medium"
  1057. },
  1058. new SyncQualityOption
  1059. {
  1060. Name = "Low",
  1061. Id = "low"
  1062. },
  1063. new SyncQualityOption
  1064. {
  1065. Name = "Custom",
  1066. Id = "custom"
  1067. }
  1068. };
  1069. }
  1070. public IEnumerable<SyncProfileOption> GetProfileOptions(string targetId, User user)
  1071. {
  1072. foreach (var provider in _providers)
  1073. {
  1074. foreach (var target in GetSyncTargets(provider))
  1075. {
  1076. if (string.Equals(target.Id, targetId, StringComparison.OrdinalIgnoreCase))
  1077. {
  1078. return GetProfileOptions(provider, target, user);
  1079. }
  1080. }
  1081. }
  1082. return new List<SyncProfileOption>();
  1083. }
  1084. public IEnumerable<SyncProfileOption> GetProfileOptions(string targetId)
  1085. {
  1086. return GetProfileOptions(targetId, null);
  1087. }
  1088. private IEnumerable<SyncProfileOption> GetProfileOptions(ISyncProvider provider, SyncTarget target, User user)
  1089. {
  1090. var hasQuality = provider as IHasSyncQuality;
  1091. if (hasQuality != null)
  1092. {
  1093. return hasQuality.GetProfileOptions(target);
  1094. }
  1095. var list = new List<SyncProfileOption>();
  1096. list.Add(new SyncProfileOption
  1097. {
  1098. Name = "Original",
  1099. Id = "Original",
  1100. Description = "Syncs original files as-is.",
  1101. EnableQualityOptions = false
  1102. });
  1103. if (user == null || user.Policy.EnableSyncTranscoding)
  1104. {
  1105. list.Add(new SyncProfileOption
  1106. {
  1107. Name = "Baseline",
  1108. Id = "baseline",
  1109. Description = "Designed for compatibility with all devices, including web browsers. Targets H264/AAC video and MP3 audio."
  1110. });
  1111. list.Add(new SyncProfileOption
  1112. {
  1113. Name = "General",
  1114. Id = "general",
  1115. Description = "Designed for compatibility with Chromecast, Roku, Smart TV's, and other similar devices. Targets H264/AAC/AC3 video and MP3 audio.",
  1116. IsDefault = true
  1117. });
  1118. }
  1119. return list;
  1120. }
  1121. protected internal void OnConversionComplete(SyncJobItem item)
  1122. {
  1123. var syncProvider = GetSyncProvider(item);
  1124. if (syncProvider is AppSyncProvider)
  1125. {
  1126. return;
  1127. }
  1128. _taskManager.QueueIfNotRunning<ServerSyncScheduledTask>();
  1129. }
  1130. }
  1131. }