diff options
Diffstat (limited to 'Emby.Server.Implementations/Dto/DtoService.cs')
| -rw-r--r-- | Emby.Server.Implementations/Dto/DtoService.cs | 1609 |
1 files changed, 1609 insertions, 0 deletions
diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs new file mode 100644 index 000000000..85549439b --- /dev/null +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -0,0 +1,1609 @@ +using MediaBrowser.Common; +using MediaBrowser.Controller.Channels; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Devices; +using MediaBrowser.Controller.Drawing; +using MediaBrowser.Controller.Dto; +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.LiveTv; +using MediaBrowser.Controller.Persistence; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Controller.Sync; +using MediaBrowser.Model.Drawing; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Querying; +using MediaBrowser.Model.Sync; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.IO; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Extensions; + +namespace Emby.Server.Implementations.Dto +{ + public class DtoService : IDtoService + { + private readonly ILogger _logger; + private readonly ILibraryManager _libraryManager; + private readonly IUserDataManager _userDataRepository; + private readonly IItemRepository _itemRepo; + + private readonly IImageProcessor _imageProcessor; + private readonly IServerConfigurationManager _config; + private readonly IFileSystem _fileSystem; + private readonly IProviderManager _providerManager; + + private readonly Func<IChannelManager> _channelManagerFactory; + private readonly ISyncManager _syncManager; + private readonly IApplicationHost _appHost; + private readonly Func<IDeviceManager> _deviceManager; + private readonly Func<IMediaSourceManager> _mediaSourceManager; + private readonly Func<ILiveTvManager> _livetvManager; + + public DtoService(ILogger logger, ILibraryManager libraryManager, IUserDataManager userDataRepository, IItemRepository itemRepo, IImageProcessor imageProcessor, IServerConfigurationManager config, IFileSystem fileSystem, IProviderManager providerManager, Func<IChannelManager> channelManagerFactory, ISyncManager syncManager, IApplicationHost appHost, Func<IDeviceManager> deviceManager, Func<IMediaSourceManager> mediaSourceManager, Func<ILiveTvManager> livetvManager) + { + _logger = logger; + _libraryManager = libraryManager; + _userDataRepository = userDataRepository; + _itemRepo = itemRepo; + _imageProcessor = imageProcessor; + _config = config; + _fileSystem = fileSystem; + _providerManager = providerManager; + _channelManagerFactory = channelManagerFactory; + _syncManager = syncManager; + _appHost = appHost; + _deviceManager = deviceManager; + _mediaSourceManager = mediaSourceManager; + _livetvManager = livetvManager; + } + + /// <summary> + /// Converts a BaseItem to a DTOBaseItem + /// </summary> + /// <param name="item">The item.</param> + /// <param name="fields">The fields.</param> + /// <param name="user">The user.</param> + /// <param name="owner">The owner.</param> + /// <returns>Task{DtoBaseItem}.</returns> + /// <exception cref="System.ArgumentNullException">item</exception> + public BaseItemDto GetBaseItemDto(BaseItem item, List<ItemFields> fields, User user = null, BaseItem owner = null) + { + var options = new DtoOptions + { + Fields = fields + }; + + return GetBaseItemDto(item, options, user, owner); + } + + public async Task<List<BaseItemDto>> GetBaseItemDtos(IEnumerable<BaseItem> items, DtoOptions options, User user = null, BaseItem owner = null) + { + if (items == null) + { + throw new ArgumentNullException("items"); + } + + if (options == null) + { + throw new ArgumentNullException("options"); + } + + var syncDictionary = GetSyncedItemProgress(options); + + var list = new List<BaseItemDto>(); + var programTuples = new List<Tuple<BaseItem, BaseItemDto>>(); + var channelTuples = new List<Tuple<BaseItemDto, LiveTvChannel>>(); + + foreach (var item in items) + { + var dto = await GetBaseItemDtoInternal(item, options, user, owner).ConfigureAwait(false); + + var tvChannel = item as LiveTvChannel; + if (tvChannel != null) + { + channelTuples.Add(new Tuple<BaseItemDto, LiveTvChannel>(dto, tvChannel)); + } + else if (item is LiveTvProgram) + { + programTuples.Add(new Tuple<BaseItem, BaseItemDto>(item, dto)); + } + + var byName = item as IItemByName; + + if (byName != null) + { + if (options.Fields.Contains(ItemFields.ItemCounts)) + { + var libraryItems = byName.GetTaggedItems(new InternalItemsQuery(user) + { + Recursive = true + }); + + SetItemByNameInfo(item, dto, libraryItems.ToList(), user); + } + } + + FillSyncInfo(dto, item, options, user, syncDictionary); + + list.Add(dto); + } + + if (programTuples.Count > 0) + { + await _livetvManager().AddInfoToProgramDto(programTuples, options.Fields, user).ConfigureAwait(false); + } + + if (channelTuples.Count > 0) + { + _livetvManager().AddChannelInfo(channelTuples, options, user); + } + + return list; + } + + public BaseItemDto GetBaseItemDto(BaseItem item, DtoOptions options, User user = null, BaseItem owner = null) + { + var syncDictionary = GetSyncedItemProgress(options); + + var dto = GetBaseItemDtoInternal(item, options, user, owner).Result; + var tvChannel = item as LiveTvChannel; + if (tvChannel != null) + { + var list = new List<Tuple<BaseItemDto, LiveTvChannel>> { new Tuple<BaseItemDto, LiveTvChannel>(dto, tvChannel) }; + _livetvManager().AddChannelInfo(list, options, user); + } + else if (item is LiveTvProgram) + { + var list = new List<Tuple<BaseItem, BaseItemDto>> { new Tuple<BaseItem, BaseItemDto>(item, dto) }; + var task = _livetvManager().AddInfoToProgramDto(list, options.Fields, user); + Task.WaitAll(task); + } + + var byName = item as IItemByName; + + if (byName != null) + { + if (options.Fields.Contains(ItemFields.ItemCounts)) + { + SetItemByNameInfo(item, dto, GetTaggedItems(byName, user), user); + } + + FillSyncInfo(dto, item, options, user, syncDictionary); + return dto; + } + + FillSyncInfo(dto, item, options, user, syncDictionary); + + return dto; + } + + private List<BaseItem> GetTaggedItems(IItemByName byName, User user) + { + var items = byName.GetTaggedItems(new InternalItemsQuery(user) + { + Recursive = true + + }).ToList(); + + return items; + } + + public Dictionary<string, SyncedItemProgress> GetSyncedItemProgress(DtoOptions options) + { + if (!options.Fields.Contains(ItemFields.BasicSyncInfo) && + !options.Fields.Contains(ItemFields.SyncInfo)) + { + return new Dictionary<string, SyncedItemProgress>(); + } + + var deviceId = options.DeviceId; + if (string.IsNullOrWhiteSpace(deviceId)) + { + return new Dictionary<string, SyncedItemProgress>(); + } + + var caps = _deviceManager().GetCapabilities(deviceId); + if (caps == null || !caps.SupportsSync) + { + return new Dictionary<string, SyncedItemProgress>(); + } + + return _syncManager.GetSyncedItemProgresses(new SyncJobItemQuery + { + TargetId = deviceId, + Statuses = new[] + { + SyncJobItemStatus.Converting, + SyncJobItemStatus.Queued, + SyncJobItemStatus.Transferring, + SyncJobItemStatus.ReadyToTransfer, + SyncJobItemStatus.Synced + } + }); + } + + public void FillSyncInfo(IEnumerable<Tuple<BaseItem, BaseItemDto>> tuples, DtoOptions options, User user) + { + if (options.Fields.Contains(ItemFields.BasicSyncInfo) || + options.Fields.Contains(ItemFields.SyncInfo)) + { + var syncProgress = GetSyncedItemProgress(options); + + foreach (var tuple in tuples) + { + var item = tuple.Item1; + + FillSyncInfo(tuple.Item2, item, options, user, syncProgress); + } + } + } + + private void FillSyncInfo(IHasSyncInfo dto, BaseItem item, DtoOptions options, User user, Dictionary<string, SyncedItemProgress> syncProgress) + { + var hasFullSyncInfo = options.Fields.Contains(ItemFields.SyncInfo); + + if (!options.Fields.Contains(ItemFields.BasicSyncInfo) && + !hasFullSyncInfo) + { + return; + } + + if (dto.SupportsSync ?? false) + { + SyncedItemProgress syncStatus; + if (syncProgress.TryGetValue(dto.Id, out syncStatus)) + { + if (syncStatus.Status == SyncJobItemStatus.Synced) + { + dto.SyncPercent = 100; + } + else + { + dto.SyncPercent = syncStatus.Progress; + } + + if (hasFullSyncInfo) + { + dto.HasSyncJob = true; + dto.SyncStatus = syncStatus.Status; + } + } + } + } + + private async Task<BaseItemDto> GetBaseItemDtoInternal(BaseItem item, DtoOptions options, User user = null, BaseItem owner = null) + { + var fields = options.Fields; + + if (item == null) + { + throw new ArgumentNullException("item"); + } + + if (fields == null) + { + throw new ArgumentNullException("fields"); + } + + var dto = new BaseItemDto + { + ServerId = _appHost.SystemId + }; + + if (item.SourceType == SourceType.Channel) + { + dto.SourceType = item.SourceType.ToString(); + } + + if (fields.Contains(ItemFields.People)) + { + AttachPeople(dto, item); + } + + if (fields.Contains(ItemFields.PrimaryImageAspectRatio)) + { + try + { + AttachPrimaryImageAspectRatio(dto, item); + } + 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 (fields.Contains(ItemFields.DisplayPreferencesId)) + { + dto.DisplayPreferencesId = item.DisplayPreferencesId.ToString("N"); + } + + if (user != null) + { + await AttachUserSpecificInfo(dto, item, user, options).ConfigureAwait(false); + } + + var hasMediaSources = item as IHasMediaSources; + if (hasMediaSources != null) + { + if (fields.Contains(ItemFields.MediaSources)) + { + if (user == null) + { + dto.MediaSources = _mediaSourceManager().GetStaticMediaSources(hasMediaSources, true).ToList(); + } + else + { + dto.MediaSources = _mediaSourceManager().GetStaticMediaSources(hasMediaSources, true, user).ToList(); + } + } + } + + if (fields.Contains(ItemFields.Studios)) + { + AttachStudios(dto, item); + } + + AttachBasicFields(dto, item, owner, options); + + var collectionFolder = item as ICollectionFolder; + if (collectionFolder != null) + { + dto.OriginalCollectionType = collectionFolder.CollectionType; + + dto.CollectionType = user == null ? + collectionFolder.CollectionType : + collectionFolder.GetViewType(user); + } + + if (fields.Contains(ItemFields.CanDelete)) + { + dto.CanDelete = user == null + ? item.CanDelete() + : item.CanDelete(user); + } + + if (fields.Contains(ItemFields.CanDownload)) + { + dto.CanDownload = user == null + ? item.CanDownload() + : item.CanDownload(user); + } + + if (fields.Contains(ItemFields.Etag)) + { + dto.Etag = item.GetEtag(user); + } + + if (item is ILiveTvRecording) + { + _livetvManager().AddInfoToRecordingDto(item, dto, user); + } + + return dto; + } + + public BaseItemDto GetItemByNameDto(BaseItem item, DtoOptions options, List<BaseItem> taggedItems, Dictionary<string, SyncedItemProgress> syncProgress, User user = null) + { + var dto = GetBaseItemDtoInternal(item, options, user).Result; + + if (taggedItems != null && options.Fields.Contains(ItemFields.ItemCounts)) + { + SetItemByNameInfo(item, dto, taggedItems, user); + } + + FillSyncInfo(dto, item, options, user, syncProgress); + + return dto; + } + + private void SetItemByNameInfo(BaseItem item, BaseItemDto dto, List<BaseItem> taggedItems, User user = null) + { + if (item is MusicArtist) + { + dto.AlbumCount = taggedItems.Count(i => i is MusicAlbum); + dto.MusicVideoCount = taggedItems.Count(i => i is MusicVideo); + dto.SongCount = taggedItems.Count(i => i is Audio); + } + else if (item is MusicGenre) + { + dto.ArtistCount = taggedItems.Count(i => i is MusicArtist); + dto.AlbumCount = taggedItems.Count(i => i is MusicAlbum); + dto.MusicVideoCount = taggedItems.Count(i => i is MusicVideo); + dto.SongCount = taggedItems.Count(i => i is Audio); + } + else if (item is GameGenre) + { + dto.GameCount = taggedItems.Count(i => i is Game); + } + else + { + // This populates them all and covers Genre, Person, Studio, Year + + dto.ArtistCount = taggedItems.Count(i => i is MusicArtist); + dto.AlbumCount = taggedItems.Count(i => i is MusicAlbum); + dto.EpisodeCount = taggedItems.Count(i => i is Episode); + dto.GameCount = taggedItems.Count(i => i is Game); + dto.MovieCount = taggedItems.Count(i => i is Movie); + dto.TrailerCount = taggedItems.Count(i => i is Trailer); + dto.MusicVideoCount = taggedItems.Count(i => i is MusicVideo); + dto.SeriesCount = taggedItems.Count(i => i is Series); + dto.ProgramCount = taggedItems.Count(i => i is LiveTvProgram); + dto.SongCount = taggedItems.Count(i => i is Audio); + } + + dto.ChildCount = taggedItems.Count; + } + + /// <summary> + /// Attaches the user specific info. + /// </summary> + private async Task AttachUserSpecificInfo(BaseItemDto dto, BaseItem item, User user, DtoOptions dtoOptions) + { + var fields = dtoOptions.Fields; + + if (item.IsFolder) + { + var folder = (Folder)item; + + if (dtoOptions.EnableUserData) + { + dto.UserData = await _userDataRepository.GetUserDataDto(item, dto, user).ConfigureAwait(false); + } + + if (!dto.ChildCount.HasValue && item.SourceType == SourceType.Library) + { + dto.ChildCount = GetChildCount(folder, user); + } + + if (fields.Contains(ItemFields.CumulativeRunTimeTicks)) + { + dto.CumulativeRunTimeTicks = item.RunTimeTicks; + } + + if (fields.Contains(ItemFields.DateLastMediaAdded)) + { + dto.DateLastMediaAdded = folder.DateLastMediaAdded; + } + } + + else + { + if (dtoOptions.EnableUserData) + { + dto.UserData = _userDataRepository.GetUserDataDto(item, user).Result; + } + } + + dto.PlayAccess = item.GetPlayAccess(user); + + if (fields.Contains(ItemFields.BasicSyncInfo) || fields.Contains(ItemFields.SyncInfo)) + { + var userCanSync = user != null && user.Policy.EnableSync; + if (userCanSync && _syncManager.SupportsSync(item)) + { + dto.SupportsSync = true; + } + } + + if (fields.Contains(ItemFields.SeasonUserData)) + { + var episode = item as Episode; + + if (episode != null) + { + var season = episode.Season; + + if (season != null) + { + dto.SeasonUserData = await _userDataRepository.GetUserDataDto(season, user).ConfigureAwait(false); + } + } + } + + var userView = item as UserView; + if (userView != null) + { + dto.HasDynamicCategories = userView.ContainsDynamicCategories(user); + } + + var collectionFolder = item as ICollectionFolder; + if (collectionFolder != null) + { + dto.HasDynamicCategories = false; + } + } + + private int GetChildCount(Folder folder, User user) + { + // Right now this is too slow to calculate for top level folders on a per-user basis + // Just return something so that apps that are expecting a value won't think the folders are empty + if (folder is ICollectionFolder || folder is UserView) + { + return new Random().Next(1, 10); + } + + return folder.GetChildCount(user); + } + + /// <summary> + /// Gets client-side Id of a server-side BaseItem + /// </summary> + /// <param name="item">The item.</param> + /// <returns>System.String.</returns> + /// <exception cref="System.ArgumentNullException">item</exception> + public string GetDtoId(BaseItem item) + { + if (item == null) + { + throw new ArgumentNullException("item"); + } + + return item.Id.ToString("N"); + } + + /// <summary> + /// Converts a UserItemData to a DTOUserItemData + /// </summary> + /// <param name="data">The data.</param> + /// <returns>DtoUserItemData.</returns> + /// <exception cref="System.ArgumentNullException"></exception> + public UserItemDataDto GetUserItemDataDto(UserItemData data) + { + if (data == null) + { + throw new ArgumentNullException("data"); + } + + return new UserItemDataDto + { + IsFavorite = data.IsFavorite, + Likes = data.Likes, + PlaybackPositionTicks = data.PlaybackPositionTicks, + PlayCount = data.PlayCount, + Rating = data.Rating, + Played = data.Played, + LastPlayedDate = data.LastPlayedDate, + Key = data.Key + }; + } + private void SetBookProperties(BaseItemDto dto, Book item) + { + dto.SeriesName = item.SeriesName; + } + private void SetPhotoProperties(BaseItemDto dto, Photo item) + { + dto.Width = item.Width; + dto.Height = item.Height; + dto.CameraMake = item.CameraMake; + dto.CameraModel = item.CameraModel; + dto.Software = item.Software; + dto.ExposureTime = item.ExposureTime; + dto.FocalLength = item.FocalLength; + dto.ImageOrientation = item.Orientation; + dto.Aperture = item.Aperture; + dto.ShutterSpeed = item.ShutterSpeed; + + dto.Latitude = item.Latitude; + dto.Longitude = item.Longitude; + dto.Altitude = item.Altitude; + dto.IsoSpeedRating = item.IsoSpeedRating; + + var album = item.AlbumEntity; + + if (album != null) + { + dto.Album = album.Name; + dto.AlbumId = album.Id.ToString("N"); + } + } + + private void SetMusicVideoProperties(BaseItemDto dto, MusicVideo item) + { + if (!string.IsNullOrEmpty(item.Album)) + { + var parentAlbum = _libraryManager.GetItemList(new InternalItemsQuery + { + IncludeItemTypes = new[] { typeof(MusicAlbum).Name }, + Name = item.Album + + }).FirstOrDefault(); + + if (parentAlbum != null) + { + dto.AlbumId = GetDtoId(parentAlbum); + } + } + + dto.Album = item.Album; + } + + private void SetGameProperties(BaseItemDto dto, Game item) + { + dto.Players = item.PlayersSupported; + dto.GameSystem = item.GameSystem; + dto.MultiPartGameFiles = item.MultiPartGameFiles; + } + + private void SetGameSystemProperties(BaseItemDto dto, GameSystem item) + { + dto.GameSystem = item.GameSystemName; + } + + private List<string> GetImageTags(BaseItem item, List<ItemImageInfo> images) + { + return images + .Select(p => GetImageCacheTag(item, p)) + .Where(i => i != null) + .ToList(); + } + + private string GetImageCacheTag(BaseItem item, ImageType type) + { + try + { + return _imageProcessor.GetImageCacheTag(item, type); + } + catch (Exception ex) + { + _logger.ErrorException("Error getting {0} image info", ex, type); + return null; + } + } + + private string GetImageCacheTag(BaseItem item, ItemImageInfo image) + { + try + { + return _imageProcessor.GetImageCacheTag(item, image); + } + catch (Exception ex) + { + _logger.ErrorException("Error getting {0} image info for {1}", ex, image.Type, image.Path); + return null; + } + } + + /// <summary> + /// Attaches People DTO's to a DTOBaseItem + /// </summary> + /// <param name="dto">The dto.</param> + /// <param name="item">The item.</param> + /// <returns>Task.</returns> + private void AttachPeople(BaseItemDto dto, BaseItem item) + { + // Ordering by person type to ensure actors and artists are at the front. + // This is taking advantage of the fact that they both begin with A + // This should be improved in the future + var people = _libraryManager.GetPeople(item).OrderBy(i => i.SortOrder ?? int.MaxValue) + .ThenBy(i => + { + if (i.IsType(PersonType.Actor)) + { + return 0; + } + if (i.IsType(PersonType.GuestStar)) + { + return 1; + } + if (i.IsType(PersonType.Director)) + { + return 2; + } + if (i.IsType(PersonType.Writer)) + { + return 3; + } + if (i.IsType(PersonType.Producer)) + { + return 4; + } + if (i.IsType(PersonType.Composer)) + { + return 4; + } + + return 10; + }) + .ToList(); + + var list = new List<BaseItemPerson>(); + + var dictionary = people.Select(p => p.Name) + .Distinct(StringComparer.OrdinalIgnoreCase).Select(c => + { + try + { + return _libraryManager.GetPerson(c); + } + catch (Exception ex) + { + _logger.ErrorException("Error getting person {0}", ex, c); + return null; + } + + }).Where(i => i != null) + .DistinctBy(i => i.Name, StringComparer.OrdinalIgnoreCase) + .ToDictionary(i => i.Name, StringComparer.OrdinalIgnoreCase); + + for (var i = 0; i < people.Count; i++) + { + var person = people[i]; + + var baseItemPerson = new BaseItemPerson + { + Name = person.Name, + Role = person.Role, + Type = person.Type + }; + + Person entity; + + if (dictionary.TryGetValue(person.Name, out entity)) + { + baseItemPerson.PrimaryImageTag = GetImageCacheTag(entity, ImageType.Primary); + baseItemPerson.Id = entity.Id.ToString("N"); + list.Add(baseItemPerson); + } + } + + dto.People = list.ToArray(); + } + + /// <summary> + /// Attaches the studios. + /// </summary> + /// <param name="dto">The dto.</param> + /// <param name="item">The item.</param> + /// <returns>Task.</returns> + private void AttachStudios(BaseItemDto dto, BaseItem item) + { + var studios = item.Studios.ToList(); + + dto.Studios = new StudioDto[studios.Count]; + + var dictionary = studios.Distinct(StringComparer.OrdinalIgnoreCase).Select(name => + { + try + { + return _libraryManager.GetStudio(name); + } + catch (IOException ex) + { + _logger.ErrorException("Error getting studio {0}", ex, name); + return null; + } + }) + .Where(i => i != null) + .DistinctBy(i => i.Name, StringComparer.OrdinalIgnoreCase) + .ToDictionary(i => i.Name, StringComparer.OrdinalIgnoreCase); + + for (var i = 0; i < studios.Count; i++) + { + var studio = studios[i]; + + var studioDto = new StudioDto + { + Name = studio + }; + + Studio entity; + + if (dictionary.TryGetValue(studio, out entity)) + { + studioDto.Id = entity.Id.ToString("N"); + studioDto.PrimaryImageTag = GetImageCacheTag(entity, ImageType.Primary); + } + + dto.Studios[i] = studioDto; + } + } + + /// <summary> + /// Gets the chapter info dto. + /// </summary> + /// <param name="chapterInfo">The chapter info.</param> + /// <param name="item">The item.</param> + /// <returns>ChapterInfoDto.</returns> + private ChapterInfoDto GetChapterInfoDto(ChapterInfo chapterInfo, BaseItem item) + { + var dto = new ChapterInfoDto + { + Name = chapterInfo.Name, + StartPositionTicks = chapterInfo.StartPositionTicks + }; + + if (!string.IsNullOrEmpty(chapterInfo.ImagePath)) + { + dto.ImageTag = GetImageCacheTag(item, new ItemImageInfo + { + Path = chapterInfo.ImagePath, + Type = ImageType.Chapter, + DateModified = chapterInfo.ImageDateModified + }); + } + + return dto; + } + + public List<ChapterInfoDto> GetChapterInfoDtos(BaseItem item) + { + return _itemRepo.GetChapters(item.Id) + .Select(c => GetChapterInfoDto(c, item)) + .ToList(); + } + + /// <summary> + /// Sets simple property values on a DTOBaseItem + /// </summary> + /// <param name="dto">The dto.</param> + /// <param name="item">The item.</param> + /// <param name="owner">The owner.</param> + /// <param name="options">The options.</param> + private void AttachBasicFields(BaseItemDto dto, BaseItem item, BaseItem owner, DtoOptions options) + { + var fields = options.Fields; + + if (fields.Contains(ItemFields.DateCreated)) + { + dto.DateCreated = item.DateCreated; + } + + if (fields.Contains(ItemFields.DisplayMediaType)) + { + dto.DisplayMediaType = item.DisplayMediaType; + } + + if (fields.Contains(ItemFields.Settings)) + { + dto.LockedFields = item.LockedFields; + dto.LockData = item.IsLocked; + dto.ForcedSortName = item.ForcedSortName; + } + dto.Container = item.Container; + + var hasBudget = item as IHasBudget; + if (hasBudget != null) + { + if (fields.Contains(ItemFields.Budget)) + { + dto.Budget = hasBudget.Budget; + } + + if (fields.Contains(ItemFields.Revenue)) + { + dto.Revenue = hasBudget.Revenue; + } + } + + dto.EndDate = item.EndDate; + + if (fields.Contains(ItemFields.HomePageUrl)) + { + dto.HomePageUrl = item.HomePageUrl; + } + + if (fields.Contains(ItemFields.ExternalUrls)) + { + dto.ExternalUrls = _providerManager.GetExternalUrls(item).ToArray(); + } + + if (fields.Contains(ItemFields.Tags)) + { + dto.Tags = item.Tags; + } + + if (fields.Contains(ItemFields.Keywords)) + { + dto.Keywords = item.Keywords; + } + + var hasAspectRatio = item as IHasAspectRatio; + if (hasAspectRatio != null) + { + dto.AspectRatio = hasAspectRatio.AspectRatio; + } + + if (fields.Contains(ItemFields.Metascore)) + { + var hasMetascore = item as IHasMetascore; + if (hasMetascore != null) + { + dto.Metascore = hasMetascore.Metascore; + } + } + + if (fields.Contains(ItemFields.AwardSummary)) + { + var hasAwards = item as IHasAwards; + if (hasAwards != null) + { + dto.AwardSummary = hasAwards.AwardSummary; + } + } + + var backdropLimit = options.GetImageLimit(ImageType.Backdrop); + if (backdropLimit > 0) + { + dto.BackdropImageTags = GetImageTags(item, item.GetImages(ImageType.Backdrop).Take(backdropLimit).ToList()); + } + + if (fields.Contains(ItemFields.ScreenshotImageTags)) + { + var screenshotLimit = options.GetImageLimit(ImageType.Screenshot); + if (screenshotLimit > 0) + { + dto.ScreenshotImageTags = GetImageTags(item, item.GetImages(ImageType.Screenshot).Take(screenshotLimit).ToList()); + } + } + + if (fields.Contains(ItemFields.Genres)) + { + dto.Genres = item.Genres; + } + + if (options.EnableImages) + { + dto.ImageTags = new Dictionary<ImageType, string>(); + + // Prevent implicitly captured closure + var currentItem = item; + foreach (var image in currentItem.ImageInfos.Where(i => !currentItem.AllowsMultipleImages(i.Type)) + .ToList()) + { + if (options.GetImageLimit(image.Type) > 0) + { + var tag = GetImageCacheTag(item, image); + + if (tag != null) + { + dto.ImageTags[image.Type] = tag; + } + } + } + } + + dto.Id = GetDtoId(item); + dto.IndexNumber = item.IndexNumber; + dto.ParentIndexNumber = item.ParentIndexNumber; + + if (item.IsFolder) + { + dto.IsFolder = true; + } + else if (item is IHasMediaSources) + { + dto.IsFolder = false; + } + + dto.MediaType = item.MediaType; + dto.LocationType = item.LocationType; + if (item.IsHD.HasValue && item.IsHD.Value) + { + dto.IsHD = item.IsHD; + } + dto.Audio = item.Audio; + + if (fields.Contains(ItemFields.Settings)) + { + dto.PreferredMetadataCountryCode = item.PreferredMetadataCountryCode; + dto.PreferredMetadataLanguage = item.PreferredMetadataLanguage; + } + + dto.CriticRating = item.CriticRating; + + if (fields.Contains(ItemFields.CriticRatingSummary)) + { + dto.CriticRatingSummary = item.CriticRatingSummary; + } + + var hasTrailers = item as IHasTrailers; + if (hasTrailers != null) + { + dto.LocalTrailerCount = hasTrailers.GetTrailerIds().Count; + } + + var hasDisplayOrder = item as IHasDisplayOrder; + if (hasDisplayOrder != null) + { + dto.DisplayOrder = hasDisplayOrder.DisplayOrder; + } + + var userView = item as UserView; + if (userView != null) + { + dto.CollectionType = userView.ViewType; + } + + if (fields.Contains(ItemFields.RemoteTrailers)) + { + dto.RemoteTrailers = hasTrailers != null ? + hasTrailers.RemoteTrailers : + new List<MediaUrl>(); + } + + dto.Name = item.Name; + dto.OfficialRating = item.OfficialRating; + + if (fields.Contains(ItemFields.Overview)) + { + dto.Overview = item.Overview; + } + + if (fields.Contains(ItemFields.OriginalTitle)) + { + dto.OriginalTitle = item.OriginalTitle; + } + + if (fields.Contains(ItemFields.ShortOverview)) + { + dto.ShortOverview = item.ShortOverview; + } + + if (fields.Contains(ItemFields.ParentId)) + { + var displayParentId = item.DisplayParentId; + if (displayParentId.HasValue) + { + dto.ParentId = displayParentId.Value.ToString("N"); + } + } + + AddInheritedImages(dto, item, options, owner); + + if (fields.Contains(ItemFields.Path)) + { + dto.Path = GetMappedPath(item); + } + + 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)) + { + if (!string.IsNullOrWhiteSpace(item.Tagline)) + { + dto.Taglines = new List<string> { item.Tagline }; + } + + if (dto.Taglines == null) + { + dto.Taglines = new List<string>(); + } + } + + dto.Type = item.GetClientTypeName(); + dto.CommunityRating = item.CommunityRating; + + if (fields.Contains(ItemFields.VoteCount)) + { + dto.VoteCount = item.VoteCount; + } + + //if (item.IsFolder) + //{ + // var folder = (Folder)item; + + // if (fields.Contains(ItemFields.IndexOptions)) + // { + // dto.IndexOptions = folder.IndexByOptionStrings.ToArray(); + // } + //} + + var supportsPlaceHolders = item as ISupportsPlaceHolders; + if (supportsPlaceHolders != null) + { + dto.IsPlaceHolder = supportsPlaceHolders.IsPlaceHolder; + } + + // Add audio info + var audio = item as Audio; + if (audio != null) + { + dto.Album = audio.Album; + dto.ExtraType = audio.ExtraType; + + var albumParent = audio.AlbumEntity; + + if (albumParent != null) + { + dto.AlbumId = GetDtoId(albumParent); + + dto.AlbumPrimaryImageTag = GetImageCacheTag(albumParent, ImageType.Primary); + } + + //if (fields.Contains(ItemFields.MediaSourceCount)) + //{ + // Songs always have one + //} + } + + var hasArtist = item as IHasArtist; + if (hasArtist != null) + { + dto.Artists = hasArtist.Artists; + + var artistItems = _libraryManager.GetArtists(new InternalItemsQuery + { + EnableTotalRecordCount = false, + ItemIds = new[] { item.Id.ToString("N") } + }); + + dto.ArtistItems = artistItems.Items + .Select(i => + { + var artist = i.Item1; + return new NameIdPair + { + Name = artist.Name, + Id = artist.Id.ToString("N") + }; + }) + .ToList(); + + // Include artists that are not in the database yet, e.g., just added via metadata editor + var foundArtists = artistItems.Items.Select(i => i.Item1.Name).ToList(); + dto.ArtistItems.AddRange(hasArtist.Artists + .Except(foundArtists, new DistinctNameComparer()) + .Select(i => + { + // This should not be necessary but we're seeing some cases of it + if (string.IsNullOrWhiteSpace(i)) + { + return null; + } + + var artist = _libraryManager.GetArtist(i); + if (artist != null) + { + return new NameIdPair + { + Name = artist.Name, + Id = artist.Id.ToString("N") + }; + } + + return null; + + }).Where(i => i != null)); + } + + var hasAlbumArtist = item as IHasAlbumArtist; + if (hasAlbumArtist != null) + { + dto.AlbumArtist = hasAlbumArtist.AlbumArtists.FirstOrDefault(); + + var artistItems = _libraryManager.GetAlbumArtists(new InternalItemsQuery + { + EnableTotalRecordCount = false, + ItemIds = new[] { item.Id.ToString("N") } + }); + + dto.AlbumArtists = artistItems.Items + .Select(i => + { + var artist = i.Item1; + return new NameIdPair + { + Name = artist.Name, + Id = artist.Id.ToString("N") + }; + }) + .ToList(); + } + + // Add video info + var video = item as Video; + if (video != null) + { + dto.VideoType = video.VideoType; + dto.Video3DFormat = video.Video3DFormat; + dto.IsoType = video.IsoType; + + if (video.HasSubtitles) + { + dto.HasSubtitles = video.HasSubtitles; + } + + if (video.AdditionalParts.Count != 0) + { + dto.PartCount = video.AdditionalParts.Count + 1; + } + + if (fields.Contains(ItemFields.MediaSourceCount)) + { + var mediaSourceCount = video.MediaSourceCount; + if (mediaSourceCount != 1) + { + dto.MediaSourceCount = mediaSourceCount; + } + } + + if (fields.Contains(ItemFields.Chapters)) + { + dto.Chapters = GetChapterInfoDtos(item); + } + + dto.ExtraType = video.ExtraType; + } + + if (fields.Contains(ItemFields.MediaStreams)) + { + // Add VideoInfo + var iHasMediaSources = item as IHasMediaSources; + + if (iHasMediaSources != null) + { + List<MediaStream> mediaStreams; + + if (dto.MediaSources != null && dto.MediaSources.Count > 0) + { + mediaStreams = dto.MediaSources.Where(i => new Guid(i.Id) == item.Id) + .SelectMany(i => i.MediaStreams) + .ToList(); + } + else + { + mediaStreams = _mediaSourceManager().GetStaticMediaSources(iHasMediaSources, true).First().MediaStreams; + } + + dto.MediaStreams = mediaStreams; + } + } + + var hasSpecialFeatures = item as IHasSpecialFeatures; + if (hasSpecialFeatures != null) + { + var specialFeatureCount = hasSpecialFeatures.SpecialFeatureIds.Count; + + if (specialFeatureCount > 0) + { + dto.SpecialFeatureCount = specialFeatureCount; + } + } + + // Add EpisodeInfo + var episode = item as Episode; + if (episode != null) + { + dto.IndexNumberEnd = episode.IndexNumberEnd; + dto.SeriesName = episode.SeriesName; + + if (fields.Contains(ItemFields.AlternateEpisodeNumbers)) + { + dto.DvdSeasonNumber = episode.DvdSeasonNumber; + dto.DvdEpisodeNumber = episode.DvdEpisodeNumber; + dto.AbsoluteEpisodeNumber = episode.AbsoluteEpisodeNumber; + } + + if (fields.Contains(ItemFields.SpecialEpisodeNumbers)) + { + dto.AirsAfterSeasonNumber = episode.AirsAfterSeasonNumber; + dto.AirsBeforeEpisodeNumber = episode.AirsBeforeEpisodeNumber; + dto.AirsBeforeSeasonNumber = episode.AirsBeforeSeasonNumber; + } + + var seasonId = episode.SeasonId; + if (seasonId.HasValue) + { + dto.SeasonId = seasonId.Value.ToString("N"); + } + + dto.SeasonName = episode.SeasonName; + + var seriesId = episode.SeriesId; + if (seriesId.HasValue) + { + dto.SeriesId = seriesId.Value.ToString("N"); + } + + Series episodeSeries = null; + + if (fields.Contains(ItemFields.SeriesGenres)) + { + episodeSeries = episodeSeries ?? episode.Series; + if (episodeSeries != null) + { + dto.SeriesGenres = episodeSeries.Genres.ToList(); + } + } + + //if (fields.Contains(ItemFields.SeriesPrimaryImage)) + { + episodeSeries = episodeSeries ?? episode.Series; + if (episodeSeries != null) + { + dto.SeriesPrimaryImageTag = GetImageCacheTag(episodeSeries, ImageType.Primary); + } + } + + if (fields.Contains(ItemFields.SeriesStudio)) + { + episodeSeries = episodeSeries ?? episode.Series; + if (episodeSeries != null) + { + dto.SeriesStudio = episodeSeries.Studios.FirstOrDefault(); + } + } + } + + // Add SeriesInfo + var series = item as Series; + if (series != null) + { + dto.AirDays = series.AirDays; + dto.AirTime = series.AirTime; + dto.SeriesStatus = series.Status; + + dto.AnimeSeriesIndex = series.AnimeSeriesIndex; + } + + // Add SeasonInfo + var season = item as Season; + if (season != null) + { + dto.SeriesName = season.SeriesName; + + var seriesId = season.SeriesId; + if (seriesId.HasValue) + { + dto.SeriesId = seriesId.Value.ToString("N"); + } + + series = null; + + if (fields.Contains(ItemFields.SeriesStudio)) + { + series = series ?? season.Series; + if (series != null) + { + dto.SeriesStudio = series.Studios.FirstOrDefault(); + } + } + + if (fields.Contains(ItemFields.SeriesPrimaryImage)) + { + series = series ?? season.Series; + if (series != null) + { + dto.SeriesPrimaryImageTag = GetImageCacheTag(series, ImageType.Primary); + } + } + } + + var game = item as Game; + + if (game != null) + { + SetGameProperties(dto, game); + } + + var gameSystem = item as GameSystem; + + if (gameSystem != null) + { + SetGameSystemProperties(dto, gameSystem); + } + + var musicVideo = item as MusicVideo; + if (musicVideo != null) + { + SetMusicVideoProperties(dto, musicVideo); + } + + var book = item as Book; + if (book != null) + { + SetBookProperties(dto, book); + } + + if (item.ProductionLocations.Count > 0 || item is Movie) + { + dto.ProductionLocations = item.ProductionLocations.ToArray(); + } + + var photo = item as Photo; + if (photo != null) + { + SetPhotoProperties(dto, photo); + } + + dto.ChannelId = item.ChannelId; + + if (item.SourceType == SourceType.Channel && !string.IsNullOrWhiteSpace(item.ChannelId)) + { + var channel = _libraryManager.GetItemById(item.ChannelId); + if (channel != null) + { + dto.ChannelName = channel.Name; + } + } + } + + private void AddInheritedImages(BaseItemDto dto, BaseItem item, DtoOptions options, BaseItem owner) + { + var logoLimit = options.GetImageLimit(ImageType.Logo); + var artLimit = options.GetImageLimit(ImageType.Art); + var thumbLimit = options.GetImageLimit(ImageType.Thumb); + var backdropLimit = options.GetImageLimit(ImageType.Backdrop); + + if (logoLimit == 0 && artLimit == 0 && thumbLimit == 0 && backdropLimit == 0) + { + return; + } + + BaseItem parent = null; + var isFirst = true; + + while (((!dto.HasLogo && logoLimit > 0) || (!dto.HasArtImage && artLimit > 0) || (!dto.HasThumb && thumbLimit > 0) || parent is Series) && + (parent = parent ?? (isFirst ? item.GetParent() ?? owner : parent)) != null) + { + if (parent == null) + { + break; + } + + var allImages = parent.ImageInfos; + + if (logoLimit > 0 && !dto.HasLogo && dto.ParentLogoItemId == null) + { + var image = allImages.FirstOrDefault(i => i.Type == ImageType.Logo); + + if (image != null) + { + dto.ParentLogoItemId = GetDtoId(parent); + dto.ParentLogoImageTag = GetImageCacheTag(parent, image); + } + } + if (artLimit > 0 && !dto.HasArtImage && dto.ParentArtItemId == null) + { + var image = allImages.FirstOrDefault(i => i.Type == ImageType.Art); + + if (image != null) + { + dto.ParentArtItemId = GetDtoId(parent); + dto.ParentArtImageTag = GetImageCacheTag(parent, image); + } + } + if (thumbLimit > 0 && !dto.HasThumb && (dto.ParentThumbItemId == null || parent is Series)) + { + var image = allImages.FirstOrDefault(i => i.Type == ImageType.Thumb); + + if (image != null) + { + dto.ParentThumbItemId = GetDtoId(parent); + dto.ParentThumbImageTag = GetImageCacheTag(parent, image); + } + } + if (backdropLimit > 0 && !dto.HasBackdrop) + { + var images = allImages.Where(i => i.Type == ImageType.Backdrop).Take(backdropLimit).ToList(); + + if (images.Count > 0) + { + dto.ParentBackdropItemId = GetDtoId(parent); + dto.ParentBackdropImageTags = GetImageTags(parent, images); + } + } + + isFirst = false; + parent = parent.GetParent(); + } + } + + private string GetMappedPath(BaseItem item) + { + var path = item.Path; + + var locationType = item.LocationType; + + if (locationType == LocationType.FileSystem || locationType == LocationType.Offline) + { + path = _libraryManager.GetPathAfterNetworkSubstitution(path, item); + } + + return path; + } + + /// <summary> + /// Attaches the primary image aspect ratio. + /// </summary> + /// <param name="dto">The dto.</param> + /// <param name="item">The item.</param> + /// <returns>Task.</returns> + public void AttachPrimaryImageAspectRatio(IItemDto dto, IHasImages item) + { + dto.PrimaryImageAspectRatio = GetPrimaryImageAspectRatio(item); + } + + public double? GetPrimaryImageAspectRatio(IHasImages item) + { + var imageInfo = item.GetImageInfo(ImageType.Primary, 0); + + if (imageInfo == null || !imageInfo.IsLocalFile) + { + return null; + } + + ImageSize size; + + try + { + size = _imageProcessor.GetImageSize(imageInfo); + } + catch + { + //_logger.ErrorException("Failed to determine primary image aspect ratio for {0}", ex, path); + return null; + } + + var supportedEnhancers = _imageProcessor.GetSupportedEnhancers(item, ImageType.Primary).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); + } + } + + var width = size.Width; + var height = size.Height; + + if (width == 0 || height == 0) + { + return null; + } + + var photo = item as Photo; + if (photo != null && photo.Orientation.HasValue) + { + switch (photo.Orientation.Value) + { + case ImageOrientation.LeftBottom: + case ImageOrientation.LeftTop: + case ImageOrientation.RightBottom: + case ImageOrientation.RightTop: + var temp = height; + height = width; + width = temp; + break; + } + } + + return width / height; + } + } +} |
