aboutsummaryrefslogtreecommitdiff
path: root/Jellyfin.Api/Controllers
diff options
context:
space:
mode:
Diffstat (limited to 'Jellyfin.Api/Controllers')
-rw-r--r--Jellyfin.Api/Controllers/DynamicHlsController.cs51
-rw-r--r--Jellyfin.Api/Controllers/ItemUpdateController.cs12
-rw-r--r--Jellyfin.Api/Controllers/ItemsController.cs4
-rw-r--r--Jellyfin.Api/Controllers/LibraryController.cs20
-rw-r--r--Jellyfin.Api/Controllers/LiveTvController.cs7
-rw-r--r--Jellyfin.Api/Controllers/MediaInfoController.cs2
-rw-r--r--Jellyfin.Api/Controllers/PlaylistsController.cs6
-rw-r--r--Jellyfin.Api/Controllers/StartupController.cs4
-rw-r--r--Jellyfin.Api/Controllers/SubtitleController.cs5
-rw-r--r--Jellyfin.Api/Controllers/SystemController.cs27
-rw-r--r--Jellyfin.Api/Controllers/TvShowsController.cs5
-rw-r--r--Jellyfin.Api/Controllers/UniversalAudioController.cs2
-rw-r--r--Jellyfin.Api/Controllers/UserController.cs46
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;
}