diff options
Diffstat (limited to 'Jellyfin.Api/Controllers')
| -rw-r--r-- | Jellyfin.Api/Controllers/DynamicHlsController.cs | 51 | ||||
| -rw-r--r-- | Jellyfin.Api/Controllers/ItemUpdateController.cs | 12 | ||||
| -rw-r--r-- | Jellyfin.Api/Controllers/ItemsController.cs | 4 | ||||
| -rw-r--r-- | Jellyfin.Api/Controllers/LibraryController.cs | 20 | ||||
| -rw-r--r-- | Jellyfin.Api/Controllers/LiveTvController.cs | 7 | ||||
| -rw-r--r-- | Jellyfin.Api/Controllers/MediaInfoController.cs | 2 | ||||
| -rw-r--r-- | Jellyfin.Api/Controllers/PlaylistsController.cs | 6 | ||||
| -rw-r--r-- | Jellyfin.Api/Controllers/StartupController.cs | 4 | ||||
| -rw-r--r-- | Jellyfin.Api/Controllers/SubtitleController.cs | 5 | ||||
| -rw-r--r-- | Jellyfin.Api/Controllers/SystemController.cs | 27 | ||||
| -rw-r--r-- | Jellyfin.Api/Controllers/TvShowsController.cs | 5 | ||||
| -rw-r--r-- | Jellyfin.Api/Controllers/UniversalAudioController.cs | 2 | ||||
| -rw-r--r-- | Jellyfin.Api/Controllers/UserController.cs | 46 |
13 files changed, 101 insertions, 90 deletions
diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs index 09cf7303e..065a4ce5c 100644 --- a/Jellyfin.Api/Controllers/DynamicHlsController.cs +++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs @@ -12,6 +12,7 @@ using Jellyfin.Api.Attributes; using Jellyfin.Api.Helpers; using Jellyfin.Api.Models.PlaybackDtos; using Jellyfin.Api.Models.StreamingDtos; +using Jellyfin.Data.Enums; using Jellyfin.Extensions; using Jellyfin.MediaEncoding.Hls.Playlist; using MediaBrowser.Common.Configuration; @@ -1641,26 +1642,55 @@ public class DynamicHlsController : BaseJellyfinApiController Path.GetFileNameWithoutExtension(outputPath)); } + var hlsArguments = GetHlsArguments(isEventPlaylist, state.SegmentLength); + return string.Format( CultureInfo.InvariantCulture, - "{0} {1} -map_metadata -1 -map_chapters -1 -threads {2} {3} {4} {5} -copyts -avoid_negative_ts disabled -max_muxing_queue_size {6} -f hls -max_delay 5000000 -hls_time {7} -hls_segment_type {8} -start_number {9}{10} -hls_segment_filename \"{12}\" -hls_playlist_type {11} -hls_list_size 0 -y \"{13}\"", + "{0} {1} -map_metadata -1 -map_chapters -1 -threads {2} {3} {4} {5} -copyts -avoid_negative_ts disabled -max_muxing_queue_size {6} -f hls -max_delay 5000000 -hls_time {7} -hls_segment_type {8} -start_number {9}{10} -hls_segment_filename \"{11}\" {12} -y \"{13}\"", inputModifier, _encodingHelper.GetInputArgument(state, _encodingOptions, segmentContainer), threads, mapArgs, - GetVideoArguments(state, startNumber, isEventPlaylist), + GetVideoArguments(state, startNumber, isEventPlaylist, segmentContainer), GetAudioArguments(state), maxMuxingQueueSize, state.SegmentLength.ToString(CultureInfo.InvariantCulture), segmentFormat, startNumber.ToString(CultureInfo.InvariantCulture), baseUrlParam, - isEventPlaylist ? "event" : "vod", EncodingUtils.NormalizePath(outputTsArg), + hlsArguments, EncodingUtils.NormalizePath(outputPath)).Trim(); } /// <summary> + /// Gets the HLS arguments for transcoding. + /// </summary> + /// <returns>The command line arguments for HLS transcoding.</returns> + private string GetHlsArguments(bool isEventPlaylist, int segmentLength) + { + var enableThrottling = _encodingOptions.EnableThrottling; + var enableSegmentDeletion = _encodingOptions.EnableSegmentDeletion; + + // Only enable segment deletion when throttling is enabled + if (enableThrottling && enableSegmentDeletion) + { + // Store enough segments for configured seconds of playback; this needs to be above throttling settings + var segmentCount = _encodingOptions.SegmentKeepSeconds / segmentLength; + + _logger.LogDebug("Using throttling and segment deletion, keeping {0} segments", segmentCount); + + return string.Format(CultureInfo.InvariantCulture, "-hls_list_size {0} -hls_flags delete_segments", segmentCount.ToString(CultureInfo.InvariantCulture)); + } + else + { + _logger.LogDebug("Using normal playback, is event playlist? {0}", isEventPlaylist); + + return string.Format(CultureInfo.InvariantCulture, "-hls_playlist_type {0} -hls_list_size 0", isEventPlaylist ? "event" : "vod"); + } + } + + /// <summary> /// Gets the audio arguments for transcoding. /// </summary> /// <param name="state">The <see cref="StreamState"/>.</param> @@ -1673,19 +1703,18 @@ public class DynamicHlsController : BaseJellyfinApiController } var audioCodec = _encodingHelper.GetAudioEncoder(state); + var bitStreamArgs = EncodingHelper.GetAudioBitStreamArguments(state, state.Request.SegmentContainer, state.MediaSource.Container); if (!state.IsOutputVideo) { if (EncodingHelper.IsCopyCodec(audioCodec)) { - var bitStreamArgs = EncodingHelper.GetAudioBitStreamArguments(state, state.Request.SegmentContainer, state.MediaSource.Container); - return "-acodec copy -strict -2" + bitStreamArgs; } var audioTranscodeParams = string.Empty; - audioTranscodeParams += "-acodec " + audioCodec; + audioTranscodeParams += "-acodec " + audioCodec + bitStreamArgs; var audioBitrate = state.OutputAudioBitrate; var audioChannels = state.OutputAudioChannels; @@ -1731,7 +1760,6 @@ public class DynamicHlsController : BaseJellyfinApiController if (EncodingHelper.IsCopyCodec(audioCodec)) { var videoCodec = _encodingHelper.GetVideoEncoder(state, _encodingOptions); - var bitStreamArgs = EncodingHelper.GetAudioBitStreamArguments(state, state.Request.SegmentContainer, state.MediaSource.Container); var copyArgs = "-codec:a:0 copy" + bitStreamArgs + strictArgs; if (EncodingHelper.IsCopyCodec(videoCodec) && state.EnableBreakOnNonKeyFrames(videoCodec)) @@ -1742,7 +1770,7 @@ public class DynamicHlsController : BaseJellyfinApiController return copyArgs; } - var args = "-codec:a:0 " + audioCodec + strictArgs; + var args = "-codec:a:0 " + audioCodec + bitStreamArgs + strictArgs; var channels = state.OutputAudioChannels; @@ -1786,8 +1814,9 @@ public class DynamicHlsController : BaseJellyfinApiController /// <param name="state">The <see cref="StreamState"/>.</param> /// <param name="startNumber">The first number in the hls sequence.</param> /// <param name="isEventPlaylist">Whether the playlist is EVENT or VOD.</param> + /// <param name="segmentContainer">The segment container.</param> /// <returns>The command line arguments for video transcoding.</returns> - private string GetVideoArguments(StreamState state, int startNumber, bool isEventPlaylist) + private string GetVideoArguments(StreamState state, int startNumber, bool isEventPlaylist, string segmentContainer) { if (state.VideoStream is null) { @@ -1809,7 +1838,7 @@ public class DynamicHlsController : BaseJellyfinApiController || string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)) { if (EncodingHelper.IsCopyCodec(codec) - && (string.Equals(state.VideoStream.VideoRangeType, "DOVI", StringComparison.OrdinalIgnoreCase) + && (state.VideoStream.VideoRangeType == VideoRangeType.DOVI || string.Equals(state.VideoStream.CodecTag, "dovi", StringComparison.OrdinalIgnoreCase) || string.Equals(state.VideoStream.CodecTag, "dvh1", StringComparison.OrdinalIgnoreCase) || string.Equals(state.VideoStream.CodecTag, "dvhe", StringComparison.OrdinalIgnoreCase))) @@ -1879,7 +1908,7 @@ public class DynamicHlsController : BaseJellyfinApiController } // TODO why was this not enabled for VOD? - if (isEventPlaylist) + if (isEventPlaylist && string.Equals(segmentContainer, "ts", StringComparison.OrdinalIgnoreCase)) { args += " -flags -global_header"; } diff --git a/Jellyfin.Api/Controllers/ItemUpdateController.cs b/Jellyfin.Api/Controllers/ItemUpdateController.cs index ece053a9a..504f2fa1d 100644 --- a/Jellyfin.Api/Controllers/ItemUpdateController.cs +++ b/Jellyfin.Api/Controllers/ItemUpdateController.cs @@ -251,8 +251,6 @@ public class ItemUpdateController : BaseJellyfinApiController channel.Height = request.Height.Value; } - item.Tags = request.Tags; - if (request.Taglines is not null) { item.Tagline = request.Taglines.FirstOrDefault(); @@ -276,12 +274,19 @@ public class ItemUpdateController : BaseJellyfinApiController item.OfficialRating = request.OfficialRating; item.CustomRating = request.CustomRating; + var currentTags = item.Tags; + var newTags = request.Tags; + var removedTags = currentTags.Except(newTags).ToList(); + var addedTags = newTags.Except(currentTags).ToList(); + item.Tags = newTags; + if (item is Series rseries) { foreach (Season season in rseries.Children) { season.OfficialRating = request.OfficialRating; season.CustomRating = request.CustomRating; + season.Tags = season.Tags.Concat(addedTags).Except(removedTags).Distinct().ToArray(); season.OnMetadataChanged(); await season.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); @@ -289,6 +294,7 @@ public class ItemUpdateController : BaseJellyfinApiController { ep.OfficialRating = request.OfficialRating; ep.CustomRating = request.CustomRating; + ep.Tags = ep.Tags.Concat(addedTags).Except(removedTags).Distinct().ToArray(); ep.OnMetadataChanged(); await ep.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); } @@ -300,6 +306,7 @@ public class ItemUpdateController : BaseJellyfinApiController { ep.OfficialRating = request.OfficialRating; ep.CustomRating = request.CustomRating; + ep.Tags = ep.Tags.Concat(addedTags).Except(removedTags).Distinct().ToArray(); ep.OnMetadataChanged(); await ep.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); } @@ -310,6 +317,7 @@ public class ItemUpdateController : BaseJellyfinApiController { track.OfficialRating = request.OfficialRating; track.CustomRating = request.CustomRating; + track.Tags = track.Tags.Concat(addedTags).Except(removedTags).Distinct().ToArray(); track.OnMetadataChanged(); await track.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); } diff --git a/Jellyfin.Api/Controllers/ItemsController.cs b/Jellyfin.Api/Controllers/ItemsController.cs index 377526729..80128536d 100644 --- a/Jellyfin.Api/Controllers/ItemsController.cs +++ b/Jellyfin.Api/Controllers/ItemsController.cs @@ -256,8 +256,7 @@ public class ItemsController : BaseJellyfinApiController .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); if (includeItemTypes.Length == 1 - && (includeItemTypes[0] == BaseItemKind.Playlist - || includeItemTypes[0] == BaseItemKind.BoxSet)) + && includeItemTypes[0] == BaseItemKind.BoxSet) { parentId = null; } @@ -503,6 +502,7 @@ public class ItemsController : BaseJellyfinApiController } } + query.Parent = null; result = folder.GetItems(query); } else diff --git a/Jellyfin.Api/Controllers/LibraryController.cs b/Jellyfin.Api/Controllers/LibraryController.cs index e094d2d77..46c0a8d52 100644 --- a/Jellyfin.Api/Controllers/LibraryController.cs +++ b/Jellyfin.Api/Controllers/LibraryController.cs @@ -969,12 +969,8 @@ public class LibraryController : BaseJellyfinApiController || string.Equals(name, "MusicBrainz", StringComparison.OrdinalIgnoreCase); } - var metadataOptions = _serverConfigurationManager.Configuration.MetadataOptions - .Where(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase)) - .ToArray(); - - return metadataOptions.Length == 0 - || metadataOptions.Any(i => !i.DisabledMetadataFetchers.Contains(name, StringComparison.OrdinalIgnoreCase)); + var metadataOptions = _serverConfigurationManager.GetMetadataOptionsForType(type); + return metadataOptions is null || !metadataOptions.DisabledMetadataFetchers.Contains(name, StringComparison.OrdinalIgnoreCase); } private bool IsImageFetcherEnabledByDefault(string name, string type, bool isNewLibrary) @@ -995,15 +991,7 @@ public class LibraryController : BaseJellyfinApiController || string.Equals(name, "Image Extractor", StringComparison.OrdinalIgnoreCase); } - var metadataOptions = _serverConfigurationManager.Configuration.MetadataOptions - .Where(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase)) - .ToArray(); - - if (metadataOptions.Length == 0) - { - return true; - } - - return metadataOptions.Any(i => !i.DisabledImageFetchers.Contains(name, StringComparison.OrdinalIgnoreCase)); + var metadataOptions = _serverConfigurationManager.GetMetadataOptionsForType(type); + return metadataOptions is null || !metadataOptions.DisabledImageFetchers.Contains(name, StringComparison.OrdinalIgnoreCase); } } diff --git a/Jellyfin.Api/Controllers/LiveTvController.cs b/Jellyfin.Api/Controllers/LiveTvController.cs index 267ba4afb..649397d68 100644 --- a/Jellyfin.Api/Controllers/LiveTvController.cs +++ b/Jellyfin.Api/Controllers/LiveTvController.cs @@ -23,7 +23,6 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; -using MediaBrowser.Controller.Session; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.LiveTv; @@ -48,7 +47,6 @@ public class LiveTvController : BaseJellyfinApiController private readonly IMediaSourceManager _mediaSourceManager; private readonly IConfigurationManager _configurationManager; private readonly TranscodingJobHelper _transcodingJobHelper; - private readonly ISessionManager _sessionManager; /// <summary> /// Initializes a new instance of the <see cref="LiveTvController"/> class. @@ -61,7 +59,6 @@ public class LiveTvController : BaseJellyfinApiController /// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param> /// <param name="configurationManager">Instance of the <see cref="IConfigurationManager"/> interface.</param> /// <param name="transcodingJobHelper">Instance of the <see cref="TranscodingJobHelper"/> class.</param> - /// <param name="sessionManager">Instance of the <see cref="ISessionManager"/> interface.</param> public LiveTvController( ILiveTvManager liveTvManager, IUserManager userManager, @@ -70,8 +67,7 @@ public class LiveTvController : BaseJellyfinApiController IDtoService dtoService, IMediaSourceManager mediaSourceManager, IConfigurationManager configurationManager, - TranscodingJobHelper transcodingJobHelper, - ISessionManager sessionManager) + TranscodingJobHelper transcodingJobHelper) { _liveTvManager = liveTvManager; _userManager = userManager; @@ -81,7 +77,6 @@ public class LiveTvController : BaseJellyfinApiController _mediaSourceManager = mediaSourceManager; _configurationManager = configurationManager; _transcodingJobHelper = transcodingJobHelper; - _sessionManager = sessionManager; } /// <summary> diff --git a/Jellyfin.Api/Controllers/MediaInfoController.cs b/Jellyfin.Api/Controllers/MediaInfoController.cs index da24616ff..bea545cfd 100644 --- a/Jellyfin.Api/Controllers/MediaInfoController.cs +++ b/Jellyfin.Api/Controllers/MediaInfoController.cs @@ -184,7 +184,7 @@ public class MediaInfoController : BaseJellyfinApiController enableTranscoding.Value, allowVideoStreamCopy.Value, allowAudioStreamCopy.Value, - Request.HttpContext.GetNormalizedRemoteIp()); + Request.HttpContext.GetNormalizedRemoteIP()); } _mediaInfoHelper.SortMediaSources(info, maxStreamingBitrate); diff --git a/Jellyfin.Api/Controllers/PlaylistsController.cs b/Jellyfin.Api/Controllers/PlaylistsController.cs index c6dbea5e2..8d2a738d4 100644 --- a/Jellyfin.Api/Controllers/PlaylistsController.cs +++ b/Jellyfin.Api/Controllers/PlaylistsController.cs @@ -64,6 +64,7 @@ public class PlaylistsController : BaseJellyfinApiController /// <param name="userId">The user id.</param> /// <param name="mediaType">The media type.</param> /// <param name="createPlaylistRequest">The create playlist payload.</param> + /// <response code="200">Playlist created.</response> /// <returns> /// A <see cref="Task" /> that represents the asynchronous operation to create a playlist. /// The task result contains an <see cref="OkResult"/> indicating success. @@ -167,6 +168,8 @@ public class PlaylistsController : BaseJellyfinApiController /// <response code="404">Playlist not found.</response> /// <returns>The original playlist items.</returns> [HttpGet("{playlistId}/Items")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult<QueryResult<BaseItemDto>> GetPlaylistItems( [FromRoute, Required] Guid playlistId, [FromQuery, Required] Guid userId, @@ -189,9 +192,7 @@ public class PlaylistsController : BaseJellyfinApiController : _userManager.GetUserById(userId); var items = playlist.GetManageableItems().ToArray(); - var count = items.Length; - if (startIndex.HasValue) { items = items.Skip(startIndex.Value).ToArray(); @@ -207,7 +208,6 @@ public class PlaylistsController : BaseJellyfinApiController .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); var dtos = _dtoService.GetBaseItemDtos(items.Select(i => i.Item2).ToList(), dtoOptions, user); - for (int index = 0; index < dtos.Count; index++) { dtos[index].PlaylistItemId = items[index].Item1.Id; diff --git a/Jellyfin.Api/Controllers/StartupController.cs b/Jellyfin.Api/Controllers/StartupController.cs index aab390d1f..1098733b2 100644 --- a/Jellyfin.Api/Controllers/StartupController.cs +++ b/Jellyfin.Api/Controllers/StartupController.cs @@ -131,6 +131,10 @@ public class StartupController : BaseJellyfinApiController public async Task<ActionResult> UpdateStartupUser([FromBody] StartupUserDto startupUserDto) { var user = _userManager.Users.First(); + if (string.IsNullOrWhiteSpace(startupUserDto.Password)) + { + return BadRequest("Password must not be empty"); + } if (startupUserDto.Name is not null) { diff --git a/Jellyfin.Api/Controllers/SubtitleController.cs b/Jellyfin.Api/Controllers/SubtitleController.cs index b3e9d6297..7d02550b6 100644 --- a/Jellyfin.Api/Controllers/SubtitleController.cs +++ b/Jellyfin.Api/Controllers/SubtitleController.cs @@ -90,7 +90,7 @@ public class SubtitleController : BaseJellyfinApiController [Authorize(Policy = Policies.RequiresElevation)] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public ActionResult<Task> DeleteSubtitle( + public async Task<ActionResult> DeleteSubtitle( [FromRoute, Required] Guid itemId, [FromRoute, Required] int index) { @@ -101,7 +101,7 @@ public class SubtitleController : BaseJellyfinApiController return NotFound(); } - _subtitleManager.DeleteSubtitles(item, index); + await _subtitleManager.DeleteSubtitles(item, index).ConfigureAwait(false); return NoContent(); } @@ -416,6 +416,7 @@ public class SubtitleController : BaseJellyfinApiController Format = body.Format, Language = body.Language, IsForced = body.IsForced, + IsHearingImpaired = body.IsHearingImpaired, Stream = memoryStream }).ConfigureAwait(false); _providerManager.QueueRefresh(video.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), RefreshPriority.High); diff --git a/Jellyfin.Api/Controllers/SystemController.cs b/Jellyfin.Api/Controllers/SystemController.cs index 4ab705f40..42ac4a9b4 100644 --- a/Jellyfin.Api/Controllers/SystemController.cs +++ b/Jellyfin.Api/Controllers/SystemController.cs @@ -4,7 +4,6 @@ using System.ComponentModel.DataAnnotations; using System.IO; using System.Linq; using System.Net.Mime; -using System.Threading.Tasks; using Jellyfin.Api.Attributes; using Jellyfin.Api.Constants; using MediaBrowser.Common.Configuration; @@ -59,10 +58,12 @@ public class SystemController : BaseJellyfinApiController /// Gets information about the server. /// </summary> /// <response code="200">Information retrieved.</response> + /// <response code="403">User does not have permission to retrieve information.</response> /// <returns>A <see cref="SystemInfo"/> with info about the system.</returns> [HttpGet("Info")] [Authorize(Policy = Policies.FirstTimeSetupOrIgnoreParentalControl)] [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] public ActionResult<SystemInfo> GetSystemInfo() { return _appHost.GetSystemInfo(Request); @@ -97,17 +98,15 @@ public class SystemController : BaseJellyfinApiController /// Restarts the application. /// </summary> /// <response code="204">Server restarted.</response> + /// <response code="403">User does not have permission to restart server.</response> /// <returns>No content. Server restarted.</returns> [HttpPost("Restart")] [Authorize(Policy = Policies.LocalAccessOrRequiresElevation)] [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] public ActionResult RestartApplication() { - Task.Run(async () => - { - await Task.Delay(100).ConfigureAwait(false); - _appHost.Restart(); - }); + _appHost.Restart(); return NoContent(); } @@ -115,17 +114,15 @@ public class SystemController : BaseJellyfinApiController /// Shuts down the application. /// </summary> /// <response code="204">Server shut down.</response> + /// <response code="403">User does not have permission to shutdown server.</response> /// <returns>No content. Server shut down.</returns> [HttpPost("Shutdown")] [Authorize(Policy = Policies.RequiresElevation)] [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] public ActionResult ShutdownApplication() { - Task.Run(async () => - { - await Task.Delay(100).ConfigureAwait(false); - await _appHost.Shutdown().ConfigureAwait(false); - }); + _appHost.Shutdown(); return NoContent(); } @@ -133,10 +130,12 @@ public class SystemController : BaseJellyfinApiController /// Gets a list of available server log files. /// </summary> /// <response code="200">Information retrieved.</response> + /// <response code="403">User does not have permission to get server logs.</response> /// <returns>An array of <see cref="LogFile"/> with the available log files.</returns> [HttpGet("Logs")] [Authorize(Policy = Policies.RequiresElevation)] [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] public ActionResult<LogFile[]> GetServerLogs() { IEnumerable<FileSystemMetadata> files; @@ -170,16 +169,18 @@ public class SystemController : BaseJellyfinApiController /// Gets information about the request endpoint. /// </summary> /// <response code="200">Information retrieved.</response> + /// <response code="403">User does not have permission to get endpoint information.</response> /// <returns><see cref="EndPointInfo"/> with information about the endpoint.</returns> [HttpGet("Endpoint")] [Authorize] [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] public ActionResult<EndPointInfo> GetEndpointInfo() { return new EndPointInfo { IsLocal = HttpContext.IsLocal(), - IsInNetwork = _network.IsInLocalNetwork(HttpContext.GetNormalizedRemoteIp()) + IsInNetwork = _network.IsInLocalNetwork(HttpContext.GetNormalizedRemoteIP()) }; } @@ -188,10 +189,12 @@ public class SystemController : BaseJellyfinApiController /// </summary> /// <param name="name">The name of the log file to get.</param> /// <response code="200">Log file retrieved.</response> + /// <response code="403">User does not have permission to get log files.</response> /// <returns>The log file.</returns> [HttpGet("Logs/Log")] [Authorize(Policy = Policies.RequiresElevation)] [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesFile(MediaTypeNames.Text.Plain)] public ActionResult GetLogFile([FromQuery, Required] string name) { diff --git a/Jellyfin.Api/Controllers/TvShowsController.cs b/Jellyfin.Api/Controllers/TvShowsController.cs index 7d23281f2..bdbbd1e0d 100644 --- a/Jellyfin.Api/Controllers/TvShowsController.cs +++ b/Jellyfin.Api/Controllers/TvShowsController.cs @@ -68,7 +68,8 @@ public class TvShowsController : BaseJellyfinApiController /// <param name="nextUpDateCutoff">Optional. Starting date of shows to show in Next Up section.</param> /// <param name="enableTotalRecordCount">Whether to enable the total records count. Defaults to true.</param> /// <param name="disableFirstEpisode">Whether to disable sending the first episode in a series as next up.</param> - /// <param name="enableRewatching">Whether to include watched episode in next up results.</param> + /// <param name="enableResumable">Whether to include resumable episodes in next up results.</param> + /// <param name="enableRewatching">Whether to include watched episodes in next up results.</param> /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the next up episodes.</returns> [HttpGet("NextUp")] [ProducesResponseType(StatusCodes.Status200OK)] @@ -86,6 +87,7 @@ public class TvShowsController : BaseJellyfinApiController [FromQuery] DateTime? nextUpDateCutoff, [FromQuery] bool enableTotalRecordCount = true, [FromQuery] bool disableFirstEpisode = false, + [FromQuery] bool enableResumable = true, [FromQuery] bool enableRewatching = false) { userId = RequestHelpers.GetUserId(User, userId); @@ -104,6 +106,7 @@ public class TvShowsController : BaseJellyfinApiController EnableTotalRecordCount = enableTotalRecordCount, DisableFirstEpisode = disableFirstEpisode, NextUpDateCutoff = nextUpDateCutoff ?? DateTime.MinValue, + EnableResumable = enableResumable, EnableRewatching = enableRewatching }, options); diff --git a/Jellyfin.Api/Controllers/UniversalAudioController.cs b/Jellyfin.Api/Controllers/UniversalAudioController.cs index 2e9035d24..7177a0440 100644 --- a/Jellyfin.Api/Controllers/UniversalAudioController.cs +++ b/Jellyfin.Api/Controllers/UniversalAudioController.cs @@ -138,7 +138,7 @@ public class UniversalAudioController : BaseJellyfinApiController true, true, true, - Request.HttpContext.GetNormalizedRemoteIp()); + Request.HttpContext.GetNormalizedRemoteIP()); } _mediaInfoHelper.SortMediaSources(info, maxStreamingBitrate); diff --git a/Jellyfin.Api/Controllers/UserController.cs b/Jellyfin.Api/Controllers/UserController.cs index e49528867..1be40111d 100644 --- a/Jellyfin.Api/Controllers/UserController.cs +++ b/Jellyfin.Api/Controllers/UserController.cs @@ -134,7 +134,7 @@ public class UserController : BaseJellyfinApiController return NotFound("User not found"); } - var result = _userManager.GetUserDto(user, HttpContext.GetNormalizedRemoteIp().ToString()); + var result = _userManager.GetUserDto(user, HttpContext.GetNormalizedRemoteIP().ToString()); return result; } @@ -217,7 +217,7 @@ public class UserController : BaseJellyfinApiController DeviceId = auth.DeviceId, DeviceName = auth.Device, Password = request.Pw, - RemoteEndPoint = HttpContext.GetNormalizedRemoteIp().ToString(), + RemoteEndPoint = HttpContext.GetNormalizedRemoteIP().ToString(), Username = request.Username }).ConfigureAwait(false); @@ -226,7 +226,7 @@ public class UserController : BaseJellyfinApiController catch (SecurityException e) { // rethrow adding IP address to message - throw new SecurityException($"[{HttpContext.GetNormalizedRemoteIp()}] {e.Message}", e); + throw new SecurityException($"[{HttpContext.GetNormalizedRemoteIP()}] {e.Message}", e); } } @@ -248,7 +248,7 @@ public class UserController : BaseJellyfinApiController catch (SecurityException e) { // rethrow adding IP address to message - throw new SecurityException($"[{HttpContext.GetNormalizedRemoteIp()}] {e.Message}", e); + throw new SecurityException($"[{HttpContext.GetNormalizedRemoteIP()}] {e.Message}", e); } } @@ -294,7 +294,7 @@ public class UserController : BaseJellyfinApiController user.Username, request.CurrentPw ?? string.Empty, request.CurrentPw ?? string.Empty, - HttpContext.GetNormalizedRemoteIp().ToString(), + HttpContext.GetNormalizedRemoteIP().ToString(), false).ConfigureAwait(false); if (success is null) @@ -323,36 +323,16 @@ public class UserController : BaseJellyfinApiController /// <response code="404">User not found.</response> /// <returns>A <see cref="NoContentResult"/> indicating success or a <see cref="ForbidResult"/> or a <see cref="NotFoundResult"/> on failure.</returns> [HttpPost("{userId}/EasyPassword")] + [Obsolete("Use Quick Connect instead")] [Authorize] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task<ActionResult> UpdateUserEasyPassword( + public ActionResult UpdateUserEasyPassword( [FromRoute, Required] Guid userId, [FromBody, Required] UpdateUserEasyPassword request) { - if (!RequestHelpers.AssertCanUpdateUser(_userManager, User, userId, true)) - { - return StatusCode(StatusCodes.Status403Forbidden, "User is not allowed to update the easy password."); - } - - var user = _userManager.GetUserById(userId); - - if (user is null) - { - return NotFound("User not found"); - } - - if (request.ResetPassword) - { - await _userManager.ResetEasyPassword(user).ConfigureAwait(false); - } - else - { - await _userManager.ChangeEasyPassword(user, request.NewPw ?? string.Empty, request.NewPassword ?? string.Empty).ConfigureAwait(false); - } - - return NoContent(); + return Forbid(); } /// <summary> @@ -495,7 +475,7 @@ public class UserController : BaseJellyfinApiController await _userManager.ChangePassword(newUser, request.Password).ConfigureAwait(false); } - var result = _userManager.GetUserDto(newUser, HttpContext.GetNormalizedRemoteIp().ToString()); + var result = _userManager.GetUserDto(newUser, HttpContext.GetNormalizedRemoteIP().ToString()); return result; } @@ -510,11 +490,11 @@ public class UserController : BaseJellyfinApiController [ProducesResponseType(StatusCodes.Status200OK)] public async Task<ActionResult<ForgotPasswordResult>> ForgotPassword([FromBody, Required] ForgotPasswordDto forgotPasswordRequest) { - var ip = HttpContext.GetNormalizedRemoteIp(); + var ip = HttpContext.GetNormalizedRemoteIP(); var isLocal = HttpContext.IsLocal() || _networkManager.IsInLocalNetwork(ip); - if (isLocal) + if (!isLocal) { _logger.LogWarning("Password reset process initiated from outside the local network with IP: {IP}", ip); } @@ -591,7 +571,7 @@ public class UserController : BaseJellyfinApiController if (filterByNetwork) { - if (!_networkManager.IsInLocalNetwork(HttpContext.GetNormalizedRemoteIp())) + if (!_networkManager.IsInLocalNetwork(HttpContext.GetNormalizedRemoteIP())) { users = users.Where(i => i.HasPermission(PermissionKind.EnableRemoteAccess)); } @@ -599,7 +579,7 @@ public class UserController : BaseJellyfinApiController var result = users .OrderBy(u => u.Username) - .Select(i => _userManager.GetUserDto(i, HttpContext.GetNormalizedRemoteIp().ToString())); + .Select(i => _userManager.GetUserDto(i, HttpContext.GetNormalizedRemoteIP().ToString())); return result; } |
