DtoBuilder.cs 41 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243
  1. using MediaBrowser.Common.Extensions;
  2. using MediaBrowser.Controller.Entities;
  3. using MediaBrowser.Controller.Entities.Audio;
  4. using MediaBrowser.Controller.Entities.Movies;
  5. using MediaBrowser.Controller.Entities.TV;
  6. using MediaBrowser.Controller.Library;
  7. using MediaBrowser.Controller.Persistence;
  8. using MediaBrowser.Model.Drawing;
  9. using MediaBrowser.Model.Dto;
  10. using MediaBrowser.Model.Entities;
  11. using MediaBrowser.Model.Logging;
  12. using MediaBrowser.Model.Querying;
  13. using System;
  14. using System.Collections.Generic;
  15. using System.IO;
  16. using System.Linq;
  17. using System.Threading.Tasks;
  18. using MoreLinq;
  19. namespace MediaBrowser.Controller.Dto
  20. {
  21. /// <summary>
  22. /// Generates DTO's from domain entities
  23. /// </summary>
  24. public class DtoBuilder
  25. {
  26. /// <summary>
  27. /// The index folder delimeter
  28. /// </summary>
  29. const string IndexFolderDelimeter = "-index-";
  30. private readonly ILogger _logger;
  31. private readonly ILibraryManager _libraryManager;
  32. private readonly IUserDataRepository _userDataRepository;
  33. private readonly IItemRepository _itemRepo;
  34. public DtoBuilder(ILogger logger, ILibraryManager libraryManager, IUserDataRepository userDataRepository, IItemRepository itemRepo)
  35. {
  36. _logger = logger;
  37. _libraryManager = libraryManager;
  38. _userDataRepository = userDataRepository;
  39. _itemRepo = itemRepo;
  40. }
  41. /// <summary>
  42. /// Converts a BaseItem to a DTOBaseItem
  43. /// </summary>
  44. /// <param name="item">The item.</param>
  45. /// <param name="fields">The fields.</param>
  46. /// <param name="user">The user.</param>
  47. /// <param name="owner">The owner.</param>
  48. /// <returns>Task{DtoBaseItem}.</returns>
  49. /// <exception cref="System.ArgumentNullException">item</exception>
  50. public async Task<BaseItemDto> GetBaseItemDto(BaseItem item, List<ItemFields> fields, User user = null, BaseItem owner = null)
  51. {
  52. if (item == null)
  53. {
  54. throw new ArgumentNullException("item");
  55. }
  56. if (fields == null)
  57. {
  58. throw new ArgumentNullException("fields");
  59. }
  60. var dto = new BaseItemDto();
  61. var tasks = new List<Task>();
  62. if (fields.Contains(ItemFields.Studios))
  63. {
  64. tasks.Add(AttachStudios(dto, item));
  65. }
  66. if (fields.Contains(ItemFields.People))
  67. {
  68. tasks.Add(AttachPeople(dto, item));
  69. }
  70. if (fields.Contains(ItemFields.PrimaryImageAspectRatio))
  71. {
  72. try
  73. {
  74. await AttachPrimaryImageAspectRatio(dto, item, _logger).ConfigureAwait(false);
  75. }
  76. catch (Exception ex)
  77. {
  78. // Have to use a catch-all unfortunately because some .net image methods throw plain Exceptions
  79. _logger.ErrorException("Error generating PrimaryImageAspectRatio for {0}", ex, item.Name);
  80. }
  81. }
  82. if (fields.Contains(ItemFields.DisplayPreferencesId))
  83. {
  84. dto.DisplayPreferencesId = item.DisplayPreferencesId.ToString("N");
  85. }
  86. if (user != null)
  87. {
  88. AttachUserSpecificInfo(dto, item, user, fields);
  89. }
  90. AttachBasicFields(dto, item, owner, fields);
  91. if (fields.Contains(ItemFields.SoundtrackIds))
  92. {
  93. var series = item as Series;
  94. if (series != null)
  95. {
  96. AttachSoundtrackIds(dto, series, user);
  97. }
  98. var movie = item as Movie;
  99. if (movie != null)
  100. {
  101. AttachSoundtrackIds(dto, movie, user);
  102. }
  103. var album = item as MusicAlbum;
  104. if (album != null)
  105. {
  106. AttachSoundtrackIds(dto, album, user);
  107. }
  108. var game = item as Game;
  109. if (game != null)
  110. {
  111. AttachSoundtrackIds(dto, game, user);
  112. }
  113. }
  114. // Make sure all the tasks we kicked off have completed.
  115. if (tasks.Count > 0)
  116. {
  117. await Task.WhenAll(tasks).ConfigureAwait(false);
  118. }
  119. return dto;
  120. }
  121. private void AttachSoundtrackIds(BaseItemDto dto, Movie item, User user)
  122. {
  123. var tmdb = item.GetProviderId(MetadataProviders.Tmdb);
  124. if (string.IsNullOrEmpty(tmdb))
  125. {
  126. return;
  127. }
  128. var recursiveChildren = user == null
  129. ? _libraryManager.RootFolder.RecursiveChildren
  130. : user.RootFolder.GetRecursiveChildren(user);
  131. dto.SoundtrackIds = recursiveChildren
  132. .Where(i =>
  133. {
  134. if (!string.IsNullOrEmpty(tmdb) &&
  135. string.Equals(tmdb, i.GetProviderId(MetadataProviders.Tmdb), StringComparison.OrdinalIgnoreCase) &&
  136. i is MusicAlbum)
  137. {
  138. return true;
  139. }
  140. return false;
  141. })
  142. .Select(GetClientItemId)
  143. .ToArray();
  144. }
  145. private void AttachSoundtrackIds(BaseItemDto dto, Series item, User user)
  146. {
  147. var tvdb = item.GetProviderId(MetadataProviders.Tvdb);
  148. if (string.IsNullOrEmpty(tvdb))
  149. {
  150. return;
  151. }
  152. var recursiveChildren = user == null
  153. ? _libraryManager.RootFolder.RecursiveChildren
  154. : user.RootFolder.GetRecursiveChildren(user);
  155. dto.SoundtrackIds = recursiveChildren
  156. .Where(i =>
  157. {
  158. if (!string.IsNullOrEmpty(tvdb) &&
  159. string.Equals(tvdb, i.GetProviderId(MetadataProviders.Tvdb), StringComparison.OrdinalIgnoreCase) &&
  160. i is MusicAlbum)
  161. {
  162. return true;
  163. }
  164. return false;
  165. })
  166. .Select(GetClientItemId)
  167. .ToArray();
  168. }
  169. private void AttachSoundtrackIds(BaseItemDto dto, Game item, User user)
  170. {
  171. var gamesdb = item.GetProviderId(MetadataProviders.Gamesdb);
  172. if (string.IsNullOrEmpty(gamesdb))
  173. {
  174. return;
  175. }
  176. var recursiveChildren = user == null
  177. ? _libraryManager.RootFolder.RecursiveChildren
  178. : user.RootFolder.GetRecursiveChildren(user);
  179. dto.SoundtrackIds = recursiveChildren
  180. .Where(i =>
  181. {
  182. if (!string.IsNullOrEmpty(gamesdb) &&
  183. string.Equals(gamesdb, i.GetProviderId(MetadataProviders.Gamesdb), StringComparison.OrdinalIgnoreCase) &&
  184. i is MusicAlbum)
  185. {
  186. return true;
  187. }
  188. return false;
  189. })
  190. .Select(GetClientItemId)
  191. .ToArray();
  192. }
  193. private void AttachSoundtrackIds(BaseItemDto dto, MusicAlbum item, User user)
  194. {
  195. var tmdb = item.GetProviderId(MetadataProviders.Tmdb);
  196. var tvdb = item.GetProviderId(MetadataProviders.Tvdb);
  197. var gamesdb = item.GetProviderId(MetadataProviders.Gamesdb);
  198. if (string.IsNullOrEmpty(tmdb) && string.IsNullOrEmpty(tvdb) && string.IsNullOrEmpty(gamesdb))
  199. {
  200. return;
  201. }
  202. var recursiveChildren = user == null
  203. ? _libraryManager.RootFolder.RecursiveChildren
  204. : user.RootFolder.GetRecursiveChildren(user);
  205. dto.SoundtrackIds = recursiveChildren
  206. .Where(i =>
  207. {
  208. if (!string.IsNullOrEmpty(tmdb) &&
  209. string.Equals(tmdb, i.GetProviderId(MetadataProviders.Tmdb), StringComparison.OrdinalIgnoreCase) &&
  210. i is Movie)
  211. {
  212. return true;
  213. }
  214. if (!string.IsNullOrEmpty(tvdb) &&
  215. string.Equals(tvdb, i.GetProviderId(MetadataProviders.Tvdb), StringComparison.OrdinalIgnoreCase) &&
  216. i is Series)
  217. {
  218. return true;
  219. }
  220. if (!string.IsNullOrEmpty(gamesdb) &&
  221. string.Equals(gamesdb, i.GetProviderId(MetadataProviders.Gamesdb), StringComparison.OrdinalIgnoreCase) &&
  222. i is Game)
  223. {
  224. return true;
  225. }
  226. return false;
  227. })
  228. .Select(GetClientItemId)
  229. .ToArray();
  230. }
  231. /// <summary>
  232. /// Attaches the user specific info.
  233. /// </summary>
  234. /// <param name="dto">The dto.</param>
  235. /// <param name="item">The item.</param>
  236. /// <param name="user">The user.</param>
  237. /// <param name="fields">The fields.</param>
  238. private void AttachUserSpecificInfo(BaseItemDto dto, BaseItem item, User user, List<ItemFields> fields)
  239. {
  240. if (item.IsFolder)
  241. {
  242. var hasItemCounts = fields.Contains(ItemFields.ItemCounts);
  243. if (hasItemCounts || fields.Contains(ItemFields.CumulativeRunTimeTicks))
  244. {
  245. var folder = (Folder)item;
  246. if (hasItemCounts)
  247. {
  248. dto.ChildCount = folder.GetChildren(user, true).Count();
  249. }
  250. SetSpecialCounts(folder, user, dto, _userDataRepository);
  251. }
  252. }
  253. var userData = _userDataRepository.GetUserData(user.Id, item.GetUserDataKey());
  254. dto.UserData = GetUserItemDataDto(userData);
  255. if (item.IsFolder)
  256. {
  257. dto.UserData.Played = dto.PlayedPercentage.HasValue && dto.PlayedPercentage.Value >= 100;
  258. }
  259. }
  260. /// <summary>
  261. /// Attaches the primary image aspect ratio.
  262. /// </summary>
  263. /// <param name="dto">The dto.</param>
  264. /// <param name="item">The item.</param>
  265. /// <param name="logger">The _logger.</param>
  266. /// <returns>Task.</returns>
  267. internal static async Task AttachPrimaryImageAspectRatio(IItemDto dto, BaseItem item, ILogger logger)
  268. {
  269. var path = item.PrimaryImagePath;
  270. if (string.IsNullOrEmpty(path))
  271. {
  272. return;
  273. }
  274. var metaFileEntry = item.ResolveArgs.GetMetaFileByPath(path);
  275. // See if we can avoid a file system lookup by looking for the file in ResolveArgs
  276. var dateModified = metaFileEntry == null ? File.GetLastWriteTimeUtc(path) : metaFileEntry.LastWriteTimeUtc;
  277. ImageSize size;
  278. try
  279. {
  280. size = await Kernel.Instance.ImageManager.GetImageSize(path, dateModified).ConfigureAwait(false);
  281. }
  282. catch (FileNotFoundException)
  283. {
  284. logger.Error("Image file does not exist: {0}", path);
  285. return;
  286. }
  287. catch (Exception ex)
  288. {
  289. logger.ErrorException("Failed to determine primary image aspect ratio for {0}", ex, path);
  290. return;
  291. }
  292. dto.OriginalPrimaryImageAspectRatio = size.Width / size.Height;
  293. var supportedEnhancers = Kernel.Instance.ImageManager.ImageEnhancers.Where(i =>
  294. {
  295. try
  296. {
  297. return i.Supports(item, ImageType.Primary);
  298. }
  299. catch (Exception ex)
  300. {
  301. logger.ErrorException("Error in image enhancer: {0}", ex, i.GetType().Name);
  302. return false;
  303. }
  304. }).ToList();
  305. foreach (var enhancer in supportedEnhancers)
  306. {
  307. try
  308. {
  309. size = enhancer.GetEnhancedImageSize(item, ImageType.Primary, 0, size);
  310. }
  311. catch (Exception ex)
  312. {
  313. logger.ErrorException("Error in image enhancer: {0}", ex, enhancer.GetType().Name);
  314. }
  315. }
  316. dto.PrimaryImageAspectRatio = size.Width / size.Height;
  317. }
  318. /// <summary>
  319. /// Sets simple property values on a DTOBaseItem
  320. /// </summary>
  321. /// <param name="dto">The dto.</param>
  322. /// <param name="item">The item.</param>
  323. /// <param name="owner">The owner.</param>
  324. /// <param name="fields">The fields.</param>
  325. private void AttachBasicFields(BaseItemDto dto, BaseItem item, BaseItem owner, List<ItemFields> fields)
  326. {
  327. if (fields.Contains(ItemFields.DateCreated))
  328. {
  329. dto.DateCreated = item.DateCreated;
  330. }
  331. if (fields.Contains(ItemFields.OriginalRunTimeTicks))
  332. {
  333. dto.OriginalRunTimeTicks = item.OriginalRunTimeTicks;
  334. }
  335. dto.DisplayMediaType = item.DisplayMediaType;
  336. if (fields.Contains(ItemFields.MetadataSettings))
  337. {
  338. dto.LockedFields = item.LockedFields;
  339. dto.EnableInternetProviders = !item.DontFetchMeta;
  340. }
  341. if (fields.Contains(ItemFields.Budget))
  342. {
  343. dto.Budget = item.Budget;
  344. }
  345. if (fields.Contains(ItemFields.Revenue))
  346. {
  347. dto.Revenue = item.Revenue;
  348. }
  349. dto.EndDate = item.EndDate;
  350. if (fields.Contains(ItemFields.HomePageUrl))
  351. {
  352. dto.HomePageUrl = item.HomePageUrl;
  353. }
  354. if (fields.Contains(ItemFields.Tags))
  355. {
  356. dto.Tags = item.Tags;
  357. }
  358. if (fields.Contains(ItemFields.ProductionLocations))
  359. {
  360. dto.ProductionLocations = item.ProductionLocations;
  361. }
  362. dto.AspectRatio = item.AspectRatio;
  363. dto.BackdropImageTags = GetBackdropImageTags(item);
  364. dto.ScreenshotImageTags = GetScreenshotImageTags(item);
  365. if (fields.Contains(ItemFields.Genres))
  366. {
  367. dto.Genres = item.Genres;
  368. }
  369. dto.ImageTags = new Dictionary<ImageType, Guid>();
  370. foreach (var image in item.Images)
  371. {
  372. var type = image.Key;
  373. var tag = GetImageCacheTag(item, type, image.Value);
  374. if (tag.HasValue)
  375. {
  376. dto.ImageTags[type] = tag.Value;
  377. }
  378. }
  379. dto.Id = GetClientItemId(item);
  380. dto.IndexNumber = item.IndexNumber;
  381. dto.IsFolder = item.IsFolder;
  382. dto.Language = item.Language;
  383. dto.MediaType = item.MediaType;
  384. dto.LocationType = item.LocationType;
  385. dto.CriticRating = item.CriticRating;
  386. if (fields.Contains(ItemFields.CriticRatingSummary))
  387. {
  388. dto.CriticRatingSummary = item.CriticRatingSummary;
  389. }
  390. var localTrailerCount = item.LocalTrailerIds.Count;
  391. if (localTrailerCount > 0)
  392. {
  393. dto.LocalTrailerCount = localTrailerCount;
  394. }
  395. dto.Name = item.Name;
  396. dto.OfficialRating = item.OfficialRating;
  397. var hasOverview = fields.Contains(ItemFields.Overview);
  398. var hasHtmlOverview = fields.Contains(ItemFields.OverviewHtml);
  399. if (hasOverview || hasHtmlOverview)
  400. {
  401. var strippedOverview = string.IsNullOrEmpty(item.Overview) ? item.Overview : item.Overview.StripHtml();
  402. if (hasOverview)
  403. {
  404. dto.Overview = strippedOverview;
  405. }
  406. // Only supply the html version if there was actually html content
  407. if (hasHtmlOverview)
  408. {
  409. dto.OverviewHtml = item.Overview;
  410. }
  411. }
  412. // If there are no backdrops, indicate what parent has them in case the Ui wants to allow inheritance
  413. if (dto.BackdropImageTags.Count == 0)
  414. {
  415. var parentWithBackdrop = GetParentBackdropItem(item, owner);
  416. if (parentWithBackdrop != null)
  417. {
  418. dto.ParentBackdropItemId = GetClientItemId(parentWithBackdrop);
  419. dto.ParentBackdropImageTags = GetBackdropImageTags(parentWithBackdrop);
  420. }
  421. }
  422. if (item.Parent != null && fields.Contains(ItemFields.ParentId))
  423. {
  424. dto.ParentId = GetClientItemId(item.Parent);
  425. }
  426. dto.ParentIndexNumber = item.ParentIndexNumber;
  427. // If there is no logo, indicate what parent has one in case the Ui wants to allow inheritance
  428. if (!dto.HasLogo)
  429. {
  430. var parentWithLogo = GetParentImageItem(item, ImageType.Logo, owner);
  431. if (parentWithLogo != null)
  432. {
  433. dto.ParentLogoItemId = GetClientItemId(parentWithLogo);
  434. dto.ParentLogoImageTag = GetImageCacheTag(parentWithLogo, ImageType.Logo, parentWithLogo.GetImage(ImageType.Logo));
  435. }
  436. }
  437. // If there is no art, indicate what parent has one in case the Ui wants to allow inheritance
  438. if (!dto.HasArtImage)
  439. {
  440. var parentWithImage = GetParentImageItem(item, ImageType.Art, owner);
  441. if (parentWithImage != null)
  442. {
  443. dto.ParentArtItemId = GetClientItemId(parentWithImage);
  444. dto.ParentArtImageTag = GetImageCacheTag(parentWithImage, ImageType.Art, parentWithImage.GetImage(ImageType.Art));
  445. }
  446. }
  447. if (fields.Contains(ItemFields.Path))
  448. {
  449. dto.Path = item.Path;
  450. }
  451. dto.PremiereDate = item.PremiereDate;
  452. dto.ProductionYear = item.ProductionYear;
  453. if (fields.Contains(ItemFields.ProviderIds))
  454. {
  455. dto.ProviderIds = item.ProviderIds;
  456. }
  457. dto.RunTimeTicks = item.RunTimeTicks;
  458. if (fields.Contains(ItemFields.SortName))
  459. {
  460. dto.SortName = item.SortName;
  461. }
  462. if (fields.Contains(ItemFields.CustomRating))
  463. {
  464. dto.CustomRating = item.CustomRating;
  465. }
  466. if (fields.Contains(ItemFields.Taglines))
  467. {
  468. dto.Taglines = item.Taglines;
  469. }
  470. if (fields.Contains(ItemFields.RemoteTrailers))
  471. {
  472. dto.RemoteTrailers = item.RemoteTrailers;
  473. }
  474. dto.Type = item.GetType().Name;
  475. dto.CommunityRating = item.CommunityRating;
  476. if (item.IsFolder)
  477. {
  478. var folder = (Folder)item;
  479. if (fields.Contains(ItemFields.IndexOptions))
  480. {
  481. dto.IndexOptions = folder.IndexByOptionStrings.ToArray();
  482. }
  483. }
  484. // Add audio info
  485. var audio = item as Audio;
  486. if (audio != null)
  487. {
  488. dto.Album = audio.Album;
  489. dto.AlbumArtist = audio.AlbumArtist;
  490. dto.Artists = new[] { audio.Artist };
  491. var albumParent = audio.FindParent<MusicAlbum>();
  492. if (albumParent != null)
  493. {
  494. dto.AlbumId = GetClientItemId(albumParent);
  495. var imagePath = albumParent.PrimaryImagePath;
  496. if (!string.IsNullOrEmpty(imagePath))
  497. {
  498. dto.AlbumPrimaryImageTag = GetImageCacheTag(albumParent, ImageType.Primary, imagePath);
  499. }
  500. }
  501. }
  502. var album = item as MusicAlbum;
  503. if (album != null)
  504. {
  505. var songs = album.RecursiveChildren.OfType<Audio>().ToList();
  506. dto.AlbumArtist = songs.Select(i => i.AlbumArtist).FirstOrDefault(i => !string.IsNullOrEmpty(i));
  507. dto.Artists =
  508. songs.Select(i => i.Artist ?? string.Empty)
  509. .Where(i => !string.IsNullOrEmpty(i))
  510. .Distinct(StringComparer.OrdinalIgnoreCase)
  511. .ToArray();
  512. }
  513. // Add video info
  514. var video = item as Video;
  515. if (video != null)
  516. {
  517. dto.VideoType = video.VideoType;
  518. dto.Video3DFormat = video.Video3DFormat;
  519. dto.IsoType = video.IsoType;
  520. dto.PartCount = video.AdditionalPartIds.Count + 1;
  521. if (fields.Contains(ItemFields.Chapters))
  522. {
  523. dto.Chapters = _itemRepo.GetChapters(video.Id).Select(c => GetChapterInfoDto(c, item)).ToList();
  524. }
  525. }
  526. if (fields.Contains(ItemFields.MediaStreams))
  527. {
  528. // Add VideoInfo
  529. var iHasMediaStreams = item as IHasMediaStreams;
  530. if (iHasMediaStreams != null)
  531. {
  532. dto.MediaStreams = iHasMediaStreams.MediaStreams;
  533. }
  534. }
  535. // Add MovieInfo
  536. var movie = item as Movie;
  537. if (movie != null)
  538. {
  539. var specialFeatureCount = movie.SpecialFeatureIds.Count;
  540. if (specialFeatureCount > 0)
  541. {
  542. dto.SpecialFeatureCount = specialFeatureCount;
  543. }
  544. }
  545. // Add EpisodeInfo
  546. var episode = item as Episode;
  547. if (episode != null)
  548. {
  549. dto.IndexNumberEnd = episode.IndexNumberEnd;
  550. }
  551. // Add SeriesInfo
  552. var series = item as Series;
  553. if (series != null)
  554. {
  555. dto.AirDays = series.AirDays;
  556. dto.AirTime = series.AirTime;
  557. dto.Status = series.Status;
  558. }
  559. if (episode != null)
  560. {
  561. series = item.FindParent<Series>();
  562. dto.SeriesId = GetClientItemId(series);
  563. dto.SeriesName = series.Name;
  564. }
  565. // Add SeasonInfo
  566. var season = item as Season;
  567. if (season != null)
  568. {
  569. series = item.FindParent<Series>();
  570. dto.SeriesId = GetClientItemId(series);
  571. dto.SeriesName = series.Name;
  572. }
  573. var game = item as Game;
  574. if (game != null)
  575. {
  576. SetGameProperties(dto, game);
  577. }
  578. var musicVideo = item as MusicVideo;
  579. if (musicVideo != null)
  580. {
  581. SetMusicVideoProperties(dto, musicVideo);
  582. }
  583. }
  584. private void SetMusicVideoProperties(BaseItemDto dto, MusicVideo item)
  585. {
  586. if (!string.IsNullOrEmpty(item.Album))
  587. {
  588. var parentAlbum = _libraryManager.RootFolder
  589. .RecursiveChildren
  590. .OfType<MusicAlbum>()
  591. .FirstOrDefault(i => string.Equals(i.Name, item.Album, StringComparison.OrdinalIgnoreCase));
  592. if (parentAlbum != null)
  593. {
  594. dto.AlbumId = GetClientItemId(parentAlbum);
  595. }
  596. }
  597. dto.Album = item.Album;
  598. dto.Artists = string.IsNullOrEmpty(item.Artist) ? new string[] { } : new[] { item.Artist };
  599. }
  600. private void SetGameProperties(BaseItemDto dto, Game item)
  601. {
  602. dto.Players = item.PlayersSupported;
  603. dto.GameSystem = item.GameSystem;
  604. }
  605. /// <summary>
  606. /// Since it can be slow to make all of these calculations independently, this method will provide a way to do them all at once
  607. /// </summary>
  608. /// <param name="folder">The folder.</param>
  609. /// <param name="user">The user.</param>
  610. /// <param name="dto">The dto.</param>
  611. /// <param name="userDataRepository">The user data repository.</param>
  612. /// <returns>Task.</returns>
  613. private static void SetSpecialCounts(Folder folder, User user, BaseItemDto dto, IUserDataRepository userDataRepository)
  614. {
  615. var rcentlyAddedItemCount = 0;
  616. var recursiveItemCount = 0;
  617. var unplayed = 0;
  618. long runtime = 0;
  619. double totalPercentPlayed = 0;
  620. // Loop through each recursive child
  621. foreach (var child in folder.GetRecursiveChildren(user, true).Where(i => !i.IsFolder).ToList())
  622. {
  623. var userdata = userDataRepository.GetUserData(user.Id, child.GetUserDataKey());
  624. recursiveItemCount++;
  625. // Check is recently added
  626. if (child.IsRecentlyAdded())
  627. {
  628. rcentlyAddedItemCount++;
  629. }
  630. var isUnplayed = true;
  631. // Incrememt totalPercentPlayed
  632. if (userdata != null)
  633. {
  634. if (userdata.Played)
  635. {
  636. totalPercentPlayed += 100;
  637. isUnplayed = false;
  638. }
  639. else if (userdata.PlaybackPositionTicks > 0 && child.RunTimeTicks.HasValue && child.RunTimeTicks.Value > 0)
  640. {
  641. double itemPercent = userdata.PlaybackPositionTicks;
  642. itemPercent /= child.RunTimeTicks.Value;
  643. totalPercentPlayed += itemPercent;
  644. }
  645. }
  646. if (isUnplayed)
  647. {
  648. unplayed++;
  649. }
  650. runtime += child.RunTimeTicks ?? 0;
  651. }
  652. dto.RecursiveItemCount = recursiveItemCount;
  653. dto.RecentlyAddedItemCount = rcentlyAddedItemCount;
  654. dto.RecursiveUnplayedItemCount = unplayed;
  655. if (recursiveItemCount > 0)
  656. {
  657. dto.PlayedPercentage = totalPercentPlayed / recursiveItemCount;
  658. }
  659. if (runtime > 0)
  660. {
  661. dto.CumulativeRunTimeTicks = runtime;
  662. }
  663. }
  664. /// <summary>
  665. /// Attaches People DTO's to a DTOBaseItem
  666. /// </summary>
  667. /// <param name="dto">The dto.</param>
  668. /// <param name="item">The item.</param>
  669. /// <returns>Task.</returns>
  670. private async Task AttachPeople(BaseItemDto dto, BaseItem item)
  671. {
  672. // Ordering by person type to ensure actors and artists are at the front.
  673. // This is taking advantage of the fact that they both begin with A
  674. // This should be improved in the future
  675. var people = item.People.OrderBy(i => i.Type).ToList();
  676. // Attach People by transforming them into BaseItemPerson (DTO)
  677. dto.People = new BaseItemPerson[people.Count];
  678. var entities = await Task.WhenAll(people.Select(p => p.Name)
  679. .Distinct(StringComparer.OrdinalIgnoreCase).Select(c =>
  680. Task.Run(async () =>
  681. {
  682. try
  683. {
  684. return await _libraryManager.GetPerson(c).ConfigureAwait(false);
  685. }
  686. catch (IOException ex)
  687. {
  688. _logger.ErrorException("Error getting person {0}", ex, c);
  689. return null;
  690. }
  691. })
  692. )).ConfigureAwait(false);
  693. var dictionary = entities.Where(i => i != null)
  694. .DistinctBy(i => i.Name)
  695. .ToDictionary(i => i.Name, StringComparer.OrdinalIgnoreCase);
  696. for (var i = 0; i < people.Count; i++)
  697. {
  698. var person = people[i];
  699. var baseItemPerson = new BaseItemPerson
  700. {
  701. Name = person.Name,
  702. Role = person.Role,
  703. Type = person.Type
  704. };
  705. Person entity;
  706. if (dictionary.TryGetValue(person.Name, out entity))
  707. {
  708. var primaryImagePath = entity.PrimaryImagePath;
  709. if (!string.IsNullOrEmpty(primaryImagePath))
  710. {
  711. baseItemPerson.PrimaryImageTag = GetImageCacheTag(entity, ImageType.Primary, primaryImagePath);
  712. }
  713. }
  714. dto.People[i] = baseItemPerson;
  715. }
  716. }
  717. /// <summary>
  718. /// Attaches the studios.
  719. /// </summary>
  720. /// <param name="dto">The dto.</param>
  721. /// <param name="item">The item.</param>
  722. /// <returns>Task.</returns>
  723. private async Task AttachStudios(BaseItemDto dto, BaseItem item)
  724. {
  725. var studios = item.Studios.ToList();
  726. dto.Studios = new StudioDto[studios.Count];
  727. var entities = await Task.WhenAll(studios.Distinct(StringComparer.OrdinalIgnoreCase).Select(c =>
  728. Task.Run(async () =>
  729. {
  730. try
  731. {
  732. return await _libraryManager.GetStudio(c).ConfigureAwait(false);
  733. }
  734. catch (IOException ex)
  735. {
  736. _logger.ErrorException("Error getting studio {0}", ex, c);
  737. return null;
  738. }
  739. })
  740. )).ConfigureAwait(false);
  741. var dictionary = entities
  742. .Where(i => i != null)
  743. .ToDictionary(i => i.Name, StringComparer.OrdinalIgnoreCase);
  744. for (var i = 0; i < studios.Count; i++)
  745. {
  746. var studio = studios[i];
  747. var studioDto = new StudioDto
  748. {
  749. Name = studio
  750. };
  751. Studio entity;
  752. if (dictionary.TryGetValue(studio, out entity))
  753. {
  754. var primaryImagePath = entity.PrimaryImagePath;
  755. if (!string.IsNullOrEmpty(primaryImagePath))
  756. {
  757. studioDto.PrimaryImageTag = GetImageCacheTag(entity, ImageType.Primary, primaryImagePath);
  758. }
  759. }
  760. dto.Studios[i] = studioDto;
  761. }
  762. }
  763. /// <summary>
  764. /// If an item does not any backdrops, this can be used to find the first parent that does have one
  765. /// </summary>
  766. /// <param name="item">The item.</param>
  767. /// <param name="owner">The owner.</param>
  768. /// <returns>BaseItem.</returns>
  769. private BaseItem GetParentBackdropItem(BaseItem item, BaseItem owner)
  770. {
  771. var parent = item.Parent ?? owner;
  772. while (parent != null)
  773. {
  774. if (parent.BackdropImagePaths != null && parent.BackdropImagePaths.Count > 0)
  775. {
  776. return parent;
  777. }
  778. parent = parent.Parent;
  779. }
  780. return null;
  781. }
  782. /// <summary>
  783. /// If an item does not have a logo, this can be used to find the first parent that does have one
  784. /// </summary>
  785. /// <param name="item">The item.</param>
  786. /// <param name="type">The type.</param>
  787. /// <param name="owner">The owner.</param>
  788. /// <returns>BaseItem.</returns>
  789. private BaseItem GetParentImageItem(BaseItem item, ImageType type, BaseItem owner)
  790. {
  791. var parent = item.Parent ?? owner;
  792. while (parent != null)
  793. {
  794. if (parent.HasImage(type))
  795. {
  796. return parent;
  797. }
  798. parent = parent.Parent;
  799. }
  800. return null;
  801. }
  802. /// <summary>
  803. /// Converts a UserItemData to a DTOUserItemData
  804. /// </summary>
  805. /// <param name="data">The data.</param>
  806. /// <returns>DtoUserItemData.</returns>
  807. /// <exception cref="System.ArgumentNullException"></exception>
  808. public static UserItemDataDto GetUserItemDataDto(UserItemData data)
  809. {
  810. if (data == null)
  811. {
  812. throw new ArgumentNullException("data");
  813. }
  814. return new UserItemDataDto
  815. {
  816. IsFavorite = data.IsFavorite,
  817. Likes = data.Likes,
  818. PlaybackPositionTicks = data.PlaybackPositionTicks,
  819. PlayCount = data.PlayCount,
  820. Rating = data.Rating,
  821. Played = data.Played,
  822. LastPlayedDate = data.LastPlayedDate
  823. };
  824. }
  825. /// <summary>
  826. /// Gets the chapter info dto.
  827. /// </summary>
  828. /// <param name="chapterInfo">The chapter info.</param>
  829. /// <param name="item">The item.</param>
  830. /// <returns>ChapterInfoDto.</returns>
  831. private ChapterInfoDto GetChapterInfoDto(ChapterInfo chapterInfo, BaseItem item)
  832. {
  833. var dto = new ChapterInfoDto
  834. {
  835. Name = chapterInfo.Name,
  836. StartPositionTicks = chapterInfo.StartPositionTicks
  837. };
  838. if (!string.IsNullOrEmpty(chapterInfo.ImagePath))
  839. {
  840. dto.ImageTag = GetImageCacheTag(item, ImageType.Chapter, chapterInfo.ImagePath);
  841. }
  842. return dto;
  843. }
  844. /// <summary>
  845. /// Converts a BaseItem to a BaseItemInfo
  846. /// </summary>
  847. /// <param name="item">The item.</param>
  848. /// <returns>BaseItemInfo.</returns>
  849. /// <exception cref="System.ArgumentNullException">item</exception>
  850. public static BaseItemInfo GetBaseItemInfo(BaseItem item)
  851. {
  852. if (item == null)
  853. {
  854. throw new ArgumentNullException("item");
  855. }
  856. var info = new BaseItemInfo
  857. {
  858. Id = GetClientItemId(item),
  859. Name = item.Name,
  860. MediaType = item.MediaType,
  861. Type = item.GetType().Name,
  862. IsFolder = item.IsFolder,
  863. RunTimeTicks = item.RunTimeTicks
  864. };
  865. var imagePath = item.PrimaryImagePath;
  866. if (!string.IsNullOrEmpty(imagePath))
  867. {
  868. try
  869. {
  870. info.PrimaryImageTag = Kernel.Instance.ImageManager.GetImageCacheTag(item, ImageType.Primary, imagePath);
  871. }
  872. catch (IOException)
  873. {
  874. }
  875. }
  876. return info;
  877. }
  878. /// <summary>
  879. /// Gets client-side Id of a server-side BaseItem
  880. /// </summary>
  881. /// <param name="item">The item.</param>
  882. /// <returns>System.String.</returns>
  883. /// <exception cref="System.ArgumentNullException">item</exception>
  884. public static string GetClientItemId(BaseItem item)
  885. {
  886. if (item == null)
  887. {
  888. throw new ArgumentNullException("item");
  889. }
  890. var indexFolder = item as IndexFolder;
  891. if (indexFolder != null)
  892. {
  893. return GetClientItemId(indexFolder.Parent) + IndexFolderDelimeter + (indexFolder.IndexName ?? string.Empty) + IndexFolderDelimeter + indexFolder.Id;
  894. }
  895. return item.Id.ToString("N");
  896. }
  897. /// <summary>
  898. /// Gets a BaseItem based upon it's client-side item id
  899. /// </summary>
  900. /// <param name="id">The id.</param>
  901. /// <param name="userManager">The user manager.</param>
  902. /// <param name="libraryManager">The library manager.</param>
  903. /// <param name="userId">The user id.</param>
  904. /// <returns>BaseItem.</returns>
  905. public static BaseItem GetItemByClientId(string id, IUserManager userManager, ILibraryManager libraryManager, Guid? userId = null)
  906. {
  907. if (string.IsNullOrEmpty(id))
  908. {
  909. throw new ArgumentNullException("id");
  910. }
  911. // If the item is an indexed folder we have to do a special routine to get it
  912. var isIndexFolder = id.IndexOf(IndexFolderDelimeter, StringComparison.OrdinalIgnoreCase) != -1;
  913. if (isIndexFolder)
  914. {
  915. if (userId.HasValue)
  916. {
  917. return GetIndexFolder(id, userId.Value, userManager, libraryManager);
  918. }
  919. }
  920. BaseItem item = null;
  921. if (userId.HasValue || !isIndexFolder)
  922. {
  923. item = libraryManager.GetItemById(new Guid(id));
  924. }
  925. // If we still don't find it, look within individual user views
  926. if (item == null && !userId.HasValue && isIndexFolder)
  927. {
  928. foreach (var user in userManager.Users)
  929. {
  930. item = GetItemByClientId(id, userManager, libraryManager, user.Id);
  931. if (item != null)
  932. {
  933. break;
  934. }
  935. }
  936. }
  937. return item;
  938. }
  939. /// <summary>
  940. /// Finds an index folder based on an Id and userId
  941. /// </summary>
  942. /// <param name="id">The id.</param>
  943. /// <param name="userId">The user id.</param>
  944. /// <param name="userManager">The user manager.</param>
  945. /// <param name="libraryManager">The library manager.</param>
  946. /// <returns>BaseItem.</returns>
  947. private static BaseItem GetIndexFolder(string id, Guid userId, IUserManager userManager, ILibraryManager libraryManager)
  948. {
  949. var user = userManager.GetUserById(userId);
  950. var stringSeparators = new[] { IndexFolderDelimeter };
  951. // Split using the delimeter
  952. var values = id.Split(stringSeparators, StringSplitOptions.None).ToList();
  953. // Get the top folder normally using the first id
  954. var folder = GetItemByClientId(values[0], userManager, libraryManager, userId) as Folder;
  955. values.RemoveAt(0);
  956. // Get indexed folders using the remaining values in the id string
  957. return GetIndexFolder(values, folder, user);
  958. }
  959. /// <summary>
  960. /// Gets indexed folders based on a list of index names and folder id's
  961. /// </summary>
  962. /// <param name="values">The values.</param>
  963. /// <param name="parentFolder">The parent folder.</param>
  964. /// <param name="user">The user.</param>
  965. /// <returns>BaseItem.</returns>
  966. private static BaseItem GetIndexFolder(List<string> values, Folder parentFolder, User user)
  967. {
  968. // The index name is first
  969. var indexBy = values[0];
  970. // The index folder id is next
  971. var indexFolderId = new Guid(values[1]);
  972. // Remove them from the lst
  973. values.RemoveRange(0, 2);
  974. // Get the IndexFolder
  975. var indexFolder = parentFolder.GetChildren(user, false, indexBy).FirstOrDefault(i => i.Id == indexFolderId) as Folder;
  976. // Nested index folder
  977. if (values.Count > 0)
  978. {
  979. return GetIndexFolder(values, indexFolder, user);
  980. }
  981. return indexFolder;
  982. }
  983. /// <summary>
  984. /// Gets the backdrop image tags.
  985. /// </summary>
  986. /// <param name="item">The item.</param>
  987. /// <returns>List{System.String}.</returns>
  988. private List<Guid> GetBackdropImageTags(BaseItem item)
  989. {
  990. return item.BackdropImagePaths
  991. .Select(p => GetImageCacheTag(item, ImageType.Backdrop, p))
  992. .Where(i => i.HasValue)
  993. .Select(i => i.Value)
  994. .ToList();
  995. }
  996. /// <summary>
  997. /// Gets the screenshot image tags.
  998. /// </summary>
  999. /// <param name="item">The item.</param>
  1000. /// <returns>List{Guid}.</returns>
  1001. private List<Guid> GetScreenshotImageTags(BaseItem item)
  1002. {
  1003. return item.ScreenshotImagePaths
  1004. .Select(p => GetImageCacheTag(item, ImageType.Screenshot, p))
  1005. .Where(i => i.HasValue)
  1006. .Select(i => i.Value)
  1007. .ToList();
  1008. }
  1009. private Guid? GetImageCacheTag(BaseItem item, ImageType type, string path)
  1010. {
  1011. try
  1012. {
  1013. return Kernel.Instance.ImageManager.GetImageCacheTag(item, type, path);
  1014. }
  1015. catch (IOException ex)
  1016. {
  1017. _logger.ErrorException("Error getting {0} image info for {1}", ex, type, path);
  1018. return null;
  1019. }
  1020. }
  1021. }
  1022. }