@ -1,215 +1,99 @@
using System ;
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 < MetadataEditorInfo >
{
[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
/// <summary>
/// Item update controller.
/// </summary>
[Authorize(Policy = Policies.RequiresElevation)]
public class ItemUpdateController : BaseJellyfinApiController
{
private readonly ILibraryManager _libraryManager ;
private readonly IProviderManager _providerManager ;
private readonly ILocalizationManager _localizationManager ;
private readonly IFileSystem _fileSystem ;
public ItemUpdateService (
ILogger < ItemUpdateService > logger ,
IServerConfigurationManager serverConfigurationManager ,
IHttpResultFactory httpResultFactory ,
private readonly IServerConfigurationManager _serverConfigurationManager ;
/// <summary>
/// Initializes a new instance of the <see cref="ItemUpdateController"/> class.
/// </summary>
/// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
/// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
/// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param>
/// <param name="localizationManager">Instance of the <see cref="ILocalizationManager"/> interface.</param>
/// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
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 )
/// <summary>
/// Updates an item.
/// </summary>
/// <param name="itemId">The item id.</param>
/// <param name="request">The new item properties.</param>
/// <response code="204">Item updated.</response>
/// <response code="404">Item not found.</response>
/// <returns>An <see cref="NoContentResult"/> on success, or a <see cref="NotFoundResult"/> if the item could not be found.</returns>
[HttpPost("/Items/{itemId}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult UpdateItem ( [ FromRoute ] Guid itemId , [ FromBody , BindRequired ] BaseItemDto request )
{
var item = _libraryManager . GetItemById ( request . 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 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 < NameValuePair > GetContentTypeOptions ( bool isForItem )
{
var list = new List < NameValuePair > ( ) ;
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 )
var item = _libraryManager . GetItemById ( itemId ) ;
if ( item = = null )
{
list . Add ( new NameValuePair
{
Name = "Books" ,
Value = "books"
} ) ;
return NotFound ( ) ;
}
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 +116,7 @@ namespace MediaBrowser.Api
if ( displayOrderChanged )
{
_providerManager . QueueRefresh (
series .Id ,
series ! .Id ,
new MetadataRefreshOptions ( new DirectoryService ( _fileSystem ) )
{
MetadataRefreshMode = MetadataRefreshMode . FullRefresh ,
@ -241,11 +125,101 @@ namespace MediaBrowser.Api
} ,
RefreshPriority . High ) ;
}
return NoContent ( ) ;
}
private DateTime NormalizeDateTime ( DateTime val )
/// <summary>
/// Gets metadata editor info for an item.
/// </summary>
/// <param name="itemId">The item id.</param>
/// <response code="200">Item metadata editor returned.</response>
/// <response code="404">Item not found.</response>
/// <returns>An <see cref="OkResult"/> on success containing the metadata editor, or a <see cref="NotFoundResult"/> if the item could not be found.</returns>
[HttpGet("/Items/{itemId}/MetadataEditor")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult < MetadataEditorInfo > 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 ;
}
/// <summary>
/// Updates an item's content type.
/// </summary>
/// <param name="itemId">The item id.</param>
/// <param name="contentType">The content type of the item.</param>
/// <response code="204">Item content type updated.</response>
/// <response code="404">Item not found.</response>
/// <returns>An <see cref="NoContentResult"/> on success, or a <see cref="NotFoundResult"/> if the item could not be found.</returns>
[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 ) ;
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 NoContent ( ) ;
}
private void UpdateItem ( BaseItemDto request , BaseItem item )
@ -361,24 +335,25 @@ namespace MediaBrowser.Api
}
}
if ( item is Audio song )
{
song . Album = request . Album ;
}
if ( item is MusicVideo musicVideo )
switch ( item )
{
musicVideo . Album = request . Album ;
}
case Audio song :
song . Album = request . Album ;
break ;
case MusicVideo musicVideo :
musicVideo . Album = request . Album ;
break ;
case Series series :
{
series . Status = GetSeriesStatus ( request ) ;
if ( item is Series series )
{
series . Status = GetSeriesStatus ( request ) ;
if ( request . AirDays ! = null )
{
series . AirDays = request . AirDays ;
series . AirTime = request . AirTime ;
}
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 < NameValuePair > GetContentTypeOptions ( bool isForItem )
{
var list = new List < NameValuePair > ( ) ;
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 ;
}
}
}