PluginsController.cs 10 KB

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