From 5d9c40ec72d31957cec48e141ca5ce4f9141b413 Mon Sep 17 00:00:00 2001 From: crobibero Date: Sun, 19 Apr 2020 16:26:20 -0600 Subject: [PATCH 01/14] move scheduled tasks to Jellyfin.Api --- .../Controllers/ScheduledTasksController.cs | 207 ++++++++++++++++++ .../Converters/LongToStringConverter.cs | 56 +++++ .../ApiServiceCollectionExtensions.cs | 2 + 3 files changed, 265 insertions(+) create mode 100644 Jellyfin.Api/Controllers/ScheduledTasksController.cs create mode 100644 Jellyfin.Server/Converters/LongToStringConverter.cs diff --git a/Jellyfin.Api/Controllers/ScheduledTasksController.cs b/Jellyfin.Api/Controllers/ScheduledTasksController.cs new file mode 100644 index 000000000..bb07af397 --- /dev/null +++ b/Jellyfin.Api/Controllers/ScheduledTasksController.cs @@ -0,0 +1,207 @@ +#nullable enable + +using System; +using System.Collections.Generic; +using System.Linq; +using MediaBrowser.Model.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace Jellyfin.Api.Controllers +{ + /// + /// Scheduled Tasks Controller. + /// + public class ScheduledTasksController : BaseJellyfinApiController + { + private readonly ITaskManager _taskManager; + + /// + /// Initializes a new instance of the class. + /// + /// Instance of the interface. + public ScheduledTasksController(ITaskManager taskManager) + { + _taskManager = taskManager; + } + + /// + /// Get tasks. + /// + /// Optional filter tasks that are hidden, or not. + /// Optional filter tasks that are enabled, or not. + /// Task list. + [HttpGet] + [ProducesResponseType(typeof(TaskInfo[]), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] + public IActionResult GetTasks( + [FromQuery] bool? isHidden = false, + [FromQuery] bool? isEnabled = false) + { + try + { + IEnumerable tasks = _taskManager.ScheduledTasks.OrderBy(o => o.Name); + + if (isHidden.HasValue) + { + var hiddenValue = isHidden.Value; + tasks = tasks.Where(o => + { + var itemIsHidden = false; + if (o.ScheduledTask is IConfigurableScheduledTask configurableScheduledTask) + { + itemIsHidden = configurableScheduledTask.IsHidden; + } + + return itemIsHidden == hiddenValue; + }); + } + + if (isEnabled.HasValue) + { + var enabledValue = isEnabled.Value; + tasks = tasks.Where(o => + { + var itemIsEnabled = false; + if (o.ScheduledTask is IConfigurableScheduledTask configurableScheduledTask) + { + itemIsEnabled = configurableScheduledTask.IsEnabled; + } + + return itemIsEnabled == enabledValue; + }); + } + + var taskInfos = tasks.Select(ScheduledTaskHelpers.GetTaskInfo); + + // TODO ToOptimizedResult + return Ok(taskInfos); + } + catch (Exception e) + { + return StatusCode(StatusCodes.Status500InternalServerError, e.Message); + } + } + + /// + /// Get task by id. + /// + /// Task Id. + /// Task Info. + [HttpGet("{TaskID}")] + [ProducesResponseType(typeof(TaskInfo), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] + public IActionResult GetTask([FromRoute] string taskId) + { + try + { + var task = _taskManager.ScheduledTasks.FirstOrDefault(i => + string.Equals(i.Id, taskId, StringComparison.OrdinalIgnoreCase)); + + if (task == null) + { + return NotFound(); + } + + var result = ScheduledTaskHelpers.GetTaskInfo(task); + return Ok(result); + } + catch (Exception e) + { + return StatusCode(StatusCodes.Status500InternalServerError, e.Message); + } + } + + /// + /// Start specified task. + /// + /// Task Id. + /// Status. + [HttpPost("Running/{TaskID}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] + public IActionResult StartTask([FromRoute] string taskId) + { + try + { + var task = _taskManager.ScheduledTasks.FirstOrDefault(o => + o.Id.Equals(taskId, StringComparison.OrdinalIgnoreCase)); + + if (task == null) + { + return NotFound(); + } + + _taskManager.Execute(task, new TaskOptions()); + return Ok(); + } + catch (Exception e) + { + return StatusCode(StatusCodes.Status500InternalServerError, e.Message); + } + } + + /// + /// Stop specified task. + /// + /// Task Id. + /// Status. + [HttpDelete("Running/{TaskID}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] + public IActionResult StopTask([FromRoute] string taskId) + { + try + { + var task = _taskManager.ScheduledTasks.FirstOrDefault(o => + o.Id.Equals(taskId, StringComparison.OrdinalIgnoreCase)); + + if (task == null) + { + return NotFound(); + } + + _taskManager.Cancel(task); + return Ok(); + } + catch (Exception e) + { + return StatusCode(StatusCodes.Status500InternalServerError, e.Message); + } + } + + /// + /// Update specified task triggers. + /// + /// Task Id. + /// Triggers. + /// Status. + [HttpPost("{TaskID}/Triggers")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] + public IActionResult UpdateTask([FromRoute] string taskId, [FromBody] TaskTriggerInfo[] triggerInfos) + { + try + { + var task = _taskManager.ScheduledTasks.FirstOrDefault(o => + o.Id.Equals(taskId, StringComparison.OrdinalIgnoreCase)); + if (task == null) + { + return NotFound(); + } + + task.Triggers = triggerInfos; + return Ok(); + } + catch (Exception e) + { + return StatusCode(StatusCodes.Status500InternalServerError, e.Message); + } + } + } +} diff --git a/Jellyfin.Server/Converters/LongToStringConverter.cs b/Jellyfin.Server/Converters/LongToStringConverter.cs new file mode 100644 index 000000000..ad66b7b0c --- /dev/null +++ b/Jellyfin.Server/Converters/LongToStringConverter.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 Jellyfin.Server.Converters +{ + /// + /// Long to String JSON converter. + /// Javascript does not support 64-bit integers. + /// + public class LongToStringConverter : JsonConverter + { + /// + /// Read JSON string as Long. + /// + /// . + /// Type. + /// Options. + /// Parsed value. + public override long Read(ref Utf8JsonReader reader, Type type, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.String) + { + // try to parse number directly from bytes + ReadOnlySpan span = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan; + if (Utf8Parser.TryParse(span, out long number, out int 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(); + } + + /// + /// Write long to JSON string. + /// + /// . + /// Value to write. + /// Options. + public override void Write(Utf8JsonWriter writer, long value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.ToString(NumberFormatInfo.InvariantInfo)); + } + } +} diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs index 71ef9a69a..afd42ac5a 100644 --- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs +++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs @@ -4,6 +4,7 @@ using Jellyfin.Api.Auth.FirstTimeSetupOrElevatedPolicy; using Jellyfin.Api.Auth.RequiresElevationPolicy; using Jellyfin.Api.Constants; using Jellyfin.Api.Controllers; +using Jellyfin.Server.Converters; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.DependencyInjection; @@ -75,6 +76,7 @@ namespace Jellyfin.Server.Extensions { // Setting the naming policy to null leaves the property names as-is when serializing objects to JSON. options.JsonSerializerOptions.PropertyNamingPolicy = null; + options.JsonSerializerOptions.Converters.Add(new LongToStringConverter()); }) .AddControllersAsServices(); } From d8fc4f91dbcc38df0e13e51a3631e87f783361de Mon Sep 17 00:00:00 2001 From: crobibero Date: Sun, 19 Apr 2020 16:29:29 -0600 Subject: [PATCH 02/14] burn ToOptimizedResult --- Jellyfin.Api/Controllers/ScheduledTasksController.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Jellyfin.Api/Controllers/ScheduledTasksController.cs b/Jellyfin.Api/Controllers/ScheduledTasksController.cs index bb07af397..f90b44967 100644 --- a/Jellyfin.Api/Controllers/ScheduledTasksController.cs +++ b/Jellyfin.Api/Controllers/ScheduledTasksController.cs @@ -75,7 +75,6 @@ namespace Jellyfin.Api.Controllers var taskInfos = tasks.Select(ScheduledTaskHelpers.GetTaskInfo); - // TODO ToOptimizedResult return Ok(taskInfos); } catch (Exception e) From 4a960892c20676ce6400f4cae1c85e8ce4d4a841 Mon Sep 17 00:00:00 2001 From: crobibero Date: Sun, 19 Apr 2020 16:31:09 -0600 Subject: [PATCH 03/14] Add Authorize and BindRequired --- Jellyfin.Api/Controllers/ScheduledTasksController.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Jellyfin.Api/Controllers/ScheduledTasksController.cs b/Jellyfin.Api/Controllers/ScheduledTasksController.cs index f90b44967..157e98519 100644 --- a/Jellyfin.Api/Controllers/ScheduledTasksController.cs +++ b/Jellyfin.Api/Controllers/ScheduledTasksController.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Linq; +using MediaBrowser.Controller.Net; using MediaBrowser.Model.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -13,6 +14,7 @@ namespace Jellyfin.Api.Controllers /// /// Scheduled Tasks Controller. /// + [Authenticated] public class ScheduledTasksController : BaseJellyfinApiController { private readonly ITaskManager _taskManager; @@ -183,7 +185,7 @@ namespace Jellyfin.Api.Controllers [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] - public IActionResult UpdateTask([FromRoute] string taskId, [FromBody] TaskTriggerInfo[] triggerInfos) + public IActionResult UpdateTask([FromRoute] string taskId, [FromBody, BindRequired] TaskTriggerInfo[] triggerInfos) { try { From a96db5f48e57a192369b220422517171c06411b6 Mon Sep 17 00:00:00 2001 From: crobibero Date: Sun, 19 Apr 2020 16:32:03 -0600 Subject: [PATCH 04/14] Remove old scheduled tasks service --- .../ScheduledTasks/ScheduledTaskService.cs | 234 ------------------ 1 file changed, 234 deletions(-) delete mode 100644 MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs diff --git a/MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs b/MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs deleted file mode 100644 index e08a8482e..000000000 --- a/MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs +++ /dev/null @@ -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 -{ - /// - /// Class GetScheduledTask - /// - [Route("/ScheduledTasks/{Id}", "GET", Summary = "Gets a scheduled task, by Id")] - public class GetScheduledTask : IReturn - { - /// - /// Gets or sets the id. - /// - /// The id. - [ApiMember(Name = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - } - - /// - /// Class GetScheduledTasks - /// - [Route("/ScheduledTasks", "GET", Summary = "Gets scheduled tasks")] - public class GetScheduledTasks : IReturn - { - [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; } - } - - /// - /// Class StartScheduledTask - /// - [Route("/ScheduledTasks/Running/{Id}", "POST", Summary = "Starts a scheduled task")] - public class StartScheduledTask : IReturnVoid - { - /// - /// Gets or sets the id. - /// - /// The id. - [ApiMember(Name = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } - } - - /// - /// Class StopScheduledTask - /// - [Route("/ScheduledTasks/Running/{Id}", "DELETE", Summary = "Stops a scheduled task")] - public class StopScheduledTask : IReturnVoid - { - /// - /// Gets or sets the id. - /// - /// The id. - [ApiMember(Name = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] - public string Id { get; set; } - } - - /// - /// Class UpdateScheduledTaskTriggers - /// - [Route("/ScheduledTasks/{Id}/Triggers", "POST", Summary = "Updates the triggers for a scheduled task")] - public class UpdateScheduledTaskTriggers : List, IReturnVoid - { - /// - /// Gets or sets the task id. - /// - /// The task id. - [ApiMember(Name = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } - } - - /// - /// Class ScheduledTasksService - /// - [Authenticated(Roles = "Admin")] - public class ScheduledTaskService : BaseApiService - { - /// - /// The task manager. - /// - private readonly ITaskManager _taskManager; - - /// - /// Initializes a new instance of the class. - /// - /// The task manager. - /// taskManager - public ScheduledTaskService( - ILogger logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - ITaskManager taskManager) - : base(logger, serverConfigurationManager, httpResultFactory) - { - _taskManager = taskManager; - } - - /// - /// Gets the specified request. - /// - /// The request. - /// IEnumerable{TaskInfo}. - public object Get(GetScheduledTasks request) - { - IEnumerable 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); - } - - /// - /// Gets the specified request. - /// - /// The request. - /// IEnumerable{TaskInfo}. - /// Task not found - 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); - } - - /// - /// Posts the specified request. - /// - /// The request. - /// Task not found - 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()); - } - - /// - /// Posts the specified request. - /// - /// The request. - /// Task not found - 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); - } - - /// - /// Posts the specified request. - /// - /// The request. - /// Task not found - 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(); - } - } -} From 8ab9949db5a1c0072ec35937cb96e93ce5b9d672 Mon Sep 17 00:00:00 2001 From: crobibero Date: Tue, 21 Apr 2020 08:02:07 -0600 Subject: [PATCH 05/14] Remove exception handler --- .../Controllers/ScheduledTasksController.cs | 151 +++++++----------- 1 file changed, 59 insertions(+), 92 deletions(-) diff --git a/Jellyfin.Api/Controllers/ScheduledTasksController.cs b/Jellyfin.Api/Controllers/ScheduledTasksController.cs index 157e98519..acbc630c2 100644 --- a/Jellyfin.Api/Controllers/ScheduledTasksController.cs +++ b/Jellyfin.Api/Controllers/ScheduledTasksController.cs @@ -41,48 +41,41 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool? isHidden = false, [FromQuery] bool? isEnabled = false) { - try + IEnumerable tasks = _taskManager.ScheduledTasks.OrderBy(o => o.Name); + + if (isHidden.HasValue) { - IEnumerable tasks = _taskManager.ScheduledTasks.OrderBy(o => o.Name); - - if (isHidden.HasValue) + var hiddenValue = isHidden.Value; + tasks = tasks.Where(o => { - var hiddenValue = isHidden.Value; - tasks = tasks.Where(o => + var itemIsHidden = false; + if (o.ScheduledTask is IConfigurableScheduledTask configurableScheduledTask) { - var itemIsHidden = false; - if (o.ScheduledTask is IConfigurableScheduledTask configurableScheduledTask) - { - itemIsHidden = configurableScheduledTask.IsHidden; - } + itemIsHidden = configurableScheduledTask.IsHidden; + } - return itemIsHidden == hiddenValue; - }); - } - - if (isEnabled.HasValue) - { - var enabledValue = isEnabled.Value; - tasks = tasks.Where(o => - { - var itemIsEnabled = false; - if (o.ScheduledTask is IConfigurableScheduledTask configurableScheduledTask) - { - itemIsEnabled = configurableScheduledTask.IsEnabled; - } - - return itemIsEnabled == enabledValue; - }); - } - - var taskInfos = tasks.Select(ScheduledTaskHelpers.GetTaskInfo); - - return Ok(taskInfos); + return itemIsHidden == hiddenValue; + }); } - catch (Exception e) + + if (isEnabled.HasValue) { - return StatusCode(StatusCodes.Status500InternalServerError, e.Message); + var enabledValue = isEnabled.Value; + tasks = tasks.Where(o => + { + var itemIsEnabled = false; + if (o.ScheduledTask is IConfigurableScheduledTask configurableScheduledTask) + { + itemIsEnabled = configurableScheduledTask.IsEnabled; + } + + return itemIsEnabled == enabledValue; + }); } + + var taskInfos = tasks.Select(ScheduledTaskHelpers.GetTaskInfo); + + return Ok(taskInfos); } /// @@ -96,23 +89,16 @@ namespace Jellyfin.Api.Controllers [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] public IActionResult GetTask([FromRoute] string taskId) { - try - { - var task = _taskManager.ScheduledTasks.FirstOrDefault(i => - string.Equals(i.Id, taskId, StringComparison.OrdinalIgnoreCase)); + var task = _taskManager.ScheduledTasks.FirstOrDefault(i => + string.Equals(i.Id, taskId, StringComparison.OrdinalIgnoreCase)); - if (task == null) - { - return NotFound(); - } - - var result = ScheduledTaskHelpers.GetTaskInfo(task); - return Ok(result); - } - catch (Exception e) + if (task == null) { - return StatusCode(StatusCodes.Status500InternalServerError, e.Message); + return NotFound(); } + + var result = ScheduledTaskHelpers.GetTaskInfo(task); + return Ok(result); } /// @@ -126,23 +112,16 @@ namespace Jellyfin.Api.Controllers [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] public IActionResult StartTask([FromRoute] string taskId) { - try - { - var task = _taskManager.ScheduledTasks.FirstOrDefault(o => - o.Id.Equals(taskId, StringComparison.OrdinalIgnoreCase)); + var task = _taskManager.ScheduledTasks.FirstOrDefault(o => + o.Id.Equals(taskId, StringComparison.OrdinalIgnoreCase)); - if (task == null) - { - return NotFound(); - } - - _taskManager.Execute(task, new TaskOptions()); - return Ok(); - } - catch (Exception e) + if (task == null) { - return StatusCode(StatusCodes.Status500InternalServerError, e.Message); + return NotFound(); } + + _taskManager.Execute(task, new TaskOptions()); + return Ok(); } /// @@ -156,23 +135,16 @@ namespace Jellyfin.Api.Controllers [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] public IActionResult StopTask([FromRoute] string taskId) { - try - { - var task = _taskManager.ScheduledTasks.FirstOrDefault(o => - o.Id.Equals(taskId, StringComparison.OrdinalIgnoreCase)); + var task = _taskManager.ScheduledTasks.FirstOrDefault(o => + o.Id.Equals(taskId, StringComparison.OrdinalIgnoreCase)); - if (task == null) - { - return NotFound(); - } - - _taskManager.Cancel(task); - return Ok(); - } - catch (Exception e) + if (task == null) { - return StatusCode(StatusCodes.Status500InternalServerError, e.Message); + return NotFound(); } + + _taskManager.Cancel(task); + return Ok(); } /// @@ -185,24 +157,19 @@ namespace Jellyfin.Api.Controllers [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] - public IActionResult UpdateTask([FromRoute] string taskId, [FromBody, BindRequired] TaskTriggerInfo[] triggerInfos) + public IActionResult UpdateTask( + [FromRoute] string taskId, + [FromBody, BindRequired] TaskTriggerInfo[] triggerInfos) { - try + var task = _taskManager.ScheduledTasks.FirstOrDefault(o => + o.Id.Equals(taskId, StringComparison.OrdinalIgnoreCase)); + if (task == null) { - var task = _taskManager.ScheduledTasks.FirstOrDefault(o => - o.Id.Equals(taskId, StringComparison.OrdinalIgnoreCase)); - if (task == null) - { - return NotFound(); - } + return NotFound(); + } - task.Triggers = triggerInfos; - return Ok(); - } - catch (Exception e) - { - return StatusCode(StatusCodes.Status500InternalServerError, e.Message); - } + task.Triggers = triggerInfos; + return Ok(); } } } From 88b856796a9e4852ae4f9938baddd4741e8285d5 Mon Sep 17 00:00:00 2001 From: crobibero Date: Tue, 21 Apr 2020 14:23:08 -0600 Subject: [PATCH 06/14] move to ActionResult --- .../Controllers/ScheduledTasksController.cs | 53 ++++++------------- 1 file changed, 16 insertions(+), 37 deletions(-) diff --git a/Jellyfin.Api/Controllers/ScheduledTasksController.cs b/Jellyfin.Api/Controllers/ScheduledTasksController.cs index acbc630c2..da7cfbc3a 100644 --- a/Jellyfin.Api/Controllers/ScheduledTasksController.cs +++ b/Jellyfin.Api/Controllers/ScheduledTasksController.cs @@ -14,7 +14,7 @@ namespace Jellyfin.Api.Controllers /// /// Scheduled Tasks Controller. /// - [Authenticated] + // [Authenticated] public class ScheduledTasksController : BaseJellyfinApiController { private readonly ITaskManager _taskManager; @@ -35,47 +35,30 @@ namespace Jellyfin.Api.Controllers /// Optional filter tasks that are enabled, or not. /// Task list. [HttpGet] - [ProducesResponseType(typeof(TaskInfo[]), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] - public IActionResult GetTasks( + [ProducesResponseType(StatusCodes.Status200OK)] + public IEnumerable GetTasks( [FromQuery] bool? isHidden = false, [FromQuery] bool? isEnabled = false) { IEnumerable tasks = _taskManager.ScheduledTasks.OrderBy(o => o.Name); - if (isHidden.HasValue) + foreach (var task in tasks) { - var hiddenValue = isHidden.Value; - tasks = tasks.Where(o => + if (task.ScheduledTask is IConfigurableScheduledTask scheduledTask) { - var itemIsHidden = false; - if (o.ScheduledTask is IConfigurableScheduledTask configurableScheduledTask) + if (isHidden.HasValue && isHidden.Value != scheduledTask.IsHidden) { - itemIsHidden = configurableScheduledTask.IsHidden; + continue; } - return itemIsHidden == hiddenValue; - }); - } - - if (isEnabled.HasValue) - { - var enabledValue = isEnabled.Value; - tasks = tasks.Where(o => - { - var itemIsEnabled = false; - if (o.ScheduledTask is IConfigurableScheduledTask configurableScheduledTask) + if (isEnabled.HasValue && isEnabled.Value != scheduledTask.IsEnabled) { - itemIsEnabled = configurableScheduledTask.IsEnabled; + continue; } + } - return itemIsEnabled == enabledValue; - }); + yield return task; } - - var taskInfos = tasks.Select(ScheduledTaskHelpers.GetTaskInfo); - - return Ok(taskInfos); } /// @@ -84,10 +67,9 @@ namespace Jellyfin.Api.Controllers /// Task Id. /// Task Info. [HttpGet("{TaskID}")] - [ProducesResponseType(typeof(TaskInfo), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] - public IActionResult GetTask([FromRoute] string taskId) + public ActionResult GetTask([FromRoute] string taskId) { var task = _taskManager.ScheduledTasks.FirstOrDefault(i => string.Equals(i.Id, taskId, StringComparison.OrdinalIgnoreCase)); @@ -109,8 +91,7 @@ namespace Jellyfin.Api.Controllers [HttpPost("Running/{TaskID}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] - public IActionResult StartTask([FromRoute] string taskId) + public ActionResult StartTask([FromRoute] string taskId) { var task = _taskManager.ScheduledTasks.FirstOrDefault(o => o.Id.Equals(taskId, StringComparison.OrdinalIgnoreCase)); @@ -132,8 +113,7 @@ namespace Jellyfin.Api.Controllers [HttpDelete("Running/{TaskID}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] - public IActionResult StopTask([FromRoute] string taskId) + public ActionResult StopTask([FromRoute] string taskId) { var task = _taskManager.ScheduledTasks.FirstOrDefault(o => o.Id.Equals(taskId, StringComparison.OrdinalIgnoreCase)); @@ -156,8 +136,7 @@ namespace Jellyfin.Api.Controllers [HttpPost("{TaskID}/Triggers")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] - public IActionResult UpdateTask( + public ActionResult UpdateTask( [FromRoute] string taskId, [FromBody, BindRequired] TaskTriggerInfo[] triggerInfos) { From f67daa84b04ae6c8ffcc42c038a65ecb8a433861 Mon Sep 17 00:00:00 2001 From: crobibero Date: Sat, 2 May 2020 17:10:59 -0600 Subject: [PATCH 07/14] Update endpoint docs --- .../Controllers/ScheduledTasksController.cs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/Jellyfin.Api/Controllers/ScheduledTasksController.cs b/Jellyfin.Api/Controllers/ScheduledTasksController.cs index da7cfbc3a..ad70bf83b 100644 --- a/Jellyfin.Api/Controllers/ScheduledTasksController.cs +++ b/Jellyfin.Api/Controllers/ScheduledTasksController.cs @@ -33,7 +33,8 @@ namespace Jellyfin.Api.Controllers /// /// Optional filter tasks that are hidden, or not. /// Optional filter tasks that are enabled, or not. - /// Task list. + /// Scheduled tasks retrieved. + /// The list of scheduled tasks. [HttpGet] [ProducesResponseType(StatusCodes.Status200OK)] public IEnumerable GetTasks( @@ -65,7 +66,9 @@ namespace Jellyfin.Api.Controllers /// Get task by id. /// /// Task Id. - /// Task Info. + /// Task retrieved. + /// Task not found. + /// An containing the task on success, or a if the task could not be found. [HttpGet("{TaskID}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] @@ -87,7 +90,9 @@ namespace Jellyfin.Api.Controllers /// Start specified task. /// /// Task Id. - /// Status. + /// Task started. + /// Task not found. + /// An on success, or a if the file could not be found. [HttpPost("Running/{TaskID}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] @@ -109,7 +114,9 @@ namespace Jellyfin.Api.Controllers /// Stop specified task. /// /// Task Id. - /// Status. + /// Task stopped. + /// Task not found. + /// An on success, or a if the file could not be found. [HttpDelete("Running/{TaskID}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] @@ -132,7 +139,9 @@ namespace Jellyfin.Api.Controllers /// /// Task Id. /// Triggers. - /// Status. + /// Task triggers updated. + /// Task not found. + /// An on success, or a if the file could not be found. [HttpPost("{TaskID}/Triggers")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] From c998935d29d04a55babdeb0adcf1d1091611b1e3 Mon Sep 17 00:00:00 2001 From: crobibero Date: Tue, 19 May 2020 09:06:37 -0600 Subject: [PATCH 08/14] Apply review suggestions --- Jellyfin.Api/Controllers/ScheduledTasksController.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Jellyfin.Api/Controllers/ScheduledTasksController.cs b/Jellyfin.Api/Controllers/ScheduledTasksController.cs index ad70bf83b..3e3359ec7 100644 --- a/Jellyfin.Api/Controllers/ScheduledTasksController.cs +++ b/Jellyfin.Api/Controllers/ScheduledTasksController.cs @@ -3,8 +3,9 @@ using System; using System.Collections.Generic; using System.Linq; -using MediaBrowser.Controller.Net; +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; @@ -14,7 +15,7 @@ namespace Jellyfin.Api.Controllers /// /// Scheduled Tasks Controller. /// - // [Authenticated] + [Authorize(Policy = Policies.RequiresElevation)] public class ScheduledTasksController : BaseJellyfinApiController { private readonly ITaskManager _taskManager; @@ -82,8 +83,7 @@ namespace Jellyfin.Api.Controllers return NotFound(); } - var result = ScheduledTaskHelpers.GetTaskInfo(task); - return Ok(result); + return ScheduledTaskHelpers.GetTaskInfo(task); } /// From 2f2bceb1104d8ea669ca21fc40200247aca956ed Mon Sep 17 00:00:00 2001 From: crobibero Date: Tue, 19 May 2020 12:56:57 -0600 Subject: [PATCH 09/14] Remove default parameter values --- Jellyfin.Api/Controllers/ScheduledTasksController.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Jellyfin.Api/Controllers/ScheduledTasksController.cs b/Jellyfin.Api/Controllers/ScheduledTasksController.cs index 3e3359ec7..19cce974e 100644 --- a/Jellyfin.Api/Controllers/ScheduledTasksController.cs +++ b/Jellyfin.Api/Controllers/ScheduledTasksController.cs @@ -39,8 +39,8 @@ namespace Jellyfin.Api.Controllers [HttpGet] [ProducesResponseType(StatusCodes.Status200OK)] public IEnumerable GetTasks( - [FromQuery] bool? isHidden = false, - [FromQuery] bool? isEnabled = false) + [FromQuery] bool? isHidden, + [FromQuery] bool? isEnabled) { IEnumerable tasks = _taskManager.ScheduledTasks.OrderBy(o => o.Name); From 341b947cdecdfc791c1bc3e72da1e68cd3754c3a Mon Sep 17 00:00:00 2001 From: crobibero Date: Fri, 22 May 2020 10:48:01 -0600 Subject: [PATCH 10/14] Move int64 converter to JsonDefaults location --- .../Extensions/ApiServiceCollectionExtensions.cs | 2 -- .../Json/Converters/JsonInt64Converter.cs | 10 +++++----- MediaBrowser.Common/Json/JsonDefaults.cs | 1 + 3 files changed, 6 insertions(+), 7 deletions(-) rename Jellyfin.Server/Converters/LongToStringConverter.cs => MediaBrowser.Common/Json/Converters/JsonInt64Converter.cs (85%) diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs index afd42ac5a..71ef9a69a 100644 --- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs +++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs @@ -4,7 +4,6 @@ using Jellyfin.Api.Auth.FirstTimeSetupOrElevatedPolicy; using Jellyfin.Api.Auth.RequiresElevationPolicy; using Jellyfin.Api.Constants; using Jellyfin.Api.Controllers; -using Jellyfin.Server.Converters; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.DependencyInjection; @@ -76,7 +75,6 @@ namespace Jellyfin.Server.Extensions { // Setting the naming policy to null leaves the property names as-is when serializing objects to JSON. options.JsonSerializerOptions.PropertyNamingPolicy = null; - options.JsonSerializerOptions.Converters.Add(new LongToStringConverter()); }) .AddControllersAsServices(); } diff --git a/Jellyfin.Server/Converters/LongToStringConverter.cs b/MediaBrowser.Common/Json/Converters/JsonInt64Converter.cs similarity index 85% rename from Jellyfin.Server/Converters/LongToStringConverter.cs rename to MediaBrowser.Common/Json/Converters/JsonInt64Converter.cs index ad66b7b0c..d18fd95d5 100644 --- a/Jellyfin.Server/Converters/LongToStringConverter.cs +++ b/MediaBrowser.Common/Json/Converters/JsonInt64Converter.cs @@ -5,16 +5,16 @@ using System.Globalization; using System.Text.Json; using System.Text.Json.Serialization; -namespace Jellyfin.Server.Converters +namespace MediaBrowser.Common.Json.Converters { /// /// Long to String JSON converter. /// Javascript does not support 64-bit integers. /// - public class LongToStringConverter : JsonConverter + public class JsonInt64Converter : JsonConverter { /// - /// Read JSON string as Long. + /// Read JSON string as int64. /// /// . /// Type. @@ -25,8 +25,8 @@ namespace Jellyfin.Server.Converters if (reader.TokenType == JsonTokenType.String) { // try to parse number directly from bytes - ReadOnlySpan span = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan; - if (Utf8Parser.TryParse(span, out long number, out int bytesConsumed) && span.Length == bytesConsumed) + var span = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan; + if (Utf8Parser.TryParse(span, out long number, out var bytesConsumed) && span.Length == bytesConsumed) { return number; } diff --git a/MediaBrowser.Common/Json/JsonDefaults.cs b/MediaBrowser.Common/Json/JsonDefaults.cs index 4a6ee0a79..a7f5fde05 100644 --- a/MediaBrowser.Common/Json/JsonDefaults.cs +++ b/MediaBrowser.Common/Json/JsonDefaults.cs @@ -23,6 +23,7 @@ namespace MediaBrowser.Common.Json options.Converters.Add(new JsonGuidConverter()); options.Converters.Add(new JsonStringEnumConverter()); + options.Converters.Add(new JsonInt64Converter()); return options; } From 6c53e36ccf1f27defae6faa5791598258bc604ab Mon Sep 17 00:00:00 2001 From: crobibero Date: Thu, 4 Jun 2020 15:17:05 -0600 Subject: [PATCH 11/14] Fix Api Routing --- Jellyfin.Api/Controllers/ScheduledTasksController.cs | 8 ++++---- MediaBrowser.Common/Json/JsonDefaults.cs | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Jellyfin.Api/Controllers/ScheduledTasksController.cs b/Jellyfin.Api/Controllers/ScheduledTasksController.cs index 19cce974e..e37e137d1 100644 --- a/Jellyfin.Api/Controllers/ScheduledTasksController.cs +++ b/Jellyfin.Api/Controllers/ScheduledTasksController.cs @@ -70,7 +70,7 @@ namespace Jellyfin.Api.Controllers /// Task retrieved. /// Task not found. /// An containing the task on success, or a if the task could not be found. - [HttpGet("{TaskID}")] + [HttpGet("{taskId}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult GetTask([FromRoute] string taskId) @@ -93,7 +93,7 @@ namespace Jellyfin.Api.Controllers /// Task started. /// Task not found. /// An on success, or a if the file could not be found. - [HttpPost("Running/{TaskID}")] + [HttpPost("Running/{taskId}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult StartTask([FromRoute] string taskId) @@ -117,7 +117,7 @@ namespace Jellyfin.Api.Controllers /// Task stopped. /// Task not found. /// An on success, or a if the file could not be found. - [HttpDelete("Running/{TaskID}")] + [HttpDelete("Running/{taskId}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult StopTask([FromRoute] string taskId) @@ -142,7 +142,7 @@ namespace Jellyfin.Api.Controllers /// Task triggers updated. /// Task not found. /// An on success, or a if the file could not be found. - [HttpPost("{TaskID}/Triggers")] + [HttpPost("{taskId}/Triggers")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult UpdateTask( diff --git a/MediaBrowser.Common/Json/JsonDefaults.cs b/MediaBrowser.Common/Json/JsonDefaults.cs index f38e2893e..35925c3a2 100644 --- a/MediaBrowser.Common/Json/JsonDefaults.cs +++ b/MediaBrowser.Common/Json/JsonDefaults.cs @@ -30,6 +30,7 @@ namespace MediaBrowser.Common.Json options.Converters.Add(new JsonGuidConverter()); options.Converters.Add(new JsonStringEnumConverter()); options.Converters.Add(new JsonNonStringKeyDictionaryConverterFactory()); + options.Converters.Add(new JsonInt64Converter()); return options; } From e4a13f0e1e946ed306f2eb1be4217cde9d2622cb Mon Sep 17 00:00:00 2001 From: crobibero Date: Fri, 19 Jun 2020 09:02:41 -0600 Subject: [PATCH 12/14] remove #nullable --- Jellyfin.Api/Controllers/ScheduledTasksController.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/Jellyfin.Api/Controllers/ScheduledTasksController.cs b/Jellyfin.Api/Controllers/ScheduledTasksController.cs index e37e137d1..f7122c413 100644 --- a/Jellyfin.Api/Controllers/ScheduledTasksController.cs +++ b/Jellyfin.Api/Controllers/ScheduledTasksController.cs @@ -1,5 +1,3 @@ -#nullable enable - using System; using System.Collections.Generic; using System.Linq; From 0c98bc42a8726dfa09244d55acf5339b3bd7a403 Mon Sep 17 00:00:00 2001 From: crobibero Date: Sat, 20 Jun 2020 15:11:10 -0600 Subject: [PATCH 13/14] Fix response code & docs --- .../Controllers/ScheduledTasksController.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Jellyfin.Api/Controllers/ScheduledTasksController.cs b/Jellyfin.Api/Controllers/ScheduledTasksController.cs index f7122c413..64de23ef2 100644 --- a/Jellyfin.Api/Controllers/ScheduledTasksController.cs +++ b/Jellyfin.Api/Controllers/ScheduledTasksController.cs @@ -88,9 +88,9 @@ namespace Jellyfin.Api.Controllers /// Start specified task. /// /// Task Id. - /// Task started. + /// Task started. /// Task not found. - /// An on success, or a if the file could not be found. + /// An on success, or a if the file could not be found. [HttpPost("Running/{taskId}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] @@ -105,14 +105,14 @@ namespace Jellyfin.Api.Controllers } _taskManager.Execute(task, new TaskOptions()); - return Ok(); + return NoContent(); } /// /// Stop specified task. /// /// Task Id. - /// Task stopped. + /// Task stopped. /// Task not found. /// An on success, or a if the file could not be found. [HttpDelete("Running/{taskId}")] @@ -129,7 +129,7 @@ namespace Jellyfin.Api.Controllers } _taskManager.Cancel(task); - return Ok(); + return NoContent(); } /// @@ -137,7 +137,7 @@ namespace Jellyfin.Api.Controllers /// /// Task Id. /// Triggers. - /// Task triggers updated. + /// Task triggers updated. /// Task not found. /// An on success, or a if the file could not be found. [HttpPost("{taskId}/Triggers")] @@ -155,7 +155,7 @@ namespace Jellyfin.Api.Controllers } task.Triggers = triggerInfos; - return Ok(); + return NoContent(); } } } From 81c0451b5e578bb8a41dcb81f2766dbd1eb7f055 Mon Sep 17 00:00:00 2001 From: crobibero Date: Sat, 20 Jun 2020 15:16:30 -0600 Subject: [PATCH 14/14] Fix response code & docs --- Jellyfin.Api/Controllers/ScheduledTasksController.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Jellyfin.Api/Controllers/ScheduledTasksController.cs b/Jellyfin.Api/Controllers/ScheduledTasksController.cs index 64de23ef2..bf5c3076e 100644 --- a/Jellyfin.Api/Controllers/ScheduledTasksController.cs +++ b/Jellyfin.Api/Controllers/ScheduledTasksController.cs @@ -92,7 +92,7 @@ namespace Jellyfin.Api.Controllers /// Task not found. /// An on success, or a if the file could not be found. [HttpPost("Running/{taskId}")] - [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult StartTask([FromRoute] string taskId) { @@ -116,7 +116,7 @@ namespace Jellyfin.Api.Controllers /// Task not found. /// An on success, or a if the file could not be found. [HttpDelete("Running/{taskId}")] - [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult StopTask([FromRoute] string taskId) { @@ -141,7 +141,7 @@ namespace Jellyfin.Api.Controllers /// Task not found. /// An on success, or a if the file could not be found. [HttpPost("{taskId}/Triggers")] - [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult UpdateTask( [FromRoute] string taskId,