< Summary - Jellyfin

Information
Class: Jellyfin.Api.Controllers.LibraryController
Assembly: Jellyfin.Api
File(s): /srv/git/jellyfin/Jellyfin.Api/Controllers/LibraryController.cs
Line coverage
24%
Covered lines: 97
Uncovered lines: 306
Coverable lines: 403
Total lines: 1034
Line coverage: 24%
Branch coverage
14%
Covered branches: 27
Total branches: 192
Branch coverage: 14%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
GetFile(...)50%2.06275%
GetThemeSongs(...)16.66%100.331836.66%
GetThemeVideos(...)16.66%100.331836.66%
GetThemeMedia(...)37.5%9.49871.42%
DeleteItem(...)33.33%27.011252.94%
DeleteItems(...)44.44%46.461855.55%
GetItemCounts(...)0%620%
GetAncestors(...)20%30.361041.17%
GetPhysicalPaths()100%210%
GetMediaFolders(...)0%620%
PostUpdatedSeries(...)0%620%
PostUpdatedMovies(...)0%4260%
PostUpdatedMedia(...)0%2040%
GetSimilarItems(...)8.82%553.573423.4%
GetLibraryOptionsInfo(...)0%2040%
GetCount(...)100%210%
TranslateParentItem(...)0%620%
GetRepresentativeItemTypes(...)0%210140%
IsSaverEnabledByDefault(...)0%2040%
IsMetadataFetcherEnabledByDefault(...)0%210140%
IsImageFetcherEnabledByDefault(...)0%342180%

File(s)

/srv/git/jellyfin/Jellyfin.Api/Controllers/LibraryController.cs

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.ComponentModel.DataAnnotations;
 4using System.Globalization;
 5using System.IO;
 6using System.Linq;
 7using System.Threading;
 8using System.Threading.Tasks;
 9using Jellyfin.Api.Attributes;
 10using Jellyfin.Api.Extensions;
 11using Jellyfin.Api.Helpers;
 12using Jellyfin.Api.ModelBinders;
 13using Jellyfin.Api.Models.LibraryDtos;
 14using Jellyfin.Data.Entities;
 15using Jellyfin.Data.Enums;
 16using Jellyfin.Extensions;
 17using MediaBrowser.Common.Api;
 18using MediaBrowser.Common.Extensions;
 19using MediaBrowser.Controller.Configuration;
 20using MediaBrowser.Controller.Dto;
 21using MediaBrowser.Controller.Entities;
 22using MediaBrowser.Controller.Entities.Audio;
 23using MediaBrowser.Controller.Entities.Movies;
 24using MediaBrowser.Controller.Entities.TV;
 25using MediaBrowser.Controller.Library;
 26using MediaBrowser.Controller.Providers;
 27using MediaBrowser.Model.Activity;
 28using MediaBrowser.Model.Configuration;
 29using MediaBrowser.Model.Dto;
 30using MediaBrowser.Model.Entities;
 31using MediaBrowser.Model.Globalization;
 32using MediaBrowser.Model.Net;
 33using MediaBrowser.Model.Querying;
 34using Microsoft.AspNetCore.Authorization;
 35using Microsoft.AspNetCore.Http;
 36using Microsoft.AspNetCore.Mvc;
 37using Microsoft.Extensions.Logging;
 38
 39namespace Jellyfin.Api.Controllers;
 40
 41/// <summary>
 42/// Library Controller.
 43/// </summary>
 44[Route("")]
 45public class LibraryController : BaseJellyfinApiController
 46{
 47    private readonly IProviderManager _providerManager;
 48    private readonly ILibraryManager _libraryManager;
 49    private readonly IUserManager _userManager;
 50    private readonly IDtoService _dtoService;
 51    private readonly IActivityManager _activityManager;
 52    private readonly ILocalizationManager _localization;
 53    private readonly ILibraryMonitor _libraryMonitor;
 54    private readonly ILogger<LibraryController> _logger;
 55    private readonly IServerConfigurationManager _serverConfigurationManager;
 56
 57    /// <summary>
 58    /// Initializes a new instance of the <see cref="LibraryController"/> class.
 59    /// </summary>
 60    /// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param>
 61    /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
 62    /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
 63    /// <param name="dtoService">Instance of the <see cref="IDtoService"/> interface.</param>
 64    /// <param name="activityManager">Instance of the <see cref="IActivityManager"/> interface.</param>
 65    /// <param name="localization">Instance of the <see cref="ILocalizationManager"/> interface.</param>
 66    /// <param name="libraryMonitor">Instance of the <see cref="ILibraryMonitor"/> interface.</param>
 67    /// <param name="logger">Instance of the <see cref="ILogger{LibraryController}"/> interface.</param>
 68    /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</p
 1469    public LibraryController(
 1470        IProviderManager providerManager,
 1471        ILibraryManager libraryManager,
 1472        IUserManager userManager,
 1473        IDtoService dtoService,
 1474        IActivityManager activityManager,
 1475        ILocalizationManager localization,
 1476        ILibraryMonitor libraryMonitor,
 1477        ILogger<LibraryController> logger,
 1478        IServerConfigurationManager serverConfigurationManager)
 79    {
 1480        _providerManager = providerManager;
 1481        _libraryManager = libraryManager;
 1482        _userManager = userManager;
 1483        _dtoService = dtoService;
 1484        _activityManager = activityManager;
 1485        _localization = localization;
 1486        _libraryMonitor = libraryMonitor;
 1487        _logger = logger;
 1488        _serverConfigurationManager = serverConfigurationManager;
 1489    }
 90
 91    /// <summary>
 92    /// Get the original file of an item.
 93    /// </summary>
 94    /// <param name="itemId">The item id.</param>
 95    /// <response code="200">File stream returned.</response>
 96    /// <response code="404">Item not found.</response>
 97    /// <returns>A <see cref="FileStreamResult"/> with the original file.</returns>
 98    [HttpGet("Items/{itemId}/File")]
 99    [Authorize]
 100    [ProducesResponseType(StatusCodes.Status200OK)]
 101    [ProducesResponseType(StatusCodes.Status404NotFound)]
 102    [ProducesFile("video/*", "audio/*")]
 103    public ActionResult GetFile([FromRoute, Required] Guid itemId)
 104    {
 1105        var item = _libraryManager.GetItemById<BaseItem>(itemId, User.GetUserId());
 1106        if (item is null)
 107        {
 1108            return NotFound();
 109        }
 110
 0111        return PhysicalFile(item.Path, MimeTypes.GetMimeType(item.Path), true);
 112    }
 113
 114    /// <summary>
 115    /// Gets critic review for an item.
 116    /// </summary>
 117    /// <response code="200">Critic reviews returned.</response>
 118    /// <returns>The list of critic reviews.</returns>
 119    [HttpGet("Items/{itemId}/CriticReviews")]
 120    [Authorize]
 121    [Obsolete("This endpoint is obsolete.")]
 122    [ProducesResponseType(StatusCodes.Status200OK)]
 123    public ActionResult<QueryResult<BaseItemDto>> GetCriticReviews()
 124    {
 125        return new QueryResult<BaseItemDto>();
 126    }
 127
 128    /// <summary>
 129    /// Get theme songs for an item.
 130    /// </summary>
 131    /// <param name="itemId">The item id.</param>
 132    /// <param name="userId">Optional. Filter by user id, and attach user data.</param>
 133    /// <param name="inheritFromParent">Optional. Determines whether or not parent items should be searched for theme me
 134    /// <param name="sortBy">Optional. Specify one or more sort orders, comma delimited. Options: Album, AlbumArtist, Ar
 135    /// <param name="sortOrder">Optional. Sort Order - Ascending, Descending.</param>
 136    /// <response code="200">Theme songs returned.</response>
 137    /// <response code="404">Item not found.</response>
 138    /// <returns>The item theme songs.</returns>
 139    [HttpGet("Items/{itemId}/ThemeSongs")]
 140    [Authorize]
 141    [ProducesResponseType(StatusCodes.Status200OK)]
 142    [ProducesResponseType(StatusCodes.Status404NotFound)]
 143    public ActionResult<ThemeMediaResult> GetThemeSongs(
 144        [FromRoute, Required] Guid itemId,
 145        [FromQuery] Guid? userId,
 146        [FromQuery] bool inheritFromParent = false,
 147        [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemSortBy[]? sortBy = null,
 148        [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[]? sortOrder = null)
 149    {
 2150        userId = RequestHelpers.GetUserId(User, userId);
 2151        var user = userId.IsNullOrEmpty()
 2152            ? null
 2153            : _userManager.GetUserById(userId.Value);
 154
 2155        var item = itemId.IsEmpty()
 2156            ? (userId.IsNullOrEmpty()
 2157                ? _libraryManager.RootFolder
 2158                : _libraryManager.GetUserRootFolder())
 2159            : _libraryManager.GetItemById<BaseItem>(itemId, user);
 2160        if (item is null)
 161        {
 2162            return NotFound();
 163        }
 164
 0165        sortOrder ??= [];
 0166        sortBy ??= [];
 0167        var orderBy = RequestHelpers.GetOrderBy(sortBy, sortOrder);
 168
 169        IReadOnlyList<BaseItem> themeItems;
 170
 0171        while (true)
 172        {
 0173            themeItems = item.GetThemeSongs(user, orderBy);
 174
 0175            if (themeItems.Count > 0 || !inheritFromParent)
 176            {
 177                break;
 178            }
 179
 0180            var parent = item.GetParent();
 0181            if (parent is null)
 182            {
 183                break;
 184            }
 185
 0186            item = parent;
 187        }
 188
 0189        var dtoOptions = new DtoOptions().AddClientFields(User);
 0190        var items = themeItems
 0191            .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item))
 0192            .ToArray();
 193
 0194        return new ThemeMediaResult
 0195        {
 0196            Items = items,
 0197            TotalRecordCount = items.Length,
 0198            OwnerId = item.Id
 0199        };
 200    }
 201
 202    /// <summary>
 203    /// Get theme videos for an item.
 204    /// </summary>
 205    /// <param name="itemId">The item id.</param>
 206    /// <param name="userId">Optional. Filter by user id, and attach user data.</param>
 207    /// <param name="inheritFromParent">Optional. Determines whether or not parent items should be searched for theme me
 208    /// <param name="sortBy">Optional. Specify one or more sort orders, comma delimited. Options: Album, AlbumArtist, Ar
 209    /// <param name="sortOrder">Optional. Sort Order - Ascending, Descending.</param>
 210    /// <response code="200">Theme videos returned.</response>
 211    /// <response code="404">Item not found.</response>
 212    /// <returns>The item theme videos.</returns>
 213    [HttpGet("Items/{itemId}/ThemeVideos")]
 214    [Authorize]
 215    [ProducesResponseType(StatusCodes.Status200OK)]
 216    [ProducesResponseType(StatusCodes.Status404NotFound)]
 217    public ActionResult<ThemeMediaResult> GetThemeVideos(
 218        [FromRoute, Required] Guid itemId,
 219        [FromQuery] Guid? userId,
 220        [FromQuery] bool inheritFromParent = false,
 221        [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemSortBy[]? sortBy = null,
 222        [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[]? sortOrder = null)
 223    {
 2224        userId = RequestHelpers.GetUserId(User, userId);
 2225        var user = userId.IsNullOrEmpty()
 2226            ? null
 2227            : _userManager.GetUserById(userId.Value);
 2228        var item = itemId.IsEmpty()
 2229            ? (userId.IsNullOrEmpty()
 2230                ? _libraryManager.RootFolder
 2231                : _libraryManager.GetUserRootFolder())
 2232            : _libraryManager.GetItemById<BaseItem>(itemId, user);
 2233        if (item is null)
 234        {
 2235            return NotFound();
 236        }
 237
 0238        sortOrder ??= [];
 0239        sortBy ??= [];
 0240        var orderBy = RequestHelpers.GetOrderBy(sortBy, sortOrder);
 241
 242        IEnumerable<BaseItem> themeItems;
 243
 0244        while (true)
 245        {
 0246            themeItems = item.GetThemeVideos(user, orderBy);
 247
 0248            if (themeItems.Any() || !inheritFromParent)
 249            {
 250                break;
 251            }
 252
 0253            var parent = item.GetParent();
 0254            if (parent is null)
 255            {
 256                break;
 257            }
 258
 0259            item = parent;
 260        }
 261
 0262        var dtoOptions = new DtoOptions().AddClientFields(User);
 0263        var items = themeItems
 0264            .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item))
 0265            .ToArray();
 266
 0267        return new ThemeMediaResult
 0268        {
 0269            Items = items,
 0270            TotalRecordCount = items.Length,
 0271            OwnerId = item.Id
 0272        };
 273    }
 274
 275    /// <summary>
 276    /// Get theme songs and videos for an item.
 277    /// </summary>
 278    /// <param name="itemId">The item id.</param>
 279    /// <param name="userId">Optional. Filter by user id, and attach user data.</param>
 280    /// <param name="inheritFromParent">Optional. Determines whether or not parent items should be searched for theme me
 281    /// <param name="sortBy">Optional. Specify one or more sort orders, comma delimited. Options: Album, AlbumArtist, Ar
 282    /// <param name="sortOrder">Optional. Sort Order - Ascending, Descending.</param>
 283    /// <response code="200">Theme songs and videos returned.</response>
 284    /// <response code="404">Item not found.</response>
 285    /// <returns>The item theme videos.</returns>
 286    [HttpGet("Items/{itemId}/ThemeMedia")]
 287    [Authorize]
 288    [ProducesResponseType(StatusCodes.Status200OK)]
 289    public ActionResult<AllThemeMediaResult> GetThemeMedia(
 290        [FromRoute, Required] Guid itemId,
 291        [FromQuery] Guid? userId,
 292        [FromQuery] bool inheritFromParent = false,
 293        [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemSortBy[]? sortBy = null,
 294        [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[]? sortOrder = null)
 295    {
 1296        var themeSongs = GetThemeSongs(
 1297            itemId,
 1298            userId,
 1299            inheritFromParent,
 1300            sortBy,
 1301            sortOrder);
 302
 1303        var themeVideos = GetThemeVideos(
 1304            itemId,
 1305            userId,
 1306            inheritFromParent,
 1307            sortBy,
 1308            sortOrder);
 309
 1310        if (themeSongs.Result is StatusCodeResult { StatusCode: StatusCodes.Status404NotFound }
 1311            || themeVideos.Result is StatusCodeResult { StatusCode: StatusCodes.Status404NotFound })
 312        {
 1313            return NotFound();
 314        }
 315
 0316        return new AllThemeMediaResult
 0317        {
 0318            ThemeSongsResult = themeSongs.Value,
 0319            ThemeVideosResult = themeVideos.Value,
 0320            SoundtrackSongsResult = new ThemeMediaResult()
 0321        };
 322    }
 323
 324    /// <summary>
 325    /// Starts a library scan.
 326    /// </summary>
 327    /// <response code="204">Library scan started.</response>
 328    /// <returns>A <see cref="NoContentResult"/>.</returns>
 329    [HttpPost("Library/Refresh")]
 330    [Authorize(Policy = Policies.RequiresElevation)]
 331    [ProducesResponseType(StatusCodes.Status204NoContent)]
 332    public async Task<ActionResult> RefreshLibrary()
 333    {
 334        try
 335        {
 336            await _libraryManager.ValidateMediaLibrary(new Progress<double>(), CancellationToken.None).ConfigureAwait(fa
 337        }
 338        catch (Exception ex)
 339        {
 340            _logger.LogError(ex, "Error refreshing library");
 341        }
 342
 343        return NoContent();
 344    }
 345
 346    /// <summary>
 347    /// Deletes an item from the library and filesystem.
 348    /// </summary>
 349    /// <param name="itemId">The item id.</param>
 350    /// <response code="204">Item deleted.</response>
 351    /// <response code="401">Unauthorized access.</response>
 352    /// <response code="404">Item not found.</response>
 353    /// <returns>A <see cref="NoContentResult"/>.</returns>
 354    [HttpDelete("Items/{itemId}")]
 355    [Authorize]
 356    [ProducesResponseType(StatusCodes.Status204NoContent)]
 357    [ProducesResponseType(StatusCodes.Status401Unauthorized)]
 358    [ProducesResponseType(StatusCodes.Status404NotFound)]
 359    public ActionResult DeleteItem(Guid itemId)
 360    {
 1361        var userId = User.GetUserId();
 1362        var isApiKey = User.GetIsApiKey();
 1363        var user = userId.IsEmpty() && isApiKey
 1364            ? null
 1365            : _userManager.GetUserById(userId);
 366
 1367        if (user is null && !isApiKey)
 368        {
 0369            return NotFound();
 370        }
 371
 1372        var item = _libraryManager.GetItemById<BaseItem>(itemId, user);
 1373        if (item is null)
 374        {
 1375            return NotFound();
 376        }
 377
 0378        if (user is not null && !item.CanDelete(user))
 379        {
 0380            return Unauthorized("Unauthorized access");
 381        }
 382
 0383        _libraryManager.DeleteItem(
 0384            item,
 0385            new DeleteOptions { DeleteFileLocation = true },
 0386            true);
 387
 0388        return NoContent();
 389    }
 390
 391    /// <summary>
 392    /// Deletes items from the library and filesystem.
 393    /// </summary>
 394    /// <param name="ids">The item ids.</param>
 395    /// <response code="204">Items deleted.</response>
 396    /// <response code="401">Unauthorized access.</response>
 397    /// <returns>A <see cref="NoContentResult"/>.</returns>
 398    [HttpDelete("Items")]
 399    [Authorize]
 400    [ProducesResponseType(StatusCodes.Status204NoContent)]
 401    [ProducesResponseType(StatusCodes.Status401Unauthorized)]
 402    [ProducesResponseType(StatusCodes.Status404NotFound)]
 403    public ActionResult DeleteItems([FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] ids)
 404    {
 1405        var isApiKey = User.GetIsApiKey();
 1406        var userId = User.GetUserId();
 1407        var user = !isApiKey && !userId.IsEmpty()
 1408            ? _userManager.GetUserById(userId) ?? throw new ResourceNotFoundException()
 1409            : null;
 410
 1411        if (!isApiKey && user is null)
 412        {
 0413            return Unauthorized("Unauthorized access");
 414        }
 415
 3416        foreach (var i in ids)
 417        {
 1418            var item = _libraryManager.GetItemById<BaseItem>(i, user);
 1419            if (item is null)
 420            {
 1421                return NotFound();
 422            }
 423
 0424            if (user is not null && !item.CanDelete(user))
 425            {
 0426                return Unauthorized("Unauthorized access");
 427            }
 428
 0429            _libraryManager.DeleteItem(
 0430                item,
 0431                new DeleteOptions { DeleteFileLocation = true },
 0432                true);
 433        }
 434
 0435        return NoContent();
 436    }
 437
 438    /// <summary>
 439    /// Get item counts.
 440    /// </summary>
 441    /// <param name="userId">Optional. Get counts from a specific user's library.</param>
 442    /// <param name="isFavorite">Optional. Get counts of favorite items.</param>
 443    /// <response code="200">Item counts returned.</response>
 444    /// <returns>Item counts.</returns>
 445    [HttpGet("Items/Counts")]
 446    [Authorize]
 447    [ProducesResponseType(StatusCodes.Status200OK)]
 448    public ActionResult<ItemCounts> GetItemCounts(
 449        [FromQuery] Guid? userId,
 450        [FromQuery] bool? isFavorite)
 451    {
 0452        userId = RequestHelpers.GetUserId(User, userId);
 0453        var user = userId.IsNullOrEmpty()
 0454            ? null
 0455            : _userManager.GetUserById(userId.Value);
 456
 0457        var counts = new ItemCounts
 0458        {
 0459            AlbumCount = GetCount(BaseItemKind.MusicAlbum, user, isFavorite),
 0460            EpisodeCount = GetCount(BaseItemKind.Episode, user, isFavorite),
 0461            MovieCount = GetCount(BaseItemKind.Movie, user, isFavorite),
 0462            SeriesCount = GetCount(BaseItemKind.Series, user, isFavorite),
 0463            SongCount = GetCount(BaseItemKind.Audio, user, isFavorite),
 0464            MusicVideoCount = GetCount(BaseItemKind.MusicVideo, user, isFavorite),
 0465            BoxSetCount = GetCount(BaseItemKind.BoxSet, user, isFavorite),
 0466            BookCount = GetCount(BaseItemKind.Book, user, isFavorite)
 0467        };
 468
 0469        return counts;
 470    }
 471
 472    /// <summary>
 473    /// Gets all parents of an item.
 474    /// </summary>
 475    /// <param name="itemId">The item id.</param>
 476    /// <param name="userId">Optional. Filter by user id, and attach user data.</param>
 477    /// <response code="200">Item parents returned.</response>
 478    /// <response code="404">Item not found.</response>
 479    /// <returns>Item parents.</returns>
 480    [HttpGet("Items/{itemId}/Ancestors")]
 481    [Authorize]
 482    [ProducesResponseType(StatusCodes.Status200OK)]
 483    [ProducesResponseType(StatusCodes.Status404NotFound)]
 484    public ActionResult<IEnumerable<BaseItemDto>> GetAncestors([FromRoute, Required] Guid itemId, [FromQuery] Guid? user
 485    {
 1486        userId = RequestHelpers.GetUserId(User, userId);
 1487        var user = userId.IsNullOrEmpty()
 1488            ? null
 1489            : _userManager.GetUserById(userId.Value);
 1490        var item = _libraryManager.GetItemById<BaseItem>(itemId, user);
 1491        if (item is null)
 492        {
 1493            return NotFound();
 494        }
 495
 0496        var baseItemDtos = new List<BaseItemDto>();
 497
 0498        var dtoOptions = new DtoOptions().AddClientFields(User);
 0499        BaseItem? parent = item.GetParent();
 500
 0501        while (parent is not null)
 502        {
 0503            if (user is not null)
 504            {
 0505                parent = TranslateParentItem(parent, user);
 0506                if (parent is null)
 507                {
 508                    break;
 509                }
 510            }
 511
 0512            baseItemDtos.Add(_dtoService.GetBaseItemDto(parent, dtoOptions, user));
 513
 0514            parent = parent.GetParent();
 515        }
 516
 0517        return baseItemDtos;
 518    }
 519
 520    /// <summary>
 521    /// Gets a list of physical paths from virtual folders.
 522    /// </summary>
 523    /// <response code="200">Physical paths returned.</response>
 524    /// <returns>List of physical paths.</returns>
 525    [HttpGet("Library/PhysicalPaths")]
 526    [Authorize(Policy = Policies.RequiresElevation)]
 527    [ProducesResponseType(StatusCodes.Status200OK)]
 528    public ActionResult<IEnumerable<string>> GetPhysicalPaths()
 529    {
 0530        return Ok(_libraryManager.RootFolder.Children
 0531            .SelectMany(c => c.PhysicalLocations));
 532    }
 533
 534    /// <summary>
 535    /// Gets all user media folders.
 536    /// </summary>
 537    /// <param name="isHidden">Optional. Filter by folders that are marked hidden, or not.</param>
 538    /// <response code="200">Media folders returned.</response>
 539    /// <returns>List of user media folders.</returns>
 540    [HttpGet("Library/MediaFolders")]
 541    [Authorize(Policy = Policies.RequiresElevation)]
 542    [ProducesResponseType(StatusCodes.Status200OK)]
 543    public ActionResult<QueryResult<BaseItemDto>> GetMediaFolders([FromQuery] bool? isHidden)
 544    {
 0545        var items = _libraryManager.GetUserRootFolder().Children
 0546            .Concat(_libraryManager.RootFolder.VirtualChildren)
 0547            .Where(i => _libraryManager.GetLibraryOptions(i).Enabled)
 0548            .OrderBy(i => i.SortName)
 0549            .ToList();
 550
 0551        if (isHidden.HasValue)
 552        {
 0553            var val = isHidden.Value;
 554
 0555            items = items.Where(i => i.IsHidden == val).ToList();
 556        }
 557
 0558        var dtoOptions = new DtoOptions().AddClientFields(User);
 0559        var resultArray = _dtoService.GetBaseItemDtos(items, dtoOptions);
 0560        return new QueryResult<BaseItemDto>(resultArray);
 561    }
 562
 563    /// <summary>
 564    /// Reports that new episodes of a series have been added by an external source.
 565    /// </summary>
 566    /// <param name="tvdbId">The tvdbId.</param>
 567    /// <response code="204">Report success.</response>
 568    /// <returns>A <see cref="NoContentResult"/>.</returns>
 569    [HttpPost("Library/Series/Added", Name = "PostAddedSeries")]
 570    [HttpPost("Library/Series/Updated")]
 571    [Authorize]
 572    [ProducesResponseType(StatusCodes.Status204NoContent)]
 573    public ActionResult PostUpdatedSeries([FromQuery] string? tvdbId)
 574    {
 0575        var series = _libraryManager.GetItemList(new InternalItemsQuery
 0576        {
 0577            IncludeItemTypes = new[] { BaseItemKind.Series },
 0578            DtoOptions = new DtoOptions(false)
 0579            {
 0580                EnableImages = false
 0581            }
 0582        }).Where(i => string.Equals(tvdbId, i.GetProviderId(MediaBrowser.Model.Entities.MetadataProvider.Tvdb), StringCo
 583
 0584        foreach (var item in series)
 585        {
 0586            _libraryMonitor.ReportFileSystemChanged(item.Path);
 587        }
 588
 0589        return NoContent();
 590    }
 591
 592    /// <summary>
 593    /// Reports that new movies have been added by an external source.
 594    /// </summary>
 595    /// <param name="tmdbId">The tmdbId.</param>
 596    /// <param name="imdbId">The imdbId.</param>
 597    /// <response code="204">Report success.</response>
 598    /// <returns>A <see cref="NoContentResult"/>.</returns>
 599    [HttpPost("Library/Movies/Added", Name = "PostAddedMovies")]
 600    [HttpPost("Library/Movies/Updated")]
 601    [Authorize]
 602    [ProducesResponseType(StatusCodes.Status204NoContent)]
 603    public ActionResult PostUpdatedMovies([FromQuery] string? tmdbId, [FromQuery] string? imdbId)
 604    {
 0605        var movies = _libraryManager.GetItemList(new InternalItemsQuery
 0606        {
 0607            IncludeItemTypes = new[] { BaseItemKind.Movie },
 0608            DtoOptions = new DtoOptions(false)
 0609            {
 0610                EnableImages = false
 0611            }
 0612        });
 613
 0614        if (!string.IsNullOrWhiteSpace(imdbId))
 615        {
 0616            movies = movies.Where(i => string.Equals(imdbId, i.GetProviderId(MediaBrowser.Model.Entities.MetadataProvide
 617        }
 0618        else if (!string.IsNullOrWhiteSpace(tmdbId))
 619        {
 0620            movies = movies.Where(i => string.Equals(tmdbId, i.GetProviderId(MediaBrowser.Model.Entities.MetadataProvide
 621        }
 622        else
 623        {
 0624            movies = new List<BaseItem>();
 625        }
 626
 0627        foreach (var item in movies)
 628        {
 0629            _libraryMonitor.ReportFileSystemChanged(item.Path);
 630        }
 631
 0632        return NoContent();
 633    }
 634
 635    /// <summary>
 636    /// Reports that new movies have been added by an external source.
 637    /// </summary>
 638    /// <param name="dto">The update paths.</param>
 639    /// <response code="204">Report success.</response>
 640    /// <returns>A <see cref="NoContentResult"/>.</returns>
 641    [HttpPost("Library/Media/Updated")]
 642    [Authorize]
 643    [ProducesResponseType(StatusCodes.Status204NoContent)]
 644    public ActionResult PostUpdatedMedia([FromBody, Required] MediaUpdateInfoDto dto)
 645    {
 0646        foreach (var item in dto.Updates)
 647        {
 0648            _libraryMonitor.ReportFileSystemChanged(item.Path ?? throw new ArgumentException("Item path can't be null.")
 649        }
 650
 0651        return NoContent();
 652    }
 653
 654    /// <summary>
 655    /// Downloads item media.
 656    /// </summary>
 657    /// <param name="itemId">The item id.</param>
 658    /// <response code="200">Media downloaded.</response>
 659    /// <response code="404">Item not found.</response>
 660    /// <returns>A <see cref="FileResult"/> containing the media stream.</returns>
 661    /// <exception cref="ArgumentException">User can't download or item can't be downloaded.</exception>
 662    [HttpGet("Items/{itemId}/Download")]
 663    [Authorize(Policy = Policies.Download)]
 664    [ProducesResponseType(StatusCodes.Status200OK)]
 665    [ProducesResponseType(StatusCodes.Status404NotFound)]
 666    [ProducesFile("video/*", "audio/*")]
 667    public async Task<ActionResult> GetDownload([FromRoute, Required] Guid itemId)
 668    {
 669        var userId = User.GetUserId();
 670        var user = userId.IsEmpty()
 671            ? null
 672            : _userManager.GetUserById(userId);
 673        var item = _libraryManager.GetItemById<BaseItem>(itemId, user);
 674        if (item is null)
 675        {
 676            return NotFound();
 677        }
 678
 679        if (user is not null)
 680        {
 681            if (!item.CanDownload(user))
 682            {
 683                throw new ArgumentException("Item does not support downloading");
 684            }
 685        }
 686        else
 687        {
 688            if (!item.CanDownload())
 689            {
 690                throw new ArgumentException("Item does not support downloading");
 691            }
 692        }
 693
 694        if (user is not null)
 695        {
 696            await LogDownloadAsync(item, user).ConfigureAwait(false);
 697        }
 698
 699        // Quotes are valid in linux. They'll possibly cause issues here.
 700        var filename = Path.GetFileName(item.Path)?.Replace("\"", string.Empty, StringComparison.Ordinal);
 701
 702        return PhysicalFile(item.Path, MimeTypes.GetMimeType(item.Path), filename, true);
 703    }
 704
 705    /// <summary>
 706    /// Gets similar items.
 707    /// </summary>
 708    /// <param name="itemId">The item id.</param>
 709    /// <param name="excludeArtistIds">Exclude artist ids.</param>
 710    /// <param name="userId">Optional. Filter by user id, and attach user data.</param>
 711    /// <param name="limit">Optional. The maximum number of records to return.</param>
 712    /// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows mul
 713    /// <response code="200">Similar items returned.</response>
 714    /// <returns>A <see cref="QueryResult{BaseItemDto}"/> containing the similar items.</returns>
 715    [HttpGet("Artists/{itemId}/Similar", Name = "GetSimilarArtists")]
 716    [HttpGet("Items/{itemId}/Similar")]
 717    [HttpGet("Albums/{itemId}/Similar", Name = "GetSimilarAlbums")]
 718    [HttpGet("Shows/{itemId}/Similar", Name = "GetSimilarShows")]
 719    [HttpGet("Movies/{itemId}/Similar", Name = "GetSimilarMovies")]
 720    [HttpGet("Trailers/{itemId}/Similar", Name = "GetSimilarTrailers")]
 721    [Authorize]
 722    [ProducesResponseType(StatusCodes.Status200OK)]
 723    public ActionResult<QueryResult<BaseItemDto>> GetSimilarItems(
 724        [FromRoute, Required] Guid itemId,
 725        [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] excludeArtistIds,
 726        [FromQuery] Guid? userId,
 727        [FromQuery] int? limit,
 728        [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields)
 729    {
 6730        userId = RequestHelpers.GetUserId(User, userId);
 6731        var user = userId.IsNullOrEmpty()
 6732            ? null
 6733            : _userManager.GetUserById(userId.Value);
 6734        var item = itemId.IsEmpty()
 6735            ? (user is null
 6736                ? _libraryManager.RootFolder
 6737                : _libraryManager.GetUserRootFolder())
 6738            : _libraryManager.GetItemById<BaseItem>(itemId, user);
 6739        if (item is null)
 740        {
 6741            return NotFound();
 742        }
 743
 0744        if (item is Episode || (item is IItemByName && item is not MusicArtist))
 745        {
 0746            return new QueryResult<BaseItemDto>();
 747        }
 748
 0749        var dtoOptions = new DtoOptions { Fields = fields }
 0750            .AddClientFields(User);
 751
 0752        var program = item as IHasProgramAttributes;
 0753        bool? isMovie = item is Movie || (program is not null && program.IsMovie) || item is Trailer;
 0754        bool? isSeries = item is Series || (program is not null && program.IsSeries);
 755
 0756        var includeItemTypes = new List<BaseItemKind>();
 0757        if (isMovie.Value)
 758        {
 0759            includeItemTypes.Add(BaseItemKind.Movie);
 0760            if (_serverConfigurationManager.Configuration.EnableExternalContentInSuggestions)
 761            {
 0762                includeItemTypes.Add(BaseItemKind.Trailer);
 0763                includeItemTypes.Add(BaseItemKind.LiveTvProgram);
 764            }
 765        }
 0766        else if (isSeries.Value)
 767        {
 0768            includeItemTypes.Add(BaseItemKind.Series);
 769        }
 770        else
 771        {
 772            // For non series and movie types these columns are typically null
 773            // isSeries = null;
 0774            isMovie = null;
 0775            includeItemTypes.Add(item.GetBaseItemKind());
 776        }
 777
 0778        var query = new InternalItemsQuery(user)
 0779        {
 0780            Genres = item.Genres,
 0781            Limit = limit,
 0782            IncludeItemTypes = includeItemTypes.ToArray(),
 0783            SimilarTo = item,
 0784            DtoOptions = dtoOptions,
 0785            EnableTotalRecordCount = !isMovie ?? true,
 0786            EnableGroupByMetadataKey = isMovie ?? false,
 0787            MinSimilarityScore = 2 // A remnant from album/artist scoring
 0788        };
 789
 790        // ExcludeArtistIds
 0791        if (excludeArtistIds.Length != 0)
 792        {
 0793            query.ExcludeArtistIds = excludeArtistIds;
 794        }
 795
 0796        List<BaseItem> itemsResult = _libraryManager.GetItemList(query);
 797
 0798        var returnList = _dtoService.GetBaseItemDtos(itemsResult, dtoOptions, user);
 799
 0800        return new QueryResult<BaseItemDto>(
 0801            query.StartIndex,
 0802            itemsResult.Count,
 0803            returnList);
 804    }
 805
 806    /// <summary>
 807    /// Gets the library options info.
 808    /// </summary>
 809    /// <param name="libraryContentType">Library content type.</param>
 810    /// <param name="isNewLibrary">Whether this is a new library.</param>
 811    /// <response code="200">Library options info returned.</response>
 812    /// <returns>Library options info.</returns>
 813    [HttpGet("Libraries/AvailableOptions")]
 814    [Authorize(Policy = Policies.FirstTimeSetupOrDefault)]
 815    [ProducesResponseType(StatusCodes.Status200OK)]
 816    public ActionResult<LibraryOptionsResultDto> GetLibraryOptionsInfo(
 817        [FromQuery] CollectionType? libraryContentType,
 818        [FromQuery] bool isNewLibrary = false)
 819    {
 0820        var result = new LibraryOptionsResultDto();
 821
 0822        var types = GetRepresentativeItemTypes(libraryContentType);
 0823        var typesList = types.ToList();
 824
 0825        var plugins = _providerManager.GetAllMetadataPlugins()
 0826            .Where(i => types.Contains(i.ItemType, StringComparison.OrdinalIgnoreCase))
 0827            .OrderBy(i => typesList.IndexOf(i.ItemType))
 0828            .ToList();
 829
 0830        result.MetadataSavers = plugins
 0831            .SelectMany(i => i.Plugins.Where(p => p.Type == MetadataPluginType.MetadataSaver))
 0832            .Select(i => new LibraryOptionInfoDto
 0833            {
 0834                Name = i.Name,
 0835                DefaultEnabled = IsSaverEnabledByDefault(i.Name, types, isNewLibrary)
 0836            })
 0837            .DistinctBy(i => i.Name, StringComparer.OrdinalIgnoreCase)
 0838            .ToArray();
 839
 0840        result.MetadataReaders = plugins
 0841            .SelectMany(i => i.Plugins.Where(p => p.Type == MetadataPluginType.LocalMetadataProvider))
 0842            .Select(i => new LibraryOptionInfoDto
 0843            {
 0844                Name = i.Name,
 0845                DefaultEnabled = true
 0846            })
 0847            .DistinctBy(i => i.Name, StringComparer.OrdinalIgnoreCase)
 0848            .ToArray();
 849
 0850        result.SubtitleFetchers = plugins
 0851            .SelectMany(i => i.Plugins.Where(p => p.Type == MetadataPluginType.SubtitleFetcher))
 0852            .Select(i => new LibraryOptionInfoDto
 0853            {
 0854                Name = i.Name,
 0855                DefaultEnabled = true
 0856            })
 0857            .DistinctBy(i => i.Name, StringComparer.OrdinalIgnoreCase)
 0858            .ToArray();
 859
 0860        result.LyricFetchers = plugins
 0861            .SelectMany(i => i.Plugins.Where(p => p.Type == MetadataPluginType.LyricFetcher))
 0862            .Select(i => new LibraryOptionInfoDto
 0863            {
 0864                Name = i.Name,
 0865                DefaultEnabled = true
 0866            })
 0867            .DistinctBy(i => i.Name, StringComparer.OrdinalIgnoreCase)
 0868            .ToArray();
 869
 0870        var typeOptions = new List<LibraryTypeOptionsDto>();
 871
 0872        foreach (var type in types)
 873        {
 0874            TypeOptions.DefaultImageOptions.TryGetValue(type, out var defaultImageOptions);
 875
 0876            typeOptions.Add(new LibraryTypeOptionsDto
 0877            {
 0878                Type = type,
 0879
 0880                MetadataFetchers = plugins
 0881                    .Where(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase))
 0882                    .SelectMany(i => i.Plugins.Where(p => p.Type == MetadataPluginType.MetadataFetcher))
 0883                    .Select(i => new LibraryOptionInfoDto
 0884                    {
 0885                        Name = i.Name,
 0886                        DefaultEnabled = IsMetadataFetcherEnabledByDefault(i.Name, type, isNewLibrary)
 0887                    })
 0888                    .DistinctBy(i => i.Name, StringComparer.OrdinalIgnoreCase)
 0889                    .ToArray(),
 0890
 0891                ImageFetchers = plugins
 0892                    .Where(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase))
 0893                    .SelectMany(i => i.Plugins.Where(p => p.Type == MetadataPluginType.ImageFetcher))
 0894                    .Select(i => new LibraryOptionInfoDto
 0895                    {
 0896                        Name = i.Name,
 0897                        DefaultEnabled = IsImageFetcherEnabledByDefault(i.Name, type, isNewLibrary)
 0898                    })
 0899                    .DistinctBy(i => i.Name, StringComparer.OrdinalIgnoreCase)
 0900                    .ToArray(),
 0901
 0902                SupportedImageTypes = plugins
 0903                    .Where(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase))
 0904                    .SelectMany(i => i.SupportedImageTypes ?? Array.Empty<ImageType>())
 0905                    .Distinct()
 0906                    .ToArray(),
 0907
 0908                DefaultImageOptions = defaultImageOptions ?? Array.Empty<ImageOption>()
 0909            });
 910        }
 911
 0912        result.TypeOptions = typeOptions.ToArray();
 913
 0914        return result;
 915    }
 916
 917    private int GetCount(BaseItemKind itemKind, User? user, bool? isFavorite)
 918    {
 0919        var query = new InternalItemsQuery(user)
 0920        {
 0921            IncludeItemTypes = new[] { itemKind },
 0922            Limit = 0,
 0923            Recursive = true,
 0924            IsVirtualItem = false,
 0925            IsFavorite = isFavorite,
 0926            DtoOptions = new DtoOptions(false)
 0927            {
 0928                EnableImages = false
 0929            }
 0930        };
 931
 0932        return _libraryManager.GetItemsResult(query).TotalRecordCount;
 933    }
 934
 935    private BaseItem? TranslateParentItem(BaseItem item, User user)
 936    {
 0937        return item.GetParent() is AggregateFolder
 0938            ? _libraryManager.GetUserRootFolder().GetChildren(user, true)
 0939                .FirstOrDefault(i => i.PhysicalLocations.Contains(item.Path))
 0940            : item;
 941    }
 942
 943    private async Task LogDownloadAsync(BaseItem item, User user)
 944    {
 945        try
 946        {
 947            await _activityManager.CreateAsync(new ActivityLog(
 948                string.Format(CultureInfo.InvariantCulture, _localization.GetLocalizedString("UserDownloadingItemWithVal
 949                "UserDownloadingContent",
 950                User.GetUserId())
 951            {
 952                ShortOverview = string.Format(CultureInfo.InvariantCulture, _localization.GetLocalizedString("AppDeviceV
 953                ItemId = item.Id.ToString("N", CultureInfo.InvariantCulture)
 954            }).ConfigureAwait(false);
 955        }
 956        catch
 957        {
 958            // Logged at lower levels
 959        }
 960    }
 961
 962    private static string[] GetRepresentativeItemTypes(CollectionType? contentType)
 963    {
 0964        return contentType switch
 0965        {
 0966            CollectionType.boxsets => new[] { "BoxSet" },
 0967            CollectionType.playlists => new[] { "Playlist" },
 0968            CollectionType.movies => new[] { "Movie" },
 0969            CollectionType.tvshows => new[] { "Series", "Season", "Episode" },
 0970            CollectionType.books => new[] { "Book" },
 0971            CollectionType.music => new[] { "MusicArtist", "MusicAlbum", "Audio", "MusicVideo" },
 0972            CollectionType.homevideos => new[] { "Video", "Photo" },
 0973            CollectionType.photos => new[] { "Video", "Photo" },
 0974            CollectionType.musicvideos => new[] { "MusicVideo" },
 0975            _ => new[] { "Series", "Season", "Episode", "Movie" }
 0976        };
 977    }
 978
 979    private bool IsSaverEnabledByDefault(string name, string[] itemTypes, bool isNewLibrary)
 980    {
 0981        if (isNewLibrary)
 982        {
 0983            return false;
 984        }
 985
 0986        var metadataOptions = _serverConfigurationManager.Configuration.MetadataOptions
 0987            .Where(i => itemTypes.Contains(i.ItemType ?? string.Empty, StringComparison.OrdinalIgnoreCase))
 0988            .ToArray();
 989
 0990        return metadataOptions.Length == 0 || metadataOptions.Any(i => !i.DisabledMetadataSavers.Contains(name, StringCo
 991    }
 992
 993    private bool IsMetadataFetcherEnabledByDefault(string name, string type, bool isNewLibrary)
 994    {
 0995        if (isNewLibrary)
 996        {
 0997            if (string.Equals(name, "TheMovieDb", StringComparison.OrdinalIgnoreCase))
 998            {
 0999                return !(string.Equals(type, "Season", StringComparison.OrdinalIgnoreCase)
 01000                         || string.Equals(type, "Episode", StringComparison.OrdinalIgnoreCase)
 01001                         || string.Equals(type, "MusicVideo", StringComparison.OrdinalIgnoreCase));
 1002            }
 1003
 01004            return string.Equals(name, "TheTVDB", StringComparison.OrdinalIgnoreCase)
 01005                   || string.Equals(name, "TheAudioDB", StringComparison.OrdinalIgnoreCase)
 01006                   || string.Equals(name, "MusicBrainz", StringComparison.OrdinalIgnoreCase);
 1007        }
 1008
 01009        var metadataOptions = _serverConfigurationManager.GetMetadataOptionsForType(type);
 01010        return metadataOptions is null || !metadataOptions.DisabledMetadataFetchers.Contains(name, StringComparison.Ordi
 1011    }
 1012
 1013    private bool IsImageFetcherEnabledByDefault(string name, string type, bool isNewLibrary)
 1014    {
 01015        if (isNewLibrary)
 1016        {
 01017            if (string.Equals(name, "TheMovieDb", StringComparison.OrdinalIgnoreCase))
 1018            {
 01019                return !string.Equals(type, "Series", StringComparison.OrdinalIgnoreCase)
 01020                       && !string.Equals(type, "Season", StringComparison.OrdinalIgnoreCase)
 01021                       && !string.Equals(type, "Episode", StringComparison.OrdinalIgnoreCase)
 01022                       && !string.Equals(type, "MusicVideo", StringComparison.OrdinalIgnoreCase);
 1023            }
 1024
 01025            return string.Equals(name, "TheTVDB", StringComparison.OrdinalIgnoreCase)
 01026                   || string.Equals(name, "Screen Grabber", StringComparison.OrdinalIgnoreCase)
 01027                   || string.Equals(name, "TheAudioDB", StringComparison.OrdinalIgnoreCase)
 01028                   || string.Equals(name, "Image Extractor", StringComparison.OrdinalIgnoreCase);
 1029        }
 1030
 01031        var metadataOptions = _serverConfigurationManager.GetMetadataOptionsForType(type);
 01032        return metadataOptions is null || !metadataOptions.DisabledImageFetchers.Contains(name, StringComparison.Ordinal
 1033    }
 1034}

Methods/Properties

.ctor(MediaBrowser.Controller.Providers.IProviderManager,MediaBrowser.Controller.Library.ILibraryManager,MediaBrowser.Controller.Library.IUserManager,MediaBrowser.Controller.Dto.IDtoService,MediaBrowser.Model.Activity.IActivityManager,MediaBrowser.Model.Globalization.ILocalizationManager,MediaBrowser.Controller.Library.ILibraryMonitor,Microsoft.Extensions.Logging.ILogger`1<Jellyfin.Api.Controllers.LibraryController>,MediaBrowser.Controller.Configuration.IServerConfigurationManager)
GetFile(System.Guid)
GetThemeSongs(System.Guid,System.Nullable`1<System.Guid>,System.Boolean,Jellyfin.Data.Enums.ItemSortBy[],Jellyfin.Data.Enums.SortOrder[])
GetThemeVideos(System.Guid,System.Nullable`1<System.Guid>,System.Boolean,Jellyfin.Data.Enums.ItemSortBy[],Jellyfin.Data.Enums.SortOrder[])
GetThemeMedia(System.Guid,System.Nullable`1<System.Guid>,System.Boolean,Jellyfin.Data.Enums.ItemSortBy[],Jellyfin.Data.Enums.SortOrder[])
DeleteItem(System.Guid)
DeleteItems(System.Guid[])
GetItemCounts(System.Nullable`1<System.Guid>,System.Nullable`1<System.Boolean>)
GetAncestors(System.Guid,System.Nullable`1<System.Guid>)
GetPhysicalPaths()
GetMediaFolders(System.Nullable`1<System.Boolean>)
PostUpdatedSeries(System.String)
PostUpdatedMovies(System.String,System.String)
PostUpdatedMedia(Jellyfin.Api.Models.LibraryDtos.MediaUpdateInfoDto)
GetSimilarItems(System.Guid,System.Guid[],System.Nullable`1<System.Guid>,System.Nullable`1<System.Int32>,MediaBrowser.Model.Querying.ItemFields[])
GetLibraryOptionsInfo(System.Nullable`1<Jellyfin.Data.Enums.CollectionType>,System.Boolean)
GetCount(Jellyfin.Data.Enums.BaseItemKind,Jellyfin.Data.Entities.User,System.Nullable`1<System.Boolean>)
TranslateParentItem(MediaBrowser.Controller.Entities.BaseItem,Jellyfin.Data.Entities.User)
GetRepresentativeItemTypes(System.Nullable`1<Jellyfin.Data.Enums.CollectionType>)
IsSaverEnabledByDefault(System.String,System.String[],System.Boolean)
IsMetadataFetcherEnabledByDefault(System.String,System.String,System.Boolean)
IsImageFetcherEnabledByDefault(System.String,System.String,System.Boolean)