using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Querying; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; namespace MediaBrowser.Controller.Dto { /// /// Generates DTO's from domain entities /// public class DtoBuilder { /// /// The index folder delimeter /// const string IndexFolderDelimeter = "-index-"; private readonly ILogger _logger; private readonly ILibraryManager _libraryManager; private readonly IUserDataRepository _userDataRepository; private readonly IItemRepository _itemRepo; public DtoBuilder(ILogger logger, ILibraryManager libraryManager, IUserDataRepository userDataRepository, IItemRepository itemRepo) { _logger = logger; _libraryManager = libraryManager; _userDataRepository = userDataRepository; _itemRepo = itemRepo; } /// /// Converts a BaseItem to a DTOBaseItem /// /// The item. /// The fields. /// The user. /// Task{DtoBaseItem}. /// item public async Task GetBaseItemDto(BaseItem item, List fields, User user = null) { if (item == null) { throw new ArgumentNullException("item"); } if (fields == null) { throw new ArgumentNullException("fields"); } var dto = new BaseItemDto(); var tasks = new List(); if (fields.Contains(ItemFields.Studios)) { tasks.Add(AttachStudios(dto, item)); } if (fields.Contains(ItemFields.People)) { tasks.Add(AttachPeople(dto, item)); } if (fields.Contains(ItemFields.PrimaryImageAspectRatio)) { try { await AttachPrimaryImageAspectRatio(dto, item, _logger).ConfigureAwait(false); } catch (Exception ex) { // Have to use a catch-all unfortunately because some .net image methods throw plain Exceptions _logger.ErrorException("Error generating PrimaryImageAspectRatio for {0}", ex, item.Name); } } if (user != null) { AttachUserSpecificInfo(dto, item, user, fields); } AttachBasicFields(dto, item, fields); if (fields.Contains(ItemFields.SoundtrackIds)) { var series = item as Series; if (series != null) { AttachSoundtrackIds(dto, series, user); } var movie = item as Movie; if (movie != null) { AttachSoundtrackIds(dto, movie, user); } var album = item as MusicAlbum; if (album != null) { AttachSoundtrackIds(dto, album, user); } var game = item as Game; if (game != null) { AttachSoundtrackIds(dto, game, user); } } // Make sure all the tasks we kicked off have completed. if (tasks.Count > 0) { await Task.WhenAll(tasks).ConfigureAwait(false); } return dto; } private void AttachSoundtrackIds(BaseItemDto dto, Movie item, User user) { var tmdb = item.GetProviderId(MetadataProviders.Tmdb); if (string.IsNullOrEmpty(tmdb)) { return; } var recursiveChildren = user == null ? _libraryManager.RootFolder.RecursiveChildren : user.RootFolder.GetRecursiveChildren(user); dto.SoundtrackIds = recursiveChildren .Where(i => { if (!string.IsNullOrEmpty(tmdb) && string.Equals(tmdb, i.GetProviderId(MetadataProviders.Tmdb), StringComparison.OrdinalIgnoreCase) && i is MusicAlbum) { return true; } return false; }) .Select(GetClientItemId) .ToArray(); } private void AttachSoundtrackIds(BaseItemDto dto, Series item, User user) { var tvdb = item.GetProviderId(MetadataProviders.Tvdb); if (string.IsNullOrEmpty(tvdb)) { return; } var recursiveChildren = user == null ? _libraryManager.RootFolder.RecursiveChildren : user.RootFolder.GetRecursiveChildren(user); dto.SoundtrackIds = recursiveChildren .Where(i => { if (!string.IsNullOrEmpty(tvdb) && string.Equals(tvdb, i.GetProviderId(MetadataProviders.Tvdb), StringComparison.OrdinalIgnoreCase) && i is MusicAlbum) { return true; } return false; }) .Select(GetClientItemId) .ToArray(); } private void AttachSoundtrackIds(BaseItemDto dto, Game item, User user) { var gamesdb = item.GetProviderId(MetadataProviders.Gamesdb); if (string.IsNullOrEmpty(gamesdb)) { return; } var recursiveChildren = user == null ? _libraryManager.RootFolder.RecursiveChildren : user.RootFolder.GetRecursiveChildren(user); dto.SoundtrackIds = recursiveChildren .Where(i => { if (!string.IsNullOrEmpty(gamesdb) && string.Equals(gamesdb, i.GetProviderId(MetadataProviders.Gamesdb), StringComparison.OrdinalIgnoreCase) && i is MusicAlbum) { return true; } return false; }) .Select(GetClientItemId) .ToArray(); } private void AttachSoundtrackIds(BaseItemDto dto, MusicAlbum item, User user) { var tmdb = item.GetProviderId(MetadataProviders.Tmdb); var tvdb = item.GetProviderId(MetadataProviders.Tvdb); var gamesdb = item.GetProviderId(MetadataProviders.Gamesdb); if (string.IsNullOrEmpty(tmdb) && string.IsNullOrEmpty(tvdb) && string.IsNullOrEmpty(gamesdb)) { return; } var recursiveChildren = user == null ? _libraryManager.RootFolder.RecursiveChildren : user.RootFolder.GetRecursiveChildren(user); dto.SoundtrackIds = recursiveChildren .Where(i => { if (!string.IsNullOrEmpty(tmdb) && string.Equals(tmdb, i.GetProviderId(MetadataProviders.Tmdb), StringComparison.OrdinalIgnoreCase) && i is Movie) { return true; } if (!string.IsNullOrEmpty(tvdb) && string.Equals(tvdb, i.GetProviderId(MetadataProviders.Tvdb), StringComparison.OrdinalIgnoreCase) && i is Series) { return true; } if (!string.IsNullOrEmpty(gamesdb) && string.Equals(gamesdb, i.GetProviderId(MetadataProviders.Gamesdb), StringComparison.OrdinalIgnoreCase) && i is Game) { return true; } return false; }) .Select(GetClientItemId) .ToArray(); } /// /// Attaches the user specific info. /// /// The dto. /// The item. /// The user. /// The fields. private void AttachUserSpecificInfo(BaseItemDto dto, BaseItem item, User user, List fields) { if (item.IsFolder && fields.Contains(ItemFields.DisplayPreferencesId)) { dto.DisplayPreferencesId = ((Folder)item).GetDisplayPreferencesId(user.Id).ToString(); } if (item.IsFolder) { if (fields.Contains(ItemFields.ItemCounts)) { var folder = (Folder)item; // Skip sorting since all we want is a count dto.ChildCount = folder.GetChildren(user, true).Count(); SetSpecialCounts(folder, user, dto, _userDataRepository); } } var userData = _userDataRepository.GetUserData(user.Id, item.GetUserDataKey()); dto.UserData = GetUserItemDataDto(userData); if (item.IsFolder) { dto.UserData.Played = dto.PlayedPercentage.HasValue && dto.PlayedPercentage.Value >= 100; } } /// /// Attaches the primary image aspect ratio. /// /// The dto. /// The item. /// The _logger. /// Task. internal static async Task AttachPrimaryImageAspectRatio(IItemDto dto, BaseItem item, ILogger logger) { var path = item.PrimaryImagePath; if (string.IsNullOrEmpty(path)) { return; } var metaFileEntry = item.ResolveArgs.GetMetaFileByPath(path); // See if we can avoid a file system lookup by looking for the file in ResolveArgs var dateModified = metaFileEntry == null ? File.GetLastWriteTimeUtc(path) : metaFileEntry.LastWriteTimeUtc; ImageSize size; try { size = await Kernel.Instance.ImageManager.GetImageSize(path, dateModified).ConfigureAwait(false); } catch (FileNotFoundException) { logger.Error("Image file does not exist: {0}", path); return; } catch (Exception ex) { logger.ErrorException("Failed to determine primary image aspect ratio for {0}", ex, path); return; } dto.OriginalPrimaryImageAspectRatio = size.Width / size.Height; var supportedEnhancers = Kernel.Instance.ImageManager.ImageEnhancers.Where(i => { try { return i.Supports(item, ImageType.Primary); } catch (Exception ex) { logger.ErrorException("Error in image enhancer: {0}", ex, i.GetType().Name); return false; } }).ToList(); foreach (var enhancer in supportedEnhancers) { try { size = enhancer.GetEnhancedImageSize(item, ImageType.Primary, 0, size); } catch (Exception ex) { logger.ErrorException("Error in image enhancer: {0}", ex, enhancer.GetType().Name); } } dto.PrimaryImageAspectRatio = size.Width / size.Height; } /// /// Sets simple property values on a DTOBaseItem /// /// The dto. /// The item. /// The fields. private void AttachBasicFields(BaseItemDto dto, BaseItem item, List fields) { if (fields.Contains(ItemFields.DateCreated)) { dto.DateCreated = item.DateCreated; } if (fields.Contains(ItemFields.OriginalRunTimeTicks)) { dto.OriginalRunTimeTicks = item.OriginalRunTimeTicks; } dto.DisplayMediaType = item.DisplayMediaType; if (fields.Contains(ItemFields.MetadataSettings)) { dto.LockedFields = item.LockedFields; dto.EnableInternetProviders = !item.DontFetchMeta; } if (fields.Contains(ItemFields.Budget)) { dto.Budget = item.Budget; } if (fields.Contains(ItemFields.Revenue)) { dto.Revenue = item.Revenue; } if (fields.Contains(ItemFields.EndDate)) { dto.EndDate = item.EndDate; } if (fields.Contains(ItemFields.HomePageUrl)) { dto.HomePageUrl = item.HomePageUrl; } if (fields.Contains(ItemFields.Tags)) { dto.Tags = item.Tags; } if (fields.Contains(ItemFields.ProductionLocations)) { dto.ProductionLocations = item.ProductionLocations; } dto.AspectRatio = item.AspectRatio; dto.BackdropImageTags = GetBackdropImageTags(item); dto.ScreenshotImageTags = GetScreenshotImageTags(item); if (fields.Contains(ItemFields.Genres)) { dto.Genres = item.Genres; } dto.ImageTags = new Dictionary(); foreach (var image in item.Images) { var type = image.Key; var tag = GetImageCacheTag(item, type, image.Value); if (tag.HasValue) { dto.ImageTags[type] = tag.Value; } } dto.Id = GetClientItemId(item); dto.IndexNumber = item.IndexNumber; dto.IsFolder = item.IsFolder; dto.Language = item.Language; dto.MediaType = item.MediaType; dto.LocationType = item.LocationType; dto.CriticRating = item.CriticRating; if (fields.Contains(ItemFields.CriticRatingSummary)) { dto.CriticRatingSummary = item.CriticRatingSummary; } var localTrailerCount = item.LocalTrailerIds.Count; if (localTrailerCount > 0) { dto.LocalTrailerCount = localTrailerCount; } dto.Name = item.Name; dto.OfficialRating = item.OfficialRating; var hasOverview = fields.Contains(ItemFields.Overview); var hasHtmlOverview = fields.Contains(ItemFields.OverviewHtml); if (hasOverview || hasHtmlOverview) { var strippedOverview = string.IsNullOrEmpty(item.Overview) ? item.Overview : item.Overview.StripHtml(); if (hasOverview) { dto.Overview = strippedOverview; } // Only supply the html version if there was actually html content if (hasHtmlOverview) { dto.OverviewHtml = item.Overview; } } // If there are no backdrops, indicate what parent has them in case the Ui wants to allow inheritance if (dto.BackdropImageTags.Count == 0) { var parentWithBackdrop = GetParentBackdropItem(item); if (parentWithBackdrop != null) { dto.ParentBackdropItemId = GetClientItemId(parentWithBackdrop); dto.ParentBackdropImageTags = GetBackdropImageTags(parentWithBackdrop); } } if (item.Parent != null && fields.Contains(ItemFields.ParentId)) { dto.ParentId = GetClientItemId(item.Parent); } dto.ParentIndexNumber = item.ParentIndexNumber; // If there is no logo, indicate what parent has one in case the Ui wants to allow inheritance if (!dto.HasLogo) { var parentWithLogo = GetParentImageItem(item, ImageType.Logo); if (parentWithLogo != null) { dto.ParentLogoItemId = GetClientItemId(parentWithLogo); dto.ParentLogoImageTag = GetImageCacheTag(parentWithLogo, ImageType.Logo, parentWithLogo.GetImage(ImageType.Logo)); } } // If there is no art, indicate what parent has one in case the Ui wants to allow inheritance if (!dto.HasArtImage) { var parentWithImage = GetParentImageItem(item, ImageType.Art); if (parentWithImage != null) { dto.ParentArtItemId = GetClientItemId(parentWithImage); dto.ParentArtImageTag = GetImageCacheTag(parentWithImage, ImageType.Art, parentWithImage.GetImage(ImageType.Art)); } } if (fields.Contains(ItemFields.Path)) { dto.Path = item.Path; } dto.PremiereDate = item.PremiereDate; dto.ProductionYear = item.ProductionYear; if (fields.Contains(ItemFields.ProviderIds)) { dto.ProviderIds = item.ProviderIds; } dto.RunTimeTicks = item.RunTimeTicks; if (fields.Contains(ItemFields.SortName)) { dto.SortName = item.SortName; } if (fields.Contains(ItemFields.CustomRating)) { dto.CustomRating = item.CustomRating; } if (fields.Contains(ItemFields.Taglines)) { dto.Taglines = item.Taglines; } if (fields.Contains(ItemFields.RemoteTrailers)) { dto.RemoteTrailers = item.RemoteTrailers; } dto.Type = item.GetType().Name; dto.CommunityRating = item.CommunityRating; if (item.IsFolder) { var folder = (Folder)item; if (fields.Contains(ItemFields.IndexOptions)) { dto.IndexOptions = folder.IndexByOptionStrings.ToArray(); } } // Add audio info var audio = item as Audio; if (audio != null) { dto.Album = audio.Album; dto.AlbumArtist = audio.AlbumArtist; dto.Artists = new[] { audio.Artist }; var albumParent = audio.FindParent(); if (albumParent != null) { dto.AlbumId = GetClientItemId(albumParent); var imagePath = albumParent.PrimaryImagePath; if (!string.IsNullOrEmpty(imagePath)) { dto.AlbumPrimaryImageTag = GetImageCacheTag(albumParent, ImageType.Primary, imagePath); } } } var album = item as MusicAlbum; if (album != null) { var songs = album.RecursiveChildren.OfType