diff --git a/Jellyfin.Api/Controllers/PackageController.cs b/Jellyfin.Api/Controllers/PackageController.cs
new file mode 100644
index 0000000000..1fb9ab697d
--- /dev/null
+++ b/Jellyfin.Api/Controllers/PackageController.cs
@@ -0,0 +1,115 @@
+#nullable enable
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using System.Threading.Tasks;
+using Jellyfin.Api.Constants;
+using MediaBrowser.Common.Updates;
+using MediaBrowser.Model.Updates;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+
+namespace Jellyfin.Api.Controllers
+{
+ ///
+ /// Package Controller.
+ ///
+ [Route("Packages")]
+ [Authorize]
+ public class PackageController : BaseJellyfinApiController
+ {
+ private readonly IInstallationManager _installationManager;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Instance of Installation Manager.
+ public PackageController(IInstallationManager installationManager)
+ {
+ _installationManager = installationManager;
+ }
+
+ ///
+ /// Gets a package, by name or assembly guid.
+ ///
+ /// The name of the package.
+ /// The guid of the associated assembly.
+ /// Package info.
+ [HttpGet("/{Name}")]
+ [ProducesResponseType(typeof(PackageInfo), StatusCodes.Status200OK)]
+ public ActionResult GetPackageInfo(
+ [FromRoute] [Required] string name,
+ [FromQuery] string? assemblyGuid)
+ {
+ var packages = _installationManager.GetAvailablePackages().GetAwaiter().GetResult();
+ var result = _installationManager.FilterPackages(
+ packages,
+ name,
+ string.IsNullOrEmpty(assemblyGuid) ? default : Guid.Parse(assemblyGuid)).FirstOrDefault();
+
+ return Ok(result);
+ }
+
+ ///
+ /// Gets available packages.
+ ///
+ /// Packages information.
+ [HttpGet]
+ [ProducesResponseType(typeof(PackageInfo[]), StatusCodes.Status200OK)]
+ public async Task> GetPackages()
+ {
+ IEnumerable packages = await _installationManager.GetAvailablePackages().ConfigureAwait(false);
+
+ return Ok(packages.ToArray());
+ }
+
+ ///
+ /// Installs a package.
+ ///
+ /// Package name.
+ /// Guid of the associated assembly.
+ /// Optional version. Defaults to latest version.
+ /// Status.
+ [HttpPost("/Installed/{Name}")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ public async Task InstallPackage(
+ [FromRoute] [Required] string name,
+ [FromQuery] string assemblyGuid,
+ [FromQuery] string version)
+ {
+ var packages = await _installationManager.GetAvailablePackages().ConfigureAwait(false);
+ var package = _installationManager.GetCompatibleVersions(
+ packages,
+ name,
+ string.IsNullOrEmpty(assemblyGuid) ? Guid.Empty : Guid.Parse(assemblyGuid),
+ string.IsNullOrEmpty(version) ? null : Version.Parse(version)).FirstOrDefault();
+
+ if (package == null)
+ {
+ return NotFound();
+ }
+
+ await _installationManager.InstallPackage(package).ConfigureAwait(false);
+
+ return Ok();
+ }
+
+ ///
+ /// Cancels a package installation.
+ ///
+ /// Installation Id.
+ /// Status.
+ [HttpDelete("/Installing/{id}")]
+ [Authorize(Policy = Policies.RequiresElevation)]
+ public IActionResult CancelPackageInstallation(
+ [FromRoute] [Required] string id)
+ {
+ _installationManager.CancelInstallation(new Guid(id));
+
+ return Ok();
+ }
+ }
+}
diff --git a/MediaBrowser.Api/PackageService.cs b/MediaBrowser.Api/PackageService.cs
deleted file mode 100644
index 444354a992..0000000000
--- a/MediaBrowser.Api/PackageService.cs
+++ /dev/null
@@ -1,171 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.Linq;
-using System.Threading.Tasks;
-using MediaBrowser.Common.Extensions;
-using MediaBrowser.Common.Updates;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Net;
-using MediaBrowser.Model.Services;
-using MediaBrowser.Model.Updates;
-using Microsoft.Extensions.Logging;
-
-namespace MediaBrowser.Api
-{
- ///
- /// Class GetPackage
- ///
- [Route("/Packages/{Name}", "GET", Summary = "Gets a package, by name or assembly guid")]
- [Authenticated]
- public class GetPackage : IReturn
- {
- ///
- /// Gets or sets the name.
- ///
- /// The name.
- [ApiMember(Name = "Name", Description = "The name of the package", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
- public string Name { get; set; }
-
- ///
- /// Gets or sets the name.
- ///
- /// The name.
- [ApiMember(Name = "AssemblyGuid", Description = "The guid of the associated assembly", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public string AssemblyGuid { get; set; }
- }
-
- ///
- /// Class GetPackages
- ///
- [Route("/Packages", "GET", Summary = "Gets available packages")]
- [Authenticated]
- public class GetPackages : IReturn
- {
- }
-
- ///
- /// Class InstallPackage
- ///
- [Route("/Packages/Installed/{Name}", "POST", Summary = "Installs a package")]
- [Authenticated(Roles = "Admin")]
- public class InstallPackage : IReturnVoid
- {
- ///
- /// Gets or sets the name.
- ///
- /// The name.
- [ApiMember(Name = "Name", Description = "Package name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
- public string Name { get; set; }
-
- ///
- /// Gets or sets the name.
- ///
- /// The name.
- [ApiMember(Name = "AssemblyGuid", Description = "Guid of the associated assembly", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
- public string AssemblyGuid { get; set; }
-
- ///
- /// Gets or sets the version.
- ///
- /// The version.
- [ApiMember(Name = "Version", Description = "Optional version. Defaults to latest version.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
- public string Version { get; set; }
- }
-
- ///
- /// Class CancelPackageInstallation
- ///
- [Route("/Packages/Installing/{Id}", "DELETE", Summary = "Cancels a package installation")]
- [Authenticated(Roles = "Admin")]
- public class CancelPackageInstallation : IReturnVoid
- {
- ///
- /// Gets or sets the id.
- ///
- /// The id.
- [ApiMember(Name = "Id", Description = "Installation Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")]
- public string Id { get; set; }
- }
-
- ///
- /// Class PackageService
- ///
- public class PackageService : BaseApiService
- {
- private readonly IInstallationManager _installationManager;
-
- public PackageService(
- ILogger logger,
- IServerConfigurationManager serverConfigurationManager,
- IHttpResultFactory httpResultFactory,
- IInstallationManager installationManager)
- : base(logger, serverConfigurationManager, httpResultFactory)
- {
- _installationManager = installationManager;
- }
-
- ///
- /// Gets the specified request.
- ///
- /// The request.
- /// System.Object.
- public object Get(GetPackage request)
- {
- var packages = _installationManager.GetAvailablePackages().GetAwaiter().GetResult();
- var result = _installationManager.FilterPackages(
- packages,
- request.Name,
- string.IsNullOrEmpty(request.AssemblyGuid) ? default : Guid.Parse(request.AssemblyGuid)).FirstOrDefault();
-
- return ToOptimizedResult(result);
- }
-
- ///
- /// Gets the specified request.
- ///
- /// The request.
- /// System.Object.
- public async Task