From e2a7e8d97e26059d034e7c338adc0eb191642d80 Mon Sep 17 00:00:00 2001 From: crobibero Date: Fri, 19 Jun 2020 13:10:10 -0600 Subject: Move LibraryService.cs to Jellyfin.Api --- Jellyfin.Api/Controllers/LibraryController.cs | 965 +++++++++++++++++++++++++- 1 file changed, 964 insertions(+), 1 deletion(-) (limited to 'Jellyfin.Api/Controllers/LibraryController.cs') diff --git a/Jellyfin.Api/Controllers/LibraryController.cs b/Jellyfin.Api/Controllers/LibraryController.cs index f45101c0c..e3e2e9489 100644 --- a/Jellyfin.Api/Controllers/LibraryController.cs +++ b/Jellyfin.Api/Controllers/LibraryController.cs @@ -1,10 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Net; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Api.Constants; +using Jellyfin.Api.Extensions; +using Jellyfin.Api.Helpers; +using Jellyfin.Api.Models.LibraryDtos; +using Jellyfin.Data.Entities; +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.Net; +using MediaBrowser.Model.Querying; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.Extensions.Logging; +using Book = MediaBrowser.Controller.Entities.Book; +using Movie = Jellyfin.Data.Entities.Movie; +using MusicAlbum = Jellyfin.Data.Entities.MusicAlbum; namespace Jellyfin.Api.Controllers { @@ -21,6 +52,8 @@ namespace Jellyfin.Api.Controllers private readonly IActivityManager _activityManager; private readonly ILocalizationManager _localization; private readonly ILibraryMonitor _libraryMonitor; + private readonly ILogger _logger; + private readonly IServerConfigurationManager _serverConfigurationManager; /// /// Initializes a new instance of the class. @@ -33,6 +66,8 @@ namespace Jellyfin.Api.Controllers /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. public LibraryController( IProviderManager providerManager, ILibraryManager libraryManager, @@ -41,7 +76,9 @@ namespace Jellyfin.Api.Controllers IAuthorizationContext authContext, IActivityManager activityManager, ILocalizationManager localization, - ILibraryMonitor libraryMonitor) + ILibraryMonitor libraryMonitor, + ILogger logger, + IServerConfigurationManager serverConfigurationManager) { _providerManager = providerManager; _libraryManager = libraryManager; @@ -51,6 +88,932 @@ namespace Jellyfin.Api.Controllers _activityManager = activityManager; _localization = localization; _libraryMonitor = libraryMonitor; + _logger = logger; + _serverConfigurationManager = serverConfigurationManager; + } + + /// + /// Get the original file of an item. + /// + /// The item id. + /// File stream returned. + /// Item not found. + /// A with the original file. + [HttpGet("/Items/{itemId}/File")] + [Authorize(Policy = Policies.DefaultAuthorization)] + public ActionResult GetFile([FromRoute] Guid itemId) + { + var item = _libraryManager.GetItemById(itemId); + if (item == null) + { + return NotFound(); + } + + using var fileStream = new FileStream(item.Path, FileMode.Open, FileAccess.Read); + return File(fileStream, MimeTypes.GetMimeType(item.Path)); + } + + /// + /// Gets critic review for an item. + /// + /// The item id. + /// Optional. The record index to start at. All items with a lower index will be dropped from the results. + /// Optional. The maximum number of records to return. + /// Critic reviews returned. + /// The list of critic reviews. + [HttpGet("/Items/{itemId}/CriticReviews")] + [Authorize(Policy = Policies.DefaultAuthorization)] + [Obsolete("This endpoint is obsolete.")] + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "itemId", Justification = "Imported from ServiceStack")] + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "startIndex", Justification = "Imported from ServiceStack")] + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "limit", Justification = "Imported from ServiceStack")] + public ActionResult> GetCriticReviews( + [FromRoute] Guid itemId, + [FromQuery] int? startIndex, + [FromQuery] int? limit) + { + return new QueryResult(); + } + + /// + /// Get theme songs for an item. + /// + /// The item id. + /// Optional. Filter by user id, and attach user data. + /// Optional. Determines whether or not parent items should be searched for theme media. + /// Theme songs returned. + /// Item not found. + /// The item theme songs. + [HttpGet("/Items/{itemId}/ThemeSongs")] + [Authorize(Policy = Policies.DefaultAuthorization)] + public ActionResult GetThemeSongs( + [FromRoute] Guid itemId, + [FromQuery] Guid userId, + [FromQuery] bool inheritFromParent) + { + var user = !userId.Equals(Guid.Empty) + ? _userManager.GetUserById(userId) + : null; + + var item = itemId.Equals(Guid.Empty) + ? (!userId.Equals(Guid.Empty) + ? _libraryManager.GetUserRootFolder() + : _libraryManager.RootFolder) + : _libraryManager.GetItemById(itemId); + + if (item == null) + { + return NotFound("Item not found."); + } + + IEnumerable themeItems; + + while (true) + { + themeItems = item.GetThemeSongs(); + + if (themeItems.Any() || !inheritFromParent) + { + break; + } + + var parent = item.GetParent(); + if (parent == null) + { + break; + } + + item = parent; + } + + var dtoOptions = new DtoOptions().AddClientFields(Request); + var items = themeItems + .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item)) + .ToArray(); + + return new ThemeMediaResult + { + Items = items, + TotalRecordCount = items.Length, + OwnerId = item.Id + }; + } + + /// + /// Get theme videos for an item. + /// + /// The item id. + /// Optional. Filter by user id, and attach user data. + /// Optional. Determines whether or not parent items should be searched for theme media. + /// Theme videos returned. + /// Item not found. + /// The item theme videos. + [HttpGet("/Items/{itemId}/ThemeVideos")] + [Authorize(Policy = Policies.DefaultAuthorization)] + public ActionResult GetThemeVideos( + [FromRoute] Guid itemId, + [FromQuery] Guid userId, + [FromQuery] bool inheritFromParent) + { + var user = !userId.Equals(Guid.Empty) + ? _userManager.GetUserById(userId) + : null; + + var item = itemId.Equals(Guid.Empty) + ? (!userId.Equals(Guid.Empty) + ? _libraryManager.GetUserRootFolder() + : _libraryManager.RootFolder) + : _libraryManager.GetItemById(itemId); + + if (item == null) + { + return NotFound("Item not found."); + } + + IEnumerable themeItems; + + while (true) + { + themeItems = item.GetThemeVideos(); + + if (themeItems.Any() || !inheritFromParent) + { + break; + } + + var parent = item.GetParent(); + if (parent == null) + { + break; + } + + item = parent; + } + + var dtoOptions = new DtoOptions().AddClientFields(Request); + var items = themeItems + .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item)) + .ToArray(); + + return new ThemeMediaResult + { + Items = items, + TotalRecordCount = items.Length, + OwnerId = item.Id + }; + } + + /// + /// Get theme songs and videos for an item. + /// + /// The item id. + /// Optional. Filter by user id, and attach user data. + /// Optional. Determines whether or not parent items should be searched for theme media. + /// Theme songs and videos returned. + /// Item not found. + /// The item theme videos. + public ActionResult GetThemeMedia( + [FromRoute] Guid itemId, + [FromQuery] Guid userId, + [FromQuery] bool inheritFromParent) + { + var themeSongs = GetThemeSongs( + itemId, + userId, + inheritFromParent); + + var themeVideos = GetThemeVideos( + itemId, + userId, + inheritFromParent); + + return new AllThemeMediaResult + { + ThemeSongsResult = themeSongs?.Value, + ThemeVideosResult = themeVideos?.Value, + SoundtrackSongsResult = new ThemeMediaResult() + }; + } + + /// + /// Starts a library scan. + /// + /// Library scan started. + /// A . + [HttpGet("/Library/Refresh")] + [Authorize(Policy = Policies.RequiresElevation)] + public async Task RefreshLibrary() + { + try + { + await _libraryManager.ValidateMediaLibrary(new SimpleProgress(), CancellationToken.None).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error refreshing library"); + } + + return NoContent(); + } + + /// + /// Deletes an item from the library and filesystem. + /// + /// The item id. + /// Item deleted. + /// Unauthorized access. + /// A . + [HttpDelete("/Items/{itemId}")] + [Authorize(Policy = Policies.DefaultAuthorization)] + public ActionResult DeleteItem(Guid itemId) + { + var item = _libraryManager.GetItemById(itemId); + var auth = _authContext.GetAuthorizationInfo(Request); + var user = auth.User; + + if (!item.CanDelete(user)) + { + return Unauthorized("Unauthorized access"); + } + + _libraryManager.DeleteItem( + item, + new DeleteOptions { DeleteFileLocation = true }, + true); + + return NoContent(); + } + + /// + /// Deletes items from the library and filesystem. + /// + /// The item ids. + /// Items deleted. + /// Unauthorized access. + /// A . + [HttpDelete("/Items")] + [Authorize(Policy = Policies.DefaultAuthorization)] + public ActionResult DeleteItems([FromQuery] string ids) + { + var itemIds = string.IsNullOrWhiteSpace(ids) + ? Array.Empty() + : RequestHelpers.Split(ids, ',', true); + + foreach (var i in itemIds) + { + var item = _libraryManager.GetItemById(i); + var auth = _authContext.GetAuthorizationInfo(Request); + var user = auth.User; + + if (!item.CanDelete(user)) + { + if (ids.Length > 1) + { + return Unauthorized("Unauthorized access"); + } + + continue; + } + + _libraryManager.DeleteItem( + item, + new DeleteOptions { DeleteFileLocation = true }, + true); + } + + return NoContent(); + } + + /// + /// Get item counts. + /// + /// Optional. Get counts from a specific user's library. + /// Optional. Get counts of favorite items. + /// Item counts returned. + /// Item counts. + [HttpGet("/Items/Counts")] + [Authorize(Policy = Policies.DefaultAuthorization)] + public ActionResult GetItemCounts( + [FromQuery] Guid userId, + [FromQuery] bool? isFavorite) + { + var user = userId.Equals(Guid.Empty) + ? null + : _userManager.GetUserById(userId); + + var counts = new ItemCounts + { + AlbumCount = GetCount(typeof(MusicAlbum), user, isFavorite), + EpisodeCount = GetCount(typeof(Episode), user, isFavorite), + MovieCount = GetCount(typeof(Movie), user, isFavorite), + SeriesCount = GetCount(typeof(Series), user, isFavorite), + SongCount = GetCount(typeof(Audio), user, isFavorite), + MusicVideoCount = GetCount(typeof(MusicVideo), user, isFavorite), + BoxSetCount = GetCount(typeof(BoxSet), user, isFavorite), + BookCount = GetCount(typeof(Book), user, isFavorite) + }; + + return counts; + } + + /// + /// Gets all parents of an item. + /// + /// The item id. + /// Optional. Filter by user id, and attach user data. + /// Item parents returned. + /// Item not found. + /// Item parents. + [HttpGet("/Items/{itemId}/Ancestors")] + [Authorize(Policy = Policies.DefaultAuthorization)] + public ActionResult> GetAncestors([FromRoute] Guid itemId, [FromQuery] Guid userId) + { + var item = _libraryManager.GetItemById(itemId); + + if (item == null) + { + return NotFound("Item not found"); + } + + var baseItemDtos = new List(); + + var user = !userId.Equals(Guid.Empty) + ? _userManager.GetUserById(userId) + : null; + + var dtoOptions = new DtoOptions().AddClientFields(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; + } + + /// + /// Gets a list of physical paths from virtual folders. + /// + /// Physical paths returned. + /// List of physical paths. + [HttpGet("/Library/PhysicalPaths")] + [Authorize(Policy = Policies.RequiresElevation)] + public ActionResult> GetPhysicalPaths() + { + return Ok(_libraryManager.RootFolder.Children + .SelectMany(c => c.PhysicalLocations)); + } + + /// + /// Gets all user media folders. + /// + /// Optional. Filter by folders that are marked hidden, or not. + /// List of user media folders. + [HttpGet("/Library/MediaFolders")] + [Authorize(Policy = Policies.DefaultAuthorization)] + public ActionResult> GetMediaFolders([FromQuery] bool? isHidden) + { + var items = _libraryManager.GetUserRootFolder().Children.Concat(_libraryManager.RootFolder.VirtualChildren).OrderBy(i => i.SortName).ToList(); + + if (isHidden.HasValue) + { + var val = isHidden.Value; + + items = items.Where(i => i.IsHidden == val).ToList(); + } + + var dtoOptions = new DtoOptions().AddClientFields(Request); + var result = new QueryResult + { + TotalRecordCount = items.Count, + Items = items.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions)).ToArray() + }; + + return result; + } + + /// + /// Reports that new episodes of a series have been added by an external source. + /// + /// The tvdbId. + /// Report success. + /// A . + [HttpPost("/Library/Series/Added")] + [HttpPost("/Library/Series/Updated")] + [Authorize(Policy = Policies.DefaultAuthorization)] + public ActionResult PostUpdatedSeries([FromQuery] string tvdbId) + { + var series = _libraryManager.GetItemList(new InternalItemsQuery + { + IncludeItemTypes = new[] { nameof(Series) }, + DtoOptions = new DtoOptions(false) + { + EnableImages = false + } + }).Where(i => string.Equals(tvdbId, i.GetProviderId(MediaBrowser.Model.Entities.MetadataProvider.Tvdb), StringComparison.OrdinalIgnoreCase)).ToArray(); + + foreach (var item in series) + { + _libraryMonitor.ReportFileSystemChanged(item.Path); + } + + return NoContent(); + } + + /// + /// Reports that new movies have been added by an external source. + /// + /// The tmdbId. + /// The imdbId. + /// Report success. + /// A . + [HttpPost("/Library/Movies/Added")] + [HttpPost("/Library/Movies/Updated")] + [Authorize(Policy = Policies.DefaultAuthorization)] + public ActionResult PostUpdatedMovies([FromRoute] string tmdbId, [FromRoute] string imdbId) + { + var movies = _libraryManager.GetItemList(new InternalItemsQuery + { + IncludeItemTypes = new[] { nameof(Movie) }, + DtoOptions = new DtoOptions(false) + { + EnableImages = false + } + }); + + if (!string.IsNullOrWhiteSpace(imdbId)) + { + movies = movies.Where(i => string.Equals(imdbId, i.GetProviderId(MediaBrowser.Model.Entities.MetadataProvider.Imdb), StringComparison.OrdinalIgnoreCase)).ToList(); + } + else if (!string.IsNullOrWhiteSpace(tmdbId)) + { + movies = movies.Where(i => string.Equals(tmdbId, i.GetProviderId(MediaBrowser.Model.Entities.MetadataProvider.Tmdb), StringComparison.OrdinalIgnoreCase)).ToList(); + } + else + { + movies = new List(); + } + + foreach (var item in movies) + { + _libraryMonitor.ReportFileSystemChanged(item.Path); + } + + return NoContent(); + } + + /// + /// Reports that new movies have been added by an external source. + /// + /// A list of updated media paths. + /// Report success. + /// A . + [HttpPost("/Library/Media/Updated")] + [Authorize(Policy = Policies.DefaultAuthorization)] + public ActionResult PostUpdatedMedia([FromBody, BindRequired] MediaUpdateInfoDto[] updates) + { + foreach (var item in updates) + { + _libraryMonitor.ReportFileSystemChanged(item.Path); + } + + return NoContent(); + } + + /// + /// Downloads item media. + /// + /// The item id. + /// Media downloaded. + /// Item not found. + /// A containing the media stream. + /// User can't download or item can't be downloaded. + [HttpGet("/Items/{itemId}/Download")] + [Authorize(Policy = Policies.Download)] + public ActionResult GetDownload([FromRoute] Guid itemId) + { + var item = _libraryManager.GetItemById(itemId); + if (item == null) + { + return NotFound(); + } + + 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"); + } + } + + 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, StringComparison.Ordinal); + 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 + filename = WebUtility.UrlEncode(filename); + } + } + + // TODO determine non-ASCII validity. + using var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read); + return File(fileStream, MimeTypes.GetMimeType(path), filename); + } + + /// + /// Gets similar items. + /// + /// The item id. + /// Exclude artist ids. + /// (Unused) Optional. include image information in output. + /// (Unused) Optional. include user data. + /// (Unused) Optional. the max number of images to return, per image type. + /// (Unused) Optional. The image types to include in the output. + /// Optional. Filter by user id, and attach user data. + /// Optional. The maximum number of records to return. + /// Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls. + /// A containing the similar items. + [HttpGet("/Artists/{itemId}/Similar")] + [HttpGet("/Items/{itemId}/Similar")] + [HttpGet("/Albums/{itemId}/Similar")] + [HttpGet("/Shows/{itemId}/Similar")] + [HttpGet("/Movies/{itemId}/Similar")] + [HttpGet("/Trailers/{itemId}/Similar")] + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "enableImages", Justification = "Imported from ServiceStack")] + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "enableUserData", Justification = "Imported from ServiceStack")] + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "imageTypeLimit", Justification = "Imported from ServiceStack")] + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "enableImageTypes", Justification = "Imported from ServiceStack")] + public ActionResult> GetSimilarItems( + [FromRoute] Guid itemId, + [FromQuery] string excludeArtistIds, + [FromQuery] bool? enableImages, + [FromQuery] bool? enableUserData, + [FromQuery] int? imageTypeLimit, + [FromQuery] string enableImageTypes, + [FromQuery] Guid userId, + [FromQuery] int? limit, + [FromQuery] string fields) + { + var item = itemId.Equals(Guid.Empty) + ? (!userId.Equals(Guid.Empty) + ? _libraryManager.GetUserRootFolder() + : _libraryManager.RootFolder) + : _libraryManager.GetItemById(itemId); + + var program = item as IHasProgramAttributes; + if (item is MediaBrowser.Controller.Entities.Movies.Movie || (program != null && program.IsMovie) || item is Trailer) + { + /* + * // TODO + return new MoviesService( + _moviesServiceLogger, + ServerConfigurationManager, + ResultFactory, + _userManager, + _libraryManager, + _dtoService, + _authContext) + { + Request = Request, + }.GetSimilarItemsResult(request);*/ + } + + if (program != null && program.IsSeries) + { + return GetSimilarItemsResult( + item, + excludeArtistIds, + userId, + limit, + fields, + new[] { nameof(Series) }); + } + + if (item is MediaBrowser.Controller.Entities.TV.Episode || (item is IItemByName && !(item is MusicArtist))) + { + return new QueryResult(); + } + + return GetSimilarItemsResult( + item, + excludeArtistIds, + userId, + limit, + fields, + new[] { item.GetType().Name }); + } + + /// + /// Gets the library options info. + /// + /// Library content type. + /// Whether this is a new library. + /// Library options info returned. + /// Library options info. + [HttpGet("/Libraries/AvailableOptions")] + [Authorize(Policy = Policies.FirstTimeSetupOrElevated)] + public ActionResult GetLibraryOptionsInfo([FromQuery] string libraryContentType, [FromQuery] bool isNewLibrary) + { + var result = new LibraryOptionsResultDto(); + + var types = GetRepresentativeItemTypes(libraryContentType); + 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 LibraryOptionInfoDto + { + 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 LibraryOptionInfoDto + { + 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 LibraryOptionInfoDto + { + 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 LibraryTypeOptionsDto + { + 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 LibraryOptionInfoDto + { + 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 LibraryOptionInfoDto + { + 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; + } + + private int GetCount(Type type, User? user, bool? isFavorite) + { + var query = new InternalItemsQuery(user) + { + IncludeItemTypes = new[] { type.Name }, + Limit = 0, + Recursive = true, + IsVirtualItem = false, + IsFavorite = isFavorite, + DtoOptions = new DtoOptions(false) + { + EnableImages = false + } + }; + + return _libraryManager.GetItemsResult(query).TotalRecordCount; + } + + 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; + } + + private void LogDownload(BaseItem item, User user, AuthorizationInfo auth) + { + try + { + _activityManager.Create(new ActivityLog( + string.Format(CultureInfo.InvariantCulture, _localization.GetLocalizedString("UserDownloadingItemWithValues"), user.Username, item.Name), + "UserDownloadingContent", + auth.UserId) + { + ShortOverview = string.Format(CultureInfo.InvariantCulture, _localization.GetLocalizedString("AppDeviceValues"), auth.Client, auth.Device), + }); + } + catch + { + // Logged at lower levels + } + } + + private QueryResult GetSimilarItemsResult( + BaseItem item, + string excludeArtistIds, + Guid userId, + int? limit, + string fields, + string[] includeItemTypes) + { + var user = !userId.Equals(Guid.Empty) ? _userManager.GetUserById(userId) : null; + var dtoOptions = new DtoOptions() + .AddItemFields(fields) + .AddClientFields(Request); + + var query = new InternalItemsQuery(user) + { + Limit = limit, + IncludeItemTypes = includeItemTypes, + SimilarTo = item, + DtoOptions = dtoOptions, + EnableTotalRecordCount = false + }; + + // ExcludeArtistIds + if (!string.IsNullOrEmpty(excludeArtistIds)) + { + query.ExcludeArtistIds = RequestHelpers.GetGuids(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; + } + + private static 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(); + + return metadataOptions.Length == 0 || 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)); } } } -- cgit v1.2.3