aboutsummaryrefslogtreecommitdiff
path: root/Jellyfin.Api/Controllers/LibraryController.cs
diff options
context:
space:
mode:
authorJoshua M. Boniface <joshua@boniface.me>2021-08-18 02:46:59 -0400
committerGitHub <noreply@github.com>2021-08-18 02:46:59 -0400
commit72d3f7020ad80ce1a53eeae8c5d57abeb22a4679 (patch)
treedd43e663838cdc7d99a4af565523df58ae23c856 /Jellyfin.Api/Controllers/LibraryController.cs
parent7aef0fce444e6d8e06386553ec7ea1401a01bbb1 (diff)
parente5cbafdb6b47377052e0d638908ef96e30a997d6 (diff)
Merge branch 'master' into patch-2
Diffstat (limited to 'Jellyfin.Api/Controllers/LibraryController.cs')
-rw-r--r--Jellyfin.Api/Controllers/LibraryController.cs213
1 files changed, 90 insertions, 123 deletions
diff --git a/Jellyfin.Api/Controllers/LibraryController.cs b/Jellyfin.Api/Controllers/LibraryController.cs
index 796d2d8aa..4ed15e1d5 100644
--- a/Jellyfin.Api/Controllers/LibraryController.cs
+++ b/Jellyfin.Api/Controllers/LibraryController.cs
@@ -8,9 +8,10 @@ using System.Net;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
+using Jellyfin.Api.Attributes;
using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions;
-using Jellyfin.Api.Helpers;
+using Jellyfin.Api.ModelBinders;
using Jellyfin.Api.Models.LibraryDtos;
using Jellyfin.Data.Entities;
using MediaBrowser.Common.Progress;
@@ -19,6 +20,7 @@ using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.Movies;
+using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.Net;
@@ -35,8 +37,6 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
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
{
@@ -105,7 +105,8 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
- public ActionResult GetFile([FromRoute] Guid itemId)
+ [ProducesFile("video/*", "audio/*")]
+ public ActionResult GetFile([FromRoute, Required] Guid itemId)
{
var item = _libraryManager.GetItemById(itemId);
if (item == null)
@@ -113,8 +114,7 @@ namespace Jellyfin.Api.Controllers
return NotFound();
}
- using var fileStream = new FileStream(item.Path, FileMode.Open, FileAccess.Read);
- return File(fileStream, MimeTypes.GetMimeType(item.Path));
+ return PhysicalFile(item.Path, MimeTypes.GetMimeType(item.Path), true);
}
/// <summary>
@@ -145,7 +145,7 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<ThemeMediaResult> GetThemeSongs(
- [FromRoute] Guid itemId,
+ [FromRoute, Required] Guid itemId,
[FromQuery] Guid? userId,
[FromQuery] bool inheritFromParent = false)
{
@@ -211,7 +211,7 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<ThemeMediaResult> GetThemeVideos(
- [FromRoute] Guid itemId,
+ [FromRoute, Required] Guid itemId,
[FromQuery] Guid? userId,
[FromQuery] bool inheritFromParent = false)
{
@@ -276,7 +276,7 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<AllThemeMediaResult> GetThemeMedia(
- [FromRoute] Guid itemId,
+ [FromRoute, Required] Guid itemId,
[FromQuery] Guid? userId,
[FromQuery] bool inheritFromParent = false)
{
@@ -303,7 +303,7 @@ namespace Jellyfin.Api.Controllers
/// </summary>
/// <response code="204">Library scan started.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns>
- [HttpGet("Library/Refresh")]
+ [HttpPost("Library/Refresh")]
[Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<ActionResult> RefreshLibrary()
@@ -361,15 +361,14 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
- public ActionResult DeleteItems([FromQuery] string? ids)
+ public ActionResult DeleteItems([FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] ids)
{
- if (string.IsNullOrEmpty(ids))
+ if (ids.Length == 0)
{
return NoContent();
}
- var itemIds = RequestHelpers.Split(ids, ',', true);
- foreach (var i in itemIds)
+ foreach (var i in ids)
{
var item = _libraryManager.GetItemById(i);
var auth = _authContext.GetAuthorizationInfo(Request);
@@ -439,7 +438,7 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
- public ActionResult<IEnumerable<BaseItemDto>> GetAncestors([FromRoute] Guid itemId, [FromQuery] Guid? userId)
+ public ActionResult<IEnumerable<BaseItemDto>> GetAncestors([FromRoute, Required] Guid itemId, [FromQuery] Guid? userId)
{
var item = _libraryManager.GetItemById(itemId);
@@ -455,7 +454,7 @@ namespace Jellyfin.Api.Controllers
: null;
var dtoOptions = new DtoOptions().AddClientFields(Request);
- BaseItem parent = item.GetParent();
+ BaseItem? parent = item.GetParent();
while (parent != null)
{
@@ -466,7 +465,7 @@ namespace Jellyfin.Api.Controllers
baseItemDtos.Add(_dtoService.GetBaseItemDto(parent, dtoOptions, user));
- parent = parent.GetParent();
+ parent = parent?.GetParent();
}
return baseItemDtos;
@@ -556,7 +555,7 @@ namespace Jellyfin.Api.Controllers
[HttpPost("Library/Movies/Updated")]
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
- public ActionResult PostUpdatedMovies([FromRoute] string? tmdbId, [FromRoute] string? imdbId)
+ public ActionResult PostUpdatedMovies([FromQuery] string? tmdbId, [FromQuery] string? imdbId)
{
var movies = _libraryManager.GetItemList(new InternalItemsQuery
{
@@ -591,17 +590,17 @@ namespace Jellyfin.Api.Controllers
/// <summary>
/// Reports that new movies have been added by an external source.
/// </summary>
- /// <param name="updates">A list of updated media paths.</param>
+ /// <param name="dto">The update paths.</param>
/// <response code="204">Report success.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("Library/Media/Updated")]
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
- public ActionResult PostUpdatedMedia([FromBody, Required] MediaUpdateInfoDto[] updates)
+ public ActionResult PostUpdatedMedia([FromBody, Required] MediaUpdateInfoDto dto)
{
- foreach (var item in updates)
+ foreach (var item in dto.Updates)
{
- _libraryMonitor.ReportFileSystemChanged(item.Path);
+ _libraryMonitor.ReportFileSystemChanged(item.Path ?? throw new ArgumentException("Item path can't be null."));
}
return NoContent();
@@ -619,7 +618,8 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.Download)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
- public async Task<ActionResult> GetDownload([FromRoute] Guid itemId)
+ [ProducesFile("video/*", "audio/*")]
+ public async Task<ActionResult> GetDownload([FromRoute, Required] Guid itemId)
{
var item = _libraryManager.GetItemById(itemId);
if (item == null)
@@ -666,7 +666,7 @@ namespace Jellyfin.Api.Controllers
}
// TODO determine non-ASCII validity.
- return PhysicalFile(path, MimeTypes.GetMimeType(path));
+ return PhysicalFile(path, MimeTypes.GetMimeType(path), filename, true);
}
/// <summary>
@@ -679,20 +679,20 @@ namespace Jellyfin.Api.Controllers
/// <param name="fields">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.</param>
/// <response code="200">Similar items returned.</response>
/// <returns>A <see cref="QueryResult{BaseItemDto}"/> containing the similar items.</returns>
- [HttpGet("Artists/{itemId}/Similar", Name = "GetSimilarArtists2")]
+ [HttpGet("Artists/{itemId}/Similar", Name = "GetSimilarArtists")]
[HttpGet("Items/{itemId}/Similar")]
- [HttpGet("Albums/{itemId}/Similar", Name = "GetSimilarAlbums2")]
- [HttpGet("Shows/{itemId}/Similar", Name = "GetSimilarShows2")]
- [HttpGet("Movies/{itemId}/Similar", Name = "GetSimilarMovies2")]
- [HttpGet("Trailers/{itemId}/Similar", Name = "GetSimilarTrailers2")]
+ [HttpGet("Albums/{itemId}/Similar", Name = "GetSimilarAlbums")]
+ [HttpGet("Shows/{itemId}/Similar", Name = "GetSimilarShows")]
+ [HttpGet("Movies/{itemId}/Similar", Name = "GetSimilarMovies")]
+ [HttpGet("Trailers/{itemId}/Similar", Name = "GetSimilarTrailers")]
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetSimilarItems(
- [FromRoute] Guid itemId,
- [FromQuery] string? excludeArtistIds,
+ [FromRoute, Required] Guid itemId,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] excludeArtistIds,
[FromQuery] Guid? userId,
[FromQuery] int? limit,
- [FromQuery] string? fields)
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields)
{
var item = itemId.Equals(Guid.Empty)
? (!userId.Equals(Guid.Empty)
@@ -700,33 +700,69 @@ namespace Jellyfin.Api.Controllers
: _libraryManager.RootFolder)
: _libraryManager.GetItemById(itemId);
+ if (item is Episode || (item is IItemByName && !(item is MusicArtist)))
+ {
+ return new QueryResult<BaseItemDto>();
+ }
+
+ var user = userId.HasValue && !userId.Equals(Guid.Empty)
+ ? _userManager.GetUserById(userId.Value)
+ : null;
+ var dtoOptions = new DtoOptions { Fields = fields }
+ .AddClientFields(Request);
+
var program = item as IHasProgramAttributes;
- var isMovie = item is MediaBrowser.Controller.Entities.Movies.Movie || (program != null && program.IsMovie) || item is Trailer;
- if (program != null && program.IsSeries)
+ bool? isMovie = item is Movie || (program != null && program.IsMovie) || item is Trailer;
+ bool? isSeries = item is Series || (program != null && program.IsSeries);
+
+ var includeItemTypes = new List<string>();
+ if (isMovie.Value)
{
- return GetSimilarItemsResult(
- item,
- excludeArtistIds,
- userId,
- limit,
- fields,
- new[] { nameof(Series) },
- false);
+ includeItemTypes.Add(nameof(Movie));
+ if (_serverConfigurationManager.Configuration.EnableExternalContentInSuggestions)
+ {
+ includeItemTypes.Add(nameof(Trailer));
+ includeItemTypes.Add(nameof(LiveTvProgram));
+ }
+ }
+ else if (isSeries.Value)
+ {
+ includeItemTypes.Add(nameof(Series));
+ }
+ else
+ {
+ // For non series and movie types these columns are typically null
+ isSeries = null;
+ isMovie = null;
+ includeItemTypes.Add(item.GetType().Name);
}
- if (item is MediaBrowser.Controller.Entities.TV.Episode || (item is IItemByName && !(item is MusicArtist)))
+ var query = new InternalItemsQuery(user)
{
- return new QueryResult<BaseItemDto>();
+ Limit = limit,
+ IncludeItemTypes = includeItemTypes.ToArray(),
+ SimilarTo = item,
+ DtoOptions = dtoOptions,
+ EnableTotalRecordCount = !isMovie ?? true,
+ EnableGroupByMetadataKey = isMovie ?? false,
+ MinSimilarityScore = 2 // A remnant from album/artist scoring
+ };
+
+ // ExcludeArtistIds
+ if (excludeArtistIds.Length != 0)
+ {
+ query.ExcludeArtistIds = excludeArtistIds;
}
- return GetSimilarItemsResult(
- item,
- excludeArtistIds,
- userId,
- limit,
- fields,
- new[] { item.GetType().Name },
- isMovie);
+ List<BaseItem> itemsResult = _libraryManager.GetItemList(query);
+
+ var returnList = _dtoService.GetBaseItemDtos(itemsResult, dtoOptions, user);
+
+ return new QueryResult<BaseItemDto>
+ {
+ Items = returnList,
+ TotalRecordCount = itemsResult.Count
+ };
}
/// <summary>
@@ -741,7 +777,7 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<LibraryOptionsResultDto> GetLibraryOptionsInfo(
[FromQuery] string? libraryContentType,
- [FromQuery] bool isNewLibrary)
+ [FromQuery] bool isNewLibrary = false)
{
var result = new LibraryOptionsResultDto();
@@ -853,7 +889,7 @@ namespace Jellyfin.Api.Controllers
return _libraryManager.GetItemsResult(query).TotalRecordCount;
}
- private BaseItem TranslateParentItem(BaseItem item, User user)
+ private BaseItem? TranslateParentItem(BaseItem item, User user)
{
return item.GetParent() is AggregateFolder
? _libraryManager.GetUserRootFolder().GetChildren(user, true)
@@ -879,75 +915,6 @@ namespace Jellyfin.Api.Controllers
}
}
- private QueryResult<BaseItemDto> GetSimilarItemsResult(
- BaseItem item,
- string? excludeArtistIds,
- Guid? userId,
- int? limit,
- string? fields,
- string[] includeItemTypes,
- bool isMovie)
- {
- var user = userId.HasValue && !userId.Equals(Guid.Empty)
- ? _userManager.GetUserById(userId.Value)
- : null;
- var dtoOptions = new DtoOptions()
- .AddItemFields(fields)
- .AddClientFields(Request);
-
- var query = new InternalItemsQuery(user)
- {
- Limit = limit,
- IncludeItemTypes = includeItemTypes,
- IsMovie = isMovie,
- SimilarTo = item,
- DtoOptions = dtoOptions,
- EnableTotalRecordCount = !isMovie,
- EnableGroupByMetadataKey = isMovie
- };
-
- // ExcludeArtistIds
- if (!string.IsNullOrEmpty(excludeArtistIds))
- {
- query.ExcludeArtistIds = RequestHelpers.GetGuids(excludeArtistIds);
- }
-
- List<BaseItem> itemsResult;
-
- if (isMovie)
- {
- var itemTypes = new List<string> { nameof(MediaBrowser.Controller.Entities.Movies.Movie) };
- if (_serverConfigurationManager.Configuration.EnableExternalContentInSuggestions)
- {
- itemTypes.Add(nameof(Trailer));
- itemTypes.Add(nameof(LiveTvProgram));
- }
-
- query.IncludeItemTypes = itemTypes.ToArray();
- itemsResult = _libraryManager.GetArtists(query).Items.Select(i => i.Item1).ToList();
- }
- else if (item is MusicArtist)
- {
- query.IncludeItemTypes = Array.Empty<string>();
-
- 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<BaseItemDto>
- {
- Items = returnList,
- TotalRecordCount = itemsResult.Count
- };
-
- return result;
- }
-
private static string[] GetRepresentativeItemTypes(string? contentType)
{
return contentType switch