using MediaBrowser.Api.Movies; using MediaBrowser.Api.Music; 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.Net; using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.TV; using MediaBrowser.Model.Activity; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.IO; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.IO; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Services; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Progress; using MediaBrowser.Model.Extensions; namespace MediaBrowser.Api.Library { [Route("/Items/{Id}/File", "GET", Summary = "Gets the original file of an item")] [Authenticated] public class GetFile { /// /// Gets or sets the id. /// /// The id. [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] public string Id { get; set; } } /// /// Class GetCriticReviews /// [Route("/Items/{Id}/CriticReviews", "GET", Summary = "Gets critic reviews for an item")] [Authenticated] public class GetCriticReviews : IReturn> { /// /// Gets or sets the id. /// /// The id. [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] public string Id { get; set; } /// /// Skips over a given number of items within the results. Use for paging. /// /// The start index. [ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] public int? StartIndex { get; set; } /// /// The maximum number of items to return /// /// The limit. [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] public int? Limit { get; set; } } /// /// Class GetThemeSongs /// [Route("/Items/{Id}/ThemeSongs", "GET", Summary = "Gets theme songs for an item")] [Authenticated] public class GetThemeSongs : IReturn { /// /// Gets or sets the user id. /// /// The user id. [ApiMember(Name = "UserId", Description = "Optional. Filter by user id, and attach user data", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] public string UserId { get; set; } /// /// Gets or sets the id. /// /// The id. [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] public string Id { get; set; } [ApiMember(Name = "InheritFromParent", Description = "Determines whether or not parent items should be searched for theme media.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] public bool InheritFromParent { get; set; } } /// /// Class GetThemeVideos /// [Route("/Items/{Id}/ThemeVideos", "GET", Summary = "Gets theme videos for an item")] [Authenticated] public class GetThemeVideos : IReturn { /// /// Gets or sets the user id. /// /// The user id. [ApiMember(Name = "UserId", Description = "Optional. Filter by user id, and attach user data", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] public string UserId { get; set; } /// /// Gets or sets the id. /// /// The id. [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] public string Id { get; set; } [ApiMember(Name = "InheritFromParent", Description = "Determines whether or not parent items should be searched for theme media.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] public bool InheritFromParent { get; set; } } /// /// Class GetThemeVideos /// [Route("/Items/{Id}/ThemeMedia", "GET", Summary = "Gets theme videos and songs for an item")] [Authenticated] public class GetThemeMedia : IReturn { /// /// Gets or sets the user id. /// /// The user id. [ApiMember(Name = "UserId", Description = "Optional. Filter by user id, and attach user data", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] public string UserId { get; set; } /// /// Gets or sets the id. /// /// The id. [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] public string Id { get; set; } [ApiMember(Name = "InheritFromParent", Description = "Determines whether or not parent items should be searched for theme media.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] public bool InheritFromParent { get; set; } } [Route("/Library/Refresh", "POST", Summary = "Starts a library scan")] [Authenticated(Roles = "Admin")] public class RefreshLibrary : IReturnVoid { } [Route("/Items/{Id}", "DELETE", Summary = "Deletes an item from the library and file system")] [Authenticated] public class DeleteItem : IReturnVoid { [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] public string Id { get; set; } } [Route("/Items", "DELETE", Summary = "Deletes an item from the library and file system")] [Authenticated] public class DeleteItems : IReturnVoid { [ApiMember(Name = "Ids", Description = "Ids", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] public string Ids { get; set; } } [Route("/Items/Counts", "GET")] [Authenticated] public class GetItemCounts : IReturn { [ApiMember(Name = "UserId", Description = "Optional. Get counts from a specific user's library.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] public string UserId { get; set; } [ApiMember(Name = "IsFavorite", Description = "Optional. Get counts of favorite items", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] public bool? IsFavorite { get; set; } } [Route("/Items/{Id}/Ancestors", "GET", Summary = "Gets all parents of an item")] [Authenticated] public class GetAncestors : IReturn { /// /// Gets or sets the user id. /// /// The user id. [ApiMember(Name = "UserId", Description = "Optional. Filter by user id, and attach user data", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] public string UserId { get; set; } /// /// Gets or sets the id. /// /// The id. [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] public string Id { get; set; } } [Route("/Items/YearIndex", "GET", Summary = "Gets a year index based on an item query.")] [Authenticated] public class GetYearIndex : IReturn> { /// /// Gets or sets the user id. /// /// The user id. [ApiMember(Name = "UserId", Description = "Optional. Filter by user id, and attach user data", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] public string UserId { get; set; } [ApiMember(Name = "IncludeItemTypes", Description = "Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] public string IncludeItemTypes { get; set; } } /// /// Class GetPhyscialPaths /// [Route("/Library/PhysicalPaths", "GET", Summary = "Gets a list of physical paths from virtual folders")] [Authenticated(Roles = "Admin")] public class GetPhyscialPaths : IReturn> { } [Route("/Library/MediaFolders", "GET", Summary = "Gets all user media folders.")] [Authenticated] public class GetMediaFolders : IReturn> { [ApiMember(Name = "IsHidden", Description = "Optional. Filter by folders that are marked hidden, or not.", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")] public bool? IsHidden { get; set; } } [Route("/Library/Series/Added", "POST", Summary = "Reports that new episodes of a series have been added by an external source")] [Route("/Library/Series/Updated", "POST", Summary = "Reports that new episodes of a series have been added by an external source")] [Authenticated] public class PostUpdatedSeries : IReturnVoid { [ApiMember(Name = "TvdbId", Description = "Tvdb Id", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")] public string TvdbId { get; set; } } [Route("/Library/Movies/Added", "POST", Summary = "Reports that new movies have been added by an external source")] [Route("/Library/Movies/Updated", "POST", Summary = "Reports that new movies have been added by an external source")] [Authenticated] public class PostUpdatedMovies : IReturnVoid { [ApiMember(Name = "TmdbId", Description = "Tmdb Id", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")] public string TmdbId { get; set; } [ApiMember(Name = "ImdbId", Description = "Imdb Id", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")] public string ImdbId { get; set; } } [Route("/Items/{Id}/Download", "GET", Summary = "Downloads item media")] [Authenticated(Roles = "download")] public class GetDownload { /// /// Gets or sets the id. /// /// The id. [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] public string Id { get; set; } } [Route("/Items/{Id}/Similar", "GET", Summary = "Gets similar items")] [Authenticated] public class GetSimilarItems : BaseGetSimilarItemsFromItem { } /// /// Class LibraryService /// public class LibraryService : BaseApiService { /// /// The _item repo /// private readonly IItemRepository _itemRepo; private readonly ILibraryManager _libraryManager; private readonly IUserManager _userManager; private readonly IUserDataManager _userDataManager; private readonly IDtoService _dtoService; private readonly IAuthorizationContext _authContext; private readonly IActivityManager _activityManager; private readonly ILocalizationManager _localization; private readonly ILiveTvManager _liveTv; private readonly ITVSeriesManager _tvManager; private readonly ILibraryMonitor _libraryMonitor; private readonly IFileSystem _fileSystem; private readonly IServerConfigurationManager _config; /// /// Initializes a new instance of the class. /// public LibraryService(IItemRepository itemRepo, ILibraryManager libraryManager, IUserManager userManager, IDtoService dtoService, IUserDataManager userDataManager, IAuthorizationContext authContext, IActivityManager activityManager, ILocalizationManager localization, ILiveTvManager liveTv, ITVSeriesManager tvManager, ILibraryMonitor libraryMonitor, IFileSystem fileSystem, IServerConfigurationManager config) { _itemRepo = itemRepo; _libraryManager = libraryManager; _userManager = userManager; _dtoService = dtoService; _userDataManager = userDataManager; _authContext = authContext; _activityManager = activityManager; _localization = localization; _liveTv = liveTv; _tvManager = tvManager; _libraryMonitor = libraryMonitor; _fileSystem = fileSystem; _config = config; } public object Get(GetSimilarItems request) { var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null; var item = string.IsNullOrEmpty(request.Id) ? (!string.IsNullOrWhiteSpace(request.UserId) ? user.RootFolder : _libraryManager.RootFolder) : _libraryManager.GetItemById(request.Id); if (item is Game) { return new GamesService(_userManager, _userDataManager, _libraryManager, _itemRepo, _dtoService, _authContext) { Request = Request, }.Get(new GetSimilarGames { Fields = request.Fields, Id = request.Id, Limit = request.Limit, UserId = request.UserId, ImageTypeLimit = request.ImageTypeLimit }); } if (item is MusicAlbum) { return new AlbumsService(_userManager, _userDataManager, _libraryManager, _itemRepo, _dtoService, _authContext) { Request = Request, }.Get(new GetSimilarAlbums { Fields = request.Fields, Id = request.Id, Limit = request.Limit, UserId = request.UserId, ExcludeArtistIds = request.ExcludeArtistIds, ImageTypeLimit = request.ImageTypeLimit }); } if (item is MusicArtist) { return new AlbumsService(_userManager, _userDataManager, _libraryManager, _itemRepo, _dtoService, _authContext) { Request = Request, }.Get(new GetSimilarArtists { Fields = request.Fields, Id = request.Id, Limit = request.Limit, UserId = request.UserId, ImageTypeLimit = request.ImageTypeLimit }); } var program = item as IHasProgramAttributes; if (item is Movie || (program != null && program.IsMovie) || item is Trailer) { return new MoviesService(_userManager, _libraryManager, _dtoService, _config, _authContext) { Request = Request, }.Get(new GetSimilarMovies { Fields = request.Fields, Id = request.Id, Limit = request.Limit, UserId = request.UserId, ImageTypeLimit = request.ImageTypeLimit }); } if (item is Series || (program != null && program.IsSeries)) { return new TvShowsService(_userManager, _userDataManager, _libraryManager, _itemRepo, _dtoService, _tvManager, _authContext) { Request = Request, }.Get(new GetSimilarShows { Fields = request.Fields, Id = request.Id, Limit = request.Limit, UserId = request.UserId, ImageTypeLimit = request.ImageTypeLimit }); } return new QueryResult(); } public object Get(GetMediaFolders request) { var items = _libraryManager.GetUserRootFolder().Children.Concat(_libraryManager.RootFolder.VirtualChildren).OrderBy(i => i.SortName).ToList(); if (request.IsHidden.HasValue) { var val = request.IsHidden.Value; items = items.Where(i => i.IsHidden == val).ToList(); } var dtoOptions = GetDtoOptions(_authContext, request); var result = new QueryResult { TotalRecordCount = items.Count, Items = items.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions)).ToArray() }; return ToOptimizedResult(result); } public void Post(PostUpdatedSeries request) { var series = _libraryManager.GetItemList(new InternalItemsQuery { IncludeItemTypes = new[] { typeof(Series).Name }, DtoOptions = new DtoOptions(false) { EnableImages = false } }).Where(i => string.Equals(request.TvdbId, i.GetProviderId(MetadataProviders.Tvdb), StringComparison.OrdinalIgnoreCase)).ToArray(); if (series.Length > 0) { foreach (var item in series) { _libraryMonitor.ReportFileSystemChanged(item.Path); } } else { Task.Run(() => _libraryManager.ValidateMediaLibrary(new SimpleProgress(), CancellationToken.None)); } } public void Post(PostUpdatedMovies request) { var movies = _libraryManager.GetItemList(new InternalItemsQuery { IncludeItemTypes = new[] { typeof(Movie).Name }, DtoOptions = new DtoOptions(false) { EnableImages = false } }); if (!string.IsNullOrWhiteSpace(request.ImdbId)) { movies = movies.Where(i => string.Equals(request.ImdbId, i.GetProviderId(MetadataProviders.Imdb), StringComparison.OrdinalIgnoreCase)).ToList(); } else if (!string.IsNullOrWhiteSpace(request.TmdbId)) { movies = movies.Where(i => string.Equals(request.TmdbId, i.GetProviderId(MetadataProviders.Tmdb), StringComparison.OrdinalIgnoreCase)).ToList(); } else { movies = new List(); } if (movies.Count > 0) { foreach (var item in movies) { _libraryMonitor.ReportFileSystemChanged(item.Path); } } else { Task.Run(() => _libraryManager.ValidateMediaLibrary(new SimpleProgress(), CancellationToken.None)); } } public Task Get(GetDownload request) { var item = _libraryManager.GetItemById(request.Id); var auth = _authContext.GetAuthorizationInfo(Request); var user = _userManager.GetUserById(auth.UserId); if (user != null) { if (!item.CanDownload(user)) { throw new ArgumentException("Item does not support downloading"); } } else { if (!item.CanDownload()) { throw new ArgumentException("Item does not support downloading"); } } var headers = new Dictionary(); if (user != null) { LogDownload(item, user, auth); } var path = item.Path; // Quotes are valid in linux. They'll possibly cause issues here var filename = (Path.GetFileName(path) ?? string.Empty).Replace("\"", string.Empty); if (!string.IsNullOrWhiteSpace(filename)) { headers["Content-Disposition"] = "attachment; filename=\"" + filename + "\""; } return ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions { Path = path, ResponseHeaders = headers }); } private void LogDownload(BaseItem item, User user, AuthorizationInfo auth) { try { _activityManager.Create(new ActivityLogEntry { Name = string.Format(_localization.GetLocalizedString("UserDownloadingItemWithValues"), user.Name, item.Name), Type = "UserDownloadingContent", ShortOverview = string.Format(_localization.GetLocalizedString("AppDeviceValues"), auth.Client, auth.Device), UserId = auth.UserId }); } catch { // Logged at lower levels } } public Task Get(GetFile request) { var item = _libraryManager.GetItemById(request.Id); var locationType = item.LocationType; if (locationType == LocationType.Remote || locationType == LocationType.Virtual) { throw new ArgumentException("This command cannot be used for remote or virtual items."); } if (_fileSystem.DirectoryExists(item.Path)) { throw new ArgumentException("This command cannot be used for directories."); } return ResultFactory.GetStaticFileResult(Request, item.Path); } /// /// Gets the specified request. /// /// The request. /// System.Object. public object Get(GetPhyscialPaths request) { var result = _libraryManager.RootFolder.Children .SelectMany(c => c.PhysicalLocations) .ToList(); return ToOptimizedSerializedResultUsingCache(result); } /// /// Gets the specified request. /// /// The request. /// System.Object. public object Get(GetAncestors request) { var result = GetAncestors(request); return ToOptimizedSerializedResultUsingCache(result); } /// /// Gets the ancestors. /// /// The request. /// Task{BaseItemDto[]}. public List GetAncestors(GetAncestors request) { var item = _libraryManager.GetItemById(request.Id); var baseItemDtos = new List(); var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null; var dtoOptions = GetDtoOptions(_authContext, request); BaseItem parent = item.GetParent(); while (parent != null) { if (user != null) { parent = TranslateParentItem(parent, user); } baseItemDtos.Add(_dtoService.GetBaseItemDto(parent, dtoOptions, user)); parent = parent.GetParent(); } return baseItemDtos; } private BaseItem TranslateParentItem(BaseItem item, User user) { if (item.GetParent() is AggregateFolder) { return user.RootFolder.GetChildren(user, true).FirstOrDefault(i => i.PhysicalLocations.Contains(item.Path)); } return item; } /// /// Gets the specified request. /// /// The request. /// System.Object. public object Get(GetCriticReviews request) { var result = GetCriticReviews(request); return ToOptimizedSerializedResultUsingCache(result); } /// /// Gets the specified request. /// /// The request. /// System.Object. public object Get(GetItemCounts request) { var user = string.IsNullOrWhiteSpace(request.UserId) ? null : _userManager.GetUserById(request.UserId); var counts = new ItemCounts { AlbumCount = GetCount(typeof(MusicAlbum), user, request), EpisodeCount = GetCount(typeof(Episode), user, request), GameCount = GetCount(typeof(Game), user, request), GameSystemCount = GetCount(typeof(GameSystem), user, request), MovieCount = GetCount(typeof(Movie), user, request), SeriesCount = GetCount(typeof(Series), user, request), SongCount = GetCount(typeof(Audio), user, request), MusicVideoCount = GetCount(typeof(MusicVideo), user, request), BoxSetCount = GetCount(typeof(BoxSet), user, request), BookCount = GetCount(typeof(Book), user, request) }; return ToOptimizedSerializedResultUsingCache(counts); } private int GetCount(Type type, User user, GetItemCounts request) { var query = new InternalItemsQuery(user) { IncludeItemTypes = new[] { type.Name }, Limit = 0, Recursive = true, IsVirtualItem = false, IsFavorite = request.IsFavorite, DtoOptions = new DtoOptions(false) { EnableImages = false } }; return _libraryManager.GetItemsResult(query).TotalRecordCount; } /// /// Posts the specified request. /// /// The request. public void Post(RefreshLibrary request) { Task.Run(() => { try { _libraryManager.ValidateMediaLibrary(new SimpleProgress(), CancellationToken.None); } catch (Exception ex) { Logger.ErrorException("Error refreshing library", ex); } }); } /// /// Deletes the specified request. /// /// The request. public void Delete(DeleteItems request) { var ids = string.IsNullOrWhiteSpace(request.Ids) ? new string[] { } : request.Ids.Split(','); var tasks = ids.Select(i => { var item = _libraryManager.GetItemById(i); var auth = _authContext.GetAuthorizationInfo(Request); var user = _userManager.GetUserById(auth.UserId); if (!item.CanDelete(user)) { if (ids.Length > 1) { throw new SecurityException("Unauthorized access"); } return Task.FromResult(true); } return item.Delete(new DeleteOptions { DeleteFileLocation = true }); }).ToArray(ids.Length); Task.WaitAll(tasks); } /// /// Deletes the specified request. /// /// The request. public void Delete(DeleteItem request) { Delete(new DeleteItems { Ids = request.Id }); } /// /// Gets the critic reviews async. /// /// The request. /// Task{ItemReviewsResult}. private QueryResult GetCriticReviews(GetCriticReviews request) { var reviews = _itemRepo.GetCriticReviews(new Guid(request.Id)); var reviewsArray = reviews.ToArray(reviews.Count); var result = new QueryResult { TotalRecordCount = reviewsArray.Length }; if (request.StartIndex.HasValue) { reviewsArray = reviewsArray.Skip(request.StartIndex.Value).ToArray(); } if (request.Limit.HasValue) { reviewsArray = reviewsArray.Take(request.Limit.Value).ToArray(); } result.Items = reviewsArray; return result; } public object Get(GetThemeMedia request) { var themeSongs = GetThemeSongs(new GetThemeSongs { InheritFromParent = request.InheritFromParent, Id = request.Id, UserId = request.UserId }); var themeVideos = GetThemeVideos(new GetThemeVideos { InheritFromParent = request.InheritFromParent, Id = request.Id, UserId = request.UserId }); return ToOptimizedSerializedResultUsingCache(new AllThemeMediaResult { ThemeSongsResult = themeSongs, ThemeVideosResult = themeVideos, SoundtrackSongsResult = new ThemeMediaResult() }); } /// /// Gets the specified request. /// /// The request. /// System.Object. public object Get(GetThemeSongs request) { var result = GetThemeSongs(request); return ToOptimizedSerializedResultUsingCache(result); } private ThemeMediaResult GetThemeSongs(GetThemeSongs request) { var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null; var item = string.IsNullOrEmpty(request.Id) ? (!string.IsNullOrWhiteSpace(request.UserId) ? user.RootFolder : (Folder)_libraryManager.RootFolder) : _libraryManager.GetItemById(request.Id); if (item == null) { throw new ResourceNotFoundException("Item not found."); } while (item.ThemeSongIds.Length == 0 && request.InheritFromParent && item.GetParent() != null) { item = item.GetParent(); } var dtoOptions = GetDtoOptions(_authContext, request); var dtos = item.ThemeSongIds.Select(_libraryManager.GetItemById) .Where(i => i != null) .OrderBy(i => i.SortName) .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item)); var items = dtos.ToArray(); return new ThemeMediaResult { Items = items, TotalRecordCount = items.Length, OwnerId = _dtoService.GetDtoId(item) }; } /// /// Gets the specified request. /// /// The request. /// System.Object. public object Get(GetThemeVideos request) { var result = GetThemeVideos(request); return ToOptimizedSerializedResultUsingCache(result); } public ThemeMediaResult GetThemeVideos(GetThemeVideos request) { var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null; var item = string.IsNullOrEmpty(request.Id) ? (!string.IsNullOrWhiteSpace(request.UserId) ? user.RootFolder : (Folder)_libraryManager.RootFolder) : _libraryManager.GetItemById(request.Id); if (item == null) { throw new ResourceNotFoundException("Item not found."); } while (item.ThemeVideoIds.Length == 0 && request.InheritFromParent && item.GetParent() != null) { item = item.GetParent(); } var dtoOptions = GetDtoOptions(_authContext, request); var dtos = item.ThemeVideoIds.Select(_libraryManager.GetItemById) .Where(i => i != null) .OrderBy(i => i.SortName) .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item)); var items = dtos.ToArray(); return new ThemeMediaResult { Items = items, TotalRecordCount = items.Length, OwnerId = _dtoService.GetDtoId(item) }; } private readonly CultureInfo _usCulture = new CultureInfo("en-US"); public object Get(GetYearIndex request) { var includeTypes = string.IsNullOrWhiteSpace(request.IncludeItemTypes) ? new string[] { } : request.IncludeItemTypes.Split(','); var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null; var query = new InternalItemsQuery(user) { IncludeItemTypes = includeTypes, Recursive = true, DtoOptions = new DtoOptions(false) { EnableImages = false } }; var items = _libraryManager.GetItemList(query); var lookup = items .ToLookup(i => i.ProductionYear ?? -1) .OrderBy(i => i.Key) .Select(i => new ItemIndex { ItemCount = i.Count(), Name = i.Key == -1 ? string.Empty : i.Key.ToString(_usCulture) }) .ToList(); return ToOptimizedSerializedResultUsingCache(lookup); } } }