The Nextcloud/WebDAV Jellyfin plugin skeleton has been created
This commit is contained in:
commit
2d5084ab29
50
Configuration/PluginConfiguration.cs
Normal file
50
Configuration/PluginConfiguration.cs
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
using MediaBrowser.Model.Plugins;
|
||||||
|
|
||||||
|
namespace Jellyfin.Plugin.Webdav.Configuration
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Configuration options for the WebDAV plugin.
|
||||||
|
/// </summary>
|
||||||
|
public class PluginConfiguration : BasePluginConfiguration
|
||||||
|
{
|
||||||
|
public PluginConfiguration()
|
||||||
|
{
|
||||||
|
BaseUrl = "";
|
||||||
|
Username = "";
|
||||||
|
Password = "";
|
||||||
|
RootPath = "/";
|
||||||
|
CacheDirectory = "";
|
||||||
|
CacheSizeMb = 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The base URL of the WebDAV endpoint.
|
||||||
|
/// </summary>
|
||||||
|
public string BaseUrl { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Username for authentication.
|
||||||
|
/// </summary>
|
||||||
|
public string Username { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Password for authentication.
|
||||||
|
/// </summary>
|
||||||
|
public string Password { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Root path within the WebDAV share.
|
||||||
|
/// </summary>
|
||||||
|
public string RootPath { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Local directory to use for cache files.
|
||||||
|
/// </summary>
|
||||||
|
public string CacheDirectory { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Maximum size of cache in megabytes.
|
||||||
|
/// </summary>
|
||||||
|
public int CacheSizeMb { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
79
Configuration/configPage.html
Normal file
79
Configuration/configPage.html
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Template</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="TemplateConfigPage" data-role="page" class="page type-interior pluginConfigurationPage" data-require="emby-input,emby-button,emby-select,emby-checkbox">
|
||||||
|
<div data-role="content">
|
||||||
|
<div class="content-primary">
|
||||||
|
<form id="TemplateConfigForm">
|
||||||
|
<div class="selectContainer">
|
||||||
|
<label class="selectLabel" for="Options">Several Options</label>
|
||||||
|
<select is="emby-select" id="Options" name="Options" class="emby-select-withcolor emby-select">
|
||||||
|
<option id="optOneOption" value="OneOption">One Option</option>
|
||||||
|
<option id="optAnotherOption" value="AnotherOption">Another Option</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="inputContainer">
|
||||||
|
<label class="inputLabel inputLabelUnfocused" for="AnInteger">An Integer</label>
|
||||||
|
<input id="AnInteger" name="AnInteger" type="number" is="emby-input" min="0" />
|
||||||
|
<div class="fieldDescription">A Description</div>
|
||||||
|
</div>
|
||||||
|
<div class="checkboxContainer checkboxContainer-withDescription">
|
||||||
|
<label class="emby-checkbox-label">
|
||||||
|
<input id="TrueFalseSetting" name="TrueFalseCheckBox" type="checkbox" is="emby-checkbox" />
|
||||||
|
<span>A Checkbox</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="inputContainer">
|
||||||
|
<label class="inputLabel inputLabelUnfocused" for="AString">A String</label>
|
||||||
|
<input id="AString" name="AString" type="text" is="emby-input" />
|
||||||
|
<div class="fieldDescription">Another Description</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button is="emby-button" type="submit" class="raised button-submit block emby-button">
|
||||||
|
<span>Save</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script type="text/javascript">
|
||||||
|
var TemplateConfig = {
|
||||||
|
pluginUniqueId: 'eb5d7894-8eef-4b36-aa6f-5d124e828ce1'
|
||||||
|
};
|
||||||
|
|
||||||
|
document.querySelector('#TemplateConfigPage')
|
||||||
|
.addEventListener('pageshow', function() {
|
||||||
|
Dashboard.showLoadingMsg();
|
||||||
|
ApiClient.getPluginConfiguration(TemplateConfig.pluginUniqueId).then(function (config) {
|
||||||
|
document.querySelector('#Options').value = config.Options;
|
||||||
|
document.querySelector('#AnInteger').value = config.AnInteger;
|
||||||
|
document.querySelector('#TrueFalseSetting').checked = config.TrueFalseSetting;
|
||||||
|
document.querySelector('#AString').value = config.AString;
|
||||||
|
Dashboard.hideLoadingMsg();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelector('#TemplateConfigForm')
|
||||||
|
.addEventListener('submit', function(e) {
|
||||||
|
Dashboard.showLoadingMsg();
|
||||||
|
ApiClient.getPluginConfiguration(TemplateConfig.pluginUniqueId).then(function (config) {
|
||||||
|
config.Options = document.querySelector('#Options').value;
|
||||||
|
config.AnInteger = document.querySelector('#AnInteger').value;
|
||||||
|
config.TrueFalseSetting = document.querySelector('#TrueFalseSetting').checked;
|
||||||
|
config.AString = document.querySelector('#AString').value;
|
||||||
|
ApiClient.updatePluginConfiguration(TemplateConfig.pluginUniqueId, config).then(function (result) {
|
||||||
|
Dashboard.processPluginConfigurationUpdateResult(result);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
30
Jellyfin.Plugin.Webdav.csproj
Normal file
30
Jellyfin.Plugin.Webdav.csproj
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<RootNamespace>Jellyfin.Plugin.Webdav</RootNamespace>
|
||||||
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
|
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<AnalysisMode>AllEnabledByDefault</AnalysisMode>
|
||||||
|
<CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Jellyfin.Controller" Version="10.9.11" />
|
||||||
|
<PackageReference Include="Jellyfin.Model" Version="10.9.11" />
|
||||||
|
<PackageReference Include="WebDav.Client" Version="2.9.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
|
||||||
|
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556" PrivateAssets="All" />
|
||||||
|
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Remove="Configuration\configPage.html" />
|
||||||
|
<EmbeddedResource Include="Configuration\configPage.html" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
51
Plugin.cs
Normal file
51
Plugin.cs
Normal file
|
|
@ -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;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The main plugin.
|
||||||
|
/// </summary>
|
||||||
|
public class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="Plugin"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="applicationPaths">Instance of the <see cref="IApplicationPaths"/> interface.</param>
|
||||||
|
/// <param name="xmlSerializer">Instance of the <see cref="IXmlSerializer"/> interface.</param>
|
||||||
|
public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer)
|
||||||
|
: base(applicationPaths, xmlSerializer)
|
||||||
|
{
|
||||||
|
Instance = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override string Name => "WebDAV";
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override Guid Id => Guid.Parse("5db89aeb-14ad-450b-bcf2-ae235ebbe299");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current plugin instance.
|
||||||
|
/// </summary>
|
||||||
|
public static Plugin? Instance { get; private set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IEnumerable<PluginPageInfo> GetPages()
|
||||||
|
{
|
||||||
|
return
|
||||||
|
[
|
||||||
|
new PluginPageInfo
|
||||||
|
{
|
||||||
|
Name = Name,
|
||||||
|
EmbeddedResourcePath = string.Format(CultureInfo.InvariantCulture, "{0}.Configuration.configPage.html", GetType().Namespace)
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
20
ServiceRegistrator.cs
Normal file
20
ServiceRegistrator.cs
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using MediaBrowser.Controller.Plugins;
|
||||||
|
using MediaBrowser.Controller.Library;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
|
||||||
|
namespace Jellyfin.Plugin.Webdav
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Registers plugin services with the DI container.
|
||||||
|
/// </summary>
|
||||||
|
public class ServiceRegistrator : IPluginServiceRegistrator
|
||||||
|
{
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void RegisterServices(IServiceCollection services, IServerApplicationHost applicationHost)
|
||||||
|
{
|
||||||
|
services.AddSingleton<WebDavClientService>();
|
||||||
|
services.AddSingleton<IHostedService, WebDavHostedService>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
35
StreamingController.cs
Normal file
35
StreamingController.cs
Normal file
|
|
@ -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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Controller for streaming WebDAV media.
|
||||||
|
/// </summary>
|
||||||
|
[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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Streams the file at the given WebDAV path.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="*path">The relative path under the configured root.</param>
|
||||||
|
[HttpGet("{*path}")]
|
||||||
|
public async Task<IActionResult> Get(string path)
|
||||||
|
{
|
||||||
|
var fullPath = $"{_config.RootPath.TrimEnd('/')}/{path}";
|
||||||
|
var stream = await _webDavClientService.GetStreamAsync(fullPath).ConfigureAwait(false);
|
||||||
|
return File(stream, "application/octet-stream");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
47
WebDavClientService.cs
Normal file
47
WebDavClientService.cs
Normal file
|
|
@ -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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Service for interacting with WebDAV endpoints.
|
||||||
|
/// </summary>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// List resources at the specified WebDAV path.
|
||||||
|
/// </summary>
|
||||||
|
public async Task<IEnumerable<WebDavResource>> ListAsync(string path)
|
||||||
|
{
|
||||||
|
var response = await _client.Propfind(path).ConfigureAwait(false);
|
||||||
|
return response.Resources;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get a raw file stream from the WebDAV endpoint.
|
||||||
|
/// </summary>
|
||||||
|
public async Task<Stream> GetStreamAsync(string path)
|
||||||
|
{
|
||||||
|
var response = await _client.GetRawFile(path).ConfigureAwait(false);
|
||||||
|
return response.Stream;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
37
WebDavHostedService.cs
Normal file
37
WebDavHostedService.cs
Normal file
|
|
@ -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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Hosted service that registers the WebDAV source as a virtual folder on server startup.
|
||||||
|
/// </summary>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue