From 2d5084ab290c664cebfc2767f644adb2f63105cd Mon Sep 17 00:00:00 2001 From: copyrights Date: Sat, 19 Apr 2025 21:51:57 +0200 Subject: [PATCH] The Nextcloud/WebDAV Jellyfin plugin skeleton has been created --- Configuration/PluginConfiguration.cs | 50 ++++++++++++++++++ Configuration/configPage.html | 79 ++++++++++++++++++++++++++++ Jellyfin.Plugin.Webdav.csproj | 30 +++++++++++ Plugin.cs | 51 ++++++++++++++++++ ServiceRegistrator.cs | 20 +++++++ StreamingController.cs | 35 ++++++++++++ WebDavClientService.cs | 47 +++++++++++++++++ WebDavHostedService.cs | 37 +++++++++++++ 8 files changed, 349 insertions(+) create mode 100644 Configuration/PluginConfiguration.cs create mode 100644 Configuration/configPage.html create mode 100644 Jellyfin.Plugin.Webdav.csproj create mode 100644 Plugin.cs create mode 100644 ServiceRegistrator.cs create mode 100644 StreamingController.cs create mode 100644 WebDavClientService.cs create mode 100644 WebDavHostedService.cs diff --git a/Configuration/PluginConfiguration.cs b/Configuration/PluginConfiguration.cs new file mode 100644 index 0000000..f440d5e --- /dev/null +++ b/Configuration/PluginConfiguration.cs @@ -0,0 +1,50 @@ +using MediaBrowser.Model.Plugins; + +namespace Jellyfin.Plugin.Webdav.Configuration +{ + /// + /// Configuration options for the WebDAV plugin. + /// + public class PluginConfiguration : BasePluginConfiguration + { + public PluginConfiguration() + { + BaseUrl = ""; + Username = ""; + Password = ""; + RootPath = "/"; + CacheDirectory = ""; + CacheSizeMb = 100; + } + + /// + /// The base URL of the WebDAV endpoint. + /// + public string BaseUrl { get; set; } + + /// + /// Username for authentication. + /// + public string Username { get; set; } + + /// + /// Password for authentication. + /// + public string Password { get; set; } + + /// + /// Root path within the WebDAV share. + /// + public string RootPath { get; set; } + + /// + /// Local directory to use for cache files. + /// + public string CacheDirectory { get; set; } + + /// + /// Maximum size of cache in megabytes. + /// + public int CacheSizeMb { get; set; } + } +} diff --git a/Configuration/configPage.html b/Configuration/configPage.html new file mode 100644 index 0000000..23f024e --- /dev/null +++ b/Configuration/configPage.html @@ -0,0 +1,79 @@ + + + + + Template + + +
+
+
+
+
+ + +
+
+ + +
A Description
+
+
+ +
+
+ + +
Another Description
+
+
+ +
+
+
+
+ +
+ + diff --git a/Jellyfin.Plugin.Webdav.csproj b/Jellyfin.Plugin.Webdav.csproj new file mode 100644 index 0000000..0c56ea9 --- /dev/null +++ b/Jellyfin.Plugin.Webdav.csproj @@ -0,0 +1,30 @@ + + + + net8.0 + Jellyfin.Plugin.Webdav + true + true + enable + AllEnabledByDefault + ../jellyfin.ruleset + + + + + + + + + + + + + + + + + + + + diff --git a/Plugin.cs b/Plugin.cs new file mode 100644 index 0000000..ecd5691 --- /dev/null +++ b/Plugin.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using Jellyfin.Plugin.Webdav.Configuration; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Plugins; +using MediaBrowser.Model.Plugins; +using MediaBrowser.Model.Serialization; + +namespace Jellyfin.Plugin.Webdav; + +/// +/// The main plugin. +/// +public class Plugin : BasePlugin, IHasWebPages +{ + /// + /// Initializes a new instance of the class. + /// + /// Instance of the interface. + /// Instance of the interface. + public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer) + : base(applicationPaths, xmlSerializer) + { + Instance = this; + } + + /// + public override string Name => "WebDAV"; + + /// + public override Guid Id => Guid.Parse("5db89aeb-14ad-450b-bcf2-ae235ebbe299"); + + /// + /// Gets the current plugin instance. + /// + public static Plugin? Instance { get; private set; } + + /// + public IEnumerable GetPages() + { + return + [ + new PluginPageInfo + { + Name = Name, + EmbeddedResourcePath = string.Format(CultureInfo.InvariantCulture, "{0}.Configuration.configPage.html", GetType().Namespace) + } + ]; + } +} diff --git a/ServiceRegistrator.cs b/ServiceRegistrator.cs new file mode 100644 index 0000000..15379ff --- /dev/null +++ b/ServiceRegistrator.cs @@ -0,0 +1,20 @@ +using Microsoft.Extensions.DependencyInjection; +using MediaBrowser.Controller.Plugins; +using MediaBrowser.Controller.Library; +using Microsoft.Extensions.Hosting; + +namespace Jellyfin.Plugin.Webdav +{ + /// + /// Registers plugin services with the DI container. + /// + public class ServiceRegistrator : IPluginServiceRegistrator + { + /// + public void RegisterServices(IServiceCollection services, IServerApplicationHost applicationHost) + { + services.AddSingleton(); + services.AddSingleton(); + } + } +} \ No newline at end of file diff --git a/StreamingController.cs b/StreamingController.cs new file mode 100644 index 0000000..8176a95 --- /dev/null +++ b/StreamingController.cs @@ -0,0 +1,35 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Jellyfin.Plugin.Webdav; +using Jellyfin.Plugin.Webdav.Configuration; + +namespace Jellyfin.Plugin.Webdav.Controllers +{ + /// + /// Controller for streaming WebDAV media. + /// + [Route("WebDav/[controller]")] + public class StreamController : ControllerBase + { + private readonly WebDavClientService _webDavClientService; + private readonly PluginConfiguration _config; + + public StreamController(WebDavClientService webDavClientService, PluginConfiguration config) + { + _webDavClientService = webDavClientService; + _config = config; + } + + /// + /// Streams the file at the given WebDAV path. + /// + /// The relative path under the configured root. + [HttpGet("{*path}")] + public async Task Get(string path) + { + var fullPath = $"{_config.RootPath.TrimEnd('/')}/{path}"; + var stream = await _webDavClientService.GetStreamAsync(fullPath).ConfigureAwait(false); + return File(stream, "application/octet-stream"); + } + } +} \ No newline at end of file diff --git a/WebDavClientService.cs b/WebDavClientService.cs new file mode 100644 index 0000000..e28a156 --- /dev/null +++ b/WebDavClientService.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Threading.Tasks; +using WebDav.Client; + +namespace Jellyfin.Plugin.Webdav +{ + /// + /// Service for interacting with WebDAV endpoints. + /// + public class WebDavClientService + { + private readonly PluginConfiguration _config; + private readonly WebDavClient _client; + + public WebDavClientService(PluginConfiguration config) + { + _config = config; + var parameters = new WebDavClientParams + { + BaseAddress = new Uri(_config.BaseUrl), + Credentials = new NetworkCredential(_config.Username, _config.Password) + }; + _client = new WebDavClient(parameters); + } + + /// + /// List resources at the specified WebDAV path. + /// + public async Task> ListAsync(string path) + { + var response = await _client.Propfind(path).ConfigureAwait(false); + return response.Resources; + } + + /// + /// Get a raw file stream from the WebDAV endpoint. + /// + public async Task GetStreamAsync(string path) + { + var response = await _client.GetRawFile(path).ConfigureAwait(false); + return response.Stream; + } + } +} \ No newline at end of file diff --git a/WebDavHostedService.cs b/WebDavHostedService.cs new file mode 100644 index 0000000..9522fd4 --- /dev/null +++ b/WebDavHostedService.cs @@ -0,0 +1,37 @@ +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Hosting; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Configuration; + +namespace Jellyfin.Plugin.Webdav +{ + /// + /// Hosted service that registers the WebDAV source as a virtual folder on server startup. + /// + public class WebDavHostedService : IHostedService + { + private readonly ILibraryManager _libraryManager; + + public WebDavHostedService(ILibraryManager libraryManager) + { + _libraryManager = libraryManager; + } + + public async Task StartAsync(CancellationToken cancellationToken) + { + // Register a virtual folder for WebDAV remote library + var options = new LibraryOptions + { + EnableInvertFolderHierarchy = false + }; + + // Name must match plugin display name + await _libraryManager + .AddVirtualFolder("WebDAV", null, options, true) + .ConfigureAwait(false); + } + + public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; + } +} \ No newline at end of file