using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using Jellyfin.Data.Entities; using MediaBrowser.Api.Movies; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Progress; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Activity; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Services; using Microsoft.Extensions.Logging; using Microsoft.Net.Http.Headers; using Book = MediaBrowser.Controller.Entities.Book; using Episode = MediaBrowser.Controller.Entities.TV.Episode; using MetadataProvider = MediaBrowser.Model.Entities.MetadataProvider; using Movie = MediaBrowser.Controller.Entities.Movies.Movie; using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum; using Series = MediaBrowser.Controller.Entities.TV.Series; 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 Guid 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 Guid 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 Guid 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 = "query", 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 Guid 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 Guid 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; } } /// /// 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 = "POST")] 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 = "POST")] public string TmdbId { get; set; } [ApiMember(Name = "ImdbId", Description = "Imdb Id", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "POST")] public string ImdbId { get; set; } } public class MediaUpdateInfo { public string Path { get; set; } // Created, Modified, Deleted public string UpdateType { get; set; } } [Route("/Library/Media/Updated", "POST", Summary = "Reports that new movies have been added by an external source")] [Authenticated] public class PostUpdatedMedia : IReturnVoid { [ApiMember(Name = "Updates", Description = "A list of updated media paths", IsRequired = false, DataType = "string", ParameterType = "body", Verb = "POST")] public List Updates { 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("/Artists/{Id}/Similar", "GET", Summary = "Finds albums similar to a given album.")] [Route("/Items/{Id}/Similar", "GET", Summary = "Gets similar items")] [Route("/Albums/{Id}/Similar", "GET", Summary = "Finds albums similar to a given album.")] [Route("/Shows/{Id}/Similar", "GET", Summary = "Finds tv shows similar to a given one.")] [Route("/Movies/{Id}/Similar", "GET", Summary = "Finds movies and trailers similar to a given movie.")] [Route("/Trailers/{Id}/Similar", "GET", Summary = "Finds movies and trailers similar to a given trailer.")] [Authenticated] public class GetSimilarItems : BaseGetSimilarItemsFromItem { } [Route("/Libraries/AvailableOptions", "GET")] [Authenticated(AllowBeforeStartupWizard = true)] public class GetLibraryOptionsInfo : IReturn { public string LibraryContentType { get; set; } public bool IsNewLibrary { get; set; } } public class LibraryOptionInfo { public string Name { get; set; } public bool DefaultEnabled { get; set; } } public class LibraryOptionsResult { public LibraryOptionInfo[] MetadataSavers { get; set; } public LibraryOptionInfo[] MetadataReaders { get; set; } public LibraryOptionInfo[] SubtitleFetchers { get; set; } public LibraryTypeOptions[] TypeOptions { get; set; } } public class LibraryTypeOptions { public string Type { get; set; } public LibraryOptionInfo[] MetadataFetchers { get; set; } public LibraryOptionInfo[] ImageFetchers { get; set; } public ImageType[] SupportedImageTypes { get; set; } public ImageOption[] DefaultImageOptions { get; set; } } /// /// Class LibraryService /// public class LibraryService : BaseApiService { private readonly IProviderManager _providerManager; private readonly ILibraryManager _libraryManager; private readonly IUserManager _userManager; private readonly IDtoService _dtoService; private readonly IAuthorizationContext _authContext; private readonly IActivityManager _activityManager; private readonly ILocalizationManager _localization; private readonly ILibraryMonitor _libraryMonitor; private readonly ILogger _moviesServiceLogger; /// /// Initializes a new instance of the class. /// public LibraryService( ILogger logger, ILogger moviesServiceLogger, IServerConfigurationManager serverConfigurationManager, IHttpResultFactory httpResultFactory, IProviderManager providerManager, ILibraryManager libraryManager, IUserManager userManager, IDtoService dtoService, IAuthorizationContext authContext, IActivityManager activityManager, ILocalizationManager localization, ILibraryMonitor libraryMonitor) : base(logger, serverConfigurationManager, httpResultFactory) { _providerManager = providerManager; _libraryManager = libraryManager; _userManager = userManager; _dtoService = dtoService; _authContext = authContext; _activityManager = activityManager; _localization = localization; _libraryMonitor = libraryMonitor; _moviesServiceLogger = moviesServiceLogger; } // Content Types available for each Library private string[] GetRepresentativeItemTypes(string contentType) { return contentType switch { CollectionType.BoxSets => new[] {"BoxSet"}, CollectionType.Playlists => new[] {"Playlist"}, CollectionType.Movies => new[] {"Movie"}, CollectionType.TvShows => new[] {"Series", "Season", "Episode"}, CollectionType.Books => new[] {"Book"}, CollectionType.Music => new[] {"MusicArtist", "MusicAlbum", "Audio", "MusicVideo"}, CollectionType.HomeVideos => new[] {"Video", "Photo"}, CollectionType.Photos => new[] {"Video", "Photo"}, CollectionType.MusicVideos => new[] {"MusicVideo"}, _ => new[] {"Series", "Season", "Episode", "Movie"} }; } private bool IsSaverEnabledByDefault(string name, string[] itemTypes, bool isNewLibrary) { if (isNewLibrary) { return false; } var metadataOptions = ServerConfigurationManager.Configuration.MetadataOptions .Where(i => itemTypes.Contains(i.ItemType ?? string.Empty, StringComparer.OrdinalIgnoreCase)) .ToArray(); if (metadataOptions.Length == 0) { return true; } return metadataOptions.Any(i => !i.DisabledMetadataSavers.Contains(name, StringComparer.OrdinalIgnoreCase)); } private bool IsMetadataFetcherEnabledByDefault(string name, string type, bool isNewLibrary) { if (isNewLibrary) { if (string.Equals(name, "TheMovieDb", StringComparison.OrdinalIgnoreCase)) { return !(string.Equals(type, "Season", StringComparison.OrdinalIgnoreCase) || string.Equals(type, "Episode", StringComparison.OrdinalIgnoreCase) || string.Equals(type, "MusicVideo", StringComparison.OrdinalIgnoreCase)); } return string.Equals(name, "TheTVDB", StringComparison.OrdinalIgnoreCase) || string.Equals(name, "TheAudioDB", StringComparison.OrdinalIgnoreCase) || string.Equals(name, "MusicBrainz", StringComparison.OrdinalIgnoreCase); } var metadataOptions = ServerConfigurationManager.Configuration.MetadataOptions .Where(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase)) .ToArray(); return metadataOptions.Length == 0 || metadataOptions.Any(i => !i.DisabledMetadataFetchers.Contains(name, StringComparer.OrdinalIgnoreCase)); } private bool IsImageFetcherEnabledByDefault(string name, string type, bool isNewLibrary) { if (isNewLibrary) { if (string.Equals(name, "TheMovieDb", StringComparison.OrdinalIgnoreCase)) { return !string.Equals(type, "Series", StringComparison.OrdinalIgnoreCase) && !string.Equals(type, "Season", StringComparison.OrdinalIgnoreCase) && !string.Equals(type, "Episode", StringComparison.OrdinalIgnoreCase) && !string.Equals(type, "MusicVideo", StringComparison.OrdinalIgnoreCase); } return string.Equals(name, "TheTVDB", StringComparison.OrdinalIgnoreCase) || string.Equals(name, "Screen Grabber", StringComparison.OrdinalIgnoreCase) || string.Equals(name, "TheAudioDB", StringComparison.OrdinalIgnoreCase) || string.Equals(name, "Image Extractor", StringComparison.OrdinalIgnoreCase); } var metadataOptions = ServerConfigurationManager.Configuration.MetadataOptions .Where(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase)) .ToArray(); if (metadataOptions.Length == 0) { return true; } return metadataOptions.Any(i => !i.DisabledImageFetchers.Contains(name, StringComparer.OrdinalIgnoreCase)); } public object Get(GetLibraryOptionsInfo request) { var result = new LibraryOptionsResult(); var types = GetRepresentativeItemTypes(request.LibraryContentType); var isNewLibrary = request.IsNewLibrary; var typesList = types.ToList(); var plugins = _providerManager.GetAllMetadataPlugins() .Where(i => types.Contains(i.ItemType, StringComparer.OrdinalIgnoreCase)) .OrderBy(i => typesList.IndexOf(i.ItemType)) .ToList(); result.MetadataSavers = plugins .SelectMany(i => i.Plugins.Where(p => p.Type == MetadataPluginType.MetadataSaver)) .Select(i => new LibraryOptionInfo { Name = i.Name, DefaultEnabled = IsSaverEnabledByDefault(i.Name, types, isNewLibrary) }) .GroupBy(i => i.Name, StringComparer.OrdinalIgnoreCase) .Select(x => x.First()) .ToArray(); result.MetadataReaders = plugins .SelectMany(i => i.Plugins.Where(p => p.Type == MetadataPluginType.LocalMetadataProvider)) .Select(i => new LibraryOptionInfo { Name = i.Name, DefaultEnabled = true }) .GroupBy(i => i.Name, StringComparer.OrdinalIgnoreCase) .Select(x => x.First()) .ToArray(); result.SubtitleFetchers = plugins .SelectMany(i => i.Plugins.Where(p => p.Type == MetadataPluginType.SubtitleFetcher)) .Select(i => new LibraryOptionInfo { Name = i.Name, DefaultEnabled = true }) .GroupBy(i => i.Name, StringComparer.OrdinalIgnoreCase) .Select(x => x.First()) .ToArray(); var typeOptions = new List(); foreach (var type in types) { TypeOptions.DefaultImageOptions.TryGetValue(type, out var defaultImageOptions); typeOptions.Add(new LibraryTypeOptions { Type = type, MetadataFetchers = plugins .Where(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase)) .SelectMany(i => i.Plugins.Where(p => p.Type == MetadataPluginType.MetadataFetcher)) .Select(i => new LibraryOptionInfo { Name = i.Name, DefaultEnabled = IsMetadataFetcherEnabledByDefault(i.Name, type, isNewLibrary) }) .GroupBy(i => i.Name, StringComparer.OrdinalIgnoreCase) .Select(x => x.First()) .ToArray(), ImageFetchers = plugins .Where(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase)) .SelectMany(i => i.Plugins.Where(p => p.Type == MetadataPluginType.ImageFetcher)) .Select(i => new LibraryOptionInfo { Name = i.Name, DefaultEnabled = IsImageFetcherEnabledByDefault(i.Name, type, isNewLibrary) }) .GroupBy(i => i.Name, StringComparer.OrdinalIgnoreCase) .Select(x => x.First()) .ToArray(), SupportedImageTypes = plugins .Where(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase)) .SelectMany(i => i.SupportedImageTypes ?? Array.Empty()) .Distinct() .ToArray(), DefaultImageOptions = defaultImageOptions ?? Array.Empty() }); } result.TypeOptions = typeOptions.ToArray(); return result; } public object Get(GetSimilarItems request) { var item = string.IsNullOrEmpty(request.Id) ? (!request.UserId.Equals(Guid.Empty) ? _libraryManager.GetUserRootFolder() : _libraryManager.RootFolder) : _libraryManager.GetItemById(request.Id); var program = item as IHasProgramAttributes; if (item is Movie || (program != null && program.IsMovie) || item is Trailer) { return new MoviesService( _moviesServiceLogger, ServerConfigurationManager, ResultFactory, _userManager, _libraryManager, _dtoService, _authContext) { Request = Request, }.GetSimilarItemsResult(request); } if (program != null && program.IsSeries) { return GetSimilarItemsResult(request, new[] { typeof(Series).Name }); } if (item is Episode || (item is IItemByName && !(item is MusicArtist))) { return new QueryResult(); } return GetSimilarItemsResult(request, new[] { item.GetType().Name }); } private QueryResult GetSimilarItemsResult(BaseGetSimilarItemsFromItem request, string[] includeItemTypes) { var user = !request.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(request.UserId) : null; var item = string.IsNullOrEmpty(request.Id) ? (!request.UserId.Equals(Guid.Empty) ? _libraryManager.GetUserRootFolder() : _libraryManager.RootFolder) : _libraryManager.GetItemById(request.Id); var dtoOptions = GetDtoOptions(_authContext, request); var query = new InternalItemsQuery(user) { Limit = request.Limit, IncludeItemTypes = includeItemTypes, SimilarTo = item, DtoOptions = dtoOptions, EnableTotalRecordCount = false }; // ExcludeArtistIds if (!string.IsNullOrEmpty(request.ExcludeArtistIds)) { query.ExcludeArtistIds = GetGuids(request.ExcludeArtistIds); } List itemsResult; if (item is MusicArtist) { query.IncludeItemTypes = Array.Empty(); itemsResult = _libraryManager.GetArtists(query).Items.Select(i => i.Item1).ToList(); } else { itemsResult = _libraryManager.GetItemList(query); } var returnList = _dtoService.GetBaseItemDtos(itemsResult, dtoOptions, user); var result = new QueryResult { Items = returnList, TotalRecordCount = itemsResult.Count }; return result; } 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 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(MetadataProvider.Tvdb), StringComparison.OrdinalIgnoreCase)).ToArray(); foreach (var item in series) { _libraryMonitor.ReportFileSystemChanged(item.Path); } } public void Post(PostUpdatedMedia request) { if (request.Updates != null) { foreach (var item in request.Updates) { _libraryMonitor.ReportFileSystemChanged(item.Path); } } } 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(MetadataProvider.Imdb), StringComparison.OrdinalIgnoreCase)).ToList(); } else if (!string.IsNullOrWhiteSpace(request.TmdbId)) { movies = movies.Where(i => string.Equals(request.TmdbId, i.GetProviderId(MetadataProvider.Tmdb), StringComparison.OrdinalIgnoreCase)).ToList(); } else { movies = new List(); } foreach (var item in movies) { _libraryMonitor.ReportFileSystemChanged(item.Path); } } public Task Get(GetDownload request) { var item = _libraryManager.GetItemById(request.Id); var auth = _authContext.GetAuthorizationInfo(Request); var user = auth.User; 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)) { // Kestrel doesn't support non-ASCII characters in headers if (Regex.IsMatch(filename, @"[^\p{IsBasicLatin}]")) { // Manually encoding non-ASCII characters, following https://tools.ietf.org/html/rfc5987#section-3.2.2 headers[HeaderNames.ContentDisposition] = "attachment; filename*=UTF-8''" + WebUtility.UrlEncode(filename); } else { headers[HeaderNames.ContentDisposition] = "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 ActivityLog( string.Format(_localization.GetLocalizedString("UserDownloadingItemWithValues"), user.Username, item.Name), "UserDownloadingContent", auth.UserId) { ShortOverview = string.Format(_localization.GetLocalizedString("AppDeviceValues"), auth.Client, auth.Device), }); } catch { // Logged at lower levels } } public Task Get(GetFile request) { var item = _libraryManager.GetItemById(request.Id); 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 ToOptimizedResult(result); } /// /// Gets the specified request. /// /// The request. /// System.Object. public object Get(GetAncestors request) { var result = GetAncestors(request); return ToOptimizedResult(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 = !request.UserId.Equals(Guid.Empty) ? _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) { return item.GetParent() is AggregateFolder ? _libraryManager.GetUserRootFolder().GetChildren(user, true) .FirstOrDefault(i => i.PhysicalLocations.Contains(item.Path)) : item; } /// /// Gets the specified request. /// /// The request. /// System.Object. public object Get(GetCriticReviews request) { return new QueryResult(); } /// /// Gets the specified request. /// /// The request. /// System.Object. public object Get(GetItemCounts request) { var user = request.UserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(request.UserId); var counts = new ItemCounts { AlbumCount = GetCount(typeof(MusicAlbum), user, request), EpisodeCount = GetCount(typeof(Episode), 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 ToOptimizedResult(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 async Task Post(RefreshLibrary request) { try { await _libraryManager.ValidateMediaLibrary(new SimpleProgress(), CancellationToken.None).ConfigureAwait(false); } catch (Exception ex) { Logger.LogError(ex, "Error refreshing library"); } } /// /// Deletes the specified request. /// /// The request. public void Delete(DeleteItems request) { var ids = string.IsNullOrWhiteSpace(request.Ids) ? Array.Empty() : request.Ids.Split(','); foreach (var i in ids) { var item = _libraryManager.GetItemById(i); var auth = _authContext.GetAuthorizationInfo(Request); var user = auth.User; if (!item.CanDelete(user)) { if (ids.Length > 1) { throw new SecurityException("Unauthorized access"); } continue; } _libraryManager.DeleteItem(item, new DeleteOptions { DeleteFileLocation = true }, true); } } /// /// Deletes the specified request. /// /// The request. public void Delete(DeleteItem request) { Delete(new DeleteItems { Ids = request.Id }); } 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 ToOptimizedResult(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 ToOptimizedResult(result); } private ThemeMediaResult GetThemeSongs(GetThemeSongs request) { var user = !request.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(request.UserId) : null; var item = string.IsNullOrEmpty(request.Id) ? (!request.UserId.Equals(Guid.Empty) ? _libraryManager.GetUserRootFolder() : _libraryManager.RootFolder) : _libraryManager.GetItemById(request.Id); if (item == null) { throw new ResourceNotFoundException("Item not found."); } IEnumerable themeItems; while (true) { themeItems = item.GetThemeSongs(); if (themeItems.Any() || !request.InheritFromParent) { break; } var parent = item.GetParent(); if (parent == null) { break; } item = parent; } var dtoOptions = GetDtoOptions(_authContext, request); var items = themeItems .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item)) .ToArray(); return new ThemeMediaResult { Items = items, TotalRecordCount = items.Length, OwnerId = item.Id }; } /// /// Gets the specified request. /// /// The request. /// System.Object. public object Get(GetThemeVideos request) { return ToOptimizedResult(GetThemeVideos(request)); } public ThemeMediaResult GetThemeVideos(GetThemeVideos request) { var user = !request.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(request.UserId) : null; var item = string.IsNullOrEmpty(request.Id) ? (!request.UserId.Equals(Guid.Empty) ? _libraryManager.GetUserRootFolder() : _libraryManager.RootFolder) : _libraryManager.GetItemById(request.Id); if (item == null) { throw new ResourceNotFoundException("Item not found."); } IEnumerable themeItems; while (true) { themeItems = item.GetThemeVideos(); if (themeItems.Any() || !request.InheritFromParent) { break; } var parent = item.GetParent(); if (parent == null) { break; } item = parent; } var dtoOptions = GetDtoOptions(_authContext, request); var items = themeItems .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item)) .ToArray(); return new ThemeMediaResult { Items = items, TotalRecordCount = items.Length, OwnerId = item.Id }; } } }