< Summary - Jellyfin

Information
Class: Jellyfin.Api.Controllers.PackageController
Assembly: Jellyfin.Api
File(s): /srv/git/jellyfin/Jellyfin.Api/Controllers/PackageController.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 50
Coverable lines: 50
Total lines: 193
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 22
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 5/16/2026 - 12:15:55 AM Line coverage: 0% (0/38) Branch coverage: 0% (0/10) Total lines: 16410/29/2026 - 12:17:11 AM Line coverage: 0% (0/50) Branch coverage: 0% (0/22) Total lines: 193 5/16/2026 - 12:15:55 AM Line coverage: 0% (0/38) Branch coverage: 0% (0/10) Total lines: 16410/29/2026 - 12:17:11 AM Line coverage: 0% (0/50) Branch coverage: 0% (0/22) Total lines: 193

Coverage delta

Coverage delta 1 -1

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%210%
GetPackageInfo()0%2040%
GetPackages()100%210%
InstallPackage()0%110100%
CancelPackageInstallation(...)100%210%
GetRepositories()100%210%
SetRepositories(...)100%210%
IsBundledPlugin(...)0%7280%

File(s)

/srv/git/jellyfin/Jellyfin.Api/Controllers/PackageController.cs

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.ComponentModel.DataAnnotations;
 4using System.Linq;
 5using System.Threading.Tasks;
 6using Jellyfin.Extensions;
 7using MediaBrowser.Common.Api;
 8using MediaBrowser.Common.Plugins;
 9using MediaBrowser.Common.Updates;
 10using MediaBrowser.Controller.Configuration;
 11using MediaBrowser.Model.Updates;
 12using Microsoft.AspNetCore.Authorization;
 13using Microsoft.AspNetCore.Http;
 14using Microsoft.AspNetCore.Mvc;
 15
 16namespace Jellyfin.Api.Controllers;
 17
 18/// <summary>
 19/// Package Controller.
 20/// </summary>
 21[Route("")]
 22[Authorize(Policy = Policies.RequiresElevation)]
 23[Tags("Plugin")]
 24public class PackageController : BaseJellyfinApiController
 25{
 26    private readonly IInstallationManager _installationManager;
 27    private readonly IServerConfigurationManager _serverConfigurationManager;
 28    private readonly IPluginManager _pluginManager;
 29
 30    /// <summary>
 31    /// Initializes a new instance of the <see cref="PackageController"/> class.
 32    /// </summary>
 33    /// <param name="installationManager">Instance of the <see cref="IInstallationManager"/> interface.</param>
 34    /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</p
 35    /// <param name="pluginManager">Instance of the <see cref="IPluginManager"/> interface.</param>
 036    public PackageController(
 037        IInstallationManager installationManager,
 038        IServerConfigurationManager serverConfigurationManager,
 039        IPluginManager pluginManager)
 40    {
 041        _installationManager = installationManager;
 042        _serverConfigurationManager = serverConfigurationManager;
 043        _pluginManager = pluginManager;
 044    }
 45
 46    /// <summary>
 47    /// Gets a package by name or assembly GUID.
 48    /// </summary>
 49    /// <param name="name">The name of the package.</param>
 50    /// <param name="assemblyGuid">The GUID of the associated assembly.</param>
 51    /// <response code="200">Package retrieved.</response>
 52    /// <returns>A <see cref="PackageInfo"/> containing package information.</returns>
 53    [HttpGet("Packages/{name}")]
 54    [ProducesResponseType(StatusCodes.Status200OK)]
 55    public async Task<ActionResult<PackageInfo>> GetPackageInfo(
 56        [FromRoute, Required] string name,
 57        [FromQuery] Guid? assemblyGuid)
 58    {
 59        // Plugins bundled with the server are not published to any repository, so querying
 60        // the configured repositories for them can only ever fail, and does so slowly.
 061        if (IsBundledPlugin(name, assemblyGuid))
 62        {
 063            return NotFound();
 64        }
 65
 066        var packages = await _installationManager.GetAvailablePackages().ConfigureAwait(false);
 067        var result = _installationManager.FilterPackages(
 068                packages,
 069                name,
 070                assemblyGuid ?? default)
 071            .FirstOrDefault();
 72
 073        if (result is null)
 74        {
 075            return NotFound();
 76        }
 77
 078        return result;
 079    }
 80
 81    /// <summary>
 82    /// Gets available packages.
 83    /// </summary>
 84    /// <response code="200">Available packages returned.</response>
 85    /// <returns>An <see cref="PackageInfo"/> containing available packages information.</returns>
 86    [HttpGet("Packages")]
 87    [ProducesResponseType(StatusCodes.Status200OK)]
 88    public async Task<IEnumerable<PackageInfo>> GetPackages()
 89    {
 090        IEnumerable<PackageInfo> packages = await _installationManager.GetAvailablePackages().ConfigureAwait(false);
 91
 092        return packages;
 093    }
 94
 95    /// <summary>
 96    /// Installs a package.
 97    /// </summary>
 98    /// <param name="name">Package name.</param>
 99    /// <param name="assemblyGuid">GUID of the associated assembly.</param>
 100    /// <param name="version">Optional version. Defaults to latest version.</param>
 101    /// <param name="repositoryUrl">Optional. Specify the repository to install from.</param>
 102    /// <response code="204">Package found.</response>
 103    /// <response code="404">Package not found.</response>
 104    /// <returns>A <see cref="NoContentResult"/> on success, or a <see cref="NotFoundResult"/> if the package could not 
 105    [HttpPost("Packages/Installed/{name}")]
 106    [ProducesResponseType(StatusCodes.Status204NoContent)]
 107    [ProducesResponseType(StatusCodes.Status404NotFound)]
 108    public async Task<ActionResult> InstallPackage(
 109        [FromRoute, Required] string name,
 110        [FromQuery] Guid? assemblyGuid,
 111        [FromQuery] string? version,
 112        [FromQuery] string? repositoryUrl)
 113    {
 0114        if (IsBundledPlugin(name, assemblyGuid))
 115        {
 0116            return NotFound();
 117        }
 118
 0119        var packages = await _installationManager.GetAvailablePackages().ConfigureAwait(false);
 0120        if (!string.IsNullOrEmpty(repositoryUrl))
 121        {
 0122            packages = packages.Where(p => p.Versions.Any(q => q.RepositoryUrl.Equals(repositoryUrl, StringComparison.Or
 0123                .ToList();
 124        }
 125
 0126        var package = _installationManager.GetCompatibleVersions(
 0127                packages,
 0128                name,
 0129                assemblyGuid ?? Guid.Empty,
 0130                specificVersion: string.IsNullOrEmpty(version) ? null : Version.Parse(version))
 0131            .FirstOrDefault();
 132
 0133        if (package is null)
 134        {
 0135            return NotFound();
 136        }
 137
 0138        await _installationManager.InstallPackage(package).ConfigureAwait(false);
 139
 0140        return NoContent();
 0141    }
 142
 143    /// <summary>
 144    /// Cancels a package installation.
 145    /// </summary>
 146    /// <param name="packageId">Installation Id.</param>
 147    /// <response code="204">Installation cancelled.</response>
 148    /// <returns>A <see cref="NoContentResult"/> on successfully cancelling a package installation.</returns>
 149    [HttpDelete("Packages/Installing/{packageId}")]
 150    [ProducesResponseType(StatusCodes.Status204NoContent)]
 151    public ActionResult CancelPackageInstallation(
 152        [FromRoute, Required] Guid packageId)
 153    {
 0154        _installationManager.CancelInstallation(packageId);
 0155        return NoContent();
 156    }
 157
 158    /// <summary>
 159    /// Gets all package repositories.
 160    /// </summary>
 161    /// <response code="200">Package repositories returned.</response>
 162    /// <returns>An <see cref="OkResult"/> containing the list of package repositories.</returns>
 163    [HttpGet("Repositories")]
 164    [ProducesResponseType(StatusCodes.Status200OK)]
 165    public ActionResult<IEnumerable<RepositoryInfo>> GetRepositories()
 166    {
 0167        return Ok(_serverConfigurationManager.Configuration.PluginRepositories.AsEnumerable());
 168    }
 169
 170    /// <summary>
 171    /// Sets the enabled and existing package repositories.
 172    /// </summary>
 173    /// <param name="repositoryInfos">The list of package repositories.</param>
 174    /// <response code="204">Package repositories saved.</response>
 175    /// <returns>A <see cref="NoContentResult"/>.</returns>
 176    [HttpPost("Repositories")]
 177    [ProducesResponseType(StatusCodes.Status204NoContent)]
 178    public ActionResult SetRepositories([FromBody, Required] RepositoryInfo[] repositoryInfos)
 179    {
 0180        _serverConfigurationManager.Configuration.PluginRepositories = repositoryInfos;
 0181        _serverConfigurationManager.SaveConfiguration();
 0182        return NoContent();
 183    }
 184
 185    private bool IsBundledPlugin(string name, Guid? assemblyGuid)
 186    {
 0187        var plugin = assemblyGuid is Guid id && !id.IsEmpty()
 0188            ? _pluginManager.GetPlugin(id)
 0189            : _pluginManager.Plugins.FirstOrDefault(p => p.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
 190
 0191        return plugin?.Instance?.CanUninstall == false;
 192    }
 193}