PluginsController.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel.DataAnnotations;
  4. using System.Diagnostics.CodeAnalysis;
  5. using System.IO;
  6. using System.Linq;
  7. using System.Text.Json;
  8. using System.Threading.Tasks;
  9. using Jellyfin.Api.Attributes;
  10. using Jellyfin.Api.Constants;
  11. using Jellyfin.Extensions.Json;
  12. using MediaBrowser.Common.Plugins;
  13. using MediaBrowser.Common.Updates;
  14. using MediaBrowser.Model.Net;
  15. using MediaBrowser.Model.Plugins;
  16. using Microsoft.AspNetCore.Authorization;
  17. using Microsoft.AspNetCore.Http;
  18. using Microsoft.AspNetCore.Mvc;
  19. namespace Jellyfin.Api.Controllers;
  20. /// <summary>
  21. /// Plugins controller.
  22. /// </summary>
  23. [Authorize(Policy = Policies.DefaultAuthorization)]
  24. public class PluginsController : BaseJellyfinApiController
  25. {
  26. private readonly IInstallationManager _installationManager;
  27. private readonly IPluginManager _pluginManager;
  28. private readonly JsonSerializerOptions _serializerOptions;
  29. /// <summary>
  30. /// Initializes a new instance of the <see cref="PluginsController"/> class.
  31. /// </summary>
  32. /// <param name="installationManager">Instance of the <see cref="IInstallationManager"/> interface.</param>
  33. /// <param name="pluginManager">Instance of the <see cref="IPluginManager"/> interface.</param>
  34. public PluginsController(
  35. IInstallationManager installationManager,
  36. IPluginManager pluginManager)
  37. {
  38. _installationManager = installationManager;
  39. _pluginManager = pluginManager;
  40. _serializerOptions = JsonDefaults.Options;
  41. }
  42. /// <summary>
  43. /// Gets a list of currently installed plugins.
  44. /// </summary>
  45. /// <response code="200">Installed plugins returned.</response>
  46. /// <returns>List of currently installed plugins.</returns>
  47. [HttpGet]
  48. [ProducesResponseType(StatusCodes.Status200OK)]
  49. public ActionResult<IEnumerable<PluginInfo>> GetPlugins()
  50. {
  51. return Ok(_pluginManager.Plugins
  52. .OrderBy(p => p.Name)
  53. .Select(p => p.GetPluginInfo()));
  54. }
  55. /// <summary>
  56. /// Enables a disabled plugin.
  57. /// </summary>
  58. /// <param name="pluginId">Plugin id.</param>
  59. /// <param name="version">Plugin version.</param>
  60. /// <response code="204">Plugin enabled.</response>
  61. /// <response code="404">Plugin not found.</response>
  62. /// <returns>An <see cref="NoContentResult"/> on success, or a <see cref="NotFoundResult"/> if the plugin could not be found.</returns>
  63. [HttpPost("{pluginId}/{version}/Enable")]
  64. [Authorize(Policy = Policies.RequiresElevation)]
  65. [ProducesResponseType(StatusCodes.Status204NoContent)]
  66. [ProducesResponseType(StatusCodes.Status404NotFound)]
  67. public ActionResult EnablePlugin([FromRoute, Required] Guid pluginId, [FromRoute, Required] Version version)
  68. {
  69. var plugin = _pluginManager.GetPlugin(pluginId, version);
  70. if (plugin is null)
  71. {
  72. return NotFound();
  73. }
  74. _pluginManager.EnablePlugin(plugin);
  75. return NoContent();
  76. }
  77. /// <summary>
  78. /// Disable a plugin.
  79. /// </summary>
  80. /// <param name="pluginId">Plugin id.</param>
  81. /// <param name="version">Plugin version.</param>
  82. /// <response code="204">Plugin disabled.</response>
  83. /// <response code="404">Plugin not found.</response>
  84. /// <returns>An <see cref="NoContentResult"/> on success, or a <see cref="NotFoundResult"/> if the plugin could not be found.</returns>
  85. [HttpPost("{pluginId}/{version}/Disable")]
  86. [Authorize(Policy = Policies.RequiresElevation)]
  87. [ProducesResponseType(StatusCodes.Status204NoContent)]
  88. [ProducesResponseType(StatusCodes.Status404NotFound)]
  89. public ActionResult DisablePlugin([FromRoute, Required] Guid pluginId, [FromRoute, Required] Version version)
  90. {
  91. var plugin = _pluginManager.GetPlugin(pluginId, version);
  92. if (plugin is null)
  93. {
  94. return NotFound();
  95. }
  96. _pluginManager.DisablePlugin(plugin);
  97. return NoContent();
  98. }
  99. /// <summary>
  100. /// Uninstalls a plugin by version.
  101. /// </summary>
  102. /// <param name="pluginId">Plugin id.</param>
  103. /// <param name="version">Plugin version.</param>
  104. /// <response code="204">Plugin uninstalled.</response>
  105. /// <response code="404">Plugin not found.</response>
  106. /// <returns>An <see cref="NoContentResult"/> on success, or a <see cref="NotFoundResult"/> if the plugin could not be found.</returns>
  107. [HttpDelete("{pluginId}/{version}")]
  108. [Authorize(Policy = Policies.RequiresElevation)]
  109. [ProducesResponseType(StatusCodes.Status204NoContent)]
  110. [ProducesResponseType(StatusCodes.Status404NotFound)]
  111. public ActionResult UninstallPluginByVersion([FromRoute, Required] Guid pluginId, [FromRoute, Required] Version version)
  112. {
  113. var plugin = _pluginManager.GetPlugin(pluginId, version);
  114. if (plugin is null)
  115. {
  116. return NotFound();
  117. }
  118. _installationManager.UninstallPlugin(plugin);
  119. return NoContent();
  120. }
  121. /// <summary>
  122. /// Uninstalls a plugin.
  123. /// </summary>
  124. /// <param name="pluginId">Plugin id.</param>
  125. /// <response code="204">Plugin uninstalled.</response>
  126. /// <response code="404">Plugin not found.</response>
  127. /// <returns>An <see cref="NoContentResult"/> on success, or a <see cref="NotFoundResult"/> if the plugin could not be found.</returns>
  128. [HttpDelete("{pluginId}")]
  129. [Authorize(Policy = Policies.RequiresElevation)]
  130. [ProducesResponseType(StatusCodes.Status204NoContent)]
  131. [ProducesResponseType(StatusCodes.Status404NotFound)]
  132. [Obsolete("Please use the UninstallPluginByVersion API.")]
  133. public ActionResult UninstallPlugin([FromRoute, Required] Guid pluginId)
  134. {
  135. // If no version is given, return the current instance.
  136. var plugins = _pluginManager.Plugins.Where(p => p.Id.Equals(pluginId)).ToList();
  137. // Select the un-instanced one first.
  138. var plugin = plugins.FirstOrDefault(p => p.Instance is null) ?? plugins.OrderBy(p => p.Manifest.Status).FirstOrDefault();
  139. if (plugin is not null)
  140. {
  141. _installationManager.UninstallPlugin(plugin);
  142. return NoContent();
  143. }
  144. return NotFound();
  145. }
  146. /// <summary>
  147. /// Gets plugin configuration.
  148. /// </summary>
  149. /// <param name="pluginId">Plugin id.</param>
  150. /// <response code="200">Plugin configuration returned.</response>
  151. /// <response code="404">Plugin not found or plugin configuration not found.</response>
  152. /// <returns>Plugin configuration.</returns>
  153. [HttpGet("{pluginId}/Configuration")]
  154. [ProducesResponseType(StatusCodes.Status200OK)]
  155. [ProducesResponseType(StatusCodes.Status404NotFound)]
  156. public ActionResult<BasePluginConfiguration> GetPluginConfiguration([FromRoute, Required] Guid pluginId)
  157. {
  158. var plugin = _pluginManager.GetPlugin(pluginId);
  159. if (plugin?.Instance is IHasPluginConfiguration configPlugin)
  160. {
  161. return configPlugin.Configuration;
  162. }
  163. return NotFound();
  164. }
  165. /// <summary>
  166. /// Updates plugin configuration.
  167. /// </summary>
  168. /// <remarks>
  169. /// Accepts plugin configuration as JSON body.
  170. /// </remarks>
  171. /// <param name="pluginId">Plugin id.</param>
  172. /// <response code="204">Plugin configuration updated.</response>
  173. /// <response code="404">Plugin not found or plugin does not have configuration.</response>
  174. /// <returns>An <see cref="NoContentResult"/> on success, or a <see cref="NotFoundResult"/> if the plugin could not be found.</returns>
  175. [HttpPost("{pluginId}/Configuration")]
  176. [ProducesResponseType(StatusCodes.Status204NoContent)]
  177. [ProducesResponseType(StatusCodes.Status404NotFound)]
  178. public async Task<ActionResult> UpdatePluginConfiguration([FromRoute, Required] Guid pluginId)
  179. {
  180. var plugin = _pluginManager.GetPlugin(pluginId);
  181. if (plugin?.Instance is not IHasPluginConfiguration configPlugin)
  182. {
  183. return NotFound();
  184. }
  185. var configuration = (BasePluginConfiguration?)await JsonSerializer.DeserializeAsync(Request.Body, configPlugin.ConfigurationType, _serializerOptions)
  186. .ConfigureAwait(false);
  187. if (configuration is not null)
  188. {
  189. configPlugin.UpdateConfiguration(configuration);
  190. }
  191. return NoContent();
  192. }
  193. /// <summary>
  194. /// Gets a plugin's image.
  195. /// </summary>
  196. /// <param name="pluginId">Plugin id.</param>
  197. /// <param name="version">Plugin version.</param>
  198. /// <response code="200">Plugin image returned.</response>
  199. /// <returns>Plugin's image.</returns>
  200. [HttpGet("{pluginId}/{version}/Image")]
  201. [ProducesResponseType(StatusCodes.Status200OK)]
  202. [ProducesResponseType(StatusCodes.Status404NotFound)]
  203. [ProducesImageFile]
  204. [AllowAnonymous]
  205. public ActionResult GetPluginImage([FromRoute, Required] Guid pluginId, [FromRoute, Required] Version version)
  206. {
  207. var plugin = _pluginManager.GetPlugin(pluginId, version);
  208. if (plugin is null)
  209. {
  210. return NotFound();
  211. }
  212. var imagePath = Path.Combine(plugin.Path, plugin.Manifest.ImagePath ?? string.Empty);
  213. if (plugin.Manifest.ImagePath is null || !System.IO.File.Exists(imagePath))
  214. {
  215. return NotFound();
  216. }
  217. imagePath = Path.Combine(plugin.Path, plugin.Manifest.ImagePath);
  218. return PhysicalFile(imagePath, MimeTypes.GetMimeType(imagePath));
  219. }
  220. /// <summary>
  221. /// Gets a plugin's manifest.
  222. /// </summary>
  223. /// <param name="pluginId">Plugin id.</param>
  224. /// <response code="204">Plugin manifest returned.</response>
  225. /// <response code="404">Plugin not found.</response>
  226. /// <returns>A <see cref="PluginManifest"/> on success, or a <see cref="NotFoundResult"/> if the plugin could not be found.</returns>
  227. [HttpPost("{pluginId}/Manifest")]
  228. [ProducesResponseType(StatusCodes.Status204NoContent)]
  229. [ProducesResponseType(StatusCodes.Status404NotFound)]
  230. public ActionResult<PluginManifest> GetPluginManifest([FromRoute, Required] Guid pluginId)
  231. {
  232. var plugin = _pluginManager.GetPlugin(pluginId);
  233. if (plugin is not null)
  234. {
  235. return plugin.Manifest;
  236. }
  237. return NotFound();
  238. }
  239. }