DtoBuilder.cs 36 KB

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