From 81d3ec7205dd559e6444c17f9ecefc37977a5911 Mon Sep 17 00:00:00 2001 From: crobibero Date: Mon, 8 Jun 2020 12:20:33 -0600 Subject: [PATCH 1/4] Move ItemUpdateService to Jellyfin.Api --- .../Controllers/ItemUpdateController.cs | 423 ++++++++++-------- 1 file changed, 237 insertions(+), 186 deletions(-) rename MediaBrowser.Api/ItemUpdateService.cs => Jellyfin.Api/Controllers/ItemUpdateController.cs (63%) diff --git a/MediaBrowser.Api/ItemUpdateService.cs b/Jellyfin.Api/Controllers/ItemUpdateController.cs similarity index 63% rename from MediaBrowser.Api/ItemUpdateService.cs rename to Jellyfin.Api/Controllers/ItemUpdateController.cs index 2db6d717a..0c5fece83 100644 --- a/MediaBrowser.Api/ItemUpdateService.cs +++ b/Jellyfin.Api/Controllers/ItemUpdateController.cs @@ -1,215 +1,101 @@ +#nullable enable + using System; using System.Collections.Generic; using System.Linq; using System.Threading; +using Jellyfin.Api.Constants; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; -using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.IO; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ModelBinding; -namespace MediaBrowser.Api +namespace Jellyfin.Api.Controllers { - [Route("/Items/{ItemId}", "POST", Summary = "Updates an item")] - public class UpdateItem : BaseItemDto, IReturnVoid - { - [ApiMember(Name = "ItemId", Description = "The id of the item", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string ItemId { get; set; } - } - - [Route("/Items/{ItemId}/MetadataEditor", "GET", Summary = "Gets metadata editor info for an item")] - public class GetMetadataEditorInfo : IReturn - { - [ApiMember(Name = "ItemId", Description = "The id of the item", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string ItemId { get; set; } - } - - [Route("/Items/{ItemId}/ContentType", "POST", Summary = "Updates an item's content type")] - public class UpdateItemContentType : IReturnVoid - { - [ApiMember(Name = "ItemId", Description = "The id of the item", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public Guid ItemId { get; set; } - - [ApiMember(Name = "ContentType", Description = "The content type of the item", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string ContentType { get; set; } - } - - [Authenticated(Roles = "admin")] - public class ItemUpdateService : BaseApiService + /// + /// Item update controller. + /// + [Authorize(Policy = Policies.RequiresElevation)] + public class ItemUpdateController : BaseJellyfinApiController { private readonly ILibraryManager _libraryManager; private readonly IProviderManager _providerManager; private readonly ILocalizationManager _localizationManager; private readonly IFileSystem _fileSystem; + private readonly IServerConfigurationManager _serverConfigurationManager; - public ItemUpdateService( - ILogger logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, + /// + /// Initializes a new instance of the class. + /// + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + public ItemUpdateController( IFileSystem fileSystem, ILibraryManager libraryManager, IProviderManager providerManager, - ILocalizationManager localizationManager) - : base(logger, serverConfigurationManager, httpResultFactory) + ILocalizationManager localizationManager, + IServerConfigurationManager serverConfigurationManager) { _libraryManager = libraryManager; _providerManager = providerManager; _localizationManager = localizationManager; _fileSystem = fileSystem; + _serverConfigurationManager = serverConfigurationManager; } - public object Get(GetMetadataEditorInfo request) + /// + /// Updates an item. + /// + /// The item id. + /// The new item properties. + /// Item updated. + /// Item not found. + /// An on success, or a if the item could not be found. + [HttpPost("/Items/{itemId}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult UpdateItem([FromRoute] Guid itemId, [FromBody, BindRequired] BaseItemDto request) { - var item = _libraryManager.GetItemById(request.ItemId); - - var info = new MetadataEditorInfo + var item = _libraryManager.GetItemById(itemId); + if (item == null) { - ParentalRatingOptions = _localizationManager.GetParentalRatings().ToArray(), - ExternalIdInfos = _providerManager.GetExternalIdInfos(item).ToArray(), - Countries = _localizationManager.GetCountries().ToArray(), - Cultures = _localizationManager.GetCultures().ToArray() - }; - - if (!item.IsVirtualItem && !(item is ICollectionFolder) && !(item is UserView) && !(item is AggregateFolder) && !(item is LiveTvChannel) && !(item is IItemByName) && - item.SourceType == SourceType.Library) - { - var inheritedContentType = _libraryManager.GetInheritedContentType(item); - var configuredContentType = _libraryManager.GetConfiguredContentType(item); - - if (string.IsNullOrWhiteSpace(inheritedContentType) || !string.IsNullOrWhiteSpace(configuredContentType)) - { - info.ContentTypeOptions = GetContentTypeOptions(true).ToArray(); - info.ContentType = configuredContentType; - - if (string.IsNullOrWhiteSpace(inheritedContentType) || string.Equals(inheritedContentType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase)) - { - info.ContentTypeOptions = info.ContentTypeOptions - .Where(i => string.IsNullOrWhiteSpace(i.Value) || string.Equals(i.Value, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase)) - .ToArray(); - } - } + return NotFound(); } - return ToOptimizedResult(info); - } - - public void Post(UpdateItemContentType request) - { - var item = _libraryManager.GetItemById(request.ItemId); - var path = item.ContainingFolderPath; - - var types = ServerConfigurationManager.Configuration.ContentTypes - .Where(i => !string.IsNullOrWhiteSpace(i.Name)) - .Where(i => !string.Equals(i.Name, path, StringComparison.OrdinalIgnoreCase)) - .ToList(); - - if (!string.IsNullOrWhiteSpace(request.ContentType)) - { - types.Add(new NameValuePair - { - Name = path, - Value = request.ContentType - }); - } - - ServerConfigurationManager.Configuration.ContentTypes = types.ToArray(); - ServerConfigurationManager.SaveConfiguration(); - } - - private List GetContentTypeOptions(bool isForItem) - { - var list = new List(); - - if (isForItem) - { - list.Add(new NameValuePair - { - Name = "Inherit", - Value = "" - }); - } - - list.Add(new NameValuePair - { - Name = "Movies", - Value = "movies" - }); - list.Add(new NameValuePair - { - Name = "Music", - Value = "music" - }); - list.Add(new NameValuePair - { - Name = "Shows", - Value = "tvshows" - }); - - if (!isForItem) - { - list.Add(new NameValuePair - { - Name = "Books", - Value = "books" - }); - } - - list.Add(new NameValuePair - { - Name = "HomeVideos", - Value = "homevideos" - }); - list.Add(new NameValuePair - { - Name = "MusicVideos", - Value = "musicvideos" - }); - list.Add(new NameValuePair - { - Name = "Photos", - Value = "photos" - }); - - if (!isForItem) - { - list.Add(new NameValuePair - { - Name = "MixedContent", - Value = "" - }); - } - - foreach (var val in list) - { - val.Name = _localizationManager.GetLocalizedString(val.Name); - } - - return list; - } - - public void Post(UpdateItem request) - { - var item = _libraryManager.GetItemById(request.ItemId); - var newLockData = request.LockData ?? false; var isLockedChanged = item.IsLocked != newLockData; var series = item as Series; - var displayOrderChanged = series != null && !string.Equals(series.DisplayOrder ?? string.Empty, request.DisplayOrder ?? string.Empty, StringComparison.OrdinalIgnoreCase); + var displayOrderChanged = series != null && !string.Equals( + series.DisplayOrder ?? string.Empty, + request.DisplayOrder ?? string.Empty, + StringComparison.OrdinalIgnoreCase); // Do this first so that metadata savers can pull the updates from the database. if (request.People != null) { - _libraryManager.UpdatePeople(item, request.People.Select(x => new PersonInfo { Name = x.Name, Role = x.Role, Type = x.Type }).ToList()); + _libraryManager.UpdatePeople( + item, + request.People.Select(x => new PersonInfo + { + Name = x.Name, + Role = x.Role, + Type = x.Type + }).ToList()); } UpdateItem(request, item); @@ -232,7 +118,7 @@ namespace MediaBrowser.Api if (displayOrderChanged) { _providerManager.QueueRefresh( - series.Id, + series!.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)) { MetadataRefreshMode = MetadataRefreshMode.FullRefresh, @@ -241,11 +127,99 @@ namespace MediaBrowser.Api }, RefreshPriority.High); } + + return Ok(); } - private DateTime NormalizeDateTime(DateTime val) + /// + /// Gets metadata editor info for an item. + /// + /// The item id. + /// Item metadata editor returned. + /// Item not found. + /// An on success containing the metadata editor, or a if the item could not be found. + [HttpGet("/Items/{itemId}/MetadataEditor")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult GetMetadataEditorInfo([FromRoute] Guid itemId) { - return DateTime.SpecifyKind(val, DateTimeKind.Utc); + var item = _libraryManager.GetItemById(itemId); + + var info = new MetadataEditorInfo + { + ParentalRatingOptions = _localizationManager.GetParentalRatings().ToArray(), + ExternalIdInfos = _providerManager.GetExternalIdInfos(item).ToArray(), + Countries = _localizationManager.GetCountries().ToArray(), + Cultures = _localizationManager.GetCultures().ToArray() + }; + + if (!item.IsVirtualItem + && !(item is ICollectionFolder) + && !(item is UserView) + && !(item is AggregateFolder) + && !(item is LiveTvChannel) + && !(item is IItemByName) + && item.SourceType == SourceType.Library) + { + var inheritedContentType = _libraryManager.GetInheritedContentType(item); + var configuredContentType = _libraryManager.GetConfiguredContentType(item); + + if (string.IsNullOrWhiteSpace(inheritedContentType) || + !string.IsNullOrWhiteSpace(configuredContentType)) + { + info.ContentTypeOptions = GetContentTypeOptions(true).ToArray(); + info.ContentType = configuredContentType; + + if (string.IsNullOrWhiteSpace(inheritedContentType) + || string.Equals(inheritedContentType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase)) + { + info.ContentTypeOptions = info.ContentTypeOptions + .Where(i => string.IsNullOrWhiteSpace(i.Value) + || string.Equals(i.Value, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase)) + .ToArray(); + } + } + } + + return info; + } + + /// + /// Updates an item's content type. + /// + /// The item id. + /// The content type of the item. + /// Item content type updated. + /// Item not found. + /// An on success, or a if the item could not be found. + [HttpPost("/Items/{itemId}/ContentType")] + public ActionResult UpdateItemContentType([FromRoute] Guid itemId, [FromQuery, BindRequired] string contentType) + { + var item = _libraryManager.GetItemById(itemId); + if (item == null) + { + return NotFound(); + } + + var path = item.ContainingFolderPath; + + var types = _serverConfigurationManager.Configuration.ContentTypes + .Where(i => !string.IsNullOrWhiteSpace(i.Name)) + .Where(i => !string.Equals(i.Name, path, StringComparison.OrdinalIgnoreCase)) + .ToList(); + + if (!string.IsNullOrWhiteSpace(contentType)) + { + types.Add(new NameValuePair + { + Name = path, + Value = contentType + }); + } + + _serverConfigurationManager.Configuration.ContentTypes = types.ToArray(); + _serverConfigurationManager.SaveConfiguration(); + return Ok(); } private void UpdateItem(BaseItemDto request, BaseItem item) @@ -361,24 +335,25 @@ namespace MediaBrowser.Api } } - if (item is Audio song) + switch (item) { - song.Album = request.Album; - } - - if (item is MusicVideo musicVideo) - { - musicVideo.Album = request.Album; - } - - if (item is Series series) - { - series.Status = GetSeriesStatus(request); - - if (request.AirDays != null) + case Audio song: + song.Album = request.Album; + break; + case MusicVideo musicVideo: + musicVideo.Album = request.Album; + break; + case Series series: { - series.AirDays = request.AirDays; - series.AirTime = request.AirTime; + series.Status = GetSeriesStatus(request); + + if (request.AirDays != null) + { + series.AirDays = request.AirDays; + series.AirTime = request.AirTime; + } + + break; } } } @@ -392,5 +367,81 @@ namespace MediaBrowser.Api return (SeriesStatus)Enum.Parse(typeof(SeriesStatus), item.Status, true); } + + private DateTime NormalizeDateTime(DateTime val) + { + return DateTime.SpecifyKind(val, DateTimeKind.Utc); + } + + private List GetContentTypeOptions(bool isForItem) + { + var list = new List(); + + if (isForItem) + { + list.Add(new NameValuePair + { + Name = "Inherit", + Value = string.Empty + }); + } + + list.Add(new NameValuePair + { + Name = "Movies", + Value = "movies" + }); + list.Add(new NameValuePair + { + Name = "Music", + Value = "music" + }); + list.Add(new NameValuePair + { + Name = "Shows", + Value = "tvshows" + }); + + if (!isForItem) + { + list.Add(new NameValuePair + { + Name = "Books", + Value = "books" + }); + } + + list.Add(new NameValuePair + { + Name = "HomeVideos", + Value = "homevideos" + }); + list.Add(new NameValuePair + { + Name = "MusicVideos", + Value = "musicvideos" + }); + list.Add(new NameValuePair + { + Name = "Photos", + Value = "photos" + }); + + if (!isForItem) + { + list.Add(new NameValuePair + { + Name = "MixedContent", + Value = string.Empty + }); + } + + foreach (var val in list) + { + val.Name = _localizationManager.GetLocalizedString(val.Name); + } + + return list; + } } } From 97ce641242e9df8475e01dd65a30aaf2ec763ffd Mon Sep 17 00:00:00 2001 From: crobibero Date: Fri, 19 Jun 2020 09:00:50 -0600 Subject: [PATCH 2/4] remove #nullable --- Jellyfin.Api/Controllers/ItemUpdateController.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Jellyfin.Api/Controllers/ItemUpdateController.cs b/Jellyfin.Api/Controllers/ItemUpdateController.cs index 0c5fece83..253799651 100644 --- a/Jellyfin.Api/Controllers/ItemUpdateController.cs +++ b/Jellyfin.Api/Controllers/ItemUpdateController.cs @@ -1,6 +1,4 @@ -#nullable enable - -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Threading; From 95bae56640289cf11886b0036fb6a685353e3dc4 Mon Sep 17 00:00:00 2001 From: crobibero Date: Sat, 20 Jun 2020 15:14:43 -0600 Subject: [PATCH 3/4] Fix response code & docs --- Jellyfin.Api/Controllers/ItemUpdateController.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Jellyfin.Api/Controllers/ItemUpdateController.cs b/Jellyfin.Api/Controllers/ItemUpdateController.cs index 253799651..0c66ff875 100644 --- a/Jellyfin.Api/Controllers/ItemUpdateController.cs +++ b/Jellyfin.Api/Controllers/ItemUpdateController.cs @@ -60,9 +60,9 @@ namespace Jellyfin.Api.Controllers /// /// The item id. /// The new item properties. - /// Item updated. + /// Item updated. /// Item not found. - /// An on success, or a if the item could not be found. + /// An on success, or a if the item could not be found. [HttpPost("/Items/{itemId}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] @@ -126,7 +126,7 @@ namespace Jellyfin.Api.Controllers RefreshPriority.High); } - return Ok(); + return NoContent(); } /// @@ -187,9 +187,9 @@ namespace Jellyfin.Api.Controllers /// /// The item id. /// The content type of the item. - /// Item content type updated. + /// Item content type updated. /// Item not found. - /// An on success, or a if the item could not be found. + /// An on success, or a if the item could not be found. [HttpPost("/Items/{itemId}/ContentType")] public ActionResult UpdateItemContentType([FromRoute] Guid itemId, [FromQuery, BindRequired] string contentType) { @@ -217,7 +217,7 @@ namespace Jellyfin.Api.Controllers _serverConfigurationManager.Configuration.ContentTypes = types.ToArray(); _serverConfigurationManager.SaveConfiguration(); - return Ok(); + return NoContent(); } private void UpdateItem(BaseItemDto request, BaseItem item) From 8c38644fcadda10dd6132c3f43b333ccdeb7b392 Mon Sep 17 00:00:00 2001 From: crobibero Date: Sat, 20 Jun 2020 15:15:59 -0600 Subject: [PATCH 4/4] Fix response code & docs --- Jellyfin.Api/Controllers/ItemUpdateController.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Jellyfin.Api/Controllers/ItemUpdateController.cs b/Jellyfin.Api/Controllers/ItemUpdateController.cs index 0c66ff875..384f250ec 100644 --- a/Jellyfin.Api/Controllers/ItemUpdateController.cs +++ b/Jellyfin.Api/Controllers/ItemUpdateController.cs @@ -64,7 +64,7 @@ namespace Jellyfin.Api.Controllers /// Item not found. /// An on success, or a if the item could not be found. [HttpPost("/Items/{itemId}")] - [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult UpdateItem([FromRoute] Guid itemId, [FromBody, BindRequired] BaseItemDto request) { @@ -191,6 +191,8 @@ namespace Jellyfin.Api.Controllers /// Item not found. /// An on success, or a if the item could not be found. [HttpPost("/Items/{itemId}/ContentType")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult UpdateItemContentType([FromRoute] Guid itemId, [FromQuery, BindRequired] string contentType) { var item = _libraryManager.GetItemById(itemId);