Pārlūkot izejas kodu

Merge branch 'api-migration' of github.com:jellyfin/jellyfin into api-instantmix

David 5 gadi atpakaļ
vecāks
revīzija
ea855c3a75

+ 1 - 4
Jellyfin.Api/Controllers/ActivityLogController.cs

@@ -35,17 +35,14 @@ namespace Jellyfin.Api.Controllers
         /// <param name="startIndex">Optional. The record index to start at. All items with a lower index will be dropped from the results.</param>
         /// <param name="limit">Optional. The maximum number of records to return.</param>
         /// <param name="minDate">Optional. The minimum date. Format = ISO.</param>
-        /// <param name="hasUserId">Optional. Only returns activities that have a user associated.</param>
         /// <response code="200">Activity log returned.</response>
         /// <returns>A <see cref="QueryResult{ActivityLogEntry}"/> containing the log entries.</returns>
         [HttpGet("Entries")]
         [ProducesResponseType(StatusCodes.Status200OK)]
-        [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "hasUserId", Justification = "Imported from ServiceStack")]
         public ActionResult<QueryResult<ActivityLogEntry>> GetLogEntries(
             [FromQuery] int? startIndex,
             [FromQuery] int? limit,
-            [FromQuery] DateTime? minDate,
-            bool? hasUserId)
+            [FromQuery] DateTime? minDate)
         {
             var filterFunc = new Func<IQueryable<ActivityLog>, IQueryable<ActivityLog>>(
                 entries => entries.Where(entry => entry.DateCreated >= minDate));

+ 3 - 7
Jellyfin.Api/Controllers/DashboardController.cs

@@ -178,14 +178,13 @@ namespace Jellyfin.Api.Controllers
         [ApiExplorerSettings(IgnoreApi = true)]
         public ActionResult GetRobotsTxt()
         {
-            return GetWebClientResource("robots.txt", string.Empty);
+            return GetWebClientResource("robots.txt");
         }
 
         /// <summary>
         /// Gets a resource from the web client.
         /// </summary>
         /// <param name="resourceName">The resource name.</param>
-        /// <param name="v">The v.</param>
         /// <response code="200">Web client returned.</response>
         /// <response code="404">Server does not host a web client.</response>
         /// <returns>The resource.</returns>
@@ -193,10 +192,7 @@ namespace Jellyfin.Api.Controllers
         [ApiExplorerSettings(IgnoreApi = true)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
-        [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "v", Justification = "Imported from ServiceStack")]
-        public ActionResult GetWebClientResource(
-            [FromRoute] string resourceName,
-            [FromQuery] string? v)
+        public ActionResult GetWebClientResource([FromRoute] string resourceName)
         {
             if (!_appConfig.HostWebClient() || WebClientUiPath == null)
             {
@@ -228,7 +224,7 @@ namespace Jellyfin.Api.Controllers
         [ApiExplorerSettings(IgnoreApi = true)]
         public ActionResult GetFavIcon()
         {
-            return GetWebClientResource("favicon.ico", string.Empty);
+            return GetWebClientResource("favicon.ico");
         }
 
         /// <summary>

+ 0 - 3
Jellyfin.Api/Controllers/FilterController.cs

@@ -125,7 +125,6 @@ namespace Jellyfin.Api.Controllers
         /// <param name="userId">Optional. User id.</param>
         /// <param name="parentId">Optional. Specify this to localize the search to a specific item or folder. Omit to use the root.</param>
         /// <param name="includeItemTypes">Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimited.</param>
-        /// <param name="mediaTypes">[Unused] Optional. Filter by MediaType. Allows multiple, comma delimited.</param>
         /// <param name="isAiring">Optional. Is item airing.</param>
         /// <param name="isMovie">Optional. Is item movie.</param>
         /// <param name="isSports">Optional. Is item sports.</param>
@@ -137,12 +136,10 @@ namespace Jellyfin.Api.Controllers
         /// <returns>Query filters.</returns>
         [HttpGet("/Items/Filters2")]
         [ProducesResponseType(StatusCodes.Status200OK)]
-        [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "mediaTypes", Justification = "Imported from ServiceStack")]
         public ActionResult<QueryFilters> GetQueryFilters(
             [FromQuery] Guid? userId,
             [FromQuery] string? parentId,
             [FromQuery] string? includeItemTypes,
-            [FromQuery] string? mediaTypes,
             [FromQuery] bool? isAiring,
             [FromQuery] bool? isMovie,
             [FromQuery] bool? isSports,

+ 1 - 4
Jellyfin.Api/Controllers/ItemRefreshController.cs

@@ -47,7 +47,6 @@ namespace Jellyfin.Api.Controllers
         /// <param name="imageRefreshMode">(Optional) Specifies the image refresh mode.</param>
         /// <param name="replaceAllMetadata">(Optional) Determines if metadata should be replaced. Only applicable if mode is FullRefresh.</param>
         /// <param name="replaceAllImages">(Optional) Determines if images should be replaced. Only applicable if mode is FullRefresh.</param>
-        /// <param name="recursive">(Unused) Indicates if the refresh should occur recursively.</param>
         /// <response code="204">Item metadata refresh queued.</response>
         /// <response code="404">Item to refresh not found.</response>
         /// <returns>An <see cref="NoContentResult"/> on success, or a <see cref="NotFoundResult"/> if the item could not be found.</returns>
@@ -55,14 +54,12 @@ namespace Jellyfin.Api.Controllers
         [Description("Refreshes metadata for an item.")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
-        [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "recursive", Justification = "Imported from ServiceStack")]
         public ActionResult Post(
             [FromRoute] Guid itemId,
             [FromQuery] MetadataRefreshMode metadataRefreshMode = MetadataRefreshMode.None,
             [FromQuery] MetadataRefreshMode imageRefreshMode = MetadataRefreshMode.None,
             [FromQuery] bool replaceAllMetadata = false,
-            [FromQuery] bool replaceAllImages = false,
-            [FromQuery] bool recursive = false)
+            [FromQuery] bool replaceAllImages = false)
         {
             var item = _libraryManager.GetItemById(itemId);
             if (item == null)

+ 2 - 22
Jellyfin.Api/Controllers/LibraryController.cs

@@ -120,22 +120,13 @@ namespace Jellyfin.Api.Controllers
         /// <summary>
         /// Gets critic review for an item.
         /// </summary>
-        /// <param name="itemId">The item id.</param>
-        /// <param name="startIndex">Optional. The record index to start at. All items with a lower index will be dropped from the results.</param>
-        /// <param name="limit">Optional. The maximum number of records to return.</param>
         /// <response code="200">Critic reviews returned.</response>
         /// <returns>The list of critic reviews.</returns>
         [HttpGet("/Items/{itemId}/CriticReviews")]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [Obsolete("This endpoint is obsolete.")]
-        [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "itemId", Justification = "Imported from ServiceStack")]
-        [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "startIndex", Justification = "Imported from ServiceStack")]
-        [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "limit", Justification = "Imported from ServiceStack")]
         [ProducesResponseType(StatusCodes.Status200OK)]
-        public ActionResult<QueryResult<BaseItemDto>> GetCriticReviews(
-            [FromRoute] Guid itemId,
-            [FromQuery] int? startIndex,
-            [FromQuery] int? limit)
+        public ActionResult<QueryResult<BaseItemDto>> GetCriticReviews()
         {
             return new QueryResult<BaseItemDto>();
         }
@@ -282,6 +273,7 @@ namespace Jellyfin.Api.Controllers
         /// <response code="404">Item not found.</response>
         /// <returns>The item theme videos.</returns>
         [HttpGet("/Items/{itemId}/ThemeMedia")]
+        [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public ActionResult<AllThemeMediaResult> GetThemeMedia(
             [FromRoute] Guid itemId,
@@ -681,10 +673,6 @@ namespace Jellyfin.Api.Controllers
         /// </summary>
         /// <param name="itemId">The item id.</param>
         /// <param name="excludeArtistIds">Exclude artist ids.</param>
-        /// <param name="enableImages">(Unused) Optional. include image information in output.</param>
-        /// <param name="enableUserData">(Unused) Optional. include user data.</param>
-        /// <param name="imageTypeLimit">(Unused) Optional. the max number of images to return, per image type.</param>
-        /// <param name="enableImageTypes">(Unused) Optional. The image types to include in the output.</param>
         /// <param name="userId">Optional. Filter by user id, and attach user data.</param>
         /// <param name="limit">Optional. The maximum number of records to return.</param>
         /// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls.</param>
@@ -696,18 +684,10 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("/Shows/{itemId}/Similar")]
         [HttpGet("/Movies/{itemId}/Similar")]
         [HttpGet("/Trailers/{itemId}/Similar")]
-        [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "enableImages", Justification = "Imported from ServiceStack")]
-        [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "enableUserData", Justification = "Imported from ServiceStack")]
-        [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "imageTypeLimit", Justification = "Imported from ServiceStack")]
-        [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "enableImageTypes", Justification = "Imported from ServiceStack")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public ActionResult<QueryResult<BaseItemDto>> GetSimilarItems(
             [FromRoute] Guid itemId,
             [FromQuery] string excludeArtistIds,
-            [FromQuery] bool? enableImages,
-            [FromQuery] bool? enableUserData,
-            [FromQuery] int? imageTypeLimit,
-            [FromQuery] string enableImageTypes,
             [FromQuery] Guid userId,
             [FromQuery] int? limit,
             [FromQuery] string fields)

+ 1 - 3
Jellyfin.Api/Controllers/LibraryStructureController.cs

@@ -50,13 +50,11 @@ namespace Jellyfin.Api.Controllers
         /// <summary>
         /// Gets all virtual folders.
         /// </summary>
-        /// <param name="userId">The user id.</param>
         /// <response code="200">Virtual folders retrieved.</response>
         /// <returns>An <see cref="IEnumerable{VirtualFolderInfo}"/> with the virtual folders.</returns>
         [HttpGet]
         [ProducesResponseType(StatusCodes.Status200OK)]
-        [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "userId", Justification = "Imported from ServiceStack")]
-        public ActionResult<IEnumerable<VirtualFolderInfo>> GetVirtualFolders([FromQuery] string userId)
+        public ActionResult<IEnumerable<VirtualFolderInfo>> GetVirtualFolders()
         {
             return _libraryManager.GetVirtualFolders(true);
         }

+ 4 - 31
Jellyfin.Api/Controllers/NotificationsController.cs

@@ -36,23 +36,11 @@ namespace Jellyfin.Api.Controllers
         /// <summary>
         /// Gets a user's notifications.
         /// </summary>
-        /// <param name="userId">The user's ID.</param>
-        /// <param name="isRead">An optional filter by notification read state.</param>
-        /// <param name="startIndex">The optional index to start at. All notifications with a lower index will be omitted from the results.</param>
-        /// <param name="limit">An optional limit on the number of notifications returned.</param>
         /// <response code="200">Notifications returned.</response>
         /// <returns>An <see cref="OkResult"/> containing a list of notifications.</returns>
         [HttpGet("{userId}")]
         [ProducesResponseType(StatusCodes.Status200OK)]
-        [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "userId", Justification = "Imported from ServiceStack")]
-        [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "isRead", Justification = "Imported from ServiceStack")]
-        [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "startIndex", Justification = "Imported from ServiceStack")]
-        [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "limit", Justification = "Imported from ServiceStack")]
-        public ActionResult<NotificationResultDto> GetNotifications(
-            [FromRoute] string userId,
-            [FromQuery] bool? isRead,
-            [FromQuery] int? startIndex,
-            [FromQuery] int? limit)
+        public ActionResult<NotificationResultDto> GetNotifications()
         {
             return new NotificationResultDto();
         }
@@ -60,14 +48,11 @@ namespace Jellyfin.Api.Controllers
         /// <summary>
         /// Gets a user's notification summary.
         /// </summary>
-        /// <param name="userId">The user's ID.</param>
         /// <response code="200">Summary of user's notifications returned.</response>
         /// <returns>An <cref see="OkResult"/> containing a summary of the users notifications.</returns>
         [HttpGet("{userId}/Summary")]
         [ProducesResponseType(StatusCodes.Status200OK)]
-        [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "userId", Justification = "Imported from ServiceStack")]
-        public ActionResult<NotificationsSummaryDto> GetNotificationsSummary(
-            [FromRoute] string userId)
+        public ActionResult<NotificationsSummaryDto> GetNotificationsSummary()
         {
             return new NotificationsSummaryDto();
         }
@@ -134,17 +119,11 @@ namespace Jellyfin.Api.Controllers
         /// <summary>
         /// Sets notifications as read.
         /// </summary>
-        /// <param name="userId">The userID.</param>
-        /// <param name="ids">A comma-separated list of the IDs of notifications which should be set as read.</param>
         /// <response code="204">Notifications set as read.</response>
         /// <returns>A <cref see="NoContentResult"/>.</returns>
         [HttpPost("{userId}/Read")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
-        [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "userId", Justification = "Imported from ServiceStack")]
-        [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "ids", Justification = "Imported from ServiceStack")]
-        public ActionResult SetRead(
-            [FromRoute] string userId,
-            [FromQuery] string ids)
+        public ActionResult SetRead()
         {
             return NoContent();
         }
@@ -152,17 +131,11 @@ namespace Jellyfin.Api.Controllers
         /// <summary>
         /// Sets notifications as unread.
         /// </summary>
-        /// <param name="userId">The userID.</param>
-        /// <param name="ids">A comma-separated list of the IDs of notifications which should be set as unread.</param>
         /// <response code="204">Notifications set as unread.</response>
         /// <returns>A <cref see="NoContentResult"/>.</returns>
         [HttpPost("{userId}/Unread")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
-        [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "userId", Justification = "Imported from ServiceStack")]
-        [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "ids", Justification = "Imported from ServiceStack")]
-        public ActionResult SetUnread(
-            [FromRoute] string userId,
-            [FromQuery] string ids)
+        public ActionResult SetUnread()
         {
             return NoContent();
         }

+ 1 - 3
Jellyfin.Api/Controllers/PluginsController.cs

@@ -42,13 +42,11 @@ namespace Jellyfin.Api.Controllers
         /// <summary>
         /// Gets a list of currently installed plugins.
         /// </summary>
-        /// <param name="isAppStoreEnabled">Optional. Unused.</param>
         /// <response code="200">Installed plugins returned.</response>
         /// <returns>List of currently installed plugins.</returns>
         [HttpGet]
         [ProducesResponseType(StatusCodes.Status200OK)]
-        [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "isAppStoreEnabled", Justification = "Imported from ServiceStack")]
-        public ActionResult<IEnumerable<PluginInfo>> GetPlugins([FromRoute] bool? isAppStoreEnabled)
+        public ActionResult<IEnumerable<PluginInfo>> GetPlugins()
         {
             return Ok(_appHost.Plugins.OrderBy(p => p.Name).Select(p => p.GetPluginInfo()));
         }

+ 161 - 0
Jellyfin.Api/Controllers/ScheduledTasksController.cs

@@ -0,0 +1,161 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Jellyfin.Api.Constants;
+using MediaBrowser.Model.Tasks;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.ModelBinding;
+
+namespace Jellyfin.Api.Controllers
+{
+    /// <summary>
+    /// Scheduled Tasks Controller.
+    /// </summary>
+    [Authorize(Policy = Policies.RequiresElevation)]
+    public class ScheduledTasksController : BaseJellyfinApiController
+    {
+        private readonly ITaskManager _taskManager;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="ScheduledTasksController"/> class.
+        /// </summary>
+        /// <param name="taskManager">Instance of the <see cref="ITaskManager"/> interface.</param>
+        public ScheduledTasksController(ITaskManager taskManager)
+        {
+            _taskManager = taskManager;
+        }
+
+        /// <summary>
+        /// Get tasks.
+        /// </summary>
+        /// <param name="isHidden">Optional filter tasks that are hidden, or not.</param>
+        /// <param name="isEnabled">Optional filter tasks that are enabled, or not.</param>
+        /// <response code="200">Scheduled tasks retrieved.</response>
+        /// <returns>The list of scheduled tasks.</returns>
+        [HttpGet]
+        [ProducesResponseType(StatusCodes.Status200OK)]
+        public IEnumerable<IScheduledTaskWorker> GetTasks(
+            [FromQuery] bool? isHidden,
+            [FromQuery] bool? isEnabled)
+        {
+            IEnumerable<IScheduledTaskWorker> tasks = _taskManager.ScheduledTasks.OrderBy(o => o.Name);
+
+            foreach (var task in tasks)
+            {
+                if (task.ScheduledTask is IConfigurableScheduledTask scheduledTask)
+                {
+                    if (isHidden.HasValue && isHidden.Value != scheduledTask.IsHidden)
+                    {
+                        continue;
+                    }
+
+                    if (isEnabled.HasValue && isEnabled.Value != scheduledTask.IsEnabled)
+                    {
+                        continue;
+                    }
+                }
+
+                yield return task;
+            }
+        }
+
+        /// <summary>
+        /// Get task by id.
+        /// </summary>
+        /// <param name="taskId">Task Id.</param>
+        /// <response code="200">Task retrieved.</response>
+        /// <response code="404">Task not found.</response>
+        /// <returns>An <see cref="OkResult"/> containing the task on success, or a <see cref="NotFoundResult"/> if the task could not be found.</returns>
+        [HttpGet("{taskId}")]
+        [ProducesResponseType(StatusCodes.Status200OK)]
+        [ProducesResponseType(StatusCodes.Status404NotFound)]
+        public ActionResult<TaskInfo> GetTask([FromRoute] string taskId)
+        {
+            var task = _taskManager.ScheduledTasks.FirstOrDefault(i =>
+                string.Equals(i.Id, taskId, StringComparison.OrdinalIgnoreCase));
+
+            if (task == null)
+            {
+                return NotFound();
+            }
+
+            return ScheduledTaskHelpers.GetTaskInfo(task);
+        }
+
+        /// <summary>
+        /// Start specified task.
+        /// </summary>
+        /// <param name="taskId">Task Id.</param>
+        /// <response code="204">Task started.</response>
+        /// <response code="404">Task not found.</response>
+        /// <returns>An <see cref="NoContentResult"/> on success, or a <see cref="NotFoundResult"/> if the file could not be found.</returns>
+        [HttpPost("Running/{taskId}")]
+        [ProducesResponseType(StatusCodes.Status204NoContent)]
+        [ProducesResponseType(StatusCodes.Status404NotFound)]
+        public ActionResult StartTask([FromRoute] string taskId)
+        {
+            var task = _taskManager.ScheduledTasks.FirstOrDefault(o =>
+                o.Id.Equals(taskId, StringComparison.OrdinalIgnoreCase));
+
+            if (task == null)
+            {
+                return NotFound();
+            }
+
+            _taskManager.Execute(task, new TaskOptions());
+            return NoContent();
+        }
+
+        /// <summary>
+        /// Stop specified task.
+        /// </summary>
+        /// <param name="taskId">Task Id.</param>
+        /// <response code="204">Task stopped.</response>
+        /// <response code="404">Task not found.</response>
+        /// <returns>An <see cref="OkResult"/> on success, or a <see cref="NotFoundResult"/> if the file could not be found.</returns>
+        [HttpDelete("Running/{taskId}")]
+        [ProducesResponseType(StatusCodes.Status204NoContent)]
+        [ProducesResponseType(StatusCodes.Status404NotFound)]
+        public ActionResult StopTask([FromRoute] string taskId)
+        {
+            var task = _taskManager.ScheduledTasks.FirstOrDefault(o =>
+                o.Id.Equals(taskId, StringComparison.OrdinalIgnoreCase));
+
+            if (task == null)
+            {
+                return NotFound();
+            }
+
+            _taskManager.Cancel(task);
+            return NoContent();
+        }
+
+        /// <summary>
+        /// Update specified task triggers.
+        /// </summary>
+        /// <param name="taskId">Task Id.</param>
+        /// <param name="triggerInfos">Triggers.</param>
+        /// <response code="204">Task triggers updated.</response>
+        /// <response code="404">Task not found.</response>
+        /// <returns>An <see cref="OkResult"/> on success, or a <see cref="NotFoundResult"/> if the file could not be found.</returns>
+        [HttpPost("{taskId}/Triggers")]
+        [ProducesResponseType(StatusCodes.Status204NoContent)]
+        [ProducesResponseType(StatusCodes.Status404NotFound)]
+        public ActionResult UpdateTask(
+            [FromRoute] string taskId,
+            [FromBody, BindRequired] TaskTriggerInfo[] triggerInfos)
+        {
+            var task = _taskManager.ScheduledTasks.FirstOrDefault(o =>
+                o.Id.Equals(taskId, StringComparison.OrdinalIgnoreCase));
+            if (task == null)
+            {
+                return NotFound();
+            }
+
+            task.Triggers = triggerInfos;
+            return NoContent();
+        }
+    }
+}

+ 1 - 4
Jellyfin.Api/Controllers/TvShowsController.cs

@@ -185,12 +185,10 @@ namespace Jellyfin.Api.Controllers
         /// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
         /// <param name="enableUserData">Optional. Include user data.</param>
         /// <param name="sortBy">Optional. Specify one or more sort orders, comma delimeted. Options: Album, AlbumArtist, Artist, Budget, CommunityRating, CriticRating, DateCreated, DatePlayed, PlayCount, PremiereDate, ProductionYear, SortName, Random, Revenue, Runtime.</param>
-        /// <param name="sortOrder">Optional. Sort order: Ascending,Descending.</param>
         /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the episodes on success or a <see cref="NotFoundResult"/> if the series was not found.</returns>
         [HttpGet("{seriesId}/Episodes")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
-        [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "sortOrder", Justification = "Imported from ServiceStack")]
         public ActionResult<QueryResult<BaseItemDto>> GetEpisodes(
             [FromRoute] string seriesId,
             [FromQuery] Guid userId,
@@ -206,8 +204,7 @@ namespace Jellyfin.Api.Controllers
             [FromQuery] int? imageTypeLimit,
             [FromQuery] string? enableImageTypes,
             [FromQuery] bool? enableUserData,
-            [FromQuery] string? sortBy,
-            [FromQuery] SortOrder? sortOrder)
+            [FromQuery] string? sortBy)
         {
             var user = _userManager.GetUserById(userId);
 

+ 1 - 4
Jellyfin.Api/Controllers/UserController.cs

@@ -68,17 +68,14 @@ namespace Jellyfin.Api.Controllers
         /// </summary>
         /// <param name="isHidden">Optional filter by IsHidden=true or false.</param>
         /// <param name="isDisabled">Optional filter by IsDisabled=true or false.</param>
-        /// <param name="isGuest">Optional filter by IsGuest=true or false.</param>
         /// <response code="200">Users returned.</response>
         /// <returns>An <see cref="IEnumerable{UserDto}"/> containing the users.</returns>
         [HttpGet]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status200OK)]
-        [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "isGuest", Justification = "Imported from ServiceStack")]
         public ActionResult<IEnumerable<UserDto>> GetUsers(
             [FromQuery] bool? isHidden,
-            [FromQuery] bool? isDisabled,
-            [FromQuery] bool? isGuest)
+            [FromQuery] bool? isDisabled)
         {
             var users = Get(isHidden, isDisabled, false, false);
             return Ok(users);

+ 1 - 1
Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs

@@ -195,7 +195,7 @@ namespace Jellyfin.Server.Extensions
 
                 // Order actions by route path, then by http method.
                 c.OrderActionsBy(description =>
-                    $"{description.ActionDescriptor.RouteValues["controller"]}_{description.HttpMethod}");
+                    $"{description.ActionDescriptor.RouteValues["controller"]}_{description.RelativePath}");
 
                 // Use method name as operationId
                 c.CustomOperationIds(description =>

+ 0 - 234
MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs

@@ -1,234 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using MediaBrowser.Common.Extensions;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Net;
-using MediaBrowser.Model.Services;
-using MediaBrowser.Model.Tasks;
-using Microsoft.Extensions.Logging;
-
-namespace MediaBrowser.Api.ScheduledTasks
-{
-    /// <summary>
-    /// Class GetScheduledTask
-    /// </summary>
-    [Route("/ScheduledTasks/{Id}", "GET", Summary = "Gets a scheduled task, by Id")]
-    public class GetScheduledTask : IReturn<TaskInfo>
-    {
-        /// <summary>
-        /// Gets or sets the id.
-        /// </summary>
-        /// <value>The id.</value>
-        [ApiMember(Name = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
-        public string Id { get; set; }
-    }
-
-    /// <summary>
-    /// Class GetScheduledTasks
-    /// </summary>
-    [Route("/ScheduledTasks", "GET", Summary = "Gets scheduled tasks")]
-    public class GetScheduledTasks : IReturn<TaskInfo[]>
-    {
-        [ApiMember(Name = "IsHidden", Description = "Optional filter tasks that are hidden, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
-        public bool? IsHidden { get; set; }
-
-        [ApiMember(Name = "IsEnabled", Description = "Optional filter tasks that are enabled, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
-        public bool? IsEnabled { get; set; }
-    }
-
-    /// <summary>
-    /// Class StartScheduledTask
-    /// </summary>
-    [Route("/ScheduledTasks/Running/{Id}", "POST", Summary = "Starts a scheduled task")]
-    public class StartScheduledTask : IReturnVoid
-    {
-        /// <summary>
-        /// Gets or sets the id.
-        /// </summary>
-        /// <value>The id.</value>
-        [ApiMember(Name = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
-        public string Id { get; set; }
-    }
-
-    /// <summary>
-    /// Class StopScheduledTask
-    /// </summary>
-    [Route("/ScheduledTasks/Running/{Id}", "DELETE", Summary = "Stops a scheduled task")]
-    public class StopScheduledTask : IReturnVoid
-    {
-        /// <summary>
-        /// Gets or sets the id.
-        /// </summary>
-        /// <value>The id.</value>
-        [ApiMember(Name = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")]
-        public string Id { get; set; }
-    }
-
-    /// <summary>
-    /// Class UpdateScheduledTaskTriggers
-    /// </summary>
-    [Route("/ScheduledTasks/{Id}/Triggers", "POST", Summary = "Updates the triggers for a scheduled task")]
-    public class UpdateScheduledTaskTriggers : List<TaskTriggerInfo>, IReturnVoid
-    {
-        /// <summary>
-        /// Gets or sets the task id.
-        /// </summary>
-        /// <value>The task id.</value>
-        [ApiMember(Name = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
-        public string Id { get; set; }
-    }
-
-    /// <summary>
-    /// Class ScheduledTasksService
-    /// </summary>
-    [Authenticated(Roles = "Admin")]
-    public class ScheduledTaskService : BaseApiService
-    {
-        /// <summary>
-        /// The task manager.
-        /// </summary>
-        private readonly ITaskManager _taskManager;
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="ScheduledTaskService" /> class.
-        /// </summary>
-        /// <param name="taskManager">The task manager.</param>
-        /// <exception cref="ArgumentNullException">taskManager</exception>
-        public ScheduledTaskService(
-            ILogger<ScheduledTaskService> logger,
-            IServerConfigurationManager serverConfigurationManager,
-            IHttpResultFactory httpResultFactory,
-            ITaskManager taskManager)
-            : base(logger, serverConfigurationManager, httpResultFactory)
-        {
-            _taskManager = taskManager;
-        }
-
-        /// <summary>
-        /// Gets the specified request.
-        /// </summary>
-        /// <param name="request">The request.</param>
-        /// <returns>IEnumerable{TaskInfo}.</returns>
-        public object Get(GetScheduledTasks request)
-        {
-            IEnumerable<IScheduledTaskWorker> result = _taskManager.ScheduledTasks
-                .OrderBy(i => i.Name);
-
-            if (request.IsHidden.HasValue)
-            {
-                var val = request.IsHidden.Value;
-
-                result = result.Where(i =>
-                {
-                    var isHidden = false;
-
-                    if (i.ScheduledTask is IConfigurableScheduledTask configurableTask)
-                    {
-                        isHidden = configurableTask.IsHidden;
-                    }
-
-                    return isHidden == val;
-                });
-            }
-
-            if (request.IsEnabled.HasValue)
-            {
-                var val = request.IsEnabled.Value;
-
-                result = result.Where(i =>
-                {
-                    var isEnabled = true;
-
-                    if (i.ScheduledTask is IConfigurableScheduledTask configurableTask)
-                    {
-                        isEnabled = configurableTask.IsEnabled;
-                    }
-
-                    return isEnabled == val;
-                });
-            }
-
-            var infos = result
-                .Select(ScheduledTaskHelpers.GetTaskInfo)
-                .ToArray();
-
-            return ToOptimizedResult(infos);
-        }
-
-        /// <summary>
-        /// Gets the specified request.
-        /// </summary>
-        /// <param name="request">The request.</param>
-        /// <returns>IEnumerable{TaskInfo}.</returns>
-        /// <exception cref="ResourceNotFoundException">Task not found</exception>
-        public object Get(GetScheduledTask request)
-        {
-            var task = _taskManager.ScheduledTasks.FirstOrDefault(i => string.Equals(i.Id, request.Id));
-
-            if (task == null)
-            {
-                throw new ResourceNotFoundException("Task not found");
-            }
-
-            var result = ScheduledTaskHelpers.GetTaskInfo(task);
-
-            return ToOptimizedResult(result);
-        }
-
-        /// <summary>
-        /// Posts the specified request.
-        /// </summary>
-        /// <param name="request">The request.</param>
-        /// <exception cref="ResourceNotFoundException">Task not found</exception>
-        public void Post(StartScheduledTask request)
-        {
-            var task = _taskManager.ScheduledTasks.FirstOrDefault(i => string.Equals(i.Id, request.Id));
-
-            if (task == null)
-            {
-                throw new ResourceNotFoundException("Task not found");
-            }
-
-            _taskManager.Execute(task, new TaskOptions());
-        }
-
-        /// <summary>
-        /// Posts the specified request.
-        /// </summary>
-        /// <param name="request">The request.</param>
-        /// <exception cref="ResourceNotFoundException">Task not found</exception>
-        public void Delete(StopScheduledTask request)
-        {
-            var task = _taskManager.ScheduledTasks.FirstOrDefault(i => string.Equals(i.Id, request.Id));
-
-            if (task == null)
-            {
-                throw new ResourceNotFoundException("Task not found");
-            }
-
-            _taskManager.Cancel(task);
-        }
-
-        /// <summary>
-        /// Posts the specified request.
-        /// </summary>
-        /// <param name="request">The request.</param>
-        /// <exception cref="ResourceNotFoundException">Task not found</exception>
-        public void Post(UpdateScheduledTaskTriggers request)
-        {
-            // We need to parse this manually because we told service stack not to with IRequiresRequestStream
-            // https://code.google.com/p/servicestack/source/browse/trunk/Common/ServiceStack.Text/ServiceStack.Text/Controller/PathInfo.cs
-            var id = GetPathValue(1).ToString();
-
-            var task = _taskManager.ScheduledTasks.FirstOrDefault(i => string.Equals(i.Id, id, StringComparison.Ordinal));
-
-            if (task == null)
-            {
-                throw new ResourceNotFoundException("Task not found");
-            }
-
-            task.Triggers = request.ToArray();
-        }
-    }
-}

+ 56 - 0
MediaBrowser.Common/Json/Converters/JsonInt64Converter.cs

@@ -0,0 +1,56 @@
+using System;
+using System.Buffers;
+using System.Buffers.Text;
+using System.Globalization;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace MediaBrowser.Common.Json.Converters
+{
+    /// <summary>
+    /// Long to String JSON converter.
+    /// Javascript does not support 64-bit integers.
+    /// </summary>
+    public class JsonInt64Converter : JsonConverter<long>
+    {
+        /// <summary>
+        /// Read JSON string as int64.
+        /// </summary>
+        /// <param name="reader"><see cref="Utf8JsonReader"/>.</param>
+        /// <param name="type">Type.</param>
+        /// <param name="options">Options.</param>
+        /// <returns>Parsed value.</returns>
+        public override long Read(ref Utf8JsonReader reader, Type type, JsonSerializerOptions options)
+        {
+            if (reader.TokenType == JsonTokenType.String)
+            {
+                // try to parse number directly from bytes
+                var span = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan;
+                if (Utf8Parser.TryParse(span, out long number, out var bytesConsumed) && span.Length == bytesConsumed)
+                {
+                    return number;
+                }
+
+                // try to parse from a string if the above failed, this covers cases with other escaped/UTF characters
+                if (long.TryParse(reader.GetString(), out number))
+                {
+                    return number;
+                }
+            }
+
+            // fallback to default handling
+            return reader.GetInt64();
+        }
+
+        /// <summary>
+        /// Write long to JSON string.
+        /// </summary>
+        /// <param name="writer"><see cref="Utf8JsonWriter"/>.</param>
+        /// <param name="value">Value to write.</param>
+        /// <param name="options">Options.</param>
+        public override void Write(Utf8JsonWriter writer, long value, JsonSerializerOptions options)
+        {
+            writer.WriteStringValue(value.ToString(NumberFormatInfo.InvariantInfo));
+        }
+    }
+}

+ 1 - 0
MediaBrowser.Common/Json/JsonDefaults.cs

@@ -31,6 +31,7 @@ namespace MediaBrowser.Common.Json
             options.Converters.Add(new JsonInt32Converter());
             options.Converters.Add(new JsonStringEnumConverter());
             options.Converters.Add(new JsonNonStringKeyDictionaryConverterFactory());
+            options.Converters.Add(new JsonInt64Converter());
 
             return options;
         }