From 9a2bcd6266fb222491abe6ea31d5e7e734699d5f Mon Sep 17 00:00:00 2001 From: David Date: Wed, 15 Jul 2020 16:15:17 +0200 Subject: Move SyncPlay api to Jellyfin.Api --- Jellyfin.Api/Controllers/TimeSyncController.cs | 39 ++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 Jellyfin.Api/Controllers/TimeSyncController.cs (limited to 'Jellyfin.Api/Controllers/TimeSyncController.cs') diff --git a/Jellyfin.Api/Controllers/TimeSyncController.cs b/Jellyfin.Api/Controllers/TimeSyncController.cs new file mode 100644 index 000000000..57a720b26 --- /dev/null +++ b/Jellyfin.Api/Controllers/TimeSyncController.cs @@ -0,0 +1,39 @@ +using System; +using System.Globalization; +using MediaBrowser.Model.SyncPlay; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Jellyfin.Api.Controllers +{ + /// + /// The time sync controller. + /// + [Route("/GetUtcTime")] + public class TimeSyncController : BaseJellyfinApiController + { + /// + /// Gets the current utc time. + /// + /// Time returned. + /// An to sync the client and server time. + [HttpGet] + [ProducesResponseType(statusCode: StatusCodes.Status200OK)] + public ActionResult GetUtcTime() + { + // Important to keep the following line at the beginning + var requestReceptionTime = DateTime.UtcNow.ToUniversalTime().ToString("o", DateTimeFormatInfo.InvariantInfo); + + var response = new UtcTimeResponse(); + response.RequestReceptionTime = requestReceptionTime; + + // Important to keep the following two lines at the end + var responseTransmissionTime = DateTime.UtcNow.ToUniversalTime().ToString("o", DateTimeFormatInfo.InvariantInfo); + response.ResponseTransmissionTime = responseTransmissionTime; + + // Implementing NTP on such a high level results in this useless + // information being sent. On the other hand it enables future additions. + return response; + } + } +} -- cgit v1.2.3 From 8f6c2e767906c1d4c62d51ae2e66af1a78edde9a Mon Sep 17 00:00:00 2001 From: crobibero Date: Tue, 4 Aug 2020 08:27:54 -0600 Subject: Remove leading slash from route attributes --- .../Services/SwaggerService.cs | 287 --------------------- Jellyfin.Api/Controllers/ActivityLogController.cs | 2 +- Jellyfin.Api/Controllers/ApiKeyController.cs | 2 +- Jellyfin.Api/Controllers/ArtistsController.cs | 2 +- Jellyfin.Api/Controllers/CollectionController.cs | 2 +- Jellyfin.Api/Controllers/ItemRefreshController.cs | 3 +- .../Controllers/LibraryStructureController.cs | 2 +- Jellyfin.Api/Controllers/SearchController.cs | 2 +- Jellyfin.Api/Controllers/SystemController.cs | 2 +- Jellyfin.Api/Controllers/TimeSyncController.cs | 2 +- Jellyfin.Api/Controllers/TvShowsController.cs | 2 +- Jellyfin.Api/Controllers/UserController.cs | 2 +- 12 files changed, 11 insertions(+), 299 deletions(-) delete mode 100644 Emby.Server.Implementations/Services/SwaggerService.cs (limited to 'Jellyfin.Api/Controllers/TimeSyncController.cs') diff --git a/Emby.Server.Implementations/Services/SwaggerService.cs b/Emby.Server.Implementations/Services/SwaggerService.cs deleted file mode 100644 index 4f011a678..000000000 --- a/Emby.Server.Implementations/Services/SwaggerService.cs +++ /dev/null @@ -1,287 +0,0 @@ -#pragma warning disable CS1591 - -using System; -using System.Collections.Generic; -using System.Linq; -using Emby.Server.Implementations.HttpServer; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Services; - -namespace Emby.Server.Implementations.Services -{ - [Route("/swagger", "GET", Summary = "Gets the swagger specifications")] - [Route("/swagger.json", "GET", Summary = "Gets the swagger specifications")] - public class GetSwaggerSpec : IReturn - { - } - - public class SwaggerSpec - { - public string swagger { get; set; } - - public string[] schemes { get; set; } - - public SwaggerInfo info { get; set; } - - public string host { get; set; } - - public string basePath { get; set; } - - public SwaggerTag[] tags { get; set; } - - public IDictionary> paths { get; set; } - - public Dictionary definitions { get; set; } - - public SwaggerComponents components { get; set; } - } - - public class SwaggerComponents - { - public Dictionary securitySchemes { get; set; } - } - - public class SwaggerSecurityScheme - { - public string name { get; set; } - - public string type { get; set; } - - public string @in { get; set; } - } - - public class SwaggerInfo - { - public string description { get; set; } - - public string version { get; set; } - - public string title { get; set; } - - public string termsOfService { get; set; } - - public SwaggerConcactInfo contact { get; set; } - } - - public class SwaggerConcactInfo - { - public string email { get; set; } - - public string name { get; set; } - - public string url { get; set; } - } - - public class SwaggerTag - { - public string description { get; set; } - - public string name { get; set; } - } - - public class SwaggerMethod - { - public string summary { get; set; } - - public string description { get; set; } - - public string[] tags { get; set; } - - public string operationId { get; set; } - - public string[] consumes { get; set; } - - public string[] produces { get; set; } - - public SwaggerParam[] parameters { get; set; } - - public Dictionary responses { get; set; } - - public Dictionary[] security { get; set; } - } - - public class SwaggerParam - { - public string @in { get; set; } - - public string name { get; set; } - - public string description { get; set; } - - public bool required { get; set; } - - public string type { get; set; } - - public string collectionFormat { get; set; } - } - - public class SwaggerResponse - { - public string description { get; set; } - - // ex. "$ref":"#/definitions/Pet" - public Dictionary schema { get; set; } - } - - public class SwaggerDefinition - { - public string type { get; set; } - - public Dictionary properties { get; set; } - } - - public class SwaggerProperty - { - public string type { get; set; } - - public string format { get; set; } - - public string description { get; set; } - - public string[] @enum { get; set; } - - public string @default { get; set; } - } - - public class SwaggerService : IService, IRequiresRequest - { - private readonly IHttpServer _httpServer; - private SwaggerSpec _spec; - - public IRequest Request { get; set; } - - public SwaggerService(IHttpServer httpServer) - { - _httpServer = httpServer; - } - - public object Get(GetSwaggerSpec request) - { - return _spec ?? (_spec = GetSpec()); - } - - private SwaggerSpec GetSpec() - { - string host = null; - Uri uri; - if (Uri.TryCreate(Request.RawUrl, UriKind.Absolute, out uri)) - { - host = uri.Host; - } - - var securitySchemes = new Dictionary(); - - securitySchemes["api_key"] = new SwaggerSecurityScheme - { - name = "api_key", - type = "apiKey", - @in = "query" - }; - - var spec = new SwaggerSpec - { - schemes = new[] { "http" }, - tags = GetTags(), - swagger = "2.0", - info = new SwaggerInfo - { - title = "Jellyfin Server API", - version = "1.0.0", - description = "Explore the Jellyfin Server API", - contact = new SwaggerConcactInfo - { - name = "Jellyfin Community", - url = "https://jellyfin.readthedocs.io/en/latest/user-docs/getting-help/" - } - }, - paths = GetPaths(), - definitions = GetDefinitions(), - basePath = "/jellyfin", - host = host, - - components = new SwaggerComponents - { - securitySchemes = securitySchemes - } - }; - - return spec; - } - - - private SwaggerTag[] GetTags() - { - return Array.Empty(); - } - - private Dictionary GetDefinitions() - { - return new Dictionary(); - } - - private IDictionary> GetPaths() - { - var paths = new SortedDictionary>(); - - // REVIEW: this can be done better - var all = ((HttpListenerHost)_httpServer).ServiceController.RestPathMap.OrderBy(i => i.Key, StringComparer.OrdinalIgnoreCase).ToList(); - - foreach (var current in all) - { - foreach (var info in current.Value) - { - if (info.IsHidden) - { - continue; - } - - if (info.Path.StartsWith("/mediabrowser", StringComparison.OrdinalIgnoreCase) - || info.Path.StartsWith("/jellyfin", StringComparison.OrdinalIgnoreCase)) - { - continue; - } - - paths[info.Path] = GetPathInfo(info); - } - } - - return paths; - } - - private Dictionary GetPathInfo(RestPath info) - { - var result = new Dictionary(); - - foreach (var verb in info.Verbs) - { - var responses = new Dictionary - { - { "200", new SwaggerResponse { description = "OK" } } - }; - - var apiKeySecurity = new Dictionary - { - { "api_key", Array.Empty() } - }; - - result[verb.ToLowerInvariant()] = new SwaggerMethod - { - summary = info.Summary, - description = info.Description, - produces = new[] { "application/json" }, - consumes = new[] { "application/json" }, - operationId = info.RequestType.Name, - tags = Array.Empty(), - - parameters = Array.Empty(), - - responses = responses, - - security = new[] { apiKeySecurity } - }; - } - - return result; - } - } -} diff --git a/Jellyfin.Api/Controllers/ActivityLogController.cs b/Jellyfin.Api/Controllers/ActivityLogController.cs index 12ea24973..a07cea9c0 100644 --- a/Jellyfin.Api/Controllers/ActivityLogController.cs +++ b/Jellyfin.Api/Controllers/ActivityLogController.cs @@ -13,7 +13,7 @@ namespace Jellyfin.Api.Controllers /// /// Activity log controller. /// - [Route("/System/ActivityLog")] + [Route("System/ActivityLog")] [Authorize(Policy = Policies.RequiresElevation)] public class ActivityLogController : BaseJellyfinApiController { diff --git a/Jellyfin.Api/Controllers/ApiKeyController.cs b/Jellyfin.Api/Controllers/ApiKeyController.cs index fef4d7262..ccb7f47f0 100644 --- a/Jellyfin.Api/Controllers/ApiKeyController.cs +++ b/Jellyfin.Api/Controllers/ApiKeyController.cs @@ -15,7 +15,7 @@ namespace Jellyfin.Api.Controllers /// /// Authentication controller. /// - [Route("/Auth")] + [Route("Auth")] public class ApiKeyController : BaseJellyfinApiController { private readonly ISessionManager _sessionManager; diff --git a/Jellyfin.Api/Controllers/ArtistsController.cs b/Jellyfin.Api/Controllers/ArtistsController.cs index d39021446..9d1010803 100644 --- a/Jellyfin.Api/Controllers/ArtistsController.cs +++ b/Jellyfin.Api/Controllers/ArtistsController.cs @@ -19,7 +19,7 @@ namespace Jellyfin.Api.Controllers /// The artists controller. /// [Authorize(Policy = Policies.DefaultAuthorization)] - [Route("/Artists")] + [Route("Artists")] public class ArtistsController : BaseJellyfinApiController { private readonly ILibraryManager _libraryManager; diff --git a/Jellyfin.Api/Controllers/CollectionController.cs b/Jellyfin.Api/Controllers/CollectionController.cs index 6f78a7d84..1e8426df4 100644 --- a/Jellyfin.Api/Controllers/CollectionController.cs +++ b/Jellyfin.Api/Controllers/CollectionController.cs @@ -16,7 +16,7 @@ namespace Jellyfin.Api.Controllers /// The collection controller. /// [Authorize(Policy = Policies.DefaultAuthorization)] - [Route("/Collections")] + [Route("Collections")] public class CollectionController : BaseJellyfinApiController { private readonly ICollectionManager _collectionManager; diff --git a/Jellyfin.Api/Controllers/ItemRefreshController.cs b/Jellyfin.Api/Controllers/ItemRefreshController.cs index 4697d869d..3f5d305c1 100644 --- a/Jellyfin.Api/Controllers/ItemRefreshController.cs +++ b/Jellyfin.Api/Controllers/ItemRefreshController.cs @@ -13,8 +13,7 @@ namespace Jellyfin.Api.Controllers /// /// Item Refresh Controller. /// - /// [Authenticated] - [Route("/Items")] + [Route("Items")] [Authorize(Policy = Policies.DefaultAuthorization)] public class ItemRefreshController : BaseJellyfinApiController { diff --git a/Jellyfin.Api/Controllers/LibraryStructureController.cs b/Jellyfin.Api/Controllers/LibraryStructureController.cs index 827879e0a..ca150f3f2 100644 --- a/Jellyfin.Api/Controllers/LibraryStructureController.cs +++ b/Jellyfin.Api/Controllers/LibraryStructureController.cs @@ -24,7 +24,7 @@ namespace Jellyfin.Api.Controllers /// /// The library structure controller. /// - [Route("/Library/VirtualFolders")] + [Route("Library/VirtualFolders")] [Authorize(Policy = Policies.FirstTimeSetupOrElevated)] public class LibraryStructureController : BaseJellyfinApiController { diff --git a/Jellyfin.Api/Controllers/SearchController.cs b/Jellyfin.Api/Controllers/SearchController.cs index 2cbd32d2f..e159a9666 100644 --- a/Jellyfin.Api/Controllers/SearchController.cs +++ b/Jellyfin.Api/Controllers/SearchController.cs @@ -23,7 +23,7 @@ namespace Jellyfin.Api.Controllers /// /// Search controller. /// - [Route("/Search/Hints")] + [Route("Search/Hints")] [Authorize(Policy = Policies.DefaultAuthorization)] public class SearchController : BaseJellyfinApiController { diff --git a/Jellyfin.Api/Controllers/SystemController.cs b/Jellyfin.Api/Controllers/SystemController.cs index e0bce3a41..6f9a75e2f 100644 --- a/Jellyfin.Api/Controllers/SystemController.cs +++ b/Jellyfin.Api/Controllers/SystemController.cs @@ -23,7 +23,7 @@ namespace Jellyfin.Api.Controllers /// /// The system controller. /// - [Route("/System")] + [Route("System")] public class SystemController : BaseJellyfinApiController { private readonly IServerApplicationHost _appHost; diff --git a/Jellyfin.Api/Controllers/TimeSyncController.cs b/Jellyfin.Api/Controllers/TimeSyncController.cs index 57a720b26..bbabcd6e6 100644 --- a/Jellyfin.Api/Controllers/TimeSyncController.cs +++ b/Jellyfin.Api/Controllers/TimeSyncController.cs @@ -9,7 +9,7 @@ namespace Jellyfin.Api.Controllers /// /// The time sync controller. /// - [Route("/GetUtcTime")] + [Route("GetUtcTime")] public class TimeSyncController : BaseJellyfinApiController { /// diff --git a/Jellyfin.Api/Controllers/TvShowsController.cs b/Jellyfin.Api/Controllers/TvShowsController.cs index 508b5e24e..d4560dfa2 100644 --- a/Jellyfin.Api/Controllers/TvShowsController.cs +++ b/Jellyfin.Api/Controllers/TvShowsController.cs @@ -22,7 +22,7 @@ namespace Jellyfin.Api.Controllers /// /// The tv shows controller. /// - [Route("/Shows")] + [Route("Shows")] [Authorize(Policy = Policies.DefaultAuthorization)] public class TvShowsController : BaseJellyfinApiController { diff --git a/Jellyfin.Api/Controllers/UserController.cs b/Jellyfin.Api/Controllers/UserController.cs index ce0c9281b..482baf641 100644 --- a/Jellyfin.Api/Controllers/UserController.cs +++ b/Jellyfin.Api/Controllers/UserController.cs @@ -27,7 +27,7 @@ namespace Jellyfin.Api.Controllers /// /// User controller. /// - [Route("/Users")] + [Route("Users")] public class UserController : BaseJellyfinApiController { private readonly IUserManager _userManager; -- cgit v1.2.3 From fffa94fc33b923863e7cfe0d57d85ae86206975e Mon Sep 17 00:00:00 2001 From: crobibero Date: Thu, 6 Aug 2020 08:17:45 -0600 Subject: Apply fixes from review --- .../FirstTimeSetupOrDefaultHandler.cs | 56 ++++++++++++++++++++++ .../FirstTimeSetupOrDefaultRequirement.cs | 11 +++++ ...IgnoreParentalControlOrFirstTimeSetupHandler.cs | 51 ++++++++++++++++++++ ...reParentalControlOrFirstTimeSetupRequirement.cs | 11 +++++ .../IgnoreParentalControlHandler.cs | 42 ++++++++++++++++ .../IgnoreParentalControlRequirement.cs | 11 +++++ .../IgnoreSchedulePolicy/IgnoreScheduleHandler.cs | 42 ---------------- .../IgnoreScheduleRequirement.cs | 11 ----- .../LocalAccessOrRequiresElevationHandler.cs | 46 ++++++++++++++++++ .../LocalAccessOrRequiresElevationRequirement.cs | 11 +++++ Jellyfin.Api/Constants/Policies.cs | 19 +++++++- Jellyfin.Api/Controllers/ApiKeyController.cs | 2 +- Jellyfin.Api/Controllers/CollectionController.cs | 5 +- .../Controllers/ConfigurationController.cs | 6 +-- Jellyfin.Api/Controllers/DevicesController.cs | 14 +++--- .../Controllers/DisplayPreferencesController.cs | 7 ++- Jellyfin.Api/Controllers/EnvironmentController.cs | 8 ++-- Jellyfin.Api/Controllers/ImageByNameController.cs | 11 +++-- Jellyfin.Api/Controllers/ImageController.cs | 2 + Jellyfin.Api/Controllers/InstantMixController.cs | 3 +- Jellyfin.Api/Controllers/ItemLookupController.cs | 21 ++++---- Jellyfin.Api/Controllers/ItemUpdateController.cs | 6 +-- Jellyfin.Api/Controllers/LibraryController.cs | 9 ++-- .../Controllers/LibraryStructureController.cs | 4 +- Jellyfin.Api/Controllers/LocalizationController.cs | 2 +- Jellyfin.Api/Controllers/MediaInfoController.cs | 5 +- .../Controllers/NotificationsController.cs | 14 ++++-- Jellyfin.Api/Controllers/PackageController.cs | 1 - Jellyfin.Api/Controllers/PersonsController.cs | 3 ++ Jellyfin.Api/Controllers/PlaylistsController.cs | 4 +- Jellyfin.Api/Controllers/PluginsController.cs | 4 +- Jellyfin.Api/Controllers/RemoteImageController.cs | 6 +-- .../Controllers/ScheduledTasksController.cs | 10 ++-- Jellyfin.Api/Controllers/SessionController.cs | 49 ++++++++++++------- Jellyfin.Api/Controllers/SubtitleController.cs | 6 +-- Jellyfin.Api/Controllers/SyncPlayController.cs | 2 +- Jellyfin.Api/Controllers/SystemController.cs | 7 +-- Jellyfin.Api/Controllers/TimeSyncController.cs | 4 +- Jellyfin.Api/Controllers/TvShowsController.cs | 13 ++--- Jellyfin.Api/Controllers/UserController.cs | 11 ++--- .../Controllers/VideoAttachmentsController.cs | 10 ++-- Jellyfin.Api/Controllers/VideosController.cs | 4 +- Jellyfin.Api/Controllers/YearsController.cs | 3 ++ .../Models/StartupDtos/StartupConfigurationDto.cs | 2 +- .../Extensions/ApiServiceCollectionExtensions.cs | 35 ++++++++++++-- .../IgnoreScheduleHandlerTests.cs | 8 ++-- 46 files changed, 432 insertions(+), 180 deletions(-) create mode 100644 Jellyfin.Api/Auth/FirstTimeSetupOrDefaultPolicy/FirstTimeSetupOrDefaultHandler.cs create mode 100644 Jellyfin.Api/Auth/FirstTimeSetupOrDefaultPolicy/FirstTimeSetupOrDefaultRequirement.cs create mode 100644 Jellyfin.Api/Auth/IgnoreParentalControlOrFirstTimeSetupPolicy/IgnoreParentalControlOrFirstTimeSetupHandler.cs create mode 100644 Jellyfin.Api/Auth/IgnoreParentalControlOrFirstTimeSetupPolicy/IgnoreParentalControlOrFirstTimeSetupRequirement.cs create mode 100644 Jellyfin.Api/Auth/IgnoreParentalControlPolicy/IgnoreParentalControlHandler.cs create mode 100644 Jellyfin.Api/Auth/IgnoreParentalControlPolicy/IgnoreParentalControlRequirement.cs delete mode 100644 Jellyfin.Api/Auth/IgnoreSchedulePolicy/IgnoreScheduleHandler.cs delete mode 100644 Jellyfin.Api/Auth/IgnoreSchedulePolicy/IgnoreScheduleRequirement.cs create mode 100644 Jellyfin.Api/Auth/LocalAccessOrRequiresElevationPolicy/LocalAccessOrRequiresElevationHandler.cs create mode 100644 Jellyfin.Api/Auth/LocalAccessOrRequiresElevationPolicy/LocalAccessOrRequiresElevationRequirement.cs (limited to 'Jellyfin.Api/Controllers/TimeSyncController.cs') diff --git a/Jellyfin.Api/Auth/FirstTimeSetupOrDefaultPolicy/FirstTimeSetupOrDefaultHandler.cs b/Jellyfin.Api/Auth/FirstTimeSetupOrDefaultPolicy/FirstTimeSetupOrDefaultHandler.cs new file mode 100644 index 000000000..67fb2b79a --- /dev/null +++ b/Jellyfin.Api/Auth/FirstTimeSetupOrDefaultPolicy/FirstTimeSetupOrDefaultHandler.cs @@ -0,0 +1,56 @@ +using System.Threading.Tasks; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Library; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; + +namespace Jellyfin.Api.Auth.FirstTimeSetupOrDefaultPolicy +{ + /// + /// Authorization handler for requiring first time setup or elevated privileges. + /// + public class FirstTimeSetupOrDefaultHandler : BaseAuthorizationHandler + { + private readonly IConfigurationManager _configurationManager; + + /// + /// Initializes a new instance of the class. + /// + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + public FirstTimeSetupOrDefaultHandler( + IConfigurationManager configurationManager, + IUserManager userManager, + INetworkManager networkManager, + IHttpContextAccessor httpContextAccessor) + : base(userManager, networkManager, httpContextAccessor) + { + _configurationManager = configurationManager; + } + + /// + protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, FirstTimeSetupOrDefaultRequirement firstTimeSetupOrElevatedRequirement) + { + if (!_configurationManager.CommonConfiguration.IsStartupWizardCompleted) + { + context.Succeed(firstTimeSetupOrElevatedRequirement); + return Task.CompletedTask; + } + + var validated = ValidateClaims(context.User); + if (validated) + { + context.Succeed(firstTimeSetupOrElevatedRequirement); + } + else + { + context.Fail(); + } + + return Task.CompletedTask; + } + } +} diff --git a/Jellyfin.Api/Auth/FirstTimeSetupOrDefaultPolicy/FirstTimeSetupOrDefaultRequirement.cs b/Jellyfin.Api/Auth/FirstTimeSetupOrDefaultPolicy/FirstTimeSetupOrDefaultRequirement.cs new file mode 100644 index 000000000..23d7ee01f --- /dev/null +++ b/Jellyfin.Api/Auth/FirstTimeSetupOrDefaultPolicy/FirstTimeSetupOrDefaultRequirement.cs @@ -0,0 +1,11 @@ +using Microsoft.AspNetCore.Authorization; + +namespace Jellyfin.Api.Auth.FirstTimeSetupOrDefaultPolicy +{ + /// + /// The authorization requirement, requiring incomplete first time setup or elevated privileges, for the authorization handler. + /// + public class FirstTimeSetupOrDefaultRequirement : IAuthorizationRequirement + { + } +} diff --git a/Jellyfin.Api/Auth/IgnoreParentalControlOrFirstTimeSetupPolicy/IgnoreParentalControlOrFirstTimeSetupHandler.cs b/Jellyfin.Api/Auth/IgnoreParentalControlOrFirstTimeSetupPolicy/IgnoreParentalControlOrFirstTimeSetupHandler.cs new file mode 100644 index 000000000..6c9258b3d --- /dev/null +++ b/Jellyfin.Api/Auth/IgnoreParentalControlOrFirstTimeSetupPolicy/IgnoreParentalControlOrFirstTimeSetupHandler.cs @@ -0,0 +1,51 @@ +using System.Threading.Tasks; +using Jellyfin.Api.Auth.IgnoreParentalControlPolicy; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Library; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; + +namespace Jellyfin.Api.Auth.IgnoreParentalControlOrFirstTimeSetupPolicy +{ + /// + /// Escape schedule controls handler. + /// + public class IgnoreParentalControlOrFirstTimeSetupHandler : BaseAuthorizationHandler + { + private readonly IConfigurationManager _configurationManager; + + /// + /// Initializes a new instance of the class. + /// + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + public IgnoreParentalControlOrFirstTimeSetupHandler( + IUserManager userManager, + INetworkManager networkManager, + IHttpContextAccessor httpContextAccessor, + IConfigurationManager configurationManager) + : base(userManager, networkManager, httpContextAccessor) + { + _configurationManager = configurationManager; + } + + /// + protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, IgnoreParentalControlRequirement requirement) + { + var validated = ValidateClaims(context.User, ignoreSchedule: true); + if (validated || !_configurationManager.CommonConfiguration.IsStartupWizardCompleted) + { + context.Succeed(requirement); + } + else + { + context.Fail(); + } + + return Task.CompletedTask; + } + } +} diff --git a/Jellyfin.Api/Auth/IgnoreParentalControlOrFirstTimeSetupPolicy/IgnoreParentalControlOrFirstTimeSetupRequirement.cs b/Jellyfin.Api/Auth/IgnoreParentalControlOrFirstTimeSetupPolicy/IgnoreParentalControlOrFirstTimeSetupRequirement.cs new file mode 100644 index 000000000..36ded0625 --- /dev/null +++ b/Jellyfin.Api/Auth/IgnoreParentalControlOrFirstTimeSetupPolicy/IgnoreParentalControlOrFirstTimeSetupRequirement.cs @@ -0,0 +1,11 @@ +using Microsoft.AspNetCore.Authorization; + +namespace Jellyfin.Api.Auth.IgnoreParentalControlOrFirstTimeSetupPolicy +{ + /// + /// Escape schedule controls requirement. + /// + public class IgnoreParentalControlOrFirstTimeSetupRequirement : IAuthorizationRequirement + { + } +} diff --git a/Jellyfin.Api/Auth/IgnoreParentalControlPolicy/IgnoreParentalControlHandler.cs b/Jellyfin.Api/Auth/IgnoreParentalControlPolicy/IgnoreParentalControlHandler.cs new file mode 100644 index 000000000..5213bc4cb --- /dev/null +++ b/Jellyfin.Api/Auth/IgnoreParentalControlPolicy/IgnoreParentalControlHandler.cs @@ -0,0 +1,42 @@ +using System.Threading.Tasks; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Library; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; + +namespace Jellyfin.Api.Auth.IgnoreParentalControlPolicy +{ + /// + /// Escape schedule controls handler. + /// + public class IgnoreParentalControlHandler : BaseAuthorizationHandler + { + /// + /// Initializes a new instance of the class. + /// + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + public IgnoreParentalControlHandler( + IUserManager userManager, + INetworkManager networkManager, + IHttpContextAccessor httpContextAccessor) + : base(userManager, networkManager, httpContextAccessor) + { + } + + /// + protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, IgnoreParentalControlRequirement requirement) + { + var validated = ValidateClaims(context.User, ignoreSchedule: true); + if (!validated) + { + context.Fail(); + return Task.CompletedTask; + } + + context.Succeed(requirement); + return Task.CompletedTask; + } + } +} diff --git a/Jellyfin.Api/Auth/IgnoreParentalControlPolicy/IgnoreParentalControlRequirement.cs b/Jellyfin.Api/Auth/IgnoreParentalControlPolicy/IgnoreParentalControlRequirement.cs new file mode 100644 index 000000000..cdad74270 --- /dev/null +++ b/Jellyfin.Api/Auth/IgnoreParentalControlPolicy/IgnoreParentalControlRequirement.cs @@ -0,0 +1,11 @@ +using Microsoft.AspNetCore.Authorization; + +namespace Jellyfin.Api.Auth.IgnoreParentalControlPolicy +{ + /// + /// Escape schedule controls requirement. + /// + public class IgnoreParentalControlRequirement : IAuthorizationRequirement + { + } +} diff --git a/Jellyfin.Api/Auth/IgnoreSchedulePolicy/IgnoreScheduleHandler.cs b/Jellyfin.Api/Auth/IgnoreSchedulePolicy/IgnoreScheduleHandler.cs deleted file mode 100644 index 9afa0b28f..000000000 --- a/Jellyfin.Api/Auth/IgnoreSchedulePolicy/IgnoreScheduleHandler.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System.Threading.Tasks; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Library; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; - -namespace Jellyfin.Api.Auth.IgnoreSchedulePolicy -{ - /// - /// Escape schedule controls handler. - /// - public class IgnoreScheduleHandler : BaseAuthorizationHandler - { - /// - /// Initializes a new instance of the class. - /// - /// Instance of the interface. - /// Instance of the interface. - /// Instance of the interface. - public IgnoreScheduleHandler( - IUserManager userManager, - INetworkManager networkManager, - IHttpContextAccessor httpContextAccessor) - : base(userManager, networkManager, httpContextAccessor) - { - } - - /// - protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, IgnoreScheduleRequirement requirement) - { - var validated = ValidateClaims(context.User, ignoreSchedule: true); - if (!validated) - { - context.Fail(); - return Task.CompletedTask; - } - - context.Succeed(requirement); - return Task.CompletedTask; - } - } -} diff --git a/Jellyfin.Api/Auth/IgnoreSchedulePolicy/IgnoreScheduleRequirement.cs b/Jellyfin.Api/Auth/IgnoreSchedulePolicy/IgnoreScheduleRequirement.cs deleted file mode 100644 index d5bb61ce6..000000000 --- a/Jellyfin.Api/Auth/IgnoreSchedulePolicy/IgnoreScheduleRequirement.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Microsoft.AspNetCore.Authorization; - -namespace Jellyfin.Api.Auth.IgnoreSchedulePolicy -{ - /// - /// Escape schedule controls requirement. - /// - public class IgnoreScheduleRequirement : IAuthorizationRequirement - { - } -} diff --git a/Jellyfin.Api/Auth/LocalAccessOrRequiresElevationPolicy/LocalAccessOrRequiresElevationHandler.cs b/Jellyfin.Api/Auth/LocalAccessOrRequiresElevationPolicy/LocalAccessOrRequiresElevationHandler.cs new file mode 100644 index 000000000..d9ab8aa68 --- /dev/null +++ b/Jellyfin.Api/Auth/LocalAccessOrRequiresElevationPolicy/LocalAccessOrRequiresElevationHandler.cs @@ -0,0 +1,46 @@ +using System.Threading.Tasks; +using Jellyfin.Api.Constants; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Library; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; + +namespace Jellyfin.Api.Auth.LocalAccessOrRequiresElevationPolicy +{ + /// + /// Local access handler. + /// + public class LocalAccessOrRequiresElevationHandler : BaseAuthorizationHandler + { + /// + /// Initializes a new instance of the class. + /// + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + public LocalAccessOrRequiresElevationHandler( + IUserManager userManager, + INetworkManager networkManager, + IHttpContextAccessor httpContextAccessor) + : base(userManager, networkManager, httpContextAccessor) + { + } + + /// + protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, LocalAccessOrRequiresElevationRequirement requirement) + { + var validated = ValidateClaims(context.User, localAccessOnly: true); + + if (validated || context.User.IsInRole(UserRoles.Administrator)) + { + context.Succeed(requirement); + } + else + { + context.Fail(); + } + + return Task.CompletedTask; + } + } +} diff --git a/Jellyfin.Api/Auth/LocalAccessOrRequiresElevationPolicy/LocalAccessOrRequiresElevationRequirement.cs b/Jellyfin.Api/Auth/LocalAccessOrRequiresElevationPolicy/LocalAccessOrRequiresElevationRequirement.cs new file mode 100644 index 000000000..ad96caa81 --- /dev/null +++ b/Jellyfin.Api/Auth/LocalAccessOrRequiresElevationPolicy/LocalAccessOrRequiresElevationRequirement.cs @@ -0,0 +1,11 @@ +using Microsoft.AspNetCore.Authorization; + +namespace Jellyfin.Api.Auth.LocalAccessOrRequiresElevationPolicy +{ + /// + /// The local access authorization requirement. + /// + public class LocalAccessOrRequiresElevationRequirement : IAuthorizationRequirement + { + } +} diff --git a/Jellyfin.Api/Constants/Policies.cs b/Jellyfin.Api/Constants/Policies.cs index 851b56d73..8de637c4e 100644 --- a/Jellyfin.Api/Constants/Policies.cs +++ b/Jellyfin.Api/Constants/Policies.cs @@ -13,7 +13,7 @@ namespace Jellyfin.Api.Constants /// /// Policy name for requiring first time setup or elevated privileges. /// - public const string FirstTimeSetupOrElevated = "FirstTimeOrElevated"; + public const string FirstTimeSetupOrElevated = "FirstTimeSetupOrElevated"; /// /// Policy name for requiring elevated privileges. @@ -28,11 +28,26 @@ namespace Jellyfin.Api.Constants /// /// Policy name for escaping schedule controls. /// - public const string IgnoreSchedule = "IgnoreSchedule"; + public const string IgnoreParentalControl = "IgnoreParentalControl"; /// /// Policy name for requiring download permission. /// public const string Download = "Download"; + + /// + /// Policy name for requiring first time setup or default permissions. + /// + public const string FirstTimeSetupOrDefault = "FirstTimeSetupOrDefault"; + + /// + /// Policy name for requiring local access or elevated privileges. + /// + public const string LocalAccessOrRequiresElevation = "LocalAccessOrRequiresElevation"; + + /// + /// Policy name for escaping schedule controls or requiring first time setup. + /// + public const string IgnoreParentalControlOrFirstTimeSetup = "IgnoreParentalControlOrFirstTimeSetup"; } } diff --git a/Jellyfin.Api/Controllers/ApiKeyController.cs b/Jellyfin.Api/Controllers/ApiKeyController.cs index ccb7f47f0..0e28d4c47 100644 --- a/Jellyfin.Api/Controllers/ApiKeyController.cs +++ b/Jellyfin.Api/Controllers/ApiKeyController.cs @@ -88,7 +88,7 @@ namespace Jellyfin.Api.Controllers [HttpDelete("Keys/{key}")] [Authorize(Policy = Policies.RequiresElevation)] [ProducesResponseType(StatusCodes.Status204NoContent)] - public ActionResult RevokeKey([FromRoute] string? key) + public ActionResult RevokeKey([FromRoute, Required] string? key) { _sessionManager.RevokeToken(key); return NoContent(); diff --git a/Jellyfin.Api/Controllers/CollectionController.cs b/Jellyfin.Api/Controllers/CollectionController.cs index b63fc5ab1..53821a188 100644 --- a/Jellyfin.Api/Controllers/CollectionController.cs +++ b/Jellyfin.Api/Controllers/CollectionController.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel.DataAnnotations; using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; @@ -86,7 +87,7 @@ namespace Jellyfin.Api.Controllers /// A indicating success. [HttpPost("{collectionId}/Items")] [ProducesResponseType(StatusCodes.Status204NoContent)] - public ActionResult AddToCollection([FromRoute] Guid collectionId, [FromQuery] string? itemIds) + public ActionResult AddToCollection([FromRoute] Guid collectionId, [FromQuery, Required] string? itemIds) { _collectionManager.AddToCollection(collectionId, RequestHelpers.Split(itemIds, ',', true)); return NoContent(); @@ -101,7 +102,7 @@ namespace Jellyfin.Api.Controllers /// A indicating success. [HttpDelete("{collectionId}/Items")] [ProducesResponseType(StatusCodes.Status204NoContent)] - public ActionResult RemoveFromCollection([FromRoute] Guid collectionId, [FromQuery] string? itemIds) + public ActionResult RemoveFromCollection([FromRoute] Guid collectionId, [FromQuery, Required] string? itemIds) { _collectionManager.RemoveFromCollection(collectionId, RequestHelpers.Split(itemIds, ',', true)); return NoContent(); diff --git a/Jellyfin.Api/Controllers/ConfigurationController.cs b/Jellyfin.Api/Controllers/ConfigurationController.cs index 7d262ed59..019703dae 100644 --- a/Jellyfin.Api/Controllers/ConfigurationController.cs +++ b/Jellyfin.Api/Controllers/ConfigurationController.cs @@ -1,3 +1,4 @@ +using System.ComponentModel.DataAnnotations; using System.Text.Json; using System.Threading.Tasks; using Jellyfin.Api.Constants; @@ -9,7 +10,6 @@ using MediaBrowser.Model.Configuration; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.ModelBinding; namespace Jellyfin.Api.Controllers { @@ -59,7 +59,7 @@ namespace Jellyfin.Api.Controllers [HttpPost("Configuration")] [Authorize(Policy = Policies.RequiresElevation)] [ProducesResponseType(StatusCodes.Status204NoContent)] - public ActionResult UpdateConfiguration([FromBody, BindRequired] ServerConfiguration configuration) + public ActionResult UpdateConfiguration([FromBody, Required] ServerConfiguration configuration) { _configurationManager.ReplaceConfiguration(configuration); return NoContent(); @@ -117,7 +117,7 @@ namespace Jellyfin.Api.Controllers [HttpPost("MediaEncoder/Path")] [Authorize(Policy = Policies.FirstTimeSetupOrElevated)] [ProducesResponseType(StatusCodes.Status204NoContent)] - public ActionResult UpdateMediaEncoderPath([FromForm, BindRequired] MediaEncoderPathDto mediaEncoderPath) + public ActionResult UpdateMediaEncoderPath([FromForm, Required] MediaEncoderPathDto mediaEncoderPath) { _mediaEncoder.UpdateEncoderPath(mediaEncoderPath.Path, mediaEncoderPath.PathType); return NoContent(); diff --git a/Jellyfin.Api/Controllers/DevicesController.cs b/Jellyfin.Api/Controllers/DevicesController.cs index 3cf7b3378..23d10e215 100644 --- a/Jellyfin.Api/Controllers/DevicesController.cs +++ b/Jellyfin.Api/Controllers/DevicesController.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel.DataAnnotations; using Jellyfin.Api.Constants; using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Security; @@ -8,7 +9,6 @@ using MediaBrowser.Model.Querying; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.ModelBinding; namespace Jellyfin.Api.Controllers { @@ -48,7 +48,7 @@ namespace Jellyfin.Api.Controllers [HttpGet] [Authorize(Policy = Policies.RequiresElevation)] [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult> GetDevices([FromQuery] bool? supportsSync, [FromQuery] Guid? userId) + public ActionResult> GetDevices([FromQuery] bool? supportsSync, [FromQuery, Required] Guid? userId) { var deviceQuery = new DeviceQuery { SupportsSync = supportsSync, UserId = userId ?? Guid.Empty }; return _deviceManager.GetDevices(deviceQuery); @@ -65,7 +65,7 @@ namespace Jellyfin.Api.Controllers [Authorize(Policy = Policies.RequiresElevation)] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public ActionResult GetDeviceInfo([FromQuery, BindRequired] string? id) + public ActionResult GetDeviceInfo([FromQuery, Required] string? id) { var deviceInfo = _deviceManager.GetDevice(id); if (deviceInfo == null) @@ -87,7 +87,7 @@ namespace Jellyfin.Api.Controllers [Authorize(Policy = Policies.RequiresElevation)] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public ActionResult GetDeviceOptions([FromQuery, BindRequired] string? id) + public ActionResult GetDeviceOptions([FromQuery, Required] string? id) { var deviceInfo = _deviceManager.GetDeviceOptions(id); if (deviceInfo == null) @@ -111,8 +111,8 @@ namespace Jellyfin.Api.Controllers [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult UpdateDeviceOptions( - [FromQuery, BindRequired] string? id, - [FromBody, BindRequired] DeviceOptions deviceOptions) + [FromQuery, Required] string? id, + [FromBody, Required] DeviceOptions deviceOptions) { var existingDeviceOptions = _deviceManager.GetDeviceOptions(id); if (existingDeviceOptions == null) @@ -134,7 +134,7 @@ namespace Jellyfin.Api.Controllers [HttpDelete] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public ActionResult DeleteDevice([FromQuery, BindRequired] string? id) + public ActionResult DeleteDevice([FromQuery, Required] string? id) { var existingDevice = _deviceManager.GetDevice(id); if (existingDevice == null) diff --git a/Jellyfin.Api/Controllers/DisplayPreferencesController.cs b/Jellyfin.Api/Controllers/DisplayPreferencesController.cs index 62f6097f3..c547d0cde 100644 --- a/Jellyfin.Api/Controllers/DisplayPreferencesController.cs +++ b/Jellyfin.Api/Controllers/DisplayPreferencesController.cs @@ -11,7 +11,6 @@ using MediaBrowser.Model.Entities; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.ModelBinding; namespace Jellyfin.Api.Controllers { @@ -99,9 +98,9 @@ namespace Jellyfin.Api.Controllers [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "displayPreferencesId", Justification = "Imported from ServiceStack")] public ActionResult UpdateDisplayPreferences( [FromRoute] string? displayPreferencesId, - [FromQuery, BindRequired] Guid userId, - [FromQuery, BindRequired] string? client, - [FromBody, BindRequired] DisplayPreferencesDto displayPreferences) + [FromQuery, Required] Guid userId, + [FromQuery, Required] string? client, + [FromBody, Required] DisplayPreferencesDto displayPreferences) { HomeSectionType[] defaults = { diff --git a/Jellyfin.Api/Controllers/EnvironmentController.cs b/Jellyfin.Api/Controllers/EnvironmentController.cs index 719bb7d86..64670f7d8 100644 --- a/Jellyfin.Api/Controllers/EnvironmentController.cs +++ b/Jellyfin.Api/Controllers/EnvironmentController.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.IO; using System.Linq; using Jellyfin.Api.Constants; @@ -8,7 +9,6 @@ using MediaBrowser.Model.IO; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.Extensions.Logging; namespace Jellyfin.Api.Controllers @@ -47,7 +47,7 @@ namespace Jellyfin.Api.Controllers [HttpGet("DirectoryContents")] [ProducesResponseType(StatusCodes.Status200OK)] public IEnumerable GetDirectoryContents( - [FromQuery, BindRequired] string path, + [FromQuery, Required] string path, [FromQuery] bool includeFiles = false, [FromQuery] bool includeDirectories = false) { @@ -75,7 +75,7 @@ namespace Jellyfin.Api.Controllers [HttpPost("ValidatePath")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public ActionResult ValidatePath([FromBody, BindRequired] ValidatePathDto validatePathDto) + public ActionResult ValidatePath([FromBody, Required] ValidatePathDto validatePathDto) { if (validatePathDto.IsFile.HasValue) { @@ -154,7 +154,7 @@ namespace Jellyfin.Api.Controllers /// Parent path. [HttpGet("ParentPath")] [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult GetParentPath([FromQuery, BindRequired] string path) + public ActionResult GetParentPath([FromQuery, Required] string path) { string? parent = Path.GetDirectoryName(path); if (string.IsNullOrEmpty(parent)) diff --git a/Jellyfin.Api/Controllers/ImageByNameController.cs b/Jellyfin.Api/Controllers/ImageByNameController.cs index 5244c35b8..528590536 100644 --- a/Jellyfin.Api/Controllers/ImageByNameController.cs +++ b/Jellyfin.Api/Controllers/ImageByNameController.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.IO; using System.Linq; using System.Net.Mime; @@ -64,7 +65,7 @@ namespace Jellyfin.Api.Controllers [Produces(MediaTypeNames.Application.Octet)] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public ActionResult GetGeneralImage([FromRoute] string? name, [FromRoute] string? type) + public ActionResult GetGeneralImage([FromRoute, Required] string? name, [FromRoute, Required] string? type) { var filename = string.Equals(type, "primary", StringComparison.OrdinalIgnoreCase) ? "folder" @@ -110,8 +111,8 @@ namespace Jellyfin.Api.Controllers [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult GetRatingImage( - [FromRoute] string? theme, - [FromRoute] string? name) + [FromRoute, Required] string? theme, + [FromRoute, Required] string? name) { return GetImageFile(_applicationPaths.RatingsPath, theme, name); } @@ -143,8 +144,8 @@ namespace Jellyfin.Api.Controllers [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult GetMediaInfoImage( - [FromRoute] string? theme, - [FromRoute] string? name) + [FromRoute, Required] string? theme, + [FromRoute, Required] string? name) { return GetImageFile(_applicationPaths.MediaInfoImagesPath, theme, name); } diff --git a/Jellyfin.Api/Controllers/ImageController.cs b/Jellyfin.Api/Controllers/ImageController.cs index 360164ad4..410456a25 100644 --- a/Jellyfin.Api/Controllers/ImageController.cs +++ b/Jellyfin.Api/Controllers/ImageController.cs @@ -84,6 +84,7 @@ namespace Jellyfin.Api.Controllers /// A . [HttpPost("Users/{userId}/Images/{imageType}")] [HttpPost("Users/{userId}/Images/{imageType}/{index?}", Name = "PostUserImage_2")] + [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "imageType", Justification = "Imported from ServiceStack")] @@ -259,6 +260,7 @@ namespace Jellyfin.Api.Controllers /// Item not found. /// The list of image infos on success, or if item not found. [HttpGet("Items/{itemId}/Images")] + [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult> GetItemImageInfos([FromRoute] Guid itemId) diff --git a/Jellyfin.Api/Controllers/InstantMixController.cs b/Jellyfin.Api/Controllers/InstantMixController.cs index 8ca232cef..73bd30c4d 100644 --- a/Jellyfin.Api/Controllers/InstantMixController.cs +++ b/Jellyfin.Api/Controllers/InstantMixController.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.Linq; using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; @@ -174,7 +175,7 @@ namespace Jellyfin.Api.Controllers [HttpGet("MusicGenres/{name}/InstantMix")] [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult> GetInstantMixFromMusicGenre( - [FromRoute] string? name, + [FromRoute, Required] string? name, [FromQuery] Guid? userId, [FromQuery] int? limit, [FromQuery] string? fields, diff --git a/Jellyfin.Api/Controllers/ItemLookupController.cs b/Jellyfin.Api/Controllers/ItemLookupController.cs index 0d9dffbfe..c9ad15bab 100644 --- a/Jellyfin.Api/Controllers/ItemLookupController.cs +++ b/Jellyfin.Api/Controllers/ItemLookupController.cs @@ -22,7 +22,6 @@ using MediaBrowser.Model.Providers; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.Extensions.Logging; namespace Jellyfin.Api.Controllers @@ -94,7 +93,7 @@ namespace Jellyfin.Api.Controllers /// The task result contains an containing the list of remote search results. /// [HttpPost("Items/RemoteSearch/Movie")] - public async Task>> GetMovieRemoteSearchResults([FromBody, BindRequired] RemoteSearchQuery query) + public async Task>> GetMovieRemoteSearchResults([FromBody, Required] RemoteSearchQuery query) { var results = await _providerManager.GetRemoteSearchResults(query, CancellationToken.None) .ConfigureAwait(false); @@ -111,7 +110,7 @@ namespace Jellyfin.Api.Controllers /// The task result contains an containing the list of remote search results. /// [HttpPost("Items/RemoteSearch/Trailer")] - public async Task>> GetTrailerRemoteSearchResults([FromBody, BindRequired] RemoteSearchQuery query) + public async Task>> GetTrailerRemoteSearchResults([FromBody, Required] RemoteSearchQuery query) { var results = await _providerManager.GetRemoteSearchResults(query, CancellationToken.None) .ConfigureAwait(false); @@ -128,7 +127,7 @@ namespace Jellyfin.Api.Controllers /// The task result contains an containing the list of remote search results. /// [HttpPost("Items/RemoteSearch/MusicVideo")] - public async Task>> GetMusicVideoRemoteSearchResults([FromBody, BindRequired] RemoteSearchQuery query) + public async Task>> GetMusicVideoRemoteSearchResults([FromBody, Required] RemoteSearchQuery query) { var results = await _providerManager.GetRemoteSearchResults(query, CancellationToken.None) .ConfigureAwait(false); @@ -145,7 +144,7 @@ namespace Jellyfin.Api.Controllers /// The task result contains an containing the list of remote search results. /// [HttpPost("Items/RemoteSearch/Series")] - public async Task>> GetSeriesRemoteSearchResults([FromBody, BindRequired] RemoteSearchQuery query) + public async Task>> GetSeriesRemoteSearchResults([FromBody, Required] RemoteSearchQuery query) { var results = await _providerManager.GetRemoteSearchResults(query, CancellationToken.None) .ConfigureAwait(false); @@ -162,7 +161,7 @@ namespace Jellyfin.Api.Controllers /// The task result contains an containing the list of remote search results. /// [HttpPost("Items/RemoteSearch/BoxSet")] - public async Task>> GetBoxSetRemoteSearchResults([FromBody, BindRequired] RemoteSearchQuery query) + public async Task>> GetBoxSetRemoteSearchResults([FromBody, Required] RemoteSearchQuery query) { var results = await _providerManager.GetRemoteSearchResults(query, CancellationToken.None) .ConfigureAwait(false); @@ -179,7 +178,7 @@ namespace Jellyfin.Api.Controllers /// The task result contains an containing the list of remote search results. /// [HttpPost("Items/RemoteSearch/MusicArtist")] - public async Task>> GetMusicArtistRemoteSearchResults([FromBody, BindRequired] RemoteSearchQuery query) + public async Task>> GetMusicArtistRemoteSearchResults([FromBody, Required] RemoteSearchQuery query) { var results = await _providerManager.GetRemoteSearchResults(query, CancellationToken.None) .ConfigureAwait(false); @@ -196,7 +195,7 @@ namespace Jellyfin.Api.Controllers /// The task result contains an containing the list of remote search results. /// [HttpPost("Items/RemoteSearch/MusicAlbum")] - public async Task>> GetMusicAlbumRemoteSearchResults([FromBody, BindRequired] RemoteSearchQuery query) + public async Task>> GetMusicAlbumRemoteSearchResults([FromBody, Required] RemoteSearchQuery query) { var results = await _providerManager.GetRemoteSearchResults(query, CancellationToken.None) .ConfigureAwait(false); @@ -214,7 +213,7 @@ namespace Jellyfin.Api.Controllers /// [HttpPost("Items/RemoteSearch/Person")] [Authorize(Policy = Policies.RequiresElevation)] - public async Task>> GetPersonRemoteSearchResults([FromBody, BindRequired] RemoteSearchQuery query) + public async Task>> GetPersonRemoteSearchResults([FromBody, Required] RemoteSearchQuery query) { var results = await _providerManager.GetRemoteSearchResults(query, CancellationToken.None) .ConfigureAwait(false); @@ -231,7 +230,7 @@ namespace Jellyfin.Api.Controllers /// The task result contains an containing the list of remote search results. /// [HttpPost("Items/RemoteSearch/Book")] - public async Task>> GetBookRemoteSearchResults([FromBody, BindRequired] RemoteSearchQuery query) + public async Task>> GetBookRemoteSearchResults([FromBody, Required] RemoteSearchQuery query) { var results = await _providerManager.GetRemoteSearchResults(query, CancellationToken.None) .ConfigureAwait(false); @@ -296,7 +295,7 @@ namespace Jellyfin.Api.Controllers [Authorize(Policy = Policies.RequiresElevation)] public async Task ApplySearchCriteria( [FromRoute] Guid itemId, - [FromBody, BindRequired] RemoteSearchResult searchResult, + [FromBody, Required] RemoteSearchResult searchResult, [FromQuery] bool replaceAllImages = true) { var item = _libraryManager.GetItemById(itemId); diff --git a/Jellyfin.Api/Controllers/ItemUpdateController.cs b/Jellyfin.Api/Controllers/ItemUpdateController.cs index a5d9d36a3..4b40c6ada 100644 --- a/Jellyfin.Api/Controllers/ItemUpdateController.cs +++ b/Jellyfin.Api/Controllers/ItemUpdateController.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.Linq; using System.Threading; using Jellyfin.Api.Constants; @@ -17,7 +18,6 @@ using MediaBrowser.Model.IO; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.ModelBinding; namespace Jellyfin.Api.Controllers { @@ -67,7 +67,7 @@ namespace Jellyfin.Api.Controllers [HttpPost("Items/{itemId}")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public ActionResult UpdateItem([FromRoute] Guid itemId, [FromBody, BindRequired] BaseItemDto request) + public ActionResult UpdateItem([FromRoute] Guid itemId, [FromBody, Required] BaseItemDto request) { var item = _libraryManager.GetItemById(itemId); if (item == null) @@ -194,7 +194,7 @@ namespace Jellyfin.Api.Controllers [HttpPost("Items/{itemId}/ContentType")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public ActionResult UpdateItemContentType([FromRoute] Guid itemId, [FromQuery, BindRequired] string? contentType) + public ActionResult UpdateItemContentType([FromRoute] Guid itemId, [FromQuery, Required] string? contentType) { var item = _libraryManager.GetItemById(itemId); if (item == null) diff --git a/Jellyfin.Api/Controllers/LibraryController.cs b/Jellyfin.Api/Controllers/LibraryController.cs index 4731a5c8b..4548e202a 100644 --- a/Jellyfin.Api/Controllers/LibraryController.cs +++ b/Jellyfin.Api/Controllers/LibraryController.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.Globalization; using System.IO; using System.Linq; @@ -32,7 +33,6 @@ using MediaBrowser.Model.Querying; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.Extensions.Logging; using Book = MediaBrowser.Controller.Entities.Book; using Movie = Jellyfin.Data.Entities.Movie; @@ -597,7 +597,7 @@ namespace Jellyfin.Api.Controllers [HttpPost("Library/Media/Updated")] [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status204NoContent)] - public ActionResult PostUpdatedMedia([FromBody, BindRequired] MediaUpdateInfoDto[] updates) + public ActionResult PostUpdatedMedia([FromBody, Required] MediaUpdateInfoDto[] updates) { foreach (var item in updates) { @@ -685,6 +685,7 @@ namespace Jellyfin.Api.Controllers [HttpGet("Shows/{itemId}/Similar", Name = "GetSimilarShows2")] [HttpGet("Movies/{itemId}/Similar", Name = "GetSimilarMovies2")] [HttpGet("Trailers/{itemId}/Similar", Name = "GetSimilarTrailers2")] + [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult> GetSimilarItems( [FromRoute] Guid itemId, @@ -736,11 +737,11 @@ namespace Jellyfin.Api.Controllers /// Library options info returned. /// Library options info. [HttpGet("Libraries/AvailableOptions")] - [Authorize(Policy = Policies.FirstTimeSetupOrElevated)] + [Authorize(Policy = Policies.FirstTimeSetupOrDefault)] [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult GetLibraryOptionsInfo( [FromQuery] string? libraryContentType, - [FromQuery] bool isNewLibrary = false) + [FromQuery] bool isNewLibrary) { var result = new LibraryOptionsResultDto(); diff --git a/Jellyfin.Api/Controllers/LibraryStructureController.cs b/Jellyfin.Api/Controllers/LibraryStructureController.cs index ca150f3f2..cdab4f356 100644 --- a/Jellyfin.Api/Controllers/LibraryStructureController.cs +++ b/Jellyfin.Api/Controllers/LibraryStructureController.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.Globalization; using System.IO; using System.Linq; @@ -17,7 +18,6 @@ using MediaBrowser.Model.Entities; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.ModelBinding; namespace Jellyfin.Api.Controllers { @@ -204,7 +204,7 @@ namespace Jellyfin.Api.Controllers [HttpPost("Paths")] [ProducesResponseType(StatusCodes.Status204NoContent)] public ActionResult AddMediaPath( - [FromBody, BindRequired] MediaPathDto mediaPathDto, + [FromBody, Required] MediaPathDto mediaPathDto, [FromQuery] bool refreshLibrary = false) { _libraryMonitor.Stop(); diff --git a/Jellyfin.Api/Controllers/LocalizationController.cs b/Jellyfin.Api/Controllers/LocalizationController.cs index 1466dd3ec..ef2e7e8b1 100644 --- a/Jellyfin.Api/Controllers/LocalizationController.cs +++ b/Jellyfin.Api/Controllers/LocalizationController.cs @@ -11,7 +11,7 @@ namespace Jellyfin.Api.Controllers /// /// Localization controller. /// - [Authorize(Policy = Policies.FirstTimeSetupOrElevated)] + [Authorize(Policy = Policies.FirstTimeSetupOrDefault)] public class LocalizationController : BaseJellyfinApiController { private readonly ILocalizationManager _localization; diff --git a/Jellyfin.Api/Controllers/MediaInfoController.cs b/Jellyfin.Api/Controllers/MediaInfoController.cs index 242cbf191..517113074 100644 --- a/Jellyfin.Api/Controllers/MediaInfoController.cs +++ b/Jellyfin.Api/Controllers/MediaInfoController.cs @@ -1,5 +1,6 @@ using System; using System.Buffers; +using System.ComponentModel.DataAnnotations; using System.Globalization; using System.Linq; using System.Net.Mime; @@ -91,7 +92,7 @@ namespace Jellyfin.Api.Controllers /// A containing a with the playback information. [HttpGet("Items/{itemId}/PlaybackInfo")] [ProducesResponseType(StatusCodes.Status200OK)] - public async Task> GetPlaybackInfo([FromRoute] Guid itemId, [FromQuery] Guid? userId) + public async Task> GetPlaybackInfo([FromRoute] Guid itemId, [FromQuery, Required] Guid? userId) { return await GetPlaybackInfoInternal(itemId, userId).ConfigureAwait(false); } @@ -281,7 +282,7 @@ namespace Jellyfin.Api.Controllers /// A indicating success. [HttpPost("LiveStreams/Close")] [ProducesResponseType(StatusCodes.Status204NoContent)] - public ActionResult CloseLiveStream([FromQuery] string? liveStreamId) + public ActionResult CloseLiveStream([FromQuery, Required] string? liveStreamId) { _mediaSourceManager.CloseLiveStream(liveStreamId).GetAwaiter().GetResult(); return NoContent(); diff --git a/Jellyfin.Api/Controllers/NotificationsController.cs b/Jellyfin.Api/Controllers/NotificationsController.cs index 1bb39b5f7..47ce48b2d 100644 --- a/Jellyfin.Api/Controllers/NotificationsController.cs +++ b/Jellyfin.Api/Controllers/NotificationsController.cs @@ -1,13 +1,16 @@ using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.Linq; using System.Threading; +using Jellyfin.Api.Constants; using Jellyfin.Api.Models.NotificationDtos; using Jellyfin.Data.Enums; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Notifications; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Notifications; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -16,6 +19,7 @@ namespace Jellyfin.Api.Controllers /// /// The notification controller. /// + [Authorize(Policy = Policies.DefaultAuthorization)] public class NotificationsController : BaseJellyfinApiController { private readonly INotificationManager _notificationManager; @@ -83,19 +87,19 @@ namespace Jellyfin.Api.Controllers /// /// Sends a notification to all admins. /// - /// The name of the notification. - /// The description of the notification. /// The URL of the notification. /// The level of the notification. + /// The name of the notification. + /// The description of the notification. /// Notification sent. /// A . [HttpPost("Admin")] [ProducesResponseType(StatusCodes.Status204NoContent)] public ActionResult CreateAdminNotification( - [FromQuery] string? name, - [FromQuery] string? description, [FromQuery] string? url, - [FromQuery] NotificationLevel? level) + [FromQuery] NotificationLevel? level, + [FromQuery, Required] string name = "", + [FromQuery, Required] string description = "") { var notification = new NotificationRequest { diff --git a/Jellyfin.Api/Controllers/PackageController.cs b/Jellyfin.Api/Controllers/PackageController.cs index 06c4213fb..3d6a87909 100644 --- a/Jellyfin.Api/Controllers/PackageController.cs +++ b/Jellyfin.Api/Controllers/PackageController.cs @@ -127,7 +127,6 @@ namespace Jellyfin.Api.Controllers /// Package repositories returned. /// An containing the list of package repositories. [HttpGet("Repositories")] - [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult> GetRepositories() { diff --git a/Jellyfin.Api/Controllers/PersonsController.cs b/Jellyfin.Api/Controllers/PersonsController.cs index 23cc23ce7..b6ccec666 100644 --- a/Jellyfin.Api/Controllers/PersonsController.cs +++ b/Jellyfin.Api/Controllers/PersonsController.cs @@ -1,6 +1,7 @@ using System; using System.Globalization; using System.Linq; +using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; using Jellyfin.Data.Entities; @@ -9,6 +10,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Querying; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -17,6 +19,7 @@ namespace Jellyfin.Api.Controllers /// /// Persons controller. /// + [Authorize(Policy = Policies.DefaultAuthorization)] public class PersonsController : BaseJellyfinApiController { private readonly ILibraryManager _libraryManager; diff --git a/Jellyfin.Api/Controllers/PlaylistsController.cs b/Jellyfin.Api/Controllers/PlaylistsController.cs index cf4660494..12c87d7c3 100644 --- a/Jellyfin.Api/Controllers/PlaylistsController.cs +++ b/Jellyfin.Api/Controllers/PlaylistsController.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel.DataAnnotations; using System.Linq; using System.Threading.Tasks; using Jellyfin.Api.Constants; @@ -14,7 +15,6 @@ using MediaBrowser.Model.Querying; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.ModelBinding; namespace Jellyfin.Api.Controllers { @@ -59,7 +59,7 @@ namespace Jellyfin.Api.Controllers [HttpPost] [ProducesResponseType(StatusCodes.Status200OK)] public async Task> CreatePlaylist( - [FromBody, BindRequired] CreatePlaylistDto createPlaylistRequest) + [FromBody, Required] CreatePlaylistDto createPlaylistRequest) { Guid[] idGuidArray = RequestHelpers.GetGuids(createPlaylistRequest.Ids); var result = await _playlistManager.CreatePlaylist(new PlaylistCreationRequest diff --git a/Jellyfin.Api/Controllers/PluginsController.cs b/Jellyfin.Api/Controllers/PluginsController.cs index fe10f0f1b..b2f34680b 100644 --- a/Jellyfin.Api/Controllers/PluginsController.cs +++ b/Jellyfin.Api/Controllers/PluginsController.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.Linq; using System.Text.Json; using System.Threading.Tasks; @@ -13,7 +14,6 @@ using MediaBrowser.Model.Plugins; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.ModelBinding; namespace Jellyfin.Api.Controllers { @@ -154,7 +154,7 @@ namespace Jellyfin.Api.Controllers [HttpPost("SecurityInfo")] [Authorize(Policy = Policies.RequiresElevation)] [ProducesResponseType(StatusCodes.Status204NoContent)] - public ActionResult UpdatePluginSecurityInfo([FromBody, BindRequired] PluginSecurityInfo pluginSecurityInfo) + public ActionResult UpdatePluginSecurityInfo([FromBody, Required] PluginSecurityInfo pluginSecurityInfo) { return NoContent(); } diff --git a/Jellyfin.Api/Controllers/RemoteImageController.cs b/Jellyfin.Api/Controllers/RemoteImageController.cs index 50a161ef6..baa3d80ac 100644 --- a/Jellyfin.Api/Controllers/RemoteImageController.cs +++ b/Jellyfin.Api/Controllers/RemoteImageController.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.IO; using System.Linq; using System.Net.Mime; @@ -18,7 +19,6 @@ using MediaBrowser.Model.Providers; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.ModelBinding; namespace Jellyfin.Api.Controllers { @@ -154,7 +154,7 @@ namespace Jellyfin.Api.Controllers [Produces(MediaTypeNames.Application.Octet)] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task GetRemoteImage([FromQuery, BindRequired] string imageUrl) + public async Task GetRemoteImage([FromQuery, Required] string imageUrl) { var urlHash = imageUrl.GetMD5(); var pointerCachePath = GetFullCachePath(urlHash.ToString()); @@ -209,7 +209,7 @@ namespace Jellyfin.Api.Controllers [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task DownloadRemoteImage( [FromRoute] Guid itemId, - [FromQuery, BindRequired] ImageType type, + [FromQuery, Required] ImageType type, [FromQuery] string? imageUrl) { var item = _libraryManager.GetItemById(itemId); diff --git a/Jellyfin.Api/Controllers/ScheduledTasksController.cs b/Jellyfin.Api/Controllers/ScheduledTasksController.cs index 3df325e3b..e672070c0 100644 --- a/Jellyfin.Api/Controllers/ScheduledTasksController.cs +++ b/Jellyfin.Api/Controllers/ScheduledTasksController.cs @@ -1,12 +1,12 @@ using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; 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 { @@ -71,7 +71,7 @@ namespace Jellyfin.Api.Controllers [HttpGet("{taskId}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public ActionResult GetTask([FromRoute] string? taskId) + public ActionResult GetTask([FromRoute, Required] string? taskId) { var task = _taskManager.ScheduledTasks.FirstOrDefault(i => string.Equals(i.Id, taskId, StringComparison.OrdinalIgnoreCase)); @@ -118,7 +118,7 @@ namespace Jellyfin.Api.Controllers [HttpDelete("Running/{taskId}")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public ActionResult StopTask([FromRoute] string? taskId) + public ActionResult StopTask([FromRoute, Required] string? taskId) { var task = _taskManager.ScheduledTasks.FirstOrDefault(o => o.Id.Equals(taskId, StringComparison.OrdinalIgnoreCase)); @@ -144,8 +144,8 @@ namespace Jellyfin.Api.Controllers [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult UpdateTask( - [FromRoute] string? taskId, - [FromBody, BindRequired] TaskTriggerInfo[] triggerInfos) + [FromRoute, Required] string? taskId, + [FromBody, Required] TaskTriggerInfo[] triggerInfos) { var task = _taskManager.ScheduledTasks.FirstOrDefault(o => o.Id.Equals(taskId, StringComparison.OrdinalIgnoreCase)); diff --git a/Jellyfin.Api/Controllers/SessionController.cs b/Jellyfin.Api/Controllers/SessionController.cs index 3e6f577f1..48b57bdb7 100644 --- a/Jellyfin.Api/Controllers/SessionController.cs +++ b/Jellyfin.Api/Controllers/SessionController.cs @@ -122,12 +122,13 @@ namespace Jellyfin.Api.Controllers /// Instruction sent to session. /// A . [HttpPost("Sessions/{sessionId}/Viewing")] + [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status204NoContent)] public ActionResult DisplayContent( - [FromRoute] string? sessionId, - [FromQuery] string? itemType, - [FromQuery] string? itemId, - [FromQuery] string? itemName) + [FromRoute, Required] string? sessionId, + [FromQuery, Required] string? itemType, + [FromQuery, Required] string? itemId, + [FromQuery, Required] string? itemName) { var command = new BrowseRequest { @@ -156,9 +157,10 @@ namespace Jellyfin.Api.Controllers /// Instruction sent to session. /// A . [HttpPost("Sessions/{sessionId}/Playing")] + [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status204NoContent)] public ActionResult Play( - [FromRoute] string? sessionId, + [FromRoute, Required] string? sessionId, [FromQuery] Guid[] itemIds, [FromQuery] long? startPositionTicks, [FromQuery] PlayCommand playCommand, @@ -190,9 +192,10 @@ namespace Jellyfin.Api.Controllers /// Playstate command sent to session. /// A . [HttpPost("Sessions/{sessionId}/Playing/{command}")] + [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status204NoContent)] public ActionResult SendPlaystateCommand( - [FromRoute] string? sessionId, + [FromRoute, Required] string? sessionId, [FromBody] PlaystateRequest playstateRequest) { _sessionManager.SendPlaystateCommand( @@ -212,10 +215,11 @@ namespace Jellyfin.Api.Controllers /// System command sent to session. /// A . [HttpPost("Sessions/{sessionId}/System/{command}")] + [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status204NoContent)] public ActionResult SendSystemCommand( - [FromRoute] string? sessionId, - [FromRoute] string? command) + [FromRoute, Required] string? sessionId, + [FromRoute, Required] string? command) { var name = command; if (Enum.TryParse(name, true, out GeneralCommandType commandType)) @@ -243,10 +247,11 @@ namespace Jellyfin.Api.Controllers /// General command sent to session. /// A . [HttpPost("Sessions/{sessionId}/Command/{command}")] + [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status204NoContent)] public ActionResult SendGeneralCommand( - [FromRoute] string? sessionId, - [FromRoute] string? command) + [FromRoute, Required] string? sessionId, + [FromRoute, Required] string? command) { var currentSession = RequestHelpers.GetSession(_sessionManager, _authContext, Request); @@ -269,9 +274,10 @@ namespace Jellyfin.Api.Controllers /// Full general command sent to session. /// A . [HttpPost("Sessions/{sessionId}/Command")] + [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status204NoContent)] public ActionResult SendFullGeneralCommand( - [FromRoute] string? sessionId, + [FromRoute, Required] string? sessionId, [FromBody, Required] GeneralCommand command) { var currentSession = RequestHelpers.GetSession(_sessionManager, _authContext, Request); @@ -302,11 +308,12 @@ namespace Jellyfin.Api.Controllers /// Message sent. /// A . [HttpPost("Sessions/{sessionId}/Message")] + [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status204NoContent)] public ActionResult SendMessageCommand( - [FromRoute] string? sessionId, - [FromQuery] string? text, - [FromQuery] string? header, + [FromRoute, Required] string? sessionId, + [FromQuery, Required] string? text, + [FromQuery, Required] string? header, [FromQuery] long? timeoutMs) { var command = new MessageCommand @@ -329,9 +336,10 @@ namespace Jellyfin.Api.Controllers /// User added to session. /// A . [HttpPost("Sessions/{sessionId}/User/{userId}")] + [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status204NoContent)] public ActionResult AddUserToSession( - [FromRoute] string? sessionId, + [FromRoute, Required] string? sessionId, [FromRoute] Guid userId) { _sessionManager.AddAdditionalUser(sessionId, userId); @@ -346,6 +354,7 @@ namespace Jellyfin.Api.Controllers /// User removed from session. /// A . [HttpDelete("Sessions/{sessionId}/User/{userId}")] + [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status204NoContent)] public ActionResult RemoveUserFromSession( [FromRoute] string? sessionId, @@ -367,9 +376,10 @@ namespace Jellyfin.Api.Controllers /// Capabilities posted. /// A . [HttpPost("Sessions/Capabilities")] + [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status204NoContent)] public ActionResult PostCapabilities( - [FromQuery] string? id, + [FromQuery, Required] string? id, [FromQuery] string? playableMediaTypes, [FromQuery] string? supportedCommands, [FromQuery] bool supportsMediaControl = false, @@ -400,9 +410,10 @@ namespace Jellyfin.Api.Controllers /// Capabilities updated. /// A . [HttpPost("Sessions/Capabilities/Full")] + [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status204NoContent)] public ActionResult PostFullCapabilities( - [FromQuery] string? id, + [FromQuery, Required] string? id, [FromBody, Required] ClientCapabilities capabilities) { if (string.IsNullOrWhiteSpace(id)) @@ -423,6 +434,7 @@ namespace Jellyfin.Api.Controllers /// Session reported to server. /// A . [HttpPost("Sessions/Viewing")] + [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status204NoContent)] public ActionResult ReportViewing( [FromQuery] string? sessionId, @@ -440,6 +452,7 @@ namespace Jellyfin.Api.Controllers /// Session end reported to server. /// A . [HttpPost("Sessions/Logout")] + [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status204NoContent)] public ActionResult ReportSessionEnded() { @@ -455,6 +468,7 @@ namespace Jellyfin.Api.Controllers /// Auth providers retrieved. /// An with the auth providers. [HttpGet("Auth/Providers")] + [Authorize(Policy = Policies.RequiresElevation)] [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult> GetAuthProviders() { @@ -468,6 +482,7 @@ namespace Jellyfin.Api.Controllers /// An with the password reset providers. [HttpGet("Auto/PasswordResetProviders")] [ProducesResponseType(StatusCodes.Status200OK)] + [Authorize(Policy = Policies.RequiresElevation)] public ActionResult> GetPasswordResetProviders() { return _userManager.GetPasswordResetProviders(); diff --git a/Jellyfin.Api/Controllers/SubtitleController.cs b/Jellyfin.Api/Controllers/SubtitleController.cs index d5633fba5..988acccc3 100644 --- a/Jellyfin.Api/Controllers/SubtitleController.cs +++ b/Jellyfin.Api/Controllers/SubtitleController.cs @@ -113,7 +113,7 @@ namespace Jellyfin.Api.Controllers [ProducesResponseType(StatusCodes.Status200OK)] public async Task>> SearchRemoteSubtitles( [FromRoute] Guid itemId, - [FromRoute] string? language, + [FromRoute, Required] string? language, [FromQuery] bool? isPerfectMatch) { var video = (Video)_libraryManager.GetItemById(itemId); @@ -133,7 +133,7 @@ namespace Jellyfin.Api.Controllers [ProducesResponseType(StatusCodes.Status204NoContent)] public async Task DownloadRemoteSubtitles( [FromRoute] Guid itemId, - [FromRoute] string? subtitleId) + [FromRoute, Required] string? subtitleId) { var video = (Video)_libraryManager.GetItemById(itemId); @@ -162,7 +162,7 @@ namespace Jellyfin.Api.Controllers [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status200OK)] [Produces(MediaTypeNames.Application.Octet)] - public async Task GetRemoteSubtitles([FromRoute] string? id) + public async Task GetRemoteSubtitles([FromRoute, Required] string? id) { var result = await _subtitleManager.GetRemoteSubtitles(id, CancellationToken.None).ConfigureAwait(false); diff --git a/Jellyfin.Api/Controllers/SyncPlayController.cs b/Jellyfin.Api/Controllers/SyncPlayController.cs index 2b1b95b1b..e16a10ba4 100644 --- a/Jellyfin.Api/Controllers/SyncPlayController.cs +++ b/Jellyfin.Api/Controllers/SyncPlayController.cs @@ -94,7 +94,7 @@ namespace Jellyfin.Api.Controllers /// /// Optional. Filter by item id. /// Groups returned. - /// An containing the available SyncPlay groups. + /// An containing the available SyncPlay groups. [HttpGet("List")] [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult> SyncPlayGetGroups([FromQuery] Guid? filterItemId) diff --git a/Jellyfin.Api/Controllers/SystemController.cs b/Jellyfin.Api/Controllers/SystemController.cs index 6f9a75e2f..08f1b421d 100644 --- a/Jellyfin.Api/Controllers/SystemController.cs +++ b/Jellyfin.Api/Controllers/SystemController.cs @@ -23,7 +23,6 @@ namespace Jellyfin.Api.Controllers /// /// The system controller. /// - [Route("System")] public class SystemController : BaseJellyfinApiController { private readonly IServerApplicationHost _appHost; @@ -60,8 +59,7 @@ namespace Jellyfin.Api.Controllers /// Information retrieved. /// A with info about the system. [HttpGet("Info")] - [Authorize(Policy = Policies.IgnoreSchedule)] - [Authorize(Policy = Policies.FirstTimeSetupOrElevated)] + [Authorize(Policy = Policies.IgnoreParentalControlOrFirstTimeSetup)] [ProducesResponseType(StatusCodes.Status200OK)] public async Task> GetSystemInfo() { @@ -99,8 +97,7 @@ namespace Jellyfin.Api.Controllers /// Server restarted. /// No content. Server restarted. [HttpPost("Restart")] - [Authorize(Policy = Policies.LocalAccessOnly)] - [Authorize(Policy = Policies.RequiresElevation)] + [Authorize(Policy = Policies.LocalAccessOrRequiresElevation)] [ProducesResponseType(StatusCodes.Status204NoContent)] public ActionResult RestartApplication() { diff --git a/Jellyfin.Api/Controllers/TimeSyncController.cs b/Jellyfin.Api/Controllers/TimeSyncController.cs index bbabcd6e6..2dc744e7c 100644 --- a/Jellyfin.Api/Controllers/TimeSyncController.cs +++ b/Jellyfin.Api/Controllers/TimeSyncController.cs @@ -9,7 +9,7 @@ namespace Jellyfin.Api.Controllers /// /// The time sync controller. /// - [Route("GetUtcTime")] + [Route("")] public class TimeSyncController : BaseJellyfinApiController { /// @@ -17,7 +17,7 @@ namespace Jellyfin.Api.Controllers /// /// Time returned. /// An to sync the client and server time. - [HttpGet] + [HttpGet("GetUtcTime")] [ProducesResponseType(statusCode: StatusCodes.Status200OK)] public ActionResult GetUtcTime() { diff --git a/Jellyfin.Api/Controllers/TvShowsController.cs b/Jellyfin.Api/Controllers/TvShowsController.cs index d4560dfa2..f463ab889 100644 --- a/Jellyfin.Api/Controllers/TvShowsController.cs +++ b/Jellyfin.Api/Controllers/TvShowsController.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.Globalization; using System.Linq; using Jellyfin.Api.Constants; @@ -68,7 +69,7 @@ namespace Jellyfin.Api.Controllers [HttpGet("NextUp")] [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult> GetNextUp( - [FromQuery] Guid? userId, + [FromQuery, Required] Guid? userId, [FromQuery] int? startIndex, [FromQuery] int? limit, [FromQuery] string? fields, @@ -126,7 +127,7 @@ namespace Jellyfin.Api.Controllers [HttpGet("Upcoming")] [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult> GetUpcomingEpisodes( - [FromQuery] Guid? userId, + [FromQuery, Required] Guid? userId, [FromQuery] int? startIndex, [FromQuery] int? limit, [FromQuery] string? fields, @@ -193,8 +194,8 @@ namespace Jellyfin.Api.Controllers [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult> GetEpisodes( - [FromRoute] string? seriesId, - [FromQuery] Guid? userId, + [FromRoute, Required] string? seriesId, + [FromQuery, Required] Guid? userId, [FromQuery] string? fields, [FromQuery] int? season, [FromQuery] string? seasonId, @@ -316,8 +317,8 @@ namespace Jellyfin.Api.Controllers [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult> GetSeasons( - [FromRoute] string? seriesId, - [FromQuery] Guid? userId, + [FromRoute, Required] string? seriesId, + [FromQuery, Required] Guid? userId, [FromQuery] string? fields, [FromQuery] bool? isSpecialSeason, [FromQuery] bool? isMissing, diff --git a/Jellyfin.Api/Controllers/UserController.cs b/Jellyfin.Api/Controllers/UserController.cs index 2ce5c7e56..d897f07b7 100644 --- a/Jellyfin.Api/Controllers/UserController.cs +++ b/Jellyfin.Api/Controllers/UserController.cs @@ -20,7 +20,6 @@ using MediaBrowser.Model.Users; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.ModelBinding; namespace Jellyfin.Api.Controllers { @@ -106,7 +105,7 @@ namespace Jellyfin.Api.Controllers /// User not found. /// An with information about the user or a if the user was not found. [HttpGet("{userId}")] - [Authorize(Policy = Policies.IgnoreSchedule)] + [Authorize(Policy = Policies.IgnoreParentalControl)] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult GetUserById([FromRoute] Guid userId) @@ -157,8 +156,8 @@ namespace Jellyfin.Api.Controllers [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task> AuthenticateUser( [FromRoute, Required] Guid userId, - [FromQuery, BindRequired] string? pw, - [FromQuery, BindRequired] string? password) + [FromQuery, Required] string? pw, + [FromQuery] string? password) { var user = _userManager.GetUserById(userId); @@ -190,7 +189,7 @@ namespace Jellyfin.Api.Controllers /// A containing an with information about the new session. [HttpPost("AuthenticateByName")] [ProducesResponseType(StatusCodes.Status200OK)] - public async Task> AuthenticateUserByName([FromBody, BindRequired] AuthenticateUserByName request) + public async Task> AuthenticateUserByName([FromBody, Required] AuthenticateUserByName request) { var auth = _authContext.GetAuthorizationInfo(Request); @@ -371,7 +370,7 @@ namespace Jellyfin.Api.Controllers /// User policy update forbidden. /// A indicating success or a or a on failure.. [HttpPost("{userId}/Policy")] - [Authorize(Policy = Policies.DefaultAuthorization)] + [Authorize(Policy = Policies.RequiresElevation)] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status403Forbidden)] diff --git a/Jellyfin.Api/Controllers/VideoAttachmentsController.cs b/Jellyfin.Api/Controllers/VideoAttachmentsController.cs index eef0a93cd..09a1c93e6 100644 --- a/Jellyfin.Api/Controllers/VideoAttachmentsController.cs +++ b/Jellyfin.Api/Controllers/VideoAttachmentsController.cs @@ -1,12 +1,11 @@ using System; +using System.ComponentModel.DataAnnotations; using System.Net.Mime; using System.Threading; using System.Threading.Tasks; -using Jellyfin.Api.Constants; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; -using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -16,7 +15,6 @@ namespace Jellyfin.Api.Controllers /// Attachments controller. /// [Route("Videos")] - [Authorize(Policy = Policies.DefaultAuthorization)] public class VideoAttachmentsController : BaseJellyfinApiController { private readonly ILibraryManager _libraryManager; @@ -49,9 +47,9 @@ namespace Jellyfin.Api.Controllers [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task> GetAttachment( - [FromRoute] Guid videoId, - [FromRoute] string? mediaSourceId, - [FromRoute] int index) + [FromRoute, Required] Guid videoId, + [FromRoute, Required] string mediaSourceId, + [FromRoute, Required] int index) { try { diff --git a/Jellyfin.Api/Controllers/VideosController.cs b/Jellyfin.Api/Controllers/VideosController.cs index ebe88a9c0..fe065c76f 100644 --- a/Jellyfin.Api/Controllers/VideosController.cs +++ b/Jellyfin.Api/Controllers/VideosController.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.Globalization; using System.Linq; using System.Net.Http; @@ -35,7 +36,6 @@ namespace Jellyfin.Api.Controllers /// /// The videos controller. /// - [Route("Videos")] public class VideosController : BaseJellyfinApiController { private readonly ILibraryManager _libraryManager; @@ -196,7 +196,7 @@ namespace Jellyfin.Api.Controllers [Authorize(Policy = Policies.RequiresElevation)] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status400BadRequest)] - public ActionResult MergeVersions([FromQuery] string? itemIds) + public ActionResult MergeVersions([FromQuery, Required] string? itemIds) { var items = RequestHelpers.Split(itemIds, ',', true) .Select(i => _libraryManager.GetItemById(i)) diff --git a/Jellyfin.Api/Controllers/YearsController.cs b/Jellyfin.Api/Controllers/YearsController.cs index d09b016a9..eb91ac23e 100644 --- a/Jellyfin.Api/Controllers/YearsController.cs +++ b/Jellyfin.Api/Controllers/YearsController.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; using Jellyfin.Data.Entities; @@ -9,6 +10,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Querying; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -17,6 +19,7 @@ namespace Jellyfin.Api.Controllers /// /// Years controller. /// + [Authorize(Policy = Policies.DefaultAuthorization)] public class YearsController : BaseJellyfinApiController { private readonly ILibraryManager _libraryManager; diff --git a/Jellyfin.Api/Models/StartupDtos/StartupConfigurationDto.cs b/Jellyfin.Api/Models/StartupDtos/StartupConfigurationDto.cs index a5f012245..66e797699 100644 --- a/Jellyfin.Api/Models/StartupDtos/StartupConfigurationDto.cs +++ b/Jellyfin.Api/Models/StartupDtos/StartupConfigurationDto.cs @@ -8,7 +8,7 @@ namespace Jellyfin.Api.Models.StartupDtos /// /// Gets or sets UI language culture. /// - public string? UICulture { get; set; } + public string UICulture { get; set; } = null!; /// /// Gets or sets the metadata country code. diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs index 6e91042df..586746430 100644 --- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs +++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs @@ -7,8 +7,11 @@ using Jellyfin.Api; using Jellyfin.Api.Auth; using Jellyfin.Api.Auth.DefaultAuthorizationPolicy; using Jellyfin.Api.Auth.DownloadPolicy; +using Jellyfin.Api.Auth.FirstTimeSetupOrDefaultPolicy; using Jellyfin.Api.Auth.FirstTimeSetupOrElevatedPolicy; -using Jellyfin.Api.Auth.IgnoreSchedulePolicy; +using Jellyfin.Api.Auth.IgnoreParentalControlOrFirstTimeSetupPolicy; +using Jellyfin.Api.Auth.IgnoreParentalControlPolicy; +using Jellyfin.Api.Auth.LocalAccessOrRequiresElevationPolicy; using Jellyfin.Api.Auth.LocalAccessPolicy; using Jellyfin.Api.Auth.RequiresElevationPolicy; using Jellyfin.Api.Constants; @@ -41,9 +44,12 @@ namespace Jellyfin.Server.Extensions { serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); return serviceCollection.AddAuthorizationCore(options => { @@ -61,6 +67,13 @@ namespace Jellyfin.Server.Extensions policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication); policy.AddRequirements(new DownloadRequirement()); }); + options.AddPolicy( + Policies.FirstTimeSetupOrDefault, + policy => + { + policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication); + policy.AddRequirements(new FirstTimeSetupOrDefaultRequirement()); + }); options.AddPolicy( Policies.FirstTimeSetupOrElevated, policy => @@ -69,11 +82,18 @@ namespace Jellyfin.Server.Extensions policy.AddRequirements(new FirstTimeSetupOrElevatedRequirement()); }); options.AddPolicy( - Policies.IgnoreSchedule, + Policies.IgnoreParentalControl, policy => { policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication); - policy.AddRequirements(new IgnoreScheduleRequirement()); + policy.AddRequirements(new IgnoreParentalControlRequirement()); + }); + options.AddPolicy( + Policies.IgnoreParentalControlOrFirstTimeSetup, + policy => + { + policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication); + policy.AddRequirements(new IgnoreParentalControlOrFirstTimeSetupRequirement()); }); options.AddPolicy( Policies.LocalAccessOnly, @@ -82,6 +102,13 @@ namespace Jellyfin.Server.Extensions policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication); policy.AddRequirements(new LocalAccessRequirement()); }); + options.AddPolicy( + Policies.LocalAccessOrRequiresElevation, + policy => + { + policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication); + policy.AddRequirements(new LocalAccessOrRequiresElevationRequirement()); + }); options.AddPolicy( Policies.RequiresElevation, policy => diff --git a/tests/Jellyfin.Api.Tests/Auth/IgnoreSchedulePolicy/IgnoreScheduleHandlerTests.cs b/tests/Jellyfin.Api.Tests/Auth/IgnoreSchedulePolicy/IgnoreScheduleHandlerTests.cs index b65d45aa0..7150c90bb 100644 --- a/tests/Jellyfin.Api.Tests/Auth/IgnoreSchedulePolicy/IgnoreScheduleHandlerTests.cs +++ b/tests/Jellyfin.Api.Tests/Auth/IgnoreSchedulePolicy/IgnoreScheduleHandlerTests.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.Threading.Tasks; using AutoFixture; using AutoFixture.AutoMoq; -using Jellyfin.Api.Auth.IgnoreSchedulePolicy; +using Jellyfin.Api.Auth.IgnoreParentalControlPolicy; using Jellyfin.Api.Constants; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; @@ -20,7 +20,7 @@ namespace Jellyfin.Api.Tests.Auth.IgnoreSchedulePolicy { private readonly Mock _configurationManagerMock; private readonly List _requirements; - private readonly IgnoreScheduleHandler _sut; + private readonly IgnoreParentalControlHandler _sut; private readonly Mock _userManagerMock; private readonly Mock _httpContextAccessor; @@ -33,11 +33,11 @@ namespace Jellyfin.Api.Tests.Auth.IgnoreSchedulePolicy { var fixture = new Fixture().Customize(new AutoMoqCustomization()); _configurationManagerMock = fixture.Freeze>(); - _requirements = new List { new IgnoreScheduleRequirement() }; + _requirements = new List { new IgnoreParentalControlRequirement() }; _userManagerMock = fixture.Freeze>(); _httpContextAccessor = fixture.Freeze>(); - _sut = fixture.Create(); + _sut = fixture.Create(); } [Theory] -- cgit v1.2.3 From c7e53bce2fa43ad38807a0589e1bc020237e49c6 Mon Sep 17 00:00:00 2001 From: Ionut Andrei Oanca Date: Sun, 15 Nov 2020 17:03:27 +0100 Subject: Patch data-races and minor changes in SyncPlay --- .../SyncPlay/GroupController.cs | 96 +++---- .../SyncPlay/SyncPlayManager.cs | 312 ++++++++++----------- Jellyfin.Api/Controllers/SyncPlayController.cs | 16 +- Jellyfin.Api/Controllers/TimeSyncController.cs | 14 +- .../SyncPlay/GroupStates/AbstractGroupState.cs | 14 +- .../SyncPlay/GroupStates/WaitingGroupState.cs | 46 +-- .../SyncPlay/IGroupStateContext.cs | 6 +- .../SyncPlay/ISyncPlayManager.cs | 16 -- .../SyncPlay/PlaybackRequests/PlayGroupRequest.cs | 4 +- .../SyncPlay/PlaybackRequests/QueueGroupRequest.cs | 4 +- .../RemoveFromPlaylistGroupRequest.cs | 5 +- .../SyncPlay/Queue/PlayQueueManager.cs | 22 +- MediaBrowser.Model/SyncPlay/GroupInfoDto.cs | 37 ++- MediaBrowser.Model/SyncPlay/GroupStateUpdate.cs | 19 +- MediaBrowser.Model/SyncPlay/GroupUpdate.cs | 29 +- MediaBrowser.Model/SyncPlay/JoinGroupRequest.cs | 13 +- MediaBrowser.Model/SyncPlay/NewGroupRequest.cs | 9 +- MediaBrowser.Model/SyncPlay/PlayQueueUpdate.cs | 45 +-- MediaBrowser.Model/SyncPlay/SendCommand.cs | 36 ++- MediaBrowser.Model/SyncPlay/UtcTimeResponse.cs | 21 +- 20 files changed, 381 insertions(+), 383 deletions(-) (limited to 'Jellyfin.Api/Controllers/TimeSyncController.cs') diff --git a/Emby.Server.Implementations/SyncPlay/GroupController.cs b/Emby.Server.Implementations/SyncPlay/GroupController.cs index a0d951b3e..48596bb42 100644 --- a/Emby.Server.Implementations/SyncPlay/GroupController.cs +++ b/Emby.Server.Implementations/SyncPlay/GroupController.cs @@ -45,11 +45,6 @@ namespace Emby.Server.Implementations.SyncPlay /// private readonly ILibraryManager _libraryManager; - /// - /// The SyncPlay manager. - /// - private readonly ISyncPlayManager _syncPlayManager; - /// /// Internal group state. /// @@ -63,19 +58,16 @@ namespace Emby.Server.Implementations.SyncPlay /// The user manager. /// The session manager. /// The library manager. - /// The SyncPlay manager. public GroupController( ILogger logger, IUserManager userManager, ISessionManager sessionManager, - ILibraryManager libraryManager, - ISyncPlayManager syncPlayManager) + ILibraryManager libraryManager) { _logger = logger; _userManager = userManager; _sessionManager = sessionManager; _libraryManager = libraryManager; - _syncPlayManager = syncPlayManager; _state = new IdleGroupState(_logger); } @@ -168,7 +160,7 @@ namespace Emby.Server.Implementations.SyncPlay /// /// The current session. /// The filtering type. - /// The array of sessions matching the filter. + /// The list of sessions matching the filter. private IEnumerable FilterSessions(SessionInfo from, SyncPlayBroadcastType type) { return type switch @@ -209,7 +201,7 @@ namespace Emby.Server.Implementations.SyncPlay /// The user. /// The queue. /// true if the user can access all the items in the queue, false otherwise. - private bool HasAccessToQueue(User user, IEnumerable queue) + private bool HasAccessToQueue(User user, IReadOnlyList queue) { // Check if queue is empty. if (!queue?.Any() ?? true) @@ -234,7 +226,7 @@ namespace Emby.Server.Implementations.SyncPlay return true; } - private bool AllUsersHaveAccessToQueue(IEnumerable queue) + private bool AllUsersHaveAccessToQueue(IReadOnlyList queue) { // Check if queue is empty. if (!queue?.Any() ?? true) @@ -262,7 +254,6 @@ namespace Emby.Server.Implementations.SyncPlay { GroupName = request.GroupName; AddSession(session); - _syncPlayManager.AddSessionToGroup(session, this); var sessionIsPlayingAnItem = session.FullNowPlayingItem != null; @@ -270,7 +261,7 @@ namespace Emby.Server.Implementations.SyncPlay if (sessionIsPlayingAnItem) { - var playlist = session.NowPlayingQueue.Select(item => item.Id); + var playlist = session.NowPlayingQueue.Select(item => item.Id).ToList(); PlayQueue.Reset(); PlayQueue.SetPlaylist(playlist); PlayQueue.SetPlayingItemById(session.FullNowPlayingItem.Id); @@ -290,14 +281,13 @@ namespace Emby.Server.Implementations.SyncPlay _state.SessionJoined(this, _state.Type, session, cancellationToken); - _logger.LogInformation("InitGroup: {0} created group {1}.", session.Id, GroupId.ToString()); + _logger.LogInformation("InitGroup: {SessionId} created group {GroupId}.", session.Id, GroupId.ToString()); } /// public void SessionJoin(SessionInfo session, JoinGroupRequest request, CancellationToken cancellationToken) { AddSession(session); - _syncPlayManager.AddSessionToGroup(session, this); var updateSession = NewSyncPlayGroupUpdate(GroupUpdateType.GroupJoined, GetInfo()); SendGroupUpdate(session, SyncPlayBroadcastType.CurrentSession, updateSession, cancellationToken); @@ -307,7 +297,7 @@ namespace Emby.Server.Implementations.SyncPlay _state.SessionJoined(this, _state.Type, session, cancellationToken); - _logger.LogInformation("SessionJoin: {0} joined group {1}.", session.Id, GroupId.ToString()); + _logger.LogInformation("SessionJoin: {SessionId} joined group {GroupId}.", session.Id, GroupId.ToString()); } /// @@ -321,7 +311,7 @@ namespace Emby.Server.Implementations.SyncPlay _state.SessionJoined(this, _state.Type, session, cancellationToken); - _logger.LogInformation("SessionRestore: {0} re-joined group {1}.", session.Id, GroupId.ToString()); + _logger.LogInformation("SessionRestore: {SessionId} re-joined group {GroupId}.", session.Id, GroupId.ToString()); } /// @@ -330,7 +320,6 @@ namespace Emby.Server.Implementations.SyncPlay _state.SessionLeaving(this, _state.Type, session, cancellationToken); RemoveSession(session); - _syncPlayManager.RemoveSessionFromGroup(session, this); var updateSession = NewSyncPlayGroupUpdate(GroupUpdateType.GroupLeft, GroupId.ToString()); SendGroupUpdate(session, SyncPlayBroadcastType.CurrentSession, updateSession, cancellationToken); @@ -338,7 +327,7 @@ namespace Emby.Server.Implementations.SyncPlay var updateOthers = NewSyncPlayGroupUpdate(GroupUpdateType.UserLeft, session.UserName); SendGroupUpdate(session, SyncPlayBroadcastType.AllExceptCurrentSession, updateOthers, cancellationToken); - _logger.LogInformation("SessionLeave: {0} left group {1}.", session.Id, GroupId.ToString()); + _logger.LogInformation("SessionLeave: {SessionId} left group {GroupId}.", session.Id, GroupId.ToString()); } /// @@ -347,27 +336,21 @@ namespace Emby.Server.Implementations.SyncPlay // The server's job is to maintain a consistent state for clients to reference // and notify clients of state changes. The actual syncing of media playback // happens client side. Clients are aware of the server's time and use it to sync. - _logger.LogInformation("HandleRequest: {0} requested {1}, group {2} in {3} state.", session.Id, request.Type, GroupId.ToString(), _state.Type); + _logger.LogInformation("HandleRequest: {SessionId} requested {RequestType}, group {GroupId} is {StateType}.", session.Id, request.Type, GroupId.ToString(), _state.Type); request.Apply(this, _state, session, cancellationToken); } /// public GroupInfoDto GetInfo() { - return new GroupInfoDto() - { - GroupId = GroupId.ToString(), - GroupName = GroupName, - State = _state.Type, - Participants = Participants.Values.Select(session => session.Session.UserName).Distinct().ToList(), - LastUpdatedAt = DateTime.UtcNow - }; + var participants = Participants.Values.Select(session => session.Session.UserName).Distinct().ToList(); + return new GroupInfoDto(GroupId, GroupName, _state.Type, participants, DateTime.UtcNow); } /// public bool HasAccessToPlayQueue(User user) { - var items = PlayQueue.GetPlaylist().Select(item => item.ItemId); + var items = PlayQueue.GetPlaylist().Select(item => item.ItemId).ToList(); return HasAccessToQueue(user, items); } @@ -383,7 +366,7 @@ namespace Emby.Server.Implementations.SyncPlay /// public void SetState(IGroupState state) { - _logger.LogInformation("SetState: {0} switching from {1} to {2}.", GroupId.ToString(), _state.Type, state.Type); + _logger.LogInformation("SetState: {GroupId} switching from {FromStateType} to {ToStateType}.", GroupId.ToString(), _state.Type, state.Type); this._state = state; } @@ -418,26 +401,19 @@ namespace Emby.Server.Implementations.SyncPlay /// public SendCommand NewSyncPlayCommand(SendCommandType type) { - return new SendCommand() - { - GroupId = GroupId.ToString(), - PlaylistItemId = PlayQueue.GetPlayingItemPlaylistId(), - PositionTicks = PositionTicks, - Command = type, - When = LastActivity, - EmittedAt = DateTime.UtcNow - }; + return new SendCommand( + GroupId, + PlayQueue.GetPlayingItemPlaylistId(), + LastActivity, + type, + PositionTicks, + DateTime.UtcNow); } /// public GroupUpdate NewSyncPlayGroupUpdate(GroupUpdateType type, T data) { - return new GroupUpdate() - { - GroupId = GroupId.ToString(), - Type = type, - Data = data - }; + return new GroupUpdate(GroupId, type, data); } /// @@ -501,10 +477,10 @@ namespace Emby.Server.Implementations.SyncPlay } /// - public bool SetPlayQueue(IEnumerable playQueue, int playingItemPosition, long startPositionTicks) + public bool SetPlayQueue(IReadOnlyList playQueue, int playingItemPosition, long startPositionTicks) { // Ignore on empty queue or invalid item position. - if (!playQueue.Any() || playingItemPosition >= playQueue.Count() || playingItemPosition < 0) + if (playQueue.Count == 0 || playingItemPosition >= playQueue.Count || playingItemPosition < 0) { return false; } @@ -547,7 +523,7 @@ namespace Emby.Server.Implementations.SyncPlay } /// - public bool RemoveFromPlayQueue(IEnumerable playlistItemIds) + public bool RemoveFromPlayQueue(IReadOnlyList playlistItemIds) { var playingItemRemoved = PlayQueue.RemoveFromPlaylist(playlistItemIds); if (playingItemRemoved) @@ -576,10 +552,10 @@ namespace Emby.Server.Implementations.SyncPlay } /// - public bool AddToPlayQueue(IEnumerable newItems, GroupQueueMode mode) + public bool AddToPlayQueue(IReadOnlyList newItems, GroupQueueMode mode) { // Ignore on empty list. - if (!newItems.Any()) + if (newItems.Count == 0) { return false; } @@ -673,16 +649,14 @@ namespace Emby.Server.Implementations.SyncPlay startPositionTicks += Math.Max(elapsedTime.Ticks, 0); } - return new PlayQueueUpdate() - { - Reason = reason, - LastUpdate = PlayQueue.LastChange, - Playlist = PlayQueue.GetPlaylist(), - PlayingItemIndex = PlayQueue.PlayingItemIndex, - StartPositionTicks = startPositionTicks, - ShuffleMode = PlayQueue.ShuffleMode, - RepeatMode = PlayQueue.RepeatMode - }; + return new PlayQueueUpdate( + reason, + PlayQueue.LastChange, + PlayQueue.GetPlaylist(), + PlayQueue.PlayingItemIndex, + startPositionTicks, + PlayQueue.ShuffleMode, + PlayQueue.RepeatMode); } } } diff --git a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs index 178536631..ee75580cc 100644 --- a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs +++ b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs @@ -72,19 +72,9 @@ namespace Emby.Server.Implementations.SyncPlay _userManager = userManager; _sessionManager = sessionManager; _libraryManager = libraryManager; - _sessionManager.SessionStarted += OnSessionManagerSessionStarted; - _sessionManager.SessionEnded += OnSessionManagerSessionEnded; - _sessionManager.PlaybackStart += OnSessionManagerPlaybackStart; - _sessionManager.PlaybackStopped += OnSessionManagerPlaybackStopped; } - /// - /// Gets all groups. - /// - /// All groups. - public IEnumerable Groups => _groups.Values; - /// public void Dispose() { @@ -92,127 +82,6 @@ namespace Emby.Server.Implementations.SyncPlay GC.SuppressFinalize(this); } - /// - /// Releases unmanaged and optionally managed resources. - /// - /// true to release both managed and unmanaged resources; false to release only unmanaged resources. - protected virtual void Dispose(bool disposing) - { - if (_disposed) - { - return; - } - - _sessionManager.SessionStarted -= OnSessionManagerSessionStarted; - _sessionManager.SessionEnded -= OnSessionManagerSessionEnded; - _sessionManager.PlaybackStart -= OnSessionManagerPlaybackStart; - _sessionManager.PlaybackStopped -= OnSessionManagerPlaybackStopped; - - _disposed = true; - } - - private void OnSessionManagerSessionStarted(object sender, SessionEventArgs e) - { - var session = e.SessionInfo; - if (!IsSessionInGroup(session)) - { - return; - } - - var groupId = GetSessionGroup(session) ?? Guid.Empty; - var request = new JoinGroupRequest() - { - GroupId = groupId - }; - JoinGroup(session, groupId, request, CancellationToken.None); - } - - private void OnSessionManagerSessionEnded(object sender, SessionEventArgs e) - { - var session = e.SessionInfo; - if (!IsSessionInGroup(session)) - { - return; - } - - // TODO: probably remove this event, not used at the moment. - } - - private void OnSessionManagerPlaybackStart(object sender, PlaybackProgressEventArgs e) - { - var session = e.Session; - if (!IsSessionInGroup(session)) - { - return; - } - - // TODO: probably remove this event, not used at the moment. - } - - private void OnSessionManagerPlaybackStopped(object sender, PlaybackStopEventArgs e) - { - var session = e.Session; - if (!IsSessionInGroup(session)) - { - return; - } - - // TODO: probably remove this event, not used at the moment. - } - - private bool IsRequestValid(SessionInfo session, GroupRequestType requestType, T request, bool checkRequest = true) - { - if (session == null || (request == null && checkRequest)) - { - return false; - } - - var user = _userManager.GetUserById(session.UserId); - - if (user.SyncPlayAccess == SyncPlayAccess.None) - { - _logger.LogWarning("IsRequestValid: {0} does not have access to SyncPlay. Requested {1}.", session.Id, requestType); - - var error = new GroupUpdate() - { - // TODO: rename to a more generic error. Next PR will fix this. - Type = GroupUpdateType.JoinGroupDenied - }; - _sessionManager.SendSyncPlayGroupUpdate(session, error, CancellationToken.None); - return false; - } - - if (requestType.Equals(GroupRequestType.NewGroup) && user.SyncPlayAccess != SyncPlayAccess.CreateAndJoinGroups) - { - _logger.LogWarning("IsRequestValid: {0} does not have permission to create groups.", session.Id); - - var error = new GroupUpdate - { - Type = GroupUpdateType.CreateGroupDenied - }; - _sessionManager.SendSyncPlayGroupUpdate(session, error, CancellationToken.None); - return false; - } - - return true; - } - - private bool IsRequestValid(SessionInfo session, GroupRequestType requestType) - { - return IsRequestValid(session, requestType, session, false); - } - - private bool IsSessionInGroup(SessionInfo session) - { - return _sessionToGroupMap.ContainsKey(session.Id); - } - - private Guid? GetSessionGroup(SessionInfo session) - { - _sessionToGroupMap.TryGetValue(session.Id, out var group); - return group?.GroupId; - } - /// public void NewGroup(SessionInfo session, NewGroupRequest request, CancellationToken cancellationToken) { @@ -229,9 +98,10 @@ namespace Emby.Server.Implementations.SyncPlay LeaveGroup(session, cancellationToken); } - var group = new GroupController(_logger, _userManager, _sessionManager, _libraryManager, this); + var group = new GroupController(_logger, _userManager, _sessionManager, _libraryManager); _groups[group.GroupId] = group; + AddSessionToGroup(session, group); group.CreateGroup(session, request, cancellationToken); } } @@ -253,25 +123,18 @@ namespace Emby.Server.Implementations.SyncPlay if (group == null) { - _logger.LogWarning("JoinGroup: {0} tried to join group {0} that does not exist.", session.Id, groupId); + _logger.LogWarning("JoinGroup: {SessionId} tried to join group {GroupId} that does not exist.", session.Id, groupId); - var error = new GroupUpdate() - { - Type = GroupUpdateType.GroupDoesNotExist - }; + var error = new GroupUpdate(Guid.Empty, GroupUpdateType.GroupDoesNotExist, string.Empty); _sessionManager.SendSyncPlayGroupUpdate(session, error, CancellationToken.None); return; } if (!group.HasAccessToPlayQueue(user)) { - _logger.LogWarning("JoinGroup: {0} does not have access to some content from the playing queue of group {1}.", session.Id, group.GroupId.ToString()); + _logger.LogWarning("JoinGroup: {SessionId} does not have access to some content from the playing queue of group {GroupId}.", session.Id, group.GroupId.ToString()); - var error = new GroupUpdate() - { - GroupId = group.GroupId.ToString(), - Type = GroupUpdateType.LibraryAccessDenied - }; + var error = new GroupUpdate(group.GroupId, GroupUpdateType.LibraryAccessDenied, string.Empty); _sessionManager.SendSyncPlayGroupUpdate(session, error, CancellationToken.None); return; } @@ -287,6 +150,7 @@ namespace Emby.Server.Implementations.SyncPlay LeaveGroup(session, cancellationToken); } + AddSessionToGroup(session, group); group.SessionJoin(session, request, cancellationToken); } } @@ -307,21 +171,19 @@ namespace Emby.Server.Implementations.SyncPlay if (group == null) { - _logger.LogWarning("LeaveGroup: {0} does not belong to any group.", session.Id); + _logger.LogWarning("LeaveGroup: {SessionId} does not belong to any group.", session.Id); - var error = new GroupUpdate() - { - Type = GroupUpdateType.NotInGroup - }; + var error = new GroupUpdate(Guid.Empty, GroupUpdateType.NotInGroup, string.Empty); _sessionManager.SendSyncPlayGroupUpdate(session, error, CancellationToken.None); return; } + RemoveSessionFromGroup(session, group); group.SessionLeave(session, cancellationToken); if (group.IsGroupEmpty()) { - _logger.LogInformation("LeaveGroup: removing empty group {0}.", group.GroupId); + _logger.LogInformation("LeaveGroup: removing empty group {GroupId}.", group.GroupId); _groups.Remove(group.GroupId, out _); } } @@ -338,11 +200,14 @@ namespace Emby.Server.Implementations.SyncPlay var user = _userManager.GetUserById(session.UserId); - return _groups - .Values - .Where(group => group.HasAccessToPlayQueue(user)) - .Select(group => group.GetInfo()) - .ToList(); + lock (_groupsLock) + { + return _groups + .Values + .Where(group => group.HasAccessToPlayQueue(user)) + .Select(group => group.GetInfo()) + .ToList(); + } } /// @@ -360,12 +225,9 @@ namespace Emby.Server.Implementations.SyncPlay if (group == null) { - _logger.LogWarning("HandleRequest: {0} does not belong to any group.", session.Id); + _logger.LogWarning("HandleRequest: {SessionId} does not belong to any group.", session.Id); - var error = new GroupUpdate() - { - Type = GroupUpdateType.NotInGroup - }; + var error = new GroupUpdate(Guid.Empty, GroupUpdateType.NotInGroup, string.Empty); _sessionManager.SendSyncPlayGroupUpdate(session, error, CancellationToken.None); return; } @@ -374,8 +236,74 @@ namespace Emby.Server.Implementations.SyncPlay } } - /// - public void AddSessionToGroup(SessionInfo session, IGroupController group) + /// + /// Releases unmanaged and optionally managed resources. + /// + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + protected virtual void Dispose(bool disposing) + { + if (_disposed) + { + return; + } + + _sessionManager.SessionStarted -= OnSessionManagerSessionStarted; + _disposed = true; + } + + private void OnSessionManagerSessionStarted(object sender, SessionEventArgs e) + { + var session = e.SessionInfo; + lock (_groupsLock) + { + if (!IsSessionInGroup(session)) + { + return; + } + + var groupId = GetSessionGroup(session); + var request = new JoinGroupRequest(groupId); + JoinGroup(session, groupId, request, CancellationToken.None); + } + } + + /// + /// Checks if a given session has joined a group. + /// + /// + /// Not thread-safe, call only under groups-lock. + /// + /// The session. + /// true if the session has joined a group, false otherwise. + private bool IsSessionInGroup(SessionInfo session) + { + return _sessionToGroupMap.ContainsKey(session.Id); + } + + /// + /// Gets the group joined by the given session, if any. + /// + /// + /// Not thread-safe, call only under groups-lock. + /// + /// The session. + /// The group identifier if the session has joined a group, an empty identifier otherwise. + private Guid GetSessionGroup(SessionInfo session) + { + _sessionToGroupMap.TryGetValue(session.Id, out var group); + return group?.GroupId ?? Guid.Empty; + } + + /// + /// Maps a session to a group. + /// + /// + /// Not thread-safe, call only under groups-lock. + /// + /// The session. + /// The group. + /// Thrown when the user is in another group already. + private void AddSessionToGroup(SessionInfo session, IGroupController group) { if (session == null) { @@ -390,8 +318,16 @@ namespace Emby.Server.Implementations.SyncPlay _sessionToGroupMap[session.Id] = group ?? throw new InvalidOperationException("Group is null!"); } - /// - public void RemoveSessionFromGroup(SessionInfo session, IGroupController group) + /// + /// Unmaps a session from a group. + /// + /// + /// Not thread-safe, call only under groups-lock. + /// + /// The session. + /// The group. + /// Thrown when the user is not found in the specified group. + private void RemoveSessionFromGroup(SessionInfo session, IGroupController group) { if (session == null) { @@ -414,5 +350,55 @@ namespace Emby.Server.Implementations.SyncPlay throw new InvalidOperationException("Session was in wrong group!"); } } + + /// + /// Checks if a given session is allowed to make a given request. + /// + /// The session. + /// The request type. + /// The request. + /// Whether to check if request is null. + /// true if the request is valid, false otherwise. Will return false also when session is null. + private bool IsRequestValid(SessionInfo session, GroupRequestType requestType, T request, bool checkRequest = true) + { + if (session == null || (request == null && checkRequest)) + { + return false; + } + + var user = _userManager.GetUserById(session.UserId); + + if (user.SyncPlayAccess == SyncPlayAccess.None) + { + _logger.LogWarning("IsRequestValid: {SessionId} does not have access to SyncPlay. Requested {RequestType}.", session.Id, requestType); + + // TODO: rename to a more generic error. Next PR will fix this. + var error = new GroupUpdate(Guid.Empty, GroupUpdateType.JoinGroupDenied, string.Empty); + _sessionManager.SendSyncPlayGroupUpdate(session, error, CancellationToken.None); + return false; + } + + if (requestType.Equals(GroupRequestType.NewGroup) && user.SyncPlayAccess != SyncPlayAccess.CreateAndJoinGroups) + { + _logger.LogWarning("IsRequestValid: {SessionId} does not have permission to create groups.", session.Id); + + var error = new GroupUpdate(Guid.Empty, GroupUpdateType.CreateGroupDenied, string.Empty); + _sessionManager.SendSyncPlayGroupUpdate(session, error, CancellationToken.None); + return false; + } + + return true; + } + + /// + /// Checks if a given session is allowed to make a given type of request. + /// + /// The session. + /// The request type. + /// true if the request is valid, false otherwise. Will return false also when session is null. + private bool IsRequestValid(SessionInfo session, GroupRequestType requestType) + { + return IsRequestValid(session, requestType, session, false); + } } } diff --git a/Jellyfin.Api/Controllers/SyncPlayController.cs b/Jellyfin.Api/Controllers/SyncPlayController.cs index 9085a71c8..8e9314b4a 100644 --- a/Jellyfin.Api/Controllers/SyncPlayController.cs +++ b/Jellyfin.Api/Controllers/SyncPlayController.cs @@ -53,10 +53,7 @@ namespace Jellyfin.Api.Controllers [FromQuery, Required] string groupName) { var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); - var newGroupRequest = new NewGroupRequest() - { - GroupName = groupName - }; + var newGroupRequest = new NewGroupRequest(groupName); _syncPlayManager.NewGroup(currentSession, newGroupRequest, CancellationToken.None); return NoContent(); } @@ -73,10 +70,7 @@ namespace Jellyfin.Api.Controllers [FromQuery, Required] Guid groupId) { var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); - var joinRequest = new JoinGroupRequest() - { - GroupId = groupId - }; + var joinRequest = new JoinGroupRequest(groupId); _syncPlayManager.JoinGroup(currentSession, groupId, joinRequest, CancellationToken.None); return NoContent(); } @@ -185,18 +179,18 @@ namespace Jellyfin.Api.Controllers /// /// Request to queue items to the playlist of a SyncPlay group. /// - /// The items to add. + /// The items to add. /// The mode in which to enqueue the items. /// Queue update request sent to all group members. /// A indicating success. [HttpPost("Queue")] [ProducesResponseType(StatusCodes.Status204NoContent)] public ActionResult SyncPlayQueue( - [FromQuery, Required] Guid[] items, + [FromQuery, Required] Guid[] itemIds, [FromQuery, Required] GroupQueueMode mode) { var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); - var syncPlayRequest = new QueueGroupRequest(items, mode); + var syncPlayRequest = new QueueGroupRequest(itemIds, mode); _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); return NoContent(); } diff --git a/Jellyfin.Api/Controllers/TimeSyncController.cs b/Jellyfin.Api/Controllers/TimeSyncController.cs index 2dc744e7c..5de560417 100644 --- a/Jellyfin.Api/Controllers/TimeSyncController.cs +++ b/Jellyfin.Api/Controllers/TimeSyncController.cs @@ -13,7 +13,7 @@ namespace Jellyfin.Api.Controllers public class TimeSyncController : BaseJellyfinApiController { /// - /// Gets the current utc time. + /// Gets the current UTC time. /// /// Time returned. /// An to sync the client and server time. @@ -22,18 +22,14 @@ namespace Jellyfin.Api.Controllers public ActionResult GetUtcTime() { // Important to keep the following line at the beginning - var requestReceptionTime = DateTime.UtcNow.ToUniversalTime().ToString("o", DateTimeFormatInfo.InvariantInfo); + var requestReceptionTime = DateTime.UtcNow.ToUniversalTime(); - var response = new UtcTimeResponse(); - response.RequestReceptionTime = requestReceptionTime; - - // Important to keep the following two lines at the end - var responseTransmissionTime = DateTime.UtcNow.ToUniversalTime().ToString("o", DateTimeFormatInfo.InvariantInfo); - response.ResponseTransmissionTime = responseTransmissionTime; + // Important to keep the following line at the end + var responseTransmissionTime = DateTime.UtcNow.ToUniversalTime(); // Implementing NTP on such a high level results in this useless // information being sent. On the other hand it enables future additions. - return response; + return new UtcTimeResponse(requestReceptionTime, responseTransmissionTime); } } } diff --git a/MediaBrowser.Controller/SyncPlay/GroupStates/AbstractGroupState.cs b/MediaBrowser.Controller/SyncPlay/GroupStates/AbstractGroupState.cs index bc2e22380..e5da0ef40 100644 --- a/MediaBrowser.Controller/SyncPlay/GroupStates/AbstractGroupState.cs +++ b/MediaBrowser.Controller/SyncPlay/GroupStates/AbstractGroupState.cs @@ -68,7 +68,7 @@ namespace MediaBrowser.Controller.SyncPlay.GroupStates if (playingItemRemoved && !context.PlayQueue.IsItemPlaying()) { - Logger.LogDebug("HandleRequest: {0} in group {1}, play queue is empty.", request.Type, context.GroupId.ToString()); + Logger.LogDebug("HandleRequest: {RequestType} in group {GroupId}, play queue is empty.", request.Type, context.GroupId.ToString()); IGroupState idleState = new IdleGroupState(Logger); context.SetState(idleState); @@ -84,7 +84,7 @@ namespace MediaBrowser.Controller.SyncPlay.GroupStates if (!result) { - Logger.LogError("HandleRequest: {0} in group {1}, unable to move item in play queue.", request.Type, context.GroupId.ToString()); + Logger.LogError("HandleRequest: {RequestType} in group {GroupId}, unable to move item in play queue.", request.Type, context.GroupId.ToString()); return; } @@ -100,7 +100,7 @@ namespace MediaBrowser.Controller.SyncPlay.GroupStates if (!result) { - Logger.LogError("HandleRequest: {0} in group {1}, unable to add items to play queue.", request.Type, context.GroupId.ToString()); + Logger.LogError("HandleRequest: {RequestType} in group {GroupId}, unable to add items to play queue.", request.Type, context.GroupId.ToString()); return; } @@ -203,18 +203,14 @@ namespace MediaBrowser.Controller.SyncPlay.GroupStates protected void SendGroupStateUpdate(IGroupStateContext context, IGroupPlaybackRequest reason, SessionInfo session, CancellationToken cancellationToken) { // Notify relevant state change event. - var stateUpdate = new GroupStateUpdate() - { - State = Type, - Reason = reason.Type - }; + var stateUpdate = new GroupStateUpdate(Type, reason.Type); var update = context.NewSyncPlayGroupUpdate(GroupUpdateType.StateUpdate, stateUpdate); context.SendGroupUpdate(session, SyncPlayBroadcastType.AllGroup, update, cancellationToken); } private void UnhandledRequest(IGroupPlaybackRequest request) { - Logger.LogWarning("HandleRequest: unhandled {0} request for {1} state.", request.Type, Type); + Logger.LogWarning("HandleRequest: unhandled {RequestType} request in {StateType} state.", request.Type, Type); } } } diff --git a/MediaBrowser.Controller/SyncPlay/GroupStates/WaitingGroupState.cs b/MediaBrowser.Controller/SyncPlay/GroupStates/WaitingGroupState.cs index 78318dd94..e33e711fb 100644 --- a/MediaBrowser.Controller/SyncPlay/GroupStates/WaitingGroupState.cs +++ b/MediaBrowser.Controller/SyncPlay/GroupStates/WaitingGroupState.cs @@ -103,7 +103,7 @@ namespace MediaBrowser.Controller.SyncPlay.GroupStates var unpauseRequest = new UnpauseGroupRequest(); playingState.HandleRequest(context, Type, unpauseRequest, session, cancellationToken); - Logger.LogDebug("SessionLeaving: {0} left the group {1}, notifying others to resume.", session.Id, context.GroupId.ToString()); + Logger.LogDebug("SessionLeaving: {SessionId} left group {GroupId}, notifying others to resume.", session.Id, context.GroupId.ToString()); } else { @@ -111,7 +111,7 @@ namespace MediaBrowser.Controller.SyncPlay.GroupStates var pausedState = new PausedGroupState(Logger); context.SetState(pausedState); - Logger.LogDebug("SessionLeaving: {0} left the group {1}, returning to previous state.", session.Id, context.GroupId.ToString()); + Logger.LogDebug("SessionLeaving: {SessionId} left group {GroupId}, returning to previous state.", session.Id, context.GroupId.ToString()); } } } @@ -131,7 +131,7 @@ namespace MediaBrowser.Controller.SyncPlay.GroupStates var setQueueStatus = context.SetPlayQueue(request.PlayingQueue, request.PlayingItemPosition, request.StartPositionTicks); if (!setQueueStatus) { - Logger.LogError("HandleRequest: {0} in group {1}, unable to set playing queue.", request.Type, context.GroupId.ToString()); + Logger.LogError("HandleRequest: {RequestType} in group {GroupId}, unable to set playing queue.", request.Type, context.GroupId.ToString()); // Ignore request and return to previous state. IGroupState newState = prevState switch { @@ -151,7 +151,7 @@ namespace MediaBrowser.Controller.SyncPlay.GroupStates // Reset status of sessions and await for all Ready events. context.SetAllBuffering(true); - Logger.LogDebug("HandleRequest: {0} in group {1}, {2} set a new play queue.", request.Type, context.GroupId.ToString(), session.Id); + Logger.LogDebug("HandleRequest: {RequestType} in group {GroupId}, {SessionId} set a new play queue.", request.Type, context.GroupId.ToString(), session.Id); } /// @@ -188,7 +188,7 @@ namespace MediaBrowser.Controller.SyncPlay.GroupStates context.SetState(newState); - Logger.LogDebug("HandleRequest: {0} in group {1}, unable to change current playing item.", request.Type, context.GroupId.ToString()); + Logger.LogDebug("HandleRequest: {RequestType} in group {GroupId}, unable to change current playing item.", request.Type, context.GroupId.ToString()); } } @@ -214,13 +214,13 @@ namespace MediaBrowser.Controller.SyncPlay.GroupStates // Reset status of sessions and await for all Ready events. context.SetAllBuffering(true); - Logger.LogDebug("HandleRequest: {0} in group {1}, waiting for all ready events.", request.Type, context.GroupId.ToString()); + Logger.LogDebug("HandleRequest: {RequestType} in group {GroupId}, waiting for all ready events.", request.Type, context.GroupId.ToString()); } else { if (ResumePlaying) { - Logger.LogDebug("HandleRequest: {0} in group {1}, ignoring sessions that are not ready and forcing the playback to start.", request.Type, context.GroupId.ToString()); + Logger.LogDebug("HandleRequest: {RequestType} in group {GroupId}, ignoring sessions that are not ready and forcing the playback to start.", request.Type, context.GroupId.ToString()); // An Unpause request is forcing the playback to start, ignoring sessions that are not ready. context.SetAllBuffering(false); @@ -326,7 +326,7 @@ namespace MediaBrowser.Controller.SyncPlay.GroupStates // Make sure the client is playing the correct item. if (!request.PlaylistItemId.Equals(context.PlayQueue.GetPlayingItemPlaylistId(), StringComparison.OrdinalIgnoreCase)) { - Logger.LogDebug("HandleRequest: {0} in group {1}, {2} has wrong playlist item.", request.Type, context.GroupId.ToString(), session.Id); + Logger.LogDebug("HandleRequest: {RequestType} in group {GroupId}, {SessionId} has wrong playlist item.", request.Type, context.GroupId.ToString(), session.Id); var playQueueUpdate = context.GetPlayQueueUpdate(PlayQueueUpdateReason.SetCurrentItem); var updateSession = context.NewSyncPlayGroupUpdate(GroupUpdateType.PlayQueue, playQueueUpdate); @@ -400,7 +400,7 @@ namespace MediaBrowser.Controller.SyncPlay.GroupStates // Make sure the client is playing the correct item. if (!request.PlaylistItemId.Equals(context.PlayQueue.GetPlayingItemPlaylistId(), StringComparison.OrdinalIgnoreCase)) { - Logger.LogDebug("HandleRequest: {0} in group {1}, {2} has wrong playlist item.", request.Type, context.GroupId.ToString(), session.Id); + Logger.LogDebug("HandleRequest: {RequestType} in group {GroupId}, {SessionId} has wrong playlist item.", request.Type, context.GroupId.ToString(), session.Id); var playQueueUpdate = context.GetPlayQueueUpdate(PlayQueueUpdateReason.SetCurrentItem); var update = context.NewSyncPlayGroupUpdate(GroupUpdateType.PlayQueue, playQueueUpdate); @@ -420,7 +420,7 @@ namespace MediaBrowser.Controller.SyncPlay.GroupStates var timeSyncThresholdTicks = TimeSpan.FromMilliseconds(context.TimeSyncOffset).Ticks; if (Math.Abs(elapsedTime.Ticks) > timeSyncThresholdTicks) { - Logger.LogWarning("HandleRequest: {0} in group {1}, {2} is not time syncing properly. Ignoring elapsed time.", request.Type, context.GroupId.ToString(), session.Id); + Logger.LogWarning("HandleRequest: {RequestType} in group {GroupId}, {SessionId} is not time syncing properly. Ignoring elapsed time.", request.Type, context.GroupId.ToString(), session.Id); elapsedTime = TimeSpan.Zero; } @@ -436,7 +436,7 @@ namespace MediaBrowser.Controller.SyncPlay.GroupStates var delayTicks = context.PositionTicks - clientPosition.Ticks; var maxPlaybackOffsetTicks = TimeSpan.FromMilliseconds(context.MaxPlaybackOffset).Ticks; - Logger.LogDebug("HandleRequest: {0} in group {1}, {2} at {3} (delay of {4} seconds).", request.Type, context.GroupId.ToString(), session.Id, clientPosition, TimeSpan.FromTicks(delayTicks).TotalSeconds); + Logger.LogDebug("HandleRequest: {RequestType} in group {GroupId}, {SessionId} at {PositionTicks} (delay of {Delay} seconds).", request.Type, context.GroupId.ToString(), session.Id, clientPosition, TimeSpan.FromTicks(delayTicks).TotalSeconds); if (ResumePlaying) { @@ -454,7 +454,7 @@ namespace MediaBrowser.Controller.SyncPlay.GroupStates // Notify relevant state change event. SendGroupStateUpdate(context, request, session, cancellationToken); - Logger.LogWarning("HandleRequest: {0} in group {1}, {2} got lost in time, correcting.", request.Type, context.GroupId.ToString(), session.Id); + Logger.LogWarning("HandleRequest: {RequestType} in group {GroupId}, {SessionId} got lost in time, correcting.", request.Type, context.GroupId.ToString(), session.Id); return; } @@ -468,7 +468,7 @@ namespace MediaBrowser.Controller.SyncPlay.GroupStates command.When = currentTime.AddTicks(delayTicks); context.SendCommand(session, SyncPlayBroadcastType.CurrentSession, command, cancellationToken); - Logger.LogInformation("HandleRequest: {0} in group {1}, others still buffering, {2} will pause when ready in {3} seconds.", request.Type, context.GroupId.ToString(), session.Id, TimeSpan.FromTicks(delayTicks).TotalSeconds); + Logger.LogInformation("HandleRequest: {RequestType} in group {GroupId}, others still buffering, {SessionId} will pause when ready in {Delay} seconds.", request.Type, context.GroupId.ToString(), session.Id, TimeSpan.FromTicks(delayTicks).TotalSeconds); } else { @@ -487,7 +487,7 @@ namespace MediaBrowser.Controller.SyncPlay.GroupStates context.SendCommand(session, filter, command, cancellationToken); - Logger.LogInformation("HandleRequest: {0} in group {1}, {2} is recovering, notifying others to resume in {3} seconds.", request.Type, context.GroupId.ToString(), session.Id, TimeSpan.FromTicks(delayTicks).TotalSeconds); + Logger.LogInformation("HandleRequest: {RequestType} in group {GroupId}, {SessionId} is recovering, notifying others to resume in {Delay} seconds.", request.Type, context.GroupId.ToString(), session.Id, TimeSpan.FromTicks(delayTicks).TotalSeconds); } else { @@ -500,7 +500,7 @@ namespace MediaBrowser.Controller.SyncPlay.GroupStates var command = context.NewSyncPlayCommand(SendCommandType.Unpause); context.SendCommand(session, SyncPlayBroadcastType.AllGroup, command, cancellationToken); - Logger.LogWarning("HandleRequest: {0} in group {1}, {2} resumed playback but did not update others in time. {3} seconds to recover.", request.Type, context.GroupId.ToString(), session.Id, TimeSpan.FromTicks(delayTicks).TotalSeconds); + Logger.LogWarning("HandleRequest: {RequestType} in group {GroupId}, {SessionId} resumed playback but did not update others in time. {Delay} seconds to recover.", request.Type, context.GroupId.ToString(), session.Id, TimeSpan.FromTicks(delayTicks).TotalSeconds); } // Change state. @@ -511,7 +511,7 @@ namespace MediaBrowser.Controller.SyncPlay.GroupStates } else { - // Check that session is really ready, tollerate player imperfections under a certain threshold. + // Check that session is really ready, tolerate player imperfections under a certain threshold. if (Math.Abs(context.PositionTicks - requestTicks) > maxPlaybackOffsetTicks) { // Session still not ready. @@ -523,7 +523,7 @@ namespace MediaBrowser.Controller.SyncPlay.GroupStates // Notify relevant state change event. SendGroupStateUpdate(context, request, session, cancellationToken); - Logger.LogWarning("HandleRequest: {0} in group {1}, {2} was seeking to wrong position, correcting.", request.Type, context.GroupId.ToString(), session.Id); + Logger.LogWarning("HandleRequest: {RequestType} in group {GroupId}, {SessionId} is seeking to wrong position, correcting.", request.Type, context.GroupId.ToString(), session.Id); return; } else @@ -549,7 +549,7 @@ namespace MediaBrowser.Controller.SyncPlay.GroupStates pausedState.HandleRequest(context, Type, request, session, cancellationToken); } - Logger.LogDebug("HandleRequest: {0} in group {1}, {2} is ready, returning to previous state.", request.Type, context.GroupId.ToString(), session.Id); + Logger.LogDebug("HandleRequest: {RequestType} in group {GroupId}, {SessionId} is ready, returning to previous state.", request.Type, context.GroupId.ToString(), session.Id); } } } @@ -569,7 +569,7 @@ namespace MediaBrowser.Controller.SyncPlay.GroupStates // Make sure the client knows the playing item, to avoid duplicate requests. if (!request.PlaylistItemId.Equals(context.PlayQueue.GetPlayingItemPlaylistId(), StringComparison.OrdinalIgnoreCase)) { - Logger.LogDebug("HandleRequest: {0} in group {1}, client provided the wrong playlist identifier.", request.Type, context.GroupId.ToString()); + Logger.LogDebug("HandleRequest: {RequestType} in group {GroupId}, {SessionId} provided the wrong playlist identifier.", request.Type, context.GroupId.ToString(), session.Id); return; } @@ -596,7 +596,7 @@ namespace MediaBrowser.Controller.SyncPlay.GroupStates context.SetState(newState); - Logger.LogDebug("HandleRequest: {0} in group {1}, no next track available.", request.Type, context.GroupId.ToString()); + Logger.LogDebug("HandleRequest: {RequestType} in group {GroupId}, no next track available.", request.Type, context.GroupId.ToString()); } } @@ -615,7 +615,7 @@ namespace MediaBrowser.Controller.SyncPlay.GroupStates // Make sure the client knows the playing item, to avoid duplicate requests. if (!request.PlaylistItemId.Equals(context.PlayQueue.GetPlayingItemPlaylistId(), StringComparison.OrdinalIgnoreCase)) { - Logger.LogDebug("HandleRequest: {0} in group {1}, client provided the wrong playlist identifier.", request.Type, context.GroupId.ToString()); + Logger.LogDebug("HandleRequest: {RequestType} in group {GroupId}, {SessionId} provided the wrong playlist identifier.", request.Type, context.GroupId.ToString(), session.Id); return; } @@ -642,7 +642,7 @@ namespace MediaBrowser.Controller.SyncPlay.GroupStates context.SetState(newState); - Logger.LogDebug("HandleRequest: {0} in group {1}, no previous track available.", request.Type, context.GroupId.ToString()); + Logger.LogDebug("HandleRequest: {RequestType} in group {GroupId}, no previous track available.", request.Type, context.GroupId.ToString()); } } @@ -653,7 +653,7 @@ namespace MediaBrowser.Controller.SyncPlay.GroupStates if (!context.IsBuffering()) { - Logger.LogDebug("HandleRequest: {0} in group {1}, returning to previous state.", request.Type, context.GroupId.ToString()); + Logger.LogDebug("HandleRequest: {RequestType} in group {GroupId}, returning to previous state.", request.Type, context.GroupId.ToString()); if (ResumePlaying) { diff --git a/MediaBrowser.Controller/SyncPlay/IGroupStateContext.cs b/MediaBrowser.Controller/SyncPlay/IGroupStateContext.cs index 3609be36b..13f1b2316 100644 --- a/MediaBrowser.Controller/SyncPlay/IGroupStateContext.cs +++ b/MediaBrowser.Controller/SyncPlay/IGroupStateContext.cs @@ -151,7 +151,7 @@ namespace MediaBrowser.Controller.SyncPlay /// The playing item position in the play queue. /// The start position ticks. /// true if the play queue has been changed; false if something went wrong. - bool SetPlayQueue(IEnumerable playQueue, int playingItemPosition, long startPositionTicks); + bool SetPlayQueue(IReadOnlyList playQueue, int playingItemPosition, long startPositionTicks); /// /// Sets the playing item. @@ -165,7 +165,7 @@ namespace MediaBrowser.Controller.SyncPlay /// /// The items to remove. /// true if playing item got removed; false otherwise. - bool RemoveFromPlayQueue(IEnumerable playlistItemIds); + bool RemoveFromPlayQueue(IReadOnlyList playlistItemIds); /// /// Moves an item in the play queue. @@ -181,7 +181,7 @@ namespace MediaBrowser.Controller.SyncPlay /// The new items to add to the play queue. /// The mode with which the items will be added. /// true if the play queue has been changed; false if something went wrong. - bool AddToPlayQueue(IEnumerable newItems, GroupQueueMode mode); + bool AddToPlayQueue(IReadOnlyList newItems, GroupQueueMode mode); /// /// Restarts current item in play queue. diff --git a/MediaBrowser.Controller/SyncPlay/ISyncPlayManager.cs b/MediaBrowser.Controller/SyncPlay/ISyncPlayManager.cs index 65146d4ae..a98001682 100644 --- a/MediaBrowser.Controller/SyncPlay/ISyncPlayManager.cs +++ b/MediaBrowser.Controller/SyncPlay/ISyncPlayManager.cs @@ -49,21 +49,5 @@ namespace MediaBrowser.Controller.SyncPlay /// The request. /// The cancellation token. void HandleRequest(SessionInfo session, IGroupPlaybackRequest request, CancellationToken cancellationToken); - - /// - /// Maps a session to a group. - /// - /// The session. - /// The group. - /// Thrown when the user is in another group already. - void AddSessionToGroup(SessionInfo session, IGroupController group); - - /// - /// Unmaps a session from a group. - /// - /// The session. - /// The group. - /// Thrown when the user is not found in the specified group. - void RemoveSessionFromGroup(SessionInfo session, IGroupController group); } } diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/PlayGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/PlayGroupRequest.cs index 306c161ed..7d27f6151 100644 --- a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/PlayGroupRequest.cs +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/PlayGroupRequest.cs @@ -19,9 +19,7 @@ namespace MediaBrowser.Controller.SyncPlay.PlaybackRequests /// The start position ticks. public PlayGroupRequest(Guid[] playingQueue, int playingItemPosition, long startPositionTicks) { - var list = new List(); - list.AddRange(playingQueue); - PlayingQueue = list; + PlayingQueue = playingQueue ?? Array.Empty(); PlayingItemPosition = playingItemPosition; StartPositionTicks = startPositionTicks; } diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/QueueGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/QueueGroupRequest.cs index 9580b5315..106daecc8 100644 --- a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/QueueGroupRequest.cs +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/QueueGroupRequest.cs @@ -18,9 +18,7 @@ namespace MediaBrowser.Controller.SyncPlay.PlaybackRequests /// The enqueue mode. public QueueGroupRequest(Guid[] items, GroupQueueMode mode) { - var list = new List(); - list.AddRange(items); - ItemIds = list; + ItemIds = items ?? Array.Empty(); Mode = mode; } diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/RemoveFromPlaylistGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/RemoveFromPlaylistGroupRequest.cs index 21c602846..1e892d819 100644 --- a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/RemoveFromPlaylistGroupRequest.cs +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/RemoveFromPlaylistGroupRequest.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Threading; using MediaBrowser.Controller.Session; @@ -16,9 +17,7 @@ namespace MediaBrowser.Controller.SyncPlay.PlaybackRequests /// The playlist ids of the items to remove. public RemoveFromPlaylistGroupRequest(string[] items) { - var list = new List(); - list.AddRange(items); - PlaylistItemIds = list; + PlaylistItemIds = items ?? Array.Empty(); } /// diff --git a/MediaBrowser.Controller/SyncPlay/Queue/PlayQueueManager.cs b/MediaBrowser.Controller/SyncPlay/Queue/PlayQueueManager.cs index 2d1d1533b..73457f447 100644 --- a/MediaBrowser.Controller/SyncPlay/Queue/PlayQueueManager.cs +++ b/MediaBrowser.Controller/SyncPlay/Queue/PlayQueueManager.cs @@ -94,7 +94,7 @@ namespace MediaBrowser.Controller.SyncPlay.Queue /// Sets a new playlist. Playing item is reset. /// /// The new items of the playlist. - public void SetPlaylist(IEnumerable items) + public void SetPlaylist(IReadOnlyList items) { SortedPlaylist.Clear(); ShuffledPlaylist.Clear(); @@ -114,7 +114,7 @@ namespace MediaBrowser.Controller.SyncPlay.Queue /// Appends new items to the playlist. The specified order is mantained. /// /// The items to add to the playlist. - public void Queue(IEnumerable items) + public void Queue(IReadOnlyList items) { var newItems = CreateQueueItemsFromArray(items); @@ -209,7 +209,7 @@ namespace MediaBrowser.Controller.SyncPlay.Queue /// Adds new items to the playlist right after the playing item. The specified order is mantained. /// /// The items to add to the playlist. - public void QueueNext(IEnumerable items) + public void QueueNext(IReadOnlyList items) { var newItems = CreateQueueItemsFromArray(items); @@ -312,13 +312,12 @@ namespace MediaBrowser.Controller.SyncPlay.Queue /// /// The items to remove. /// true if playing item got removed; false otherwise. - public bool RemoveFromPlaylist(IEnumerable playlistItemIds) + public bool RemoveFromPlaylist(IReadOnlyList playlistItemIds) { var playingItem = GetPlayingItem(); - var playlistItemIdsList = playlistItemIds.ToList(); - SortedPlaylist.RemoveAll(item => playlistItemIdsList.Contains(item.PlaylistItemId)); - ShuffledPlaylist.RemoveAll(item => playlistItemIdsList.Contains(item.PlaylistItemId)); + SortedPlaylist.RemoveAll(item => playlistItemIds.Contains(item.PlaylistItemId)); + ShuffledPlaylist.RemoveAll(item => playlistItemIds.Contains(item.PlaylistItemId)); LastChange = DateTime.UtcNow; @@ -369,8 +368,7 @@ namespace MediaBrowser.Controller.SyncPlay.Queue var queueItem = playlist[oldIndex]; playlist.RemoveAt(oldIndex); - newIndex = Math.Min(newIndex, playlist.Count); - newIndex = Math.Max(newIndex, 0); + newIndex = Math.Clamp(newIndex, 0, playlist.Count); playlist.Insert(newIndex, queueItem); LastChange = DateTime.UtcNow; @@ -489,7 +487,7 @@ namespace MediaBrowser.Controller.SyncPlay.Queue } else { - PlayingItemIndex--; + PlayingItemIndex = SortedPlaylist.Count - 1; return false; } } @@ -519,7 +517,7 @@ namespace MediaBrowser.Controller.SyncPlay.Queue } else { - PlayingItemIndex++; + PlayingItemIndex = 0; return false; } } @@ -558,7 +556,7 @@ namespace MediaBrowser.Controller.SyncPlay.Queue /// Creates a list from the array of items. Each item is given an unique playlist identifier. /// /// The list of queue items. - private List CreateQueueItemsFromArray(IEnumerable items) + private List CreateQueueItemsFromArray(IReadOnlyList items) { var list = new List(); foreach (var item in items) diff --git a/MediaBrowser.Model/SyncPlay/GroupInfoDto.cs b/MediaBrowser.Model/SyncPlay/GroupInfoDto.cs index 16a75eb68..8c0960b83 100644 --- a/MediaBrowser.Model/SyncPlay/GroupInfoDto.cs +++ b/MediaBrowser.Model/SyncPlay/GroupInfoDto.cs @@ -11,41 +11,48 @@ namespace MediaBrowser.Model.SyncPlay /// /// Initializes a new instance of the class. /// - public GroupInfoDto() + /// The group identifier. + /// The group name. + /// The group state. + /// The participants. + /// The date when this DTO has been created. + public GroupInfoDto(Guid groupId, string groupName, GroupStateType state, IReadOnlyList participants, DateTime lastUpdatedAt) { - GroupId = string.Empty; - GroupName = string.Empty; - Participants = new List(); + GroupId = groupId; + GroupName = groupName; + State = state; + Participants = participants; + LastUpdatedAt = lastUpdatedAt; } /// - /// Gets or sets the group identifier. + /// Gets the group identifier. /// /// The group identifier. - public string GroupId { get; set; } + public Guid GroupId { get; } /// - /// Gets or sets the group name. + /// Gets the group name. /// /// The group name. - public string GroupName { get; set; } + public string GroupName { get; } /// - /// Gets or sets the group state. + /// Gets the group state. /// /// The group state. - public GroupStateType State { get; set; } + public GroupStateType State { get; } /// - /// Gets or sets the participants. + /// Gets the participants. /// /// The participants. - public IReadOnlyList Participants { get; set; } + public IReadOnlyList Participants { get; } /// - /// Gets or sets the date when this dto has been updated. + /// Gets the date when this DTO has been created. /// - /// The date when this dto has been updated. - public DateTime LastUpdatedAt { get; set; } + /// The date when this DTO has been created. + public DateTime LastUpdatedAt { get; } } } diff --git a/MediaBrowser.Model/SyncPlay/GroupStateUpdate.cs b/MediaBrowser.Model/SyncPlay/GroupStateUpdate.cs index 532b5a56f..7f7deb86b 100644 --- a/MediaBrowser.Model/SyncPlay/GroupStateUpdate.cs +++ b/MediaBrowser.Model/SyncPlay/GroupStateUpdate.cs @@ -6,15 +6,26 @@ namespace MediaBrowser.Model.SyncPlay public class GroupStateUpdate { /// - /// Gets or sets the state of the group. + /// Initializes a new instance of the class. + /// + /// The state of the group. + /// The reason of the state change. + public GroupStateUpdate(GroupStateType state, PlaybackRequestType reason) + { + State = state; + Reason = reason; + } + + /// + /// Gets the state of the group. /// /// The state of the group. - public GroupStateType State { get; set; } + public GroupStateType State { get; } /// - /// Gets or sets the reason of the state change. + /// Gets the reason of the state change. /// /// The reason of the state change. - public PlaybackRequestType Reason { get; set; } + public PlaybackRequestType Reason { get; } } } diff --git a/MediaBrowser.Model/SyncPlay/GroupUpdate.cs b/MediaBrowser.Model/SyncPlay/GroupUpdate.cs index 12d6058ac..6f159d653 100644 --- a/MediaBrowser.Model/SyncPlay/GroupUpdate.cs +++ b/MediaBrowser.Model/SyncPlay/GroupUpdate.cs @@ -1,4 +1,4 @@ -#nullable disable +using System; namespace MediaBrowser.Model.SyncPlay { @@ -9,21 +9,34 @@ namespace MediaBrowser.Model.SyncPlay public class GroupUpdate { /// - /// Gets or sets the group identifier. + /// Initializes a new instance of the class. + /// + /// The group identifier. + /// The update type. + /// The update data. + public GroupUpdate(Guid groupId, GroupUpdateType type, T data) + { + GroupId = groupId; + Type = type; + Data = data; + } + + /// + /// Gets the group identifier. /// /// The group identifier. - public string GroupId { get; set; } + public Guid GroupId { get; } /// - /// Gets or sets the update type. + /// Gets the update type. /// /// The update type. - public GroupUpdateType Type { get; set; } + public GroupUpdateType Type { get; } /// - /// Gets or sets the data. + /// Gets the update data. /// - /// The data. - public T Data { get; set; } + /// The update data. + public T Data { get; } } } diff --git a/MediaBrowser.Model/SyncPlay/JoinGroupRequest.cs b/MediaBrowser.Model/SyncPlay/JoinGroupRequest.cs index 27a29b899..7402c4ce2 100644 --- a/MediaBrowser.Model/SyncPlay/JoinGroupRequest.cs +++ b/MediaBrowser.Model/SyncPlay/JoinGroupRequest.cs @@ -8,9 +8,18 @@ namespace MediaBrowser.Model.SyncPlay public class JoinGroupRequest { /// - /// Gets or sets the group identifier. + /// Initializes a new instance of the class. + /// + /// The identifier of the group to join. + public JoinGroupRequest(Guid groupId) + { + GroupId = groupId; + } + + /// + /// Gets the group identifier. /// /// The identifier of the group to join. - public Guid GroupId { get; set; } + public Guid GroupId { get; } } } diff --git a/MediaBrowser.Model/SyncPlay/NewGroupRequest.cs b/MediaBrowser.Model/SyncPlay/NewGroupRequest.cs index eb61a68d1..ba4bd3ef1 100644 --- a/MediaBrowser.Model/SyncPlay/NewGroupRequest.cs +++ b/MediaBrowser.Model/SyncPlay/NewGroupRequest.cs @@ -8,15 +8,16 @@ namespace MediaBrowser.Model.SyncPlay /// /// Initializes a new instance of the class. /// - public NewGroupRequest() + /// The name of the new group. + public NewGroupRequest(string groupName) { - GroupName = string.Empty; + GroupName = groupName; } /// - /// Gets or sets the group name. + /// Gets the group name. /// /// The name of the new group. - public string GroupName { get; set; } + public string GroupName { get; } } } diff --git a/MediaBrowser.Model/SyncPlay/PlayQueueUpdate.cs b/MediaBrowser.Model/SyncPlay/PlayQueueUpdate.cs index d193b4c66..a851229f7 100644 --- a/MediaBrowser.Model/SyncPlay/PlayQueueUpdate.cs +++ b/MediaBrowser.Model/SyncPlay/PlayQueueUpdate.cs @@ -11,51 +11,64 @@ namespace MediaBrowser.Model.SyncPlay /// /// Initializes a new instance of the class. /// - public PlayQueueUpdate() + /// The reason for the update. + /// The UTC time of the last change to the playing queue. + /// The playlist. + /// The playing item index in the playlist. + /// The start position ticks. + /// The shuffle mode. + /// The repeat mode. + public PlayQueueUpdate(PlayQueueUpdateReason reason, DateTime lastUpdate, IReadOnlyList playlist, int playingItemIndex, long startPositionTicks, GroupShuffleMode shuffleMode, GroupRepeatMode repeatMode) { - Playlist = new List(); + Reason = reason; + LastUpdate = lastUpdate; + Playlist = playlist; + PlayingItemIndex = playingItemIndex; + StartPositionTicks = startPositionTicks; + ShuffleMode = shuffleMode; + RepeatMode = repeatMode; } /// - /// Gets or sets the request type that originated this update. + /// Gets the request type that originated this update. /// /// The reason for the update. - public PlayQueueUpdateReason Reason { get; set; } + public PlayQueueUpdateReason Reason { get; } /// - /// Gets or sets the UTC time of the last change to the playing queue. + /// Gets the UTC time of the last change to the playing queue. /// /// The UTC time of the last change to the playing queue. - public DateTime LastUpdate { get; set; } + public DateTime LastUpdate { get; } /// - /// Gets or sets the playlist. + /// Gets the playlist. /// /// The playlist. - public IReadOnlyList Playlist { get; set; } + public IReadOnlyList Playlist { get; } /// - /// Gets or sets the playing item index in the playlist. + /// Gets the playing item index in the playlist. /// /// The playing item index in the playlist. - public int PlayingItemIndex { get; set; } + public int PlayingItemIndex { get; } /// - /// Gets or sets the start position ticks. + /// Gets the start position ticks. /// /// The start position ticks. - public long StartPositionTicks { get; set; } + public long StartPositionTicks { get; } /// - /// Gets or sets the shuffle mode. + /// Gets the shuffle mode. /// /// The shuffle mode. - public GroupShuffleMode ShuffleMode { get; set; } + public GroupShuffleMode ShuffleMode { get; } /// - /// Gets or sets the repeat mode. + /// Gets the repeat mode. /// /// The repeat mode. - public GroupRepeatMode RepeatMode { get; set; } + public GroupRepeatMode RepeatMode { get; } } } diff --git a/MediaBrowser.Model/SyncPlay/SendCommand.cs b/MediaBrowser.Model/SyncPlay/SendCommand.cs index a3aa54b38..ab4c64130 100644 --- a/MediaBrowser.Model/SyncPlay/SendCommand.cs +++ b/MediaBrowser.Model/SyncPlay/SendCommand.cs @@ -10,23 +10,33 @@ namespace MediaBrowser.Model.SyncPlay /// /// Initializes a new instance of the class. /// - public SendCommand() + /// The group identifier. + /// The playlist identifier of the playing item. + /// The UTC time when to execute the command. + /// The command. + /// The position ticks, for commands that require it. + /// The UTC time when this command has been emitted. + public SendCommand(Guid groupId, string playlistItemId, DateTime when, SendCommandType command, long? positionTicks, DateTime emittedAt) { - GroupId = string.Empty; - PlaylistItemId = string.Empty; + GroupId = groupId; + PlaylistItemId = playlistItemId; + When = when; + Command = command; + PositionTicks = positionTicks; + EmittedAt = emittedAt; } /// - /// Gets or sets the group identifier. + /// Gets the group identifier. /// /// The group identifier. - public string GroupId { get; set; } + public Guid GroupId { get; } /// - /// Gets or sets the playlist identifier of the playing item. + /// Gets the playlist identifier of the playing item. /// /// The playlist identifier of the playing item. - public string PlaylistItemId { get; set; } + public string PlaylistItemId { get; } /// /// Gets or sets the UTC time when to execute the command. @@ -35,21 +45,21 @@ namespace MediaBrowser.Model.SyncPlay public DateTime When { get; set; } /// - /// Gets or sets the position ticks. + /// Gets the position ticks. /// /// The position ticks. - public long? PositionTicks { get; set; } + public long? PositionTicks { get; } /// - /// Gets or sets the command. + /// Gets the command. /// /// The command. - public SendCommandType Command { get; set; } + public SendCommandType Command { get; } /// - /// Gets or sets the UTC time when this command has been emitted. + /// Gets the UTC time when this command has been emitted. /// /// The UTC time when this command has been emitted. - public DateTime EmittedAt { get; set; } + public DateTime EmittedAt { get; } } } diff --git a/MediaBrowser.Model/SyncPlay/UtcTimeResponse.cs b/MediaBrowser.Model/SyncPlay/UtcTimeResponse.cs index 8ec5eaab3..219e7b1e0 100644 --- a/MediaBrowser.Model/SyncPlay/UtcTimeResponse.cs +++ b/MediaBrowser.Model/SyncPlay/UtcTimeResponse.cs @@ -1,4 +1,4 @@ -#nullable disable +using System; namespace MediaBrowser.Model.SyncPlay { @@ -8,15 +8,26 @@ namespace MediaBrowser.Model.SyncPlay public class UtcTimeResponse { /// - /// Gets or sets the UTC time when request has been received. + /// Initializes a new instance of the class. + /// + /// The UTC time when request has been received. + /// The UTC time when response has been sent. + public UtcTimeResponse(DateTime requestReceptionTime, DateTime responseTransmissionTime) + { + RequestReceptionTime = requestReceptionTime; + ResponseTransmissionTime = responseTransmissionTime; + } + + /// + /// Gets the UTC time when request has been received. /// /// The UTC time when request has been received. - public string RequestReceptionTime { get; set; } + public DateTime RequestReceptionTime { get; } /// - /// Gets or sets the UTC time when response has been sent. + /// Gets the UTC time when response has been sent. /// /// The UTC time when response has been sent. - public string ResponseTransmissionTime { get; set; } + public DateTime ResponseTransmissionTime { get; } } } -- cgit v1.2.3 From f5973d57e8c070692450f0e04e01615c78c954d9 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sat, 21 Nov 2020 14:26:03 +0100 Subject: Remove UTF8 bom from some files --- Jellyfin.Api/Controllers/ApiKeyController.cs | 2 +- Jellyfin.Api/Controllers/ArtistsController.cs | 2 +- Jellyfin.Api/Controllers/BrandingController.cs | 2 +- Jellyfin.Api/Controllers/CollectionController.cs | 2 +- Jellyfin.Api/Controllers/DashboardController.cs | 2 +- Jellyfin.Api/Controllers/FilterController.cs | 2 +- Jellyfin.Api/Controllers/GenresController.cs | 2 +- Jellyfin.Api/Controllers/HlsSegmentController.cs | 2 +- Jellyfin.Api/Controllers/ImageController.cs | 2 +- Jellyfin.Api/Controllers/InstantMixController.cs | 2 +- Jellyfin.Api/Controllers/ItemLookupController.cs | 2 +- Jellyfin.Api/Controllers/ItemUpdateController.cs | 2 +- Jellyfin.Api/Controllers/LocalizationController.cs | 2 +- Jellyfin.Api/Controllers/MediaInfoController.cs | 2 +- Jellyfin.Api/Controllers/MoviesController.cs | 2 +- Jellyfin.Api/Controllers/MusicGenresController.cs | 2 +- Jellyfin.Api/Controllers/PersonsController.cs | 2 +- Jellyfin.Api/Controllers/PlaylistsController.cs | 2 +- Jellyfin.Api/Controllers/PlaystateController.cs | 2 +- Jellyfin.Api/Controllers/PluginsController.cs | 2 +- Jellyfin.Api/Controllers/StudiosController.cs | 2 +- Jellyfin.Api/Controllers/SuggestionsController.cs | 2 +- Jellyfin.Api/Controllers/SyncPlayController.cs | 2 +- Jellyfin.Api/Controllers/SystemController.cs | 2 +- Jellyfin.Api/Controllers/TimeSyncController.cs | 2 +- Jellyfin.Api/Controllers/UniversalAudioController.cs | 2 +- Jellyfin.Api/Controllers/UserController.cs | 2 +- Jellyfin.Api/Controllers/UserViewsController.cs | 2 +- Jellyfin.Api/Controllers/YearsController.cs | 2 +- 29 files changed, 29 insertions(+), 29 deletions(-) (limited to 'Jellyfin.Api/Controllers/TimeSyncController.cs') diff --git a/Jellyfin.Api/Controllers/ApiKeyController.cs b/Jellyfin.Api/Controllers/ApiKeyController.cs index e8d6ccdf2..8c43d786a 100644 --- a/Jellyfin.Api/Controllers/ApiKeyController.cs +++ b/Jellyfin.Api/Controllers/ApiKeyController.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.ComponentModel.DataAnnotations; using System.Globalization; using Jellyfin.Api.Constants; diff --git a/Jellyfin.Api/Controllers/ArtistsController.cs b/Jellyfin.Api/Controllers/ArtistsController.cs index f684c649a..c65dc8620 100644 --- a/Jellyfin.Api/Controllers/ArtistsController.cs +++ b/Jellyfin.Api/Controllers/ArtistsController.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.ComponentModel.DataAnnotations; using System.Linq; using Jellyfin.Api.Constants; diff --git a/Jellyfin.Api/Controllers/BrandingController.cs b/Jellyfin.Api/Controllers/BrandingController.cs index 1d4836f27..d3ea41201 100644 --- a/Jellyfin.Api/Controllers/BrandingController.cs +++ b/Jellyfin.Api/Controllers/BrandingController.cs @@ -1,4 +1,4 @@ -using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Configuration; using MediaBrowser.Model.Branding; using Microsoft.AspNetCore.Http; diff --git a/Jellyfin.Api/Controllers/CollectionController.cs b/Jellyfin.Api/Controllers/CollectionController.cs index 2a342c2cb..2a7b2b5c6 100644 --- a/Jellyfin.Api/Controllers/CollectionController.cs +++ b/Jellyfin.Api/Controllers/CollectionController.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.ComponentModel.DataAnnotations; using System.Threading.Tasks; using Jellyfin.Api.Constants; diff --git a/Jellyfin.Api/Controllers/DashboardController.cs b/Jellyfin.Api/Controllers/DashboardController.cs index a859ac114..ccc81dfc5 100644 --- a/Jellyfin.Api/Controllers/DashboardController.cs +++ b/Jellyfin.Api/Controllers/DashboardController.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; diff --git a/Jellyfin.Api/Controllers/FilterController.cs b/Jellyfin.Api/Controllers/FilterController.cs index c97a1ed14..31cb9e273 100644 --- a/Jellyfin.Api/Controllers/FilterController.cs +++ b/Jellyfin.Api/Controllers/FilterController.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using Jellyfin.Api.Constants; using Jellyfin.Api.ModelBinders; diff --git a/Jellyfin.Api/Controllers/GenresController.cs b/Jellyfin.Api/Controllers/GenresController.cs index 2dd504770..d2b41e0a8 100644 --- a/Jellyfin.Api/Controllers/GenresController.cs +++ b/Jellyfin.Api/Controllers/GenresController.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.ComponentModel.DataAnnotations; using System.Linq; using Jellyfin.Api.Constants; diff --git a/Jellyfin.Api/Controllers/HlsSegmentController.cs b/Jellyfin.Api/Controllers/HlsSegmentController.cs index ccdbbb297..f51987732 100644 --- a/Jellyfin.Api/Controllers/HlsSegmentController.cs +++ b/Jellyfin.Api/Controllers/HlsSegmentController.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.ComponentModel.DataAnnotations; using System.Diagnostics.CodeAnalysis; using System.IO; diff --git a/Jellyfin.Api/Controllers/ImageController.cs b/Jellyfin.Api/Controllers/ImageController.cs index 366f70163..f48c1df72 100644 --- a/Jellyfin.Api/Controllers/ImageController.cs +++ b/Jellyfin.Api/Controllers/ImageController.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Diagnostics.CodeAnalysis; diff --git a/Jellyfin.Api/Controllers/InstantMixController.cs b/Jellyfin.Api/Controllers/InstantMixController.cs index d17a26db4..6913afd0f 100644 --- a/Jellyfin.Api/Controllers/InstantMixController.cs +++ b/Jellyfin.Api/Controllers/InstantMixController.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; diff --git a/Jellyfin.Api/Controllers/ItemLookupController.cs b/Jellyfin.Api/Controllers/ItemLookupController.cs index a7c1a6388..6c38f77ce 100644 --- a/Jellyfin.Api/Controllers/ItemLookupController.cs +++ b/Jellyfin.Api/Controllers/ItemLookupController.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.IO; diff --git a/Jellyfin.Api/Controllers/ItemUpdateController.cs b/Jellyfin.Api/Controllers/ItemUpdateController.cs index 0a6ed31ae..9e1a39853 100644 --- a/Jellyfin.Api/Controllers/ItemUpdateController.cs +++ b/Jellyfin.Api/Controllers/ItemUpdateController.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; diff --git a/Jellyfin.Api/Controllers/LocalizationController.cs b/Jellyfin.Api/Controllers/LocalizationController.cs index ef2e7e8b1..3d8b9e0ca 100644 --- a/Jellyfin.Api/Controllers/LocalizationController.cs +++ b/Jellyfin.Api/Controllers/LocalizationController.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using Jellyfin.Api.Constants; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Globalization; diff --git a/Jellyfin.Api/Controllers/MediaInfoController.cs b/Jellyfin.Api/Controllers/MediaInfoController.cs index 186024585..b42e6686e 100644 --- a/Jellyfin.Api/Controllers/MediaInfoController.cs +++ b/Jellyfin.Api/Controllers/MediaInfoController.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Buffers; using System.ComponentModel.DataAnnotations; using System.Linq; diff --git a/Jellyfin.Api/Controllers/MoviesController.cs b/Jellyfin.Api/Controllers/MoviesController.cs index ebc148fe5..75dfd4e68 100644 --- a/Jellyfin.Api/Controllers/MoviesController.cs +++ b/Jellyfin.Api/Controllers/MoviesController.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Globalization; using System.Linq; diff --git a/Jellyfin.Api/Controllers/MusicGenresController.cs b/Jellyfin.Api/Controllers/MusicGenresController.cs index 8c6104302..e7d0a61c5 100644 --- a/Jellyfin.Api/Controllers/MusicGenresController.cs +++ b/Jellyfin.Api/Controllers/MusicGenresController.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.ComponentModel.DataAnnotations; using System.Linq; using Jellyfin.Api.Constants; diff --git a/Jellyfin.Api/Controllers/PersonsController.cs b/Jellyfin.Api/Controllers/PersonsController.cs index 9dc79b388..aaad36551 100644 --- a/Jellyfin.Api/Controllers/PersonsController.cs +++ b/Jellyfin.Api/Controllers/PersonsController.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.ComponentModel.DataAnnotations; using System.Linq; using Jellyfin.Api.Constants; diff --git a/Jellyfin.Api/Controllers/PlaylistsController.cs b/Jellyfin.Api/Controllers/PlaylistsController.cs index bc47ecbd1..3e55434c0 100644 --- a/Jellyfin.Api/Controllers/PlaylistsController.cs +++ b/Jellyfin.Api/Controllers/PlaylistsController.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Threading.Tasks; diff --git a/Jellyfin.Api/Controllers/PlaystateController.cs b/Jellyfin.Api/Controllers/PlaystateController.cs index 5c15e9a0d..7a60b0deb 100644 --- a/Jellyfin.Api/Controllers/PlaystateController.cs +++ b/Jellyfin.Api/Controllers/PlaystateController.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.ComponentModel.DataAnnotations; using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; diff --git a/Jellyfin.Api/Controllers/PluginsController.cs b/Jellyfin.Api/Controllers/PluginsController.cs index 0f8ceba29..98f1bc2d2 100644 --- a/Jellyfin.Api/Controllers/PluginsController.cs +++ b/Jellyfin.Api/Controllers/PluginsController.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; diff --git a/Jellyfin.Api/Controllers/StudiosController.cs b/Jellyfin.Api/Controllers/StudiosController.cs index af28b4f59..5090bf1de 100644 --- a/Jellyfin.Api/Controllers/StudiosController.cs +++ b/Jellyfin.Api/Controllers/StudiosController.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.ComponentModel.DataAnnotations; using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; diff --git a/Jellyfin.Api/Controllers/SuggestionsController.cs b/Jellyfin.Api/Controllers/SuggestionsController.cs index 69292186e..9f1dec712 100644 --- a/Jellyfin.Api/Controllers/SuggestionsController.cs +++ b/Jellyfin.Api/Controllers/SuggestionsController.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.ComponentModel.DataAnnotations; using System.Linq; using Jellyfin.Api.Constants; diff --git a/Jellyfin.Api/Controllers/SyncPlayController.cs b/Jellyfin.Api/Controllers/SyncPlayController.cs index e16a10ba4..346431e60 100644 --- a/Jellyfin.Api/Controllers/SyncPlayController.cs +++ b/Jellyfin.Api/Controllers/SyncPlayController.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Threading; diff --git a/Jellyfin.Api/Controllers/SystemController.cs b/Jellyfin.Api/Controllers/SystemController.cs index 4cb1984a2..92875d735 100644 --- a/Jellyfin.Api/Controllers/SystemController.cs +++ b/Jellyfin.Api/Controllers/SystemController.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.IO; diff --git a/Jellyfin.Api/Controllers/TimeSyncController.cs b/Jellyfin.Api/Controllers/TimeSyncController.cs index 2dc744e7c..27c7186fc 100644 --- a/Jellyfin.Api/Controllers/TimeSyncController.cs +++ b/Jellyfin.Api/Controllers/TimeSyncController.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Globalization; using MediaBrowser.Model.SyncPlay; using Microsoft.AspNetCore.Http; diff --git a/Jellyfin.Api/Controllers/UniversalAudioController.cs b/Jellyfin.Api/Controllers/UniversalAudioController.cs index 255532307..34c9f32fa 100644 --- a/Jellyfin.Api/Controllers/UniversalAudioController.cs +++ b/Jellyfin.Api/Controllers/UniversalAudioController.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Globalization; diff --git a/Jellyfin.Api/Controllers/UserController.cs b/Jellyfin.Api/Controllers/UserController.cs index 0f7c25d0e..9805b84b1 100644 --- a/Jellyfin.Api/Controllers/UserController.cs +++ b/Jellyfin.Api/Controllers/UserController.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; diff --git a/Jellyfin.Api/Controllers/UserViewsController.cs b/Jellyfin.Api/Controllers/UserViewsController.cs index 60fd1df01..e1483ce9d 100644 --- a/Jellyfin.Api/Controllers/UserViewsController.cs +++ b/Jellyfin.Api/Controllers/UserViewsController.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Globalization; diff --git a/Jellyfin.Api/Controllers/YearsController.cs b/Jellyfin.Api/Controllers/YearsController.cs index 9c3ecb4ce..ec7c3de97 100644 --- a/Jellyfin.Api/Controllers/YearsController.cs +++ b/Jellyfin.Api/Controllers/YearsController.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; -- cgit v1.2.3 From 223b42aed3395f7d01ea513bf352cdf4fd3e7002 Mon Sep 17 00:00:00 2001 From: crobibero Date: Wed, 10 Feb 2021 17:09:23 -0700 Subject: Create BaseItemKind enum --- Emby.Server.Implementations/Dto/DtoService.cs | 4 +- Jellyfin.Api/Controllers/ArtistsController.cs | 18 +- Jellyfin.Api/Controllers/CollectionController.cs | 1 - Jellyfin.Api/Controllers/DashboardController.cs | 6 - Jellyfin.Api/Controllers/DynamicHlsController.cs | 1 - Jellyfin.Api/Controllers/FilterController.cs | 37 ++--- Jellyfin.Api/Controllers/GenresController.cs | 9 +- Jellyfin.Api/Controllers/HlsSegmentController.cs | 2 - Jellyfin.Api/Controllers/InstantMixController.cs | 1 - Jellyfin.Api/Controllers/ItemLookupController.cs | 2 - Jellyfin.Api/Controllers/ItemsController.cs | 27 ++- Jellyfin.Api/Controllers/LibraryController.cs | 1 - Jellyfin.Api/Controllers/MusicGenresController.cs | 9 +- Jellyfin.Api/Controllers/PackageController.cs | 1 - Jellyfin.Api/Controllers/PersonsController.cs | 1 - Jellyfin.Api/Controllers/PlaylistsController.cs | 1 - Jellyfin.Api/Controllers/PluginsController.cs | 2 - Jellyfin.Api/Controllers/SearchController.cs | 9 +- Jellyfin.Api/Controllers/StudiosController.cs | 9 +- Jellyfin.Api/Controllers/SuggestionsController.cs | 1 - Jellyfin.Api/Controllers/SyncPlayController.cs | 1 - Jellyfin.Api/Controllers/SystemController.cs | 1 - Jellyfin.Api/Controllers/TimeSyncController.cs | 1 - Jellyfin.Api/Controllers/TrailersController.cs | 4 +- Jellyfin.Api/Controllers/TvShowsController.cs | 1 - Jellyfin.Api/Controllers/UserLibraryController.cs | 5 +- Jellyfin.Api/Controllers/UserViewsController.cs | 1 - Jellyfin.Api/Controllers/VideoHlsController.cs | 1 - Jellyfin.Api/Controllers/YearsController.cs | 15 +- Jellyfin.Api/Helpers/RequestHelpers.cs | 16 ++ Jellyfin.Data/Enums/BaseItemKind.cs | 190 ++++++++++++++++++++++ MediaBrowser.Controller/Entities/BaseItem.cs | 5 + MediaBrowser.Model/Dto/BaseItemDto.cs | 3 +- 33 files changed, 289 insertions(+), 97 deletions(-) create mode 100644 Jellyfin.Data/Enums/BaseItemKind.cs (limited to 'Jellyfin.Api/Controllers/TimeSyncController.cs') diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index e3ab0d6ea..54b18a8c8 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -249,7 +249,7 @@ namespace Emby.Server.Implementations.Dto var activeRecording = liveTvManager.GetActiveRecordingInfo(item.Path); if (activeRecording != null) { - dto.Type = "Recording"; + dto.Type = BaseItemKind.Recording; dto.CanDownload = false; dto.RunTimeTicks = null; @@ -904,7 +904,7 @@ namespace Emby.Server.Implementations.Dto } } - dto.Type = item.GetClientTypeName(); + dto.Type = item.GetBaseItemKind(); if ((item.CommunityRating ?? 0) > 0) { dto.CommunityRating = item.CommunityRating; diff --git a/Jellyfin.Api/Controllers/ArtistsController.cs b/Jellyfin.Api/Controllers/ArtistsController.cs index fed7ed3e5..4b2e5e7ea 100644 --- a/Jellyfin.Api/Controllers/ArtistsController.cs +++ b/Jellyfin.Api/Controllers/ArtistsController.cs @@ -3,8 +3,10 @@ using System.ComponentModel.DataAnnotations; using System.Linq; using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; +using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; @@ -88,8 +90,8 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? searchTerm, [FromQuery] Guid? parentId, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] excludeItemTypes, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] includeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters, [FromQuery] bool? isFavorite, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] mediaTypes, @@ -127,8 +129,8 @@ namespace Jellyfin.Api.Controllers var query = new InternalItemsQuery(user) { - ExcludeItemTypes = excludeItemTypes, - IncludeItemTypes = includeItemTypes, + ExcludeItemTypes = RequestHelpers.GetItemTypeStrings(excludeItemTypes), + IncludeItemTypes = RequestHelpers.GetItemTypeStrings(includeItemTypes), MediaTypes = mediaTypes, StartIndex = startIndex, Limit = limit, @@ -287,8 +289,8 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? searchTerm, [FromQuery] Guid? parentId, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] excludeItemTypes, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] includeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters, [FromQuery] bool? isFavorite, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] mediaTypes, @@ -326,8 +328,8 @@ namespace Jellyfin.Api.Controllers var query = new InternalItemsQuery(user) { - ExcludeItemTypes = excludeItemTypes, - IncludeItemTypes = includeItemTypes, + ExcludeItemTypes = RequestHelpers.GetItemTypeStrings(excludeItemTypes), + IncludeItemTypes = RequestHelpers.GetItemTypeStrings(includeItemTypes), MediaTypes = mediaTypes, StartIndex = startIndex, Limit = limit, diff --git a/Jellyfin.Api/Controllers/CollectionController.cs b/Jellyfin.Api/Controllers/CollectionController.cs index 2a7b2b5c6..852d1e9cb 100644 --- a/Jellyfin.Api/Controllers/CollectionController.cs +++ b/Jellyfin.Api/Controllers/CollectionController.cs @@ -3,7 +3,6 @@ using System.ComponentModel.DataAnnotations; using System.Threading.Tasks; using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; -using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; using MediaBrowser.Controller.Collections; using MediaBrowser.Controller.Dto; diff --git a/Jellyfin.Api/Controllers/DashboardController.cs b/Jellyfin.Api/Controllers/DashboardController.cs index ff7895373..b2baa9cea 100644 --- a/Jellyfin.Api/Controllers/DashboardController.cs +++ b/Jellyfin.Api/Controllers/DashboardController.cs @@ -7,17 +7,11 @@ using Jellyfin.Api.Attributes; using Jellyfin.Api.Models; using MediaBrowser.Common.Plugins; using MediaBrowser.Controller; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Extensions; -using MediaBrowser.Controller.Plugins; using MediaBrowser.Model.Net; using MediaBrowser.Model.Plugins; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; -using Microsoft.Net.Http.Headers; namespace Jellyfin.Api.Controllers { diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs index 6e85737d2..e375645cf 100644 --- a/Jellyfin.Api/Controllers/DynamicHlsController.cs +++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs @@ -15,7 +15,6 @@ using Jellyfin.Api.Helpers; using Jellyfin.Api.Models.PlaybackDtos; using Jellyfin.Api.Models.StreamingDtos; using MediaBrowser.Common.Configuration; -using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Dlna; diff --git a/Jellyfin.Api/Controllers/FilterController.cs b/Jellyfin.Api/Controllers/FilterController.cs index 9220b988f..223b2a2b6 100644 --- a/Jellyfin.Api/Controllers/FilterController.cs +++ b/Jellyfin.Api/Controllers/FilterController.cs @@ -1,13 +1,12 @@ using System; using System.Linq; using Jellyfin.Api.Constants; +using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Audio; -using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Playlists; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Querying; using Microsoft.AspNetCore.Authorization; @@ -51,7 +50,7 @@ namespace Jellyfin.Api.Controllers public ActionResult GetQueryFiltersLegacy( [FromQuery] Guid? userId, [FromQuery] Guid? parentId, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] includeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] mediaTypes) { var user = userId.HasValue && !userId.Equals(Guid.Empty) @@ -60,10 +59,10 @@ namespace Jellyfin.Api.Controllers BaseItem? item = null; if (includeItemTypes.Length != 1 - || !(string.Equals(includeItemTypes[0], nameof(BoxSet), StringComparison.OrdinalIgnoreCase) - || string.Equals(includeItemTypes[0], nameof(Playlist), StringComparison.OrdinalIgnoreCase) - || string.Equals(includeItemTypes[0], nameof(Trailer), StringComparison.OrdinalIgnoreCase) - || string.Equals(includeItemTypes[0], "Program", StringComparison.OrdinalIgnoreCase))) + || !(includeItemTypes[0] == BaseItemKind.BoxSet + || includeItemTypes[0] == BaseItemKind.Playlist + || includeItemTypes[0] == BaseItemKind.Trailer + || includeItemTypes[0] == BaseItemKind.Program)) { item = _libraryManager.GetParentItem(parentId, user?.Id); } @@ -72,7 +71,7 @@ namespace Jellyfin.Api.Controllers { User = user, MediaTypes = mediaTypes, - IncludeItemTypes = includeItemTypes, + IncludeItemTypes = RequestHelpers.GetItemTypeStrings(includeItemTypes), Recursive = true, EnableTotalRecordCount = false, DtoOptions = new DtoOptions @@ -137,7 +136,7 @@ namespace Jellyfin.Api.Controllers public ActionResult GetQueryFilters( [FromQuery] Guid? userId, [FromQuery] Guid? parentId, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] includeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes, [FromQuery] bool? isAiring, [FromQuery] bool? isMovie, [FromQuery] bool? isSports, @@ -152,10 +151,10 @@ namespace Jellyfin.Api.Controllers BaseItem? parentItem = null; if (includeItemTypes.Length == 1 - && (string.Equals(includeItemTypes[0], nameof(BoxSet), StringComparison.OrdinalIgnoreCase) - || string.Equals(includeItemTypes[0], nameof(Playlist), StringComparison.OrdinalIgnoreCase) - || string.Equals(includeItemTypes[0], nameof(Trailer), StringComparison.OrdinalIgnoreCase) - || string.Equals(includeItemTypes[0], "Program", StringComparison.OrdinalIgnoreCase))) + && (includeItemTypes[0] == BaseItemKind.BoxSet + || includeItemTypes[0] == BaseItemKind.Playlist + || includeItemTypes[0] == BaseItemKind.Trailer + || includeItemTypes[0] == BaseItemKind.Program)) { parentItem = null; } @@ -167,7 +166,7 @@ namespace Jellyfin.Api.Controllers var filters = new QueryFilters(); var genreQuery = new InternalItemsQuery(user) { - IncludeItemTypes = includeItemTypes, + IncludeItemTypes = RequestHelpers.GetItemTypeStrings(includeItemTypes), DtoOptions = new DtoOptions { Fields = Array.Empty(), @@ -192,10 +191,10 @@ namespace Jellyfin.Api.Controllers } if (includeItemTypes.Length == 1 - && (string.Equals(includeItemTypes[0], nameof(MusicAlbum), StringComparison.OrdinalIgnoreCase) - || string.Equals(includeItemTypes[0], nameof(MusicVideo), StringComparison.OrdinalIgnoreCase) - || string.Equals(includeItemTypes[0], nameof(MusicArtist), StringComparison.OrdinalIgnoreCase) - || string.Equals(includeItemTypes[0], nameof(Audio), StringComparison.OrdinalIgnoreCase))) + && (includeItemTypes[0] == BaseItemKind.MusicAlbum + || includeItemTypes[0] == BaseItemKind.MusicVideo + || includeItemTypes[0] == BaseItemKind.MusicArtist + || includeItemTypes[0] == BaseItemKind.Audio)) { filters.Genres = _libraryManager.GetMusicGenres(genreQuery).Items.Select(i => new NameGuidPair { diff --git a/Jellyfin.Api/Controllers/GenresController.cs b/Jellyfin.Api/Controllers/GenresController.cs index b6755ed5e..7bcf4674c 100644 --- a/Jellyfin.Api/Controllers/GenresController.cs +++ b/Jellyfin.Api/Controllers/GenresController.cs @@ -6,6 +6,7 @@ using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; @@ -74,8 +75,8 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? searchTerm, [FromQuery] Guid? parentId, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] excludeItemTypes, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] includeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes, [FromQuery] bool? isFavorite, [FromQuery] int? imageTypeLimit, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes, @@ -96,8 +97,8 @@ namespace Jellyfin.Api.Controllers var query = new InternalItemsQuery(user) { - ExcludeItemTypes = excludeItemTypes, - IncludeItemTypes = includeItemTypes, + ExcludeItemTypes = RequestHelpers.GetItemTypeStrings(excludeItemTypes), + IncludeItemTypes = RequestHelpers.GetItemTypeStrings(includeItemTypes), StartIndex = startIndex, Limit = limit, IsFavorite = isFavorite, diff --git a/Jellyfin.Api/Controllers/HlsSegmentController.cs b/Jellyfin.Api/Controllers/HlsSegmentController.cs index f51987732..25abe73ed 100644 --- a/Jellyfin.Api/Controllers/HlsSegmentController.cs +++ b/Jellyfin.Api/Controllers/HlsSegmentController.cs @@ -2,13 +2,11 @@ using System; using System.ComponentModel.DataAnnotations; using System.Diagnostics.CodeAnalysis; using System.IO; -using System.Linq; using System.Threading.Tasks; using Jellyfin.Api.Attributes; using Jellyfin.Api.Constants; using Jellyfin.Api.Helpers; using MediaBrowser.Common.Configuration; -using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Model.IO; diff --git a/Jellyfin.Api/Controllers/InstantMixController.cs b/Jellyfin.Api/Controllers/InstantMixController.cs index 244625752..f061755c3 100644 --- a/Jellyfin.Api/Controllers/InstantMixController.cs +++ b/Jellyfin.Api/Controllers/InstantMixController.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; -using System.Linq; using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; using Jellyfin.Api.ModelBinders; diff --git a/Jellyfin.Api/Controllers/ItemLookupController.cs b/Jellyfin.Api/Controllers/ItemLookupController.cs index 6c38f77ce..dfc68ffce 100644 --- a/Jellyfin.Api/Controllers/ItemLookupController.cs +++ b/Jellyfin.Api/Controllers/ItemLookupController.cs @@ -2,8 +2,6 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.IO; -using System.Linq; -using System.Net.Mime; using System.Text.Json; using System.Threading; using System.Threading.Tasks; diff --git a/Jellyfin.Api/Controllers/ItemsController.cs b/Jellyfin.Api/Controllers/ItemsController.cs index 7d7747495..2c9760f6d 100644 --- a/Jellyfin.Api/Controllers/ItemsController.cs +++ b/Jellyfin.Api/Controllers/ItemsController.cs @@ -1,6 +1,5 @@ using System; using System.ComponentModel.DataAnnotations; -using System.Globalization; using System.Linq; using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; @@ -178,8 +177,8 @@ namespace Jellyfin.Api.Controllers [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder, [FromQuery] Guid? parentId, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] excludeItemTypes, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] includeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters, [FromQuery] bool? isFavorite, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] mediaTypes, @@ -233,8 +232,8 @@ namespace Jellyfin.Api.Controllers .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); if (includeItemTypes.Length == 1 - && (includeItemTypes[0].Equals("Playlist", StringComparison.OrdinalIgnoreCase) - || includeItemTypes[0].Equals("BoxSet", StringComparison.OrdinalIgnoreCase))) + && (includeItemTypes[0] == BaseItemKind.Playlist + || includeItemTypes[0] == BaseItemKind.BoxSet)) { parentId = null; } @@ -251,7 +250,7 @@ namespace Jellyfin.Api.Controllers && string.Equals(hasCollectionType.CollectionType, CollectionType.Playlists, StringComparison.OrdinalIgnoreCase)) { recursive = true; - includeItemTypes = new[] { "Playlist" }; + includeItemTypes = new[] { BaseItemKind.Playlist }; } var enabledChannels = user!.GetPreferenceValues(PreferenceKind.EnabledChannels); @@ -286,8 +285,8 @@ namespace Jellyfin.Api.Controllers { IsPlayed = isPlayed, MediaTypes = mediaTypes, - IncludeItemTypes = includeItemTypes, - ExcludeItemTypes = excludeItemTypes, + IncludeItemTypes = RequestHelpers.GetItemTypeStrings(includeItemTypes), + ExcludeItemTypes = RequestHelpers.GetItemTypeStrings(excludeItemTypes), Recursive = recursive ?? false, OrderBy = RequestHelpers.GetOrderBy(sortBy, sortOrder), IsFavorite = isFavorite, @@ -611,8 +610,8 @@ namespace Jellyfin.Api.Controllers [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder, [FromQuery] Guid? parentId, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] excludeItemTypes, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] includeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters, [FromQuery] bool? isFavorite, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] mediaTypes, @@ -773,8 +772,8 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] excludeItemTypes, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] includeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes, [FromQuery] bool enableTotalRecordCount = true, [FromQuery] bool? enableImages = true) { @@ -810,8 +809,8 @@ namespace Jellyfin.Api.Controllers CollapseBoxSetItems = false, EnableTotalRecordCount = enableTotalRecordCount, AncestorIds = ancestorIds, - IncludeItemTypes = includeItemTypes, - ExcludeItemTypes = excludeItemTypes, + IncludeItemTypes = RequestHelpers.GetItemTypeStrings(includeItemTypes), + ExcludeItemTypes = RequestHelpers.GetItemTypeStrings(excludeItemTypes), SearchTerm = searchTerm }); diff --git a/Jellyfin.Api/Controllers/LibraryController.cs b/Jellyfin.Api/Controllers/LibraryController.cs index 28d359ac3..db4aa9668 100644 --- a/Jellyfin.Api/Controllers/LibraryController.cs +++ b/Jellyfin.Api/Controllers/LibraryController.cs @@ -11,7 +11,6 @@ using System.Threading.Tasks; using Jellyfin.Api.Attributes; using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; -using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; using Jellyfin.Api.Models.LibraryDtos; using Jellyfin.Data.Entities; diff --git a/Jellyfin.Api/Controllers/MusicGenresController.cs b/Jellyfin.Api/Controllers/MusicGenresController.cs index 2608a9cd0..7f7058b5e 100644 --- a/Jellyfin.Api/Controllers/MusicGenresController.cs +++ b/Jellyfin.Api/Controllers/MusicGenresController.cs @@ -6,6 +6,7 @@ using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; @@ -74,8 +75,8 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? searchTerm, [FromQuery] Guid? parentId, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] excludeItemTypes, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] includeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes, [FromQuery] bool? isFavorite, [FromQuery] int? imageTypeLimit, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes, @@ -96,8 +97,8 @@ namespace Jellyfin.Api.Controllers var query = new InternalItemsQuery(user) { - ExcludeItemTypes = excludeItemTypes, - IncludeItemTypes = includeItemTypes, + ExcludeItemTypes = RequestHelpers.GetItemTypeStrings(excludeItemTypes), + IncludeItemTypes = RequestHelpers.GetItemTypeStrings(includeItemTypes), StartIndex = startIndex, Limit = limit, IsFavorite = isFavorite, diff --git a/Jellyfin.Api/Controllers/PackageController.cs b/Jellyfin.Api/Controllers/PackageController.cs index c589f54ac..5dd49ef2f 100644 --- a/Jellyfin.Api/Controllers/PackageController.cs +++ b/Jellyfin.Api/Controllers/PackageController.cs @@ -4,7 +4,6 @@ using System.ComponentModel.DataAnnotations; using System.Linq; using System.Threading.Tasks; using Jellyfin.Api.Constants; -using MediaBrowser.Common.Json; using MediaBrowser.Common.Updates; using MediaBrowser.Controller.Configuration; using MediaBrowser.Model.Updates; diff --git a/Jellyfin.Api/Controllers/PersonsController.cs b/Jellyfin.Api/Controllers/PersonsController.cs index 17e631197..70a94e27c 100644 --- a/Jellyfin.Api/Controllers/PersonsController.cs +++ b/Jellyfin.Api/Controllers/PersonsController.cs @@ -3,7 +3,6 @@ using System.ComponentModel.DataAnnotations; using System.Linq; using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; -using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Entities; using MediaBrowser.Controller.Dto; diff --git a/Jellyfin.Api/Controllers/PlaylistsController.cs b/Jellyfin.Api/Controllers/PlaylistsController.cs index a55e4ad2f..1667d6ede 100644 --- a/Jellyfin.Api/Controllers/PlaylistsController.cs +++ b/Jellyfin.Api/Controllers/PlaylistsController.cs @@ -6,7 +6,6 @@ using System.Threading.Tasks; using Jellyfin.Api.Attributes; using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; -using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; using Jellyfin.Api.Models.PlaylistDtos; using MediaBrowser.Controller.Dto; diff --git a/Jellyfin.Api/Controllers/PluginsController.cs b/Jellyfin.Api/Controllers/PluginsController.cs index b73611c97..b2e8bee91 100644 --- a/Jellyfin.Api/Controllers/PluginsController.cs +++ b/Jellyfin.Api/Controllers/PluginsController.cs @@ -1,10 +1,8 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; -using System.Globalization; using System.IO; using System.Linq; -using System.Net.Mime; using System.Text.Json; using System.Threading.Tasks; using Jellyfin.Api.Attributes; diff --git a/Jellyfin.Api/Controllers/SearchController.cs b/Jellyfin.Api/Controllers/SearchController.cs index 08255ff8f..6c22050a7 100644 --- a/Jellyfin.Api/Controllers/SearchController.cs +++ b/Jellyfin.Api/Controllers/SearchController.cs @@ -6,6 +6,7 @@ using System.Linq; using Jellyfin.Api.Constants; using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; @@ -83,8 +84,8 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? limit, [FromQuery] Guid? userId, [FromQuery, Required] string searchTerm, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] includeItemTypes, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] excludeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] mediaTypes, [FromQuery] Guid? parentId, [FromQuery] bool? isMovie, @@ -109,8 +110,8 @@ namespace Jellyfin.Api.Controllers IncludeStudios = includeStudios, StartIndex = startIndex, UserId = userId ?? Guid.Empty, - IncludeItemTypes = includeItemTypes, - ExcludeItemTypes = excludeItemTypes, + IncludeItemTypes = RequestHelpers.GetItemTypeStrings(includeItemTypes), + ExcludeItemTypes = RequestHelpers.GetItemTypeStrings(excludeItemTypes), MediaTypes = mediaTypes, ParentId = parentId, diff --git a/Jellyfin.Api/Controllers/StudiosController.cs b/Jellyfin.Api/Controllers/StudiosController.cs index bb54c59f6..da8f8b199 100644 --- a/Jellyfin.Api/Controllers/StudiosController.cs +++ b/Jellyfin.Api/Controllers/StudiosController.cs @@ -5,6 +5,7 @@ using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; @@ -73,8 +74,8 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? searchTerm, [FromQuery] Guid? parentId, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] excludeItemTypes, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] includeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes, [FromQuery] bool? isFavorite, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, @@ -96,8 +97,8 @@ namespace Jellyfin.Api.Controllers var query = new InternalItemsQuery(user) { - ExcludeItemTypes = excludeItemTypes, - IncludeItemTypes = includeItemTypes, + ExcludeItemTypes = RequestHelpers.GetItemTypeStrings(excludeItemTypes), + IncludeItemTypes = RequestHelpers.GetItemTypeStrings(includeItemTypes), StartIndex = startIndex, Limit = limit, IsFavorite = isFavorite, diff --git a/Jellyfin.Api/Controllers/SuggestionsController.cs b/Jellyfin.Api/Controllers/SuggestionsController.cs index 9f1dec712..a55f13e66 100644 --- a/Jellyfin.Api/Controllers/SuggestionsController.cs +++ b/Jellyfin.Api/Controllers/SuggestionsController.cs @@ -3,7 +3,6 @@ using System.ComponentModel.DataAnnotations; using System.Linq; using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; -using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Enums; using MediaBrowser.Controller.Dto; diff --git a/Jellyfin.Api/Controllers/SyncPlayController.cs b/Jellyfin.Api/Controllers/SyncPlayController.cs index 82cbe58df..f878f2329 100644 --- a/Jellyfin.Api/Controllers/SyncPlayController.cs +++ b/Jellyfin.Api/Controllers/SyncPlayController.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Threading; diff --git a/Jellyfin.Api/Controllers/SystemController.cs b/Jellyfin.Api/Controllers/SystemController.cs index e67a27ae3..bbbe5fb8d 100644 --- a/Jellyfin.Api/Controllers/SystemController.cs +++ b/Jellyfin.Api/Controllers/SystemController.cs @@ -5,7 +5,6 @@ using System.IO; using System.Linq; using System.Net; using System.Net.Mime; -using System.Threading; using System.Threading.Tasks; using Jellyfin.Api.Attributes; using Jellyfin.Api.Constants; diff --git a/Jellyfin.Api/Controllers/TimeSyncController.cs b/Jellyfin.Api/Controllers/TimeSyncController.cs index c730ac12b..7df51c7af 100644 --- a/Jellyfin.Api/Controllers/TimeSyncController.cs +++ b/Jellyfin.Api/Controllers/TimeSyncController.cs @@ -1,5 +1,4 @@ using System; -using System.Globalization; using MediaBrowser.Model.SyncPlay; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; diff --git a/Jellyfin.Api/Controllers/TrailersController.cs b/Jellyfin.Api/Controllers/TrailersController.cs index 242b8f068..dd3836551 100644 --- a/Jellyfin.Api/Controllers/TrailersController.cs +++ b/Jellyfin.Api/Controllers/TrailersController.cs @@ -148,7 +148,7 @@ namespace Jellyfin.Api.Controllers [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder, [FromQuery] Guid? parentId, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] excludeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters, [FromQuery] bool? isFavorite, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] mediaTypes, @@ -194,7 +194,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool enableTotalRecordCount = true, [FromQuery] bool? enableImages = true) { - var includeItemTypes = new[] { "Trailer" }; + var includeItemTypes = new[] { BaseItemKind.Trailer }; return _itemsController .GetItems( diff --git a/Jellyfin.Api/Controllers/TvShowsController.cs b/Jellyfin.Api/Controllers/TvShowsController.cs index 223f58859..e1c67f830 100644 --- a/Jellyfin.Api/Controllers/TvShowsController.cs +++ b/Jellyfin.Api/Controllers/TvShowsController.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; -using System.Globalization; using System.Linq; using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; diff --git a/Jellyfin.Api/Controllers/UserLibraryController.cs b/Jellyfin.Api/Controllers/UserLibraryController.cs index 0e65591cc..1d70406ac 100644 --- a/Jellyfin.Api/Controllers/UserLibraryController.cs +++ b/Jellyfin.Api/Controllers/UserLibraryController.cs @@ -8,6 +8,7 @@ using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; +using Jellyfin.Data.Enums; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; @@ -269,7 +270,7 @@ namespace Jellyfin.Api.Controllers [FromRoute, Required] Guid userId, [FromQuery] Guid? parentId, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] includeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes, [FromQuery] bool? isPlayed, [FromQuery] bool? enableImages, [FromQuery] int? imageTypeLimit, @@ -296,7 +297,7 @@ namespace Jellyfin.Api.Controllers new LatestItemsQuery { GroupItems = groupItems, - IncludeItemTypes = includeItemTypes, + IncludeItemTypes = RequestHelpers.GetItemTypeStrings(includeItemTypes), IsPlayed = isPlayed, Limit = limit, ParentId = parentId ?? Guid.Empty, diff --git a/Jellyfin.Api/Controllers/UserViewsController.cs b/Jellyfin.Api/Controllers/UserViewsController.cs index e1483ce9d..7bc5ecdf1 100644 --- a/Jellyfin.Api/Controllers/UserViewsController.cs +++ b/Jellyfin.Api/Controllers/UserViewsController.cs @@ -4,7 +4,6 @@ using System.ComponentModel.DataAnnotations; using System.Globalization; using System.Linq; using Jellyfin.Api.Extensions; -using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; using Jellyfin.Api.Models.UserViewDtos; using MediaBrowser.Controller.Dto; diff --git a/Jellyfin.Api/Controllers/VideoHlsController.cs b/Jellyfin.Api/Controllers/VideoHlsController.cs index 7e743ee0c..ba51aa43e 100644 --- a/Jellyfin.Api/Controllers/VideoHlsController.cs +++ b/Jellyfin.Api/Controllers/VideoHlsController.cs @@ -12,7 +12,6 @@ using Jellyfin.Api.Helpers; using Jellyfin.Api.Models.PlaybackDtos; using Jellyfin.Api.Models.StreamingDtos; using MediaBrowser.Common.Configuration; -using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Dlna; diff --git a/Jellyfin.Api/Controllers/YearsController.cs b/Jellyfin.Api/Controllers/YearsController.cs index 7c27752f7..d6dc6650c 100644 --- a/Jellyfin.Api/Controllers/YearsController.cs +++ b/Jellyfin.Api/Controllers/YearsController.cs @@ -74,8 +74,8 @@ namespace Jellyfin.Api.Controllers [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder, [FromQuery] Guid? parentId, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] excludeItemTypes, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] includeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] mediaTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] sortBy, [FromQuery] bool? enableUserData, @@ -101,8 +101,8 @@ namespace Jellyfin.Api.Controllers var query = new InternalItemsQuery(user) { - ExcludeItemTypes = excludeItemTypes, - IncludeItemTypes = includeItemTypes, + ExcludeItemTypes = RequestHelpers.GetItemTypeStrings(excludeItemTypes), + IncludeItemTypes = RequestHelpers.GetItemTypeStrings(includeItemTypes), MediaTypes = mediaTypes, DtoOptions = dtoOptions }; @@ -193,16 +193,17 @@ namespace Jellyfin.Api.Controllers return _dtoService.GetBaseItemDto(item, dtoOptions); } - private bool FilterItem(BaseItem f, IReadOnlyCollection excludeItemTypes, IReadOnlyCollection includeItemTypes, IReadOnlyCollection mediaTypes) + private bool FilterItem(BaseItem f, IReadOnlyCollection excludeItemTypes, IReadOnlyCollection includeItemTypes, IReadOnlyCollection mediaTypes) { + var baseItemKind = f.GetBaseItemKind(); // Exclude item types - if (excludeItemTypes.Count > 0 && excludeItemTypes.Contains(f.GetType().Name, StringComparer.OrdinalIgnoreCase)) + if (excludeItemTypes.Count > 0 && excludeItemTypes.Contains(baseItemKind)) { return false; } // Include item types - if (includeItemTypes.Count > 0 && !includeItemTypes.Contains(f.GetType().Name, StringComparer.OrdinalIgnoreCase)) + if (includeItemTypes.Count > 0 && !includeItemTypes.Contains(baseItemKind)) { return false; } diff --git a/Jellyfin.Api/Helpers/RequestHelpers.cs b/Jellyfin.Api/Helpers/RequestHelpers.cs index db0ccc657..94856e03e 100644 --- a/Jellyfin.Api/Helpers/RequestHelpers.cs +++ b/Jellyfin.Api/Helpers/RequestHelpers.cs @@ -129,5 +129,21 @@ namespace Jellyfin.Api.Helpers TotalRecordCount = result.TotalRecordCount }; } + + internal static string[] GetItemTypeStrings(IReadOnlyList itemKinds) + { + if (itemKinds.Count == 0) + { + return Array.Empty(); + } + + var itemTypes = new string[itemKinds.Count]; + for (var i = 0; i < itemKinds.Count; i++) + { + itemTypes[i] = itemKinds[i].ToString(); + } + + return itemTypes; + } } } diff --git a/Jellyfin.Data/Enums/BaseItemKind.cs b/Jellyfin.Data/Enums/BaseItemKind.cs new file mode 100644 index 000000000..aac30279e --- /dev/null +++ b/Jellyfin.Data/Enums/BaseItemKind.cs @@ -0,0 +1,190 @@ +namespace Jellyfin.Data.Enums +{ + /// + /// The base item kind. + /// + /// + /// This enum is generated from all classes that inherit from BaseItem. + /// + public enum BaseItemKind + { + /// + /// Item is aggregate folder. + /// + AggregateFolder, + + /// + /// Item is audio. + /// + Audio, + + /// + /// Item is audio book. + /// + AudioBook, + + /// + /// Item is base plugin folder. + /// + BasePluginFolder, + + /// + /// Item is book. + /// + Book, + + /// + /// Item is box set. + /// + BoxSet, + + /// + /// Item is channel. + /// + Channel, + + /// + /// Item is channel folder item. + /// + ChannelFolderItem, + + /// + /// Item is collection folder. + /// + CollectionFolder, + + /// + /// Item is episode. + /// + Episode, + + /// + /// Item is folder. + /// + Folder, + + /// + /// Item is genre. + /// + Genre, + + /// + /// Item is manual playlists folder. + /// + ManualPlaylistsFolder, + + /// + /// Item is movie. + /// + Movie, + + /// + /// Item is music album. + /// + MusicAlbum, + + /// + /// Item is music artist. + /// + MusicArtist, + + /// + /// Item is music genre. + /// + MusicGenre, + + /// + /// Item is music video. + /// + MusicVideo, + + /// + /// Item is person. + /// + Person, + + /// + /// Item is photo. + /// + Photo, + + /// + /// Item is photo album. + /// + PhotoAlbum, + + /// + /// Item is playlist. + /// + Playlist, + + /// + /// Item is program + /// + Program, + + /// + /// Item is recording. + /// + /// + /// Manually added. + /// + Recording, + + /// + /// Item is season. + /// + Season, + + /// + /// Item is series. + /// + Series, + + /// + /// Item is studio. + /// + Studio, + + /// + /// Item is trailer. + /// + Trailer, + + /// + /// Item is live tv channel. + /// + /// + /// Type is overridden. + /// + TvChannel, + + /// + /// Item is live tv program. + /// + /// + /// Type is overridden. + /// + TvProgram, + + /// + /// Item is user root folder. + /// + UserRootFolder, + + /// + /// Item is user view. + /// + UserView, + + /// + /// Item is video. + /// + Video, + + /// + /// Item is year. + /// + Year + } +} \ No newline at end of file diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index cbb02aabd..7598b29e6 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -1998,6 +1998,11 @@ namespace MediaBrowser.Controller.Entities return GetType().Name; } + public BaseItemKind GetBaseItemKind() + { + return Enum.Parse(GetClientTypeName()); + } + /// /// Gets the linked child. /// diff --git a/MediaBrowser.Model/Dto/BaseItemDto.cs b/MediaBrowser.Model/Dto/BaseItemDto.cs index 3f7aac9cd..2f9f9d3cd 100644 --- a/MediaBrowser.Model/Dto/BaseItemDto.cs +++ b/MediaBrowser.Model/Dto/BaseItemDto.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using Jellyfin.Data.Enums; using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Library; @@ -276,7 +277,7 @@ namespace MediaBrowser.Model.Dto /// Gets or sets the type. /// /// The type. - public string Type { get; set; } + public BaseItemKind Type { get; set; } /// /// Gets or sets the people. -- cgit v1.2.3 From 653df7d8e5b3474010b3797d7f15d51225932ca7 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Tue, 21 Sep 2021 01:21:45 +0200 Subject: Specify DateTimeStyles when possible --- Emby.Server.Implementations/Data/SqliteExtensions.cs | 6 +++--- Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs | 2 +- Jellyfin.Api/Controllers/TimeSyncController.cs | 4 ++-- Jellyfin.Api/Controllers/TvShowsController.cs | 2 +- MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs | 12 ++++++------ MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs | 6 +++--- .../Probing/ProbeResultNormalizer.cs | 6 +++--- MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs | 16 ++++++++-------- .../Probing/ProbeResultNormalizerTests.cs | 6 +++--- 9 files changed, 30 insertions(+), 30 deletions(-) (limited to 'Jellyfin.Api/Controllers/TimeSyncController.cs') diff --git a/Emby.Server.Implementations/Data/SqliteExtensions.cs b/Emby.Server.Implementations/Data/SqliteExtensions.cs index 3289e7609..381eb92a8 100644 --- a/Emby.Server.Implementations/Data/SqliteExtensions.cs +++ b/Emby.Server.Implementations/Data/SqliteExtensions.cs @@ -94,7 +94,7 @@ namespace Emby.Server.Implementations.Data dateText, _datetimeFormats, DateTimeFormatInfo.InvariantInfo, - DateTimeStyles.None).ToUniversalTime(); + DateTimeStyles.AdjustToUniversal); } public static bool TryReadDateTime(this IReadOnlyList reader, int index, out DateTime result) @@ -108,9 +108,9 @@ namespace Emby.Server.Implementations.Data var dateText = item.ToString(); - if (DateTime.TryParseExact(dateText, _datetimeFormats, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.None, out var dateTimeResult)) + if (DateTime.TryParseExact(dateText, _datetimeFormats, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.AdjustToUniversal, out var dateTimeResult)) { - result = dateTimeResult.ToUniversalTime(); + result = dateTimeResult; return true; } diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 026b6bc0b..64e54aa99 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -1990,7 +1990,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV writer.WriteElementString( "dateadded", - DateTime.UtcNow.ToLocalTime().ToString(DateAddedFormat, CultureInfo.InvariantCulture)); + DateTime.Now.ToString(DateAddedFormat, CultureInfo.InvariantCulture)); if (item.ProductionYear.HasValue) { diff --git a/Jellyfin.Api/Controllers/TimeSyncController.cs b/Jellyfin.Api/Controllers/TimeSyncController.cs index 7df51c7af..e7c5a7125 100644 --- a/Jellyfin.Api/Controllers/TimeSyncController.cs +++ b/Jellyfin.Api/Controllers/TimeSyncController.cs @@ -21,10 +21,10 @@ namespace Jellyfin.Api.Controllers public ActionResult GetUtcTime() { // Important to keep the following line at the beginning - var requestReceptionTime = DateTime.UtcNow.ToUniversalTime(); + var requestReceptionTime = DateTime.UtcNow; // Important to keep the following line at the end - var responseTransmissionTime = DateTime.UtcNow.ToUniversalTime(); + var responseTransmissionTime = DateTime.UtcNow; // Implementing NTP on such a high level results in this useless // information being sent. On the other hand it enables future additions. diff --git a/Jellyfin.Api/Controllers/TvShowsController.cs b/Jellyfin.Api/Controllers/TvShowsController.cs index 7c5b8a43b..6eada67cf 100644 --- a/Jellyfin.Api/Controllers/TvShowsController.cs +++ b/Jellyfin.Api/Controllers/TvShowsController.cs @@ -147,7 +147,7 @@ namespace Jellyfin.Api.Controllers ? _userManager.GetUserById(userId.Value) : null; - var minPremiereDate = DateTime.Now.Date.ToUniversalTime().AddDays(-1); + var minPremiereDate = DateTime.UtcNow.Date.AddDays(-1); var parentIdGuid = parentId ?? Guid.Empty; diff --git a/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs b/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs index 9103bf647..7c9e681d6 100644 --- a/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs +++ b/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs @@ -145,9 +145,9 @@ namespace MediaBrowser.LocalMetadata.Parsers if (!string.IsNullOrWhiteSpace(val)) { - if (DateTime.TryParse(val, out var added)) + if (DateTime.TryParse(val, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out var added)) { - item.DateCreated = added.ToUniversalTime(); + item.DateCreated = added; } else { @@ -535,9 +535,9 @@ namespace MediaBrowser.LocalMetadata.Parsers if (!string.IsNullOrWhiteSpace(firstAired)) { - if (DateTime.TryParseExact(firstAired, "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal, out var airDate) && airDate.Year > 1850) + if (DateTime.TryParseExact(firstAired, "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal | DateTimeStyles.AdjustToUniversal, out var airDate) && airDate.Year > 1850) { - item.PremiereDate = airDate.ToUniversalTime(); + item.PremiereDate = airDate; item.ProductionYear = airDate.Year; } } @@ -552,9 +552,9 @@ namespace MediaBrowser.LocalMetadata.Parsers if (!string.IsNullOrWhiteSpace(firstAired)) { - if (DateTime.TryParseExact(firstAired, "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal, out var airDate) && airDate.Year > 1850) + if (DateTime.TryParseExact(firstAired, "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal | DateTimeStyles.AdjustToUniversal, out var airDate) && airDate.Year > 1850) { - item.EndDate = airDate.ToUniversalTime(); + item.EndDate = airDate; } } diff --git a/MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs b/MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs index 9196fe139..a9e753726 100644 --- a/MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs +++ b/MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs @@ -63,10 +63,10 @@ namespace MediaBrowser.MediaEncoding.Probing public static DateTime? GetDictionaryDateTime(IReadOnlyDictionary tags, string key) { if (tags.TryGetValue(key, out var val) - && (DateTime.TryParse(val, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.AssumeUniversal, out var dateTime) - || DateTime.TryParseExact(val, "yyyy", DateTimeFormatInfo.CurrentInfo, DateTimeStyles.AssumeUniversal, out dateTime))) + && (DateTime.TryParse(val, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal, out var dateTime) + || DateTime.TryParseExact(val, "yyyy", DateTimeFormatInfo.CurrentInfo, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal, out dateTime))) { - return dateTime.ToUniversalTime(); + return dateTime; } return null; diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index c377f1720..26f629a31 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -1450,9 +1450,9 @@ namespace MediaBrowser.MediaEncoding.Probing // Credit to MCEBuddy: https://mcebuddy2x.codeplex.com/ // DateTime is reported along with timezone info (typically Z i.e. UTC hence assume None) - if (tags.TryGetValue("WM/MediaOriginalBroadcastDateTime", out var premiereDateString) && DateTime.TryParse(year, null, DateTimeStyles.None, out var parsedDate)) + if (tags.TryGetValue("WM/MediaOriginalBroadcastDateTime", out var premiereDateString) && DateTime.TryParse(year, null, DateTimeStyles.AdjustToUniversal, out var parsedDate)) { - video.PremiereDate = parsedDate.ToUniversalTime(); + video.PremiereDate = parsedDate; } var description = tags.GetValueOrDefault("WM/SubTitleDescription"); @@ -1468,7 +1468,7 @@ namespace MediaBrowser.MediaEncoding.Probing // e.g. -> CBeebies Bedtime Hour. The Mystery: Animated adventures of two friends who live on an island in the middle of the big city. Some of Abney and Teal's favourite objects are missing. [S] if (string.IsNullOrWhiteSpace(subTitle) && !string.IsNullOrWhiteSpace(description) - && description.AsSpan()[0..Math.Min(description.Length, MaxSubtitleDescriptionExtractionLength)].IndexOf(':') != -1) // Check within the Subtitle size limit, otherwise from description it can get too long creating an invalid filename + && description.AsSpan()[..Math.Min(description.Length, MaxSubtitleDescriptionExtractionLength)].Contains(':')) // Check within the Subtitle size limit, otherwise from description it can get too long creating an invalid filename { string[] descriptionParts = description.Split(':'); if (descriptionParts.Length > 0) diff --git a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs index b3efb8634..f7f4ea065 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs @@ -268,9 +268,9 @@ namespace MediaBrowser.XbmcMetadata.Parsers if (!string.IsNullOrWhiteSpace(val)) { - if (DateTime.TryParse(val, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out var added)) + if (DateTime.TryParse(val, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal, out var added)) { - item.DateCreated = added.ToUniversalTime(); + item.DateCreated = added; } else { @@ -384,9 +384,9 @@ namespace MediaBrowser.XbmcMetadata.Parsers var val = reader.ReadElementContentAsString(); if (!string.IsNullOrWhiteSpace(val) && userData != null) { - if (DateTime.TryParse(val, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out var added)) + if (DateTime.TryParse(val, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal, out var added)) { - userData.LastPlayedDate = added.ToUniversalTime(); + userData.LastPlayedDate = added; } else { @@ -685,9 +685,9 @@ namespace MediaBrowser.XbmcMetadata.Parsers if (!string.IsNullOrWhiteSpace(val)) { - if (DateTime.TryParseExact(val, formatString, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out var date) && date.Year > 1850) + if (DateTime.TryParseExact(val, formatString, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal, out var date) && date.Year > 1850) { - item.PremiereDate = date.ToUniversalTime(); + item.PremiereDate = date; item.ProductionYear = date.Year; } } @@ -703,9 +703,9 @@ namespace MediaBrowser.XbmcMetadata.Parsers if (!string.IsNullOrWhiteSpace(val)) { - if (DateTime.TryParseExact(val, formatString, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out var date) && date.Year > 1850) + if (DateTime.TryParseExact(val, formatString, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal, out var date) && date.Year > 1850) { - item.EndDate = date.ToUniversalTime(); + item.EndDate = date; } } diff --git a/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs b/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs index d002d5a34..d0d472e4d 100644 --- a/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs +++ b/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs @@ -69,7 +69,7 @@ namespace Jellyfin.MediaEncoding.Tests.Probing Assert.Equal("Album", res.Album); Assert.Equal(2021, res.ProductionYear); Assert.True(res.PremiereDate.HasValue); - Assert.Equal(DateTime.Parse("2021-01-01T00:00Z", DateTimeFormatInfo.CurrentInfo).ToUniversalTime(), res.PremiereDate); + Assert.Equal(DateTime.Parse("2021-01-01T00:00Z", DateTimeFormatInfo.CurrentInfo, DateTimeStyles.AdjustToUniversal), res.PremiereDate); } [Fact] @@ -85,7 +85,7 @@ namespace Jellyfin.MediaEncoding.Tests.Probing Assert.Equal("City to City", res.Album); Assert.Equal(1978, res.ProductionYear); Assert.True(res.PremiereDate.HasValue); - Assert.Equal(DateTime.Parse("1978-01-01T00:00Z", DateTimeFormatInfo.CurrentInfo).ToUniversalTime(), res.PremiereDate); + Assert.Equal(DateTime.Parse("1978-01-01T00:00Z", DateTimeFormatInfo.CurrentInfo, DateTimeStyles.AdjustToUniversal), res.PremiereDate); Assert.Contains("Electronic", res.Genres); Assert.Contains("Ambient", res.Genres); Assert.Contains("Pop", res.Genres); @@ -105,7 +105,7 @@ namespace Jellyfin.MediaEncoding.Tests.Probing Assert.Equal("Eyes wide open", res.Album); Assert.Equal(2020, res.ProductionYear); Assert.True(res.PremiereDate.HasValue); - Assert.Equal(DateTime.Parse("2020-10-26T00:00Z", DateTimeFormatInfo.CurrentInfo).ToUniversalTime(), res.PremiereDate); + Assert.Equal(DateTime.Parse("2020-10-26T00:00Z", DateTimeFormatInfo.CurrentInfo, DateTimeStyles.AdjustToUniversal), res.PremiereDate); Assert.Equal(22, res.People.Length); Assert.Equal("Krysta Youngs", res.People[0].Name); Assert.Equal(PersonType.Composer, res.People[0].Type); -- cgit v1.2.3