using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using Jellyfin.Api.Constants;
using Jellyfin.Api.Models.PluginDtos;
using MediaBrowser.Common;
using MediaBrowser.Common.Json;
using MediaBrowser.Common.Plugins;
using MediaBrowser.Common.Updates;
using MediaBrowser.Model.Plugins;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace Jellyfin.Api.Controllers
{
///
/// Plugins controller.
///
[Authorize(Policy = Policies.DefaultAuthorization)]
public class PluginsController : BaseJellyfinApiController
{
private readonly IApplicationHost _appHost;
private readonly IInstallationManager _installationManager;
private readonly JsonSerializerOptions _serializerOptions = JsonDefaults.GetOptions();
///
/// Initializes a new instance of the class.
///
/// Instance of the interface.
/// Instance of the interface.
public PluginsController(
IApplicationHost appHost,
IInstallationManager installationManager)
{
_appHost = appHost;
_installationManager = installationManager;
}
///
/// Gets a list of currently installed plugins.
///
/// Installed plugins returned.
/// List of currently installed plugins.
[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult> GetPlugins()
{
return Ok(_appHost.Plugins.OrderBy(p => p.Name).Select(p => p.GetPluginInfo()));
}
///
/// Uninstalls a plugin.
///
/// Plugin id.
/// Plugin uninstalled.
/// Plugin not found.
/// An on success, or a if the file could not be found.
[HttpDelete("{pluginId}")]
[Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult UninstallPlugin([FromRoute] Guid pluginId)
{
var plugin = _appHost.Plugins.FirstOrDefault(p => p.Id == pluginId);
if (plugin == null)
{
return NotFound();
}
_installationManager.UninstallPlugin(plugin);
return NoContent();
}
///
/// Gets plugin configuration.
///
/// Plugin id.
/// Plugin configuration returned.
/// Plugin not found or plugin configuration not found.
/// Plugin configuration.
[HttpGet("{pluginId}/Configuration")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult GetPluginConfiguration([FromRoute] Guid pluginId)
{
if (!(_appHost.Plugins.FirstOrDefault(p => p.Id == pluginId) is IHasPluginConfiguration plugin))
{
return NotFound();
}
return plugin.Configuration;
}
///
/// Updates plugin configuration.
///
///
/// Accepts plugin configuration as JSON body.
///
/// Plugin id.
/// Plugin configuration updated.
/// Plugin not found or plugin does not have configuration.
///
/// A that represents the asynchronous operation to update plugin configuration.
/// The task result contains an indicating success, or
/// when plugin not found or plugin doesn't have configuration.
///
[HttpPost("{pluginId}/Configuration")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task UpdatePluginConfiguration([FromRoute] Guid pluginId)
{
if (!(_appHost.Plugins.FirstOrDefault(p => p.Id == pluginId) is IHasPluginConfiguration plugin))
{
return NotFound();
}
var configuration = (BasePluginConfiguration)await JsonSerializer.DeserializeAsync(Request.Body, plugin.ConfigurationType, _serializerOptions)
.ConfigureAwait(false);
plugin.UpdateConfiguration(configuration);
return NoContent();
}
///
/// Get plugin security info.
///
/// Plugin security info returned.
/// Plugin security info.
[Obsolete("This endpoint should not be used.")]
[HttpGet("SecurityInfo")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult GetPluginSecurityInfo()
{
return new PluginSecurityInfo
{
IsMbSupporter = true,
SupporterKey = "IAmTotallyLegit"
};
}
///
/// Updates plugin security info.
///
/// Plugin security info.
/// Plugin security info updated.
/// An .
[Obsolete("This endpoint should not be used.")]
[HttpPost("SecurityInfo")]
[Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult UpdatePluginSecurityInfo([FromBody, Required] PluginSecurityInfo pluginSecurityInfo)
{
return NoContent();
}
///
/// Gets registration status for a feature.
///
/// Feature name.
/// Registration status returned.
/// Mb registration record.
[Obsolete("This endpoint should not be used.")]
[HttpPost("RegistrationRecords/{name}")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult GetRegistrationStatus([FromRoute] string? name)
{
return new MBRegistrationRecord
{
IsRegistered = true,
RegChecked = true,
TrialVersion = false,
IsValid = true,
RegError = false
};
}
///
/// Gets registration status for a feature.
///
/// Feature name.
/// Not implemented.
/// Not Implemented.
/// This endpoint is not implemented.
[Obsolete("Paid plugins are not supported")]
[HttpGet("Registrations/{name}")]
[ProducesResponseType(StatusCodes.Status501NotImplemented)]
public ActionResult GetRegistration([FromRoute] string? name)
{
// TODO Once we have proper apps and plugins and decide to break compatibility with paid plugins,
// delete all these registration endpoints. They are only kept for compatibility.
throw new NotImplementedException();
}
}
}