PluginsController.cs 11 KB

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