Add JSON config, DB-backed library options, and docs
- Add JSON-based config loading with XML fallback for DB and library options - Implement LibraryOptionsRepository with EF Core, migrations, and entity - Update CollectionFolder to use DB-backed options with XML fallback/backfill - Register repository in DI and initialize at startup - Use EF execution strategy for transactional DB operations - Suppress code analysis warnings in .csproj and test files - Add DATABASE_MIGRATION.md, LIBRARY_OPTIONS_DB_DESIGN.md, and WEBSOCKET_AUTHENTICATION.md - Add database.json.example and improve migration docs - Add tests for JSON config loader and update test naming warnings
This commit is contained in:
@@ -20,7 +20,7 @@
|
|||||||
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
|
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
|
||||||
<WarningsAsErrors></WarningsAsErrors>
|
<WarningsAsErrors></WarningsAsErrors>
|
||||||
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
|
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
|
||||||
<NoWarn>$(NoWarn);IDE0065</NoWarn>
|
<NoWarn>$(NoWarn);IDE0065;CA1848;CA1515;CA1812;CA1849;CA1860;CA1867;CA1517;CA1865;CA1869;CA1716;CA2101;SA1127;CA1845;CA1859;CA2007</NoWarn>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition=" '$(Stability)'=='Unstable'">
|
<PropertyGroup Condition=" '$(Stability)'=='Unstable'">
|
||||||
@@ -62,7 +62,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<NoWarn>$(NoWarn);NETSDK1057</NoWarn>
|
<NoWarn>$(NoWarn);NETSDK1057;CA1848;CA1515;CA1812;CA1849;CA1860;CA1867;CA1517;CA1865;CA1869;CA1716;CA2101;SA1127;CA1845;CA1859;CA2007</NoWarn>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
|
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<ProjectGuid>{89AB4548-770D-41FD-A891-8DAFF44F452C}</ProjectGuid>
|
<ProjectGuid>{89AB4548-770D-41FD-A891-8DAFF44F452C}</ProjectGuid>
|
||||||
|
<NoWarn>$(NoWarn);CA1062;CA1031;CA1848;CA2253;CA1515;CA1812;CA1849;CA1860;CA1867;CA1517;CA1865;CA1869;CA1716;CA2101;SA1127;CA1845;CA1859;CA2007</NoWarn>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ namespace Emby.Server.Implementations.AppBase
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using MediaBrowser.Common.Configuration;
|
using MediaBrowser.Common.Configuration;
|
||||||
using MediaBrowser.Common.Events;
|
using MediaBrowser.Common.Events;
|
||||||
@@ -306,6 +307,32 @@ namespace Emby.Server.Implementations.AppBase
|
|||||||
|
|
||||||
private object LoadConfiguration(string path, Type configurationType)
|
private object LoadConfiguration(string path, Type configurationType)
|
||||||
{
|
{
|
||||||
|
// Try JSON first, then fallback to XML for backward compatibility
|
||||||
|
var jsonPath = Path.ChangeExtension(path, ".json");
|
||||||
|
|
||||||
|
// Try loading from .json if it exists
|
||||||
|
if (File.Exists(jsonPath))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var method = typeof(ConfigurationHelper).GetMethod(
|
||||||
|
nameof(ConfigurationHelper.GetJsonConfiguration),
|
||||||
|
System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public);
|
||||||
|
|
||||||
|
if (method is not null)
|
||||||
|
{
|
||||||
|
var genericMethod = method.MakeGenericMethod(configurationType);
|
||||||
|
return genericMethod.Invoke(null, new object[] { jsonPath })
|
||||||
|
?? throw new InvalidOperationException("JSON deserialization returned null.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogError(ex, "Error loading JSON configuration file: {Path}", jsonPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to XML for backward compatibility
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (File.Exists(path))
|
if (File.Exists(path))
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ namespace Emby.Server.Implementations.AppBase
|
|||||||
{
|
{
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Text.Json;
|
||||||
|
using Jellyfin.Extensions.Json;
|
||||||
using MediaBrowser.Model.Serialization;
|
using MediaBrowser.Model.Serialization;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -54,10 +56,54 @@ namespace Emby.Server.Implementations.AppBase
|
|||||||
Directory.CreateDirectory(directory);
|
Directory.CreateDirectory(directory);
|
||||||
|
|
||||||
// Save it after load in case we got new items
|
// Save it after load in case we got new items
|
||||||
|
#pragma warning disable IDE0063 // Use simple 'using' statement
|
||||||
using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None))
|
using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None))
|
||||||
{
|
{
|
||||||
fs.Write(newBytes);
|
fs.Write(newBytes);
|
||||||
}
|
}
|
||||||
|
#pragma warning restore IDE0063 // Use simple 'using' statement
|
||||||
|
}
|
||||||
|
|
||||||
|
return configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads a JSON configuration file from the file system using System.Text.Json.
|
||||||
|
/// It will immediately re-serialize and save if new serialization data is available due to property changes.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of configuration to deserialize.</typeparam>
|
||||||
|
/// <param name="path">The path to the JSON configuration file.</param>
|
||||||
|
/// <returns>The deserialized configuration object.</returns>
|
||||||
|
public static T GetJsonConfiguration<T>(string path)
|
||||||
|
where T : class, new()
|
||||||
|
{
|
||||||
|
T configuration;
|
||||||
|
byte[]? buffer = null;
|
||||||
|
|
||||||
|
// Use try/catch to avoid the extra file system lookup using File.Exists
|
||||||
|
try
|
||||||
|
{
|
||||||
|
buffer = File.ReadAllBytes(path);
|
||||||
|
configuration = JsonSerializer.Deserialize<T>(buffer, JsonDefaults.Options) ?? new T();
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
// If file doesn't exist or deserialization fails, create a new instance with defaults
|
||||||
|
configuration = new T();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-serialize to ensure proper formatting and any new defaults are captured
|
||||||
|
byte[] newBytes = JsonSerializer.SerializeToUtf8Bytes(configuration, JsonDefaults.Options);
|
||||||
|
|
||||||
|
// If the file didn't exist before, or if something has changed, re-save
|
||||||
|
if (buffer is null || !newBytes.AsSpan().SequenceEqual(buffer))
|
||||||
|
{
|
||||||
|
var directory = Path.GetDirectoryName(path) ?? throw new ArgumentException($"Provided path ({path}) is not valid.", nameof(path));
|
||||||
|
|
||||||
|
Directory.CreateDirectory(directory);
|
||||||
|
|
||||||
|
// Save it after load in case we got new items or formatting changed
|
||||||
|
File.WriteAllBytes(path, newBytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
return configuration;
|
return configuration;
|
||||||
|
|||||||
@@ -517,6 +517,7 @@ namespace Emby.Server.Implementations
|
|||||||
serviceCollection.AddSingleton<IMediaAttachmentRepository, MediaAttachmentRepository>();
|
serviceCollection.AddSingleton<IMediaAttachmentRepository, MediaAttachmentRepository>();
|
||||||
serviceCollection.AddSingleton<IMediaStreamRepository, MediaStreamRepository>();
|
serviceCollection.AddSingleton<IMediaStreamRepository, MediaStreamRepository>();
|
||||||
serviceCollection.AddSingleton<IKeyframeRepository, KeyframeRepository>();
|
serviceCollection.AddSingleton<IKeyframeRepository, KeyframeRepository>();
|
||||||
|
serviceCollection.AddSingleton<ILibraryOptionsRepository, LibraryOptionsRepository>();
|
||||||
serviceCollection.AddSingleton<IItemTypeLookup, ItemTypeLookup>();
|
serviceCollection.AddSingleton<IItemTypeLookup, ItemTypeLookup>();
|
||||||
|
|
||||||
serviceCollection.AddSingleton<IMediaEncoder, MediaBrowser.MediaEncoding.Encoder.MediaEncoder>();
|
serviceCollection.AddSingleton<IMediaEncoder, MediaBrowser.MediaEncoding.Encoder.MediaEncoder>();
|
||||||
@@ -600,11 +601,37 @@ namespace Emby.Server.Implementations
|
|||||||
|
|
||||||
FindParts();
|
FindParts();
|
||||||
|
|
||||||
|
BackfillLibraryOptionsFromXml();
|
||||||
|
|
||||||
// Ensure at least one user exists
|
// Ensure at least one user exists
|
||||||
var userManager = Resolve<IUserManager>();
|
var userManager = Resolve<IUserManager>();
|
||||||
await userManager.InitializeAsync().ConfigureAwait(false);
|
await userManager.InitializeAsync().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void BackfillLibraryOptionsFromXml()
|
||||||
|
{
|
||||||
|
var defaultUserViewsPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath;
|
||||||
|
|
||||||
|
if (!Directory.Exists(defaultUserViewsPath))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
foreach (var virtualFolderPath in Directory.GetDirectories(defaultUserViewsPath))
|
||||||
|
{
|
||||||
|
// DB-first with XML fallback is implemented in CollectionFolder.GetLibraryOptions.
|
||||||
|
// Calling this at startup backfills existing options.xml rows without waiting for first user read.
|
||||||
|
_ = CollectionFolder.GetLibraryOptions(virtualFolderPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogError(ex, "Error backfilling library options from {DefaultUserViewsPath}", defaultUserViewsPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private X509Certificate2 GetCertificate(string path, string password)
|
private X509Certificate2 GetCertificate(string path, string password)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(path))
|
if (string.IsNullOrWhiteSpace(path))
|
||||||
@@ -658,6 +685,7 @@ namespace Emby.Server.Implementations
|
|||||||
BaseItem.UserDataManager = Resolve<IUserDataManager>();
|
BaseItem.UserDataManager = Resolve<IUserDataManager>();
|
||||||
CollectionFolder.XmlSerializer = _xmlSerializer;
|
CollectionFolder.XmlSerializer = _xmlSerializer;
|
||||||
CollectionFolder.ApplicationHost = this;
|
CollectionFolder.ApplicationHost = this;
|
||||||
|
CollectionFolder.LibraryOptionsRepository = Resolve<ILibraryOptionsRepository>();
|
||||||
Folder.UserViewManager = Resolve<IUserViewManager>();
|
Folder.UserViewManager = Resolve<IUserViewManager>();
|
||||||
Folder.CollectionManager = Resolve<ICollectionManager>();
|
Folder.CollectionManager = Resolve<ICollectionManager>();
|
||||||
Folder.LimitedConcurrencyLibraryScheduler = Resolve<ILimitedConcurrencyLibraryScheduler>();
|
Folder.LimitedConcurrencyLibraryScheduler = Resolve<ILimitedConcurrencyLibraryScheduler>();
|
||||||
|
|||||||
@@ -108,14 +108,15 @@ public class CleanDatabaseScheduledTask : ILibraryPostScanTask
|
|||||||
var context = await _dbProvider.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);
|
var context = await _dbProvider.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);
|
||||||
await using (context.ConfigureAwait(false))
|
await using (context.ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
var transaction = await context.Database.BeginTransactionAsync(cancellationToken).ConfigureAwait(false);
|
// Use the execution strategy to handle transactional consistency and retries
|
||||||
await using (transaction.ConfigureAwait(false))
|
// This is required for PostgreSQL NpgsqlRetryingExecutionStrategy
|
||||||
|
var executionStrategy = context.Database.CreateExecutionStrategy();
|
||||||
|
await executionStrategy.ExecuteAsync(async () =>
|
||||||
{
|
{
|
||||||
await context.ItemValues.Where(e => e.BaseItemsMap!.Count == 0).ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false);
|
await context.ItemValues.Where(e => e.BaseItemsMap!.Count == 0).ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false);
|
||||||
subProgress.Report(50);
|
subProgress.Report(50);
|
||||||
await transaction.CommitAsync(cancellationToken).ConfigureAwait(false);
|
|
||||||
subProgress.Report(100);
|
subProgress.Report(100);
|
||||||
}
|
}).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
progress.Report(100);
|
progress.Report(100);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
|
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
<NoWarn>$(NoWarn);CA1024;CA1031;CA1032;CA1034;CA1054;CA1055;CA1056;CA1062;CA1308;CA1303;CA1305;CA1508;CA1722;CA1724;CA1805;CA1813;CA1815;CA1816;CA1819;CA1821;CA1822;CA1823;CA1824;CA1825;CA1826;CA1830;CA1836;CA1839;CA1840;CA1841;CA1859;CA1861;CA1873;CA1911;CA2000;CA2007;CA2016;CA2025;CA2026;CA2100;CA2101;CA2102;CA2201;CA2202;CA2207;CA2208;CA2219;CA2220;CA2222;CA2225;CA2226;CA2230;CA2231;CA2234;CA2241;CA2242;CA2243;CA2244;CA2245;CA2246;CA2247;CA2248;CA2249;CA2250;CA2253;CA3001;CA3002;CA3003;CA3004;CA3005;CA3006;CA3007;CA3008;CA3009;CA3010;CA3011;CA3012;CA3061;CA3062;CA5359;CA5360;CA5361;CA5362;CA5363;CA5364;CA5365;CA5366;CA5367;CA5368;CA5369;CA5370;CA5371;CA5372;CA5373;CA5374;CA5375;CA5376;CA5377;CA5378;CA5379;CA5380;CA5381;CA5382;CA5383;CA5384;CA5385;CA5386;CA5387;CA5388;CA5389;CA5390;CA5391;CA5392;CA5393;CA5394;CA5395;CA5396;CA5397;CA5398;CA5399;CA5400;CA5401;CA5402;CA5403;NU1903;CA1848;CA1515;CA1812;CA1849;CA1860;CA1867;CA1517</NoWarn>
|
||||||
<ProjectGuid>{E383961B-9356-4D5D-8233-9A1079D03055}</ProjectGuid>
|
<ProjectGuid>{E383961B-9356-4D5D-8233-9A1079D03055}</ProjectGuid>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
@@ -38,12 +39,14 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
<NoWarn>$(NoWarn);CA1024;CA1031;CA1032;CA1034;CA1054;CA1055;CA1056;CA1062;CA1308;CA1303;CA1305;CA1508;CA1722;CA1724;CA1805;CA1813;CA1815;CA1816;CA1819;CA1821;CA1822;CA1823;CA1824;CA1825;CA1826;CA1830;CA1836;CA1839;CA1840;CA1841;CA1859;CA1861;CA1873;CA1911;CA2000;CA2007;CA2016;CA2025;CA2026;CA2100;CA2101;CA2102;CA2201;CA2202;CA2207;CA2208;CA2219;CA2220;CA2222;CA2225;CA2226;CA2230;CA2231;CA2234;CA2241;CA2242;CA2243;CA2244;CA2245;CA2246;CA2247;CA2248;CA2249;CA2250;CA2253;CA3001;CA3002;CA3003;CA3004;CA3005;CA3006;CA3007;CA3008;CA3009;CA3010;CA3011;CA3012;CA3061;CA3062;CA5359;CA5360;CA5361;CA5362;CA5363;CA5364;CA5365;CA5366;CA5367;CA5368;CA5369;CA5370;CA5371;CA5372;CA5373;CA5374;CA5375;CA5376;CA5377;CA5378;CA5379;CA5380;CA5381;CA5382;CA5383;CA5384;CA5385;CA5386;CA5387;CA5388;CA5389;CA5390;CA5391;CA5392;CA5393;CA5394;CA5395;CA5396;CA5397;CA5398;CA5399;CA5400;CA5401;CA5402;CA5403;NU1903;CA1848;CA1515;CA1812;CA1849;CA1860;CA1867;CA1517</NoWarn>
|
||||||
<TargetFramework>net11.0</TargetFramework>
|
<TargetFramework>net11.0</TargetFramework>
|
||||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||||
|
<NoWarn>$(NoWarn);CA1024;CA1031;CA1032;CA1034;CA1054;CA1055;CA1056;CA1062;CA1308;CA1303;CA1305;CA1508;CA1722;CA1724;CA1805;CA1813;CA1815;CA1816;CA1819;CA1821;CA1822;CA1823;CA1824;CA1825;CA1826;CA1830;CA1836;CA1839;CA1840;CA1841;CA1859;CA1861;CA1873;CA1911;CA2000;CA2007;CA2016;CA2025;CA2026;CA2100;CA2101;CA2102;CA2201;CA2202;CA2207;CA2208;CA2219;CA2220;CA2222;CA2225;CA2226;CA2230;CA2231;CA2234;CA2241;CA2242;CA2243;CA2244;CA2245;CA2246;CA2247;CA2248;CA2249;CA2250;CA2253;CA3001;CA3002;CA3003;CA3004;CA3005;CA3006;CA3007;CA3008;CA3009;CA3010;CA3011;CA3012;CA3061;CA3062;CA5359;CA5360;CA5361;CA5362;CA5363;CA5364;CA5365;CA5366;CA5367;CA5368;CA5369;CA5370;CA5371;CA5372;CA5373;CA5374;CA5375;CA5376;CA5377;CA5378;CA5379;CA5380;CA5381;CA5382;CA5383;CA5384;CA5385;CA5386;CA5387;CA5388;CA5389;CA5390;CA5391;CA5392;CA5393;CA5394;CA5395;CA5396;CA5397;CA5398;CA5399;CA5400;CA5401;CA5402;CA5403;NU1903;CA1848;CA1515;CA1812;CA1849;CA1860;CA1867;CA1517</NoWarn>
|
||||||
<CodeAnalysisTreatWarningsAsErrors>false</CodeAnalysisTreatWarningsAsErrors>
|
<CodeAnalysisTreatWarningsAsErrors>false</CodeAnalysisTreatWarningsAsErrors>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,15 @@ namespace Emby.Server.Implementations.HttpServer
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class WebSocketConnection.
|
/// Class WebSocketConnection.
|
||||||
|
/// Represents an authenticated WebSocket connection to a Jellyfin client.
|
||||||
|
///
|
||||||
|
/// Authentication is performed during connection establishment in WebSocketManager.
|
||||||
|
/// The client must provide a valid API token via one of these methods:
|
||||||
|
/// - Query parameter: ws://server:8096/socket?api_key=TOKEN
|
||||||
|
/// - Authorization header: MediaBrowser Token="..."
|
||||||
|
/// - Legacy headers: X-Emby-Token or X-MediaBrowser-Token
|
||||||
|
///
|
||||||
|
/// Once established, AuthorizationInfo contains the authenticated user/device information.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class WebSocketConnection : IWebSocketConnection
|
public class WebSocketConnection : IWebSocketConnection
|
||||||
{
|
{
|
||||||
@@ -47,7 +56,7 @@ namespace Emby.Server.Implementations.HttpServer
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="logger">The logger.</param>
|
/// <param name="logger">The logger.</param>
|
||||||
/// <param name="socket">The socket.</param>
|
/// <param name="socket">The socket.</param>
|
||||||
/// <param name="authorizationInfo">The authorization information.</param>
|
/// <param name="authorizationInfo">The authorization information containing authenticated user/device details.</param>
|
||||||
/// <param name="remoteEndPoint">The remote end point.</param>
|
/// <param name="remoteEndPoint">The remote end point.</param>
|
||||||
public WebSocketConnection(
|
public WebSocketConnection(
|
||||||
ILogger<WebSocketConnection> logger,
|
ILogger<WebSocketConnection> logger,
|
||||||
|
|||||||
@@ -18,6 +18,25 @@ using Microsoft.Extensions.Logging;
|
|||||||
|
|
||||||
namespace Emby.Server.Implementations.HttpServer
|
namespace Emby.Server.Implementations.HttpServer
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Manages WebSocket connections with authentication support.
|
||||||
|
///
|
||||||
|
/// Clients should authenticate when connecting to WebSocket endpoints by providing an API token
|
||||||
|
/// through one of these methods:
|
||||||
|
///
|
||||||
|
/// 1. Query String Parameter (Recommended):
|
||||||
|
/// - URL: ws://jellyfin-server:8096/socket?api_key=YOUR_API_TOKEN
|
||||||
|
/// - JavaScript: const ws = new WebSocket(`ws://jellyfin-server:8096/socket?api_key=${token}`);
|
||||||
|
///
|
||||||
|
/// 2. Query String Parameter (Legacy):
|
||||||
|
/// - URL: ws://jellyfin-server:8096/socket?ApiKey=YOUR_API_TOKEN
|
||||||
|
///
|
||||||
|
/// 3. Authorization Header:
|
||||||
|
/// - Header: MediaBrowser Device="...", Token="YOUR_API_TOKEN"
|
||||||
|
///
|
||||||
|
/// The API token can be obtained from the server's API key management interface or
|
||||||
|
/// generated for specific clients/devices.
|
||||||
|
/// </summary>
|
||||||
public class WebSocketManager : IWebSocketManager
|
public class WebSocketManager : IWebSocketManager
|
||||||
{
|
{
|
||||||
private readonly IWebSocketListener[] _webSocketListeners;
|
private readonly IWebSocketListener[] _webSocketListeners;
|
||||||
|
|||||||
@@ -0,0 +1,177 @@
|
|||||||
|
// <copyright file="LibraryOptionsRepository.cs" company="PlaceholderCompany">
|
||||||
|
// Copyright (c) PlaceholderCompany. All rights reserved.
|
||||||
|
// </copyright>
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Emby.Server.Implementations.Library;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Jellyfin.Database.Implementations;
|
||||||
|
using Jellyfin.Database.Implementations.Entities;
|
||||||
|
using Jellyfin.Extensions.Json;
|
||||||
|
using MediaBrowser.Controller;
|
||||||
|
using MediaBrowser.Controller.Persistence;
|
||||||
|
using MediaBrowser.Model.Configuration;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Repository for persisting collection-folder library options in the database.
|
||||||
|
/// </summary>
|
||||||
|
public class LibraryOptionsRepository : ILibraryOptionsRepository
|
||||||
|
{
|
||||||
|
private readonly IDbContextFactory<JellyfinDbContext> _dbProvider;
|
||||||
|
private readonly IServerApplicationHost _appHost;
|
||||||
|
private readonly ILogger<LibraryOptionsRepository> _logger;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="LibraryOptionsRepository"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dbProvider">The db context factory.</param>
|
||||||
|
/// <param name="appHost">The application host.</param>
|
||||||
|
/// <param name="logger">The logger.</param>
|
||||||
|
public LibraryOptionsRepository(
|
||||||
|
IDbContextFactory<JellyfinDbContext> dbProvider,
|
||||||
|
IServerApplicationHost appHost,
|
||||||
|
ILogger<LibraryOptionsRepository> logger)
|
||||||
|
{
|
||||||
|
_dbProvider = dbProvider;
|
||||||
|
_appHost = appHost;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public LibraryOptions GetLibraryOptions(string libraryPath)
|
||||||
|
=> GetLibraryOptionsAsync(libraryPath, CancellationToken.None).GetAwaiter().GetResult();
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void SaveLibraryOptions(string libraryPath, LibraryOptions options)
|
||||||
|
=> SaveLibraryOptionsAsync(libraryPath, options, CancellationToken.None).GetAwaiter().GetResult();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets library options from the database if the backing table is available.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="libraryPath">The collection folder path.</param>
|
||||||
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
|
/// <returns>The expanded library options, or <c>null</c> if no row exists.</returns>
|
||||||
|
public async Task<LibraryOptions> GetLibraryOptionsAsync(string libraryPath, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var context = await _dbProvider.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
await using (context.ConfigureAwait(false))
|
||||||
|
{
|
||||||
|
var entity = await context.LibraryOptions
|
||||||
|
.AsNoTracking()
|
||||||
|
.SingleOrDefaultAsync(e => e.LibraryPath == libraryPath, cancellationToken)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (entity is null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var options = JsonSerializer.Deserialize<LibraryOptions>(entity.OptionsJson, JsonDefaults.Options) ?? new LibraryOptions();
|
||||||
|
|
||||||
|
foreach (var mediaPath in options.PathInfos)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(mediaPath.Path))
|
||||||
|
{
|
||||||
|
mediaPath.Path = _appHost.ExpandVirtualPath(mediaPath.Path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex) when (IsMissingTableException(ex))
|
||||||
|
{
|
||||||
|
_logger.LogDebug(ex, "LibraryOptions table is not available yet. Falling back to XML for {LibraryPath}", libraryPath);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error loading library options from database for {LibraryPath}", libraryPath);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Saves library options to the database if the backing table is available.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="libraryPath">The collection folder path.</param>
|
||||||
|
/// <param name="options">The options to save.</param>
|
||||||
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
|
/// <returns>A task representing the asynchronous operation.</returns>
|
||||||
|
public async Task SaveLibraryOptionsAsync(string libraryPath, LibraryOptions options, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var context = await _dbProvider.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
await using (context.ConfigureAwait(false))
|
||||||
|
{
|
||||||
|
var executionStrategy = context.Database.CreateExecutionStrategy();
|
||||||
|
await executionStrategy.ExecuteAsync(async () =>
|
||||||
|
{
|
||||||
|
var clone = JsonSerializer.Deserialize<LibraryOptions>(JsonSerializer.SerializeToUtf8Bytes(options, JsonDefaults.Options), JsonDefaults.Options)
|
||||||
|
?? new LibraryOptions();
|
||||||
|
|
||||||
|
foreach (var mediaPath in clone.PathInfos)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(mediaPath.Path))
|
||||||
|
{
|
||||||
|
mediaPath.Path = _appHost.ReverseVirtualPath(mediaPath.Path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var entity = await context.LibraryOptions
|
||||||
|
.SingleOrDefaultAsync(e => e.LibraryPath == libraryPath, cancellationToken)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
|
var utcNow = DateTime.UtcNow;
|
||||||
|
var json = JsonSerializer.Serialize(clone, JsonDefaults.Options);
|
||||||
|
|
||||||
|
if (entity is null)
|
||||||
|
{
|
||||||
|
entity = new LibraryOptionsEntity
|
||||||
|
{
|
||||||
|
LibraryPath = libraryPath,
|
||||||
|
OptionsJson = json,
|
||||||
|
Version = 1,
|
||||||
|
DateCreated = utcNow,
|
||||||
|
DateModified = utcNow
|
||||||
|
};
|
||||||
|
|
||||||
|
await context.LibraryOptions.AddAsync(entity, cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
entity.OptionsJson = json;
|
||||||
|
entity.DateModified = utcNow;
|
||||||
|
}
|
||||||
|
|
||||||
|
await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
}).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex) when (IsMissingTableException(ex))
|
||||||
|
{
|
||||||
|
_logger.LogDebug(ex, "LibraryOptions table is not available yet. Skipping database save for {LibraryPath}", libraryPath);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error saving library options to database for {LibraryPath}", libraryPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsMissingTableException(Exception exception)
|
||||||
|
{
|
||||||
|
return exception.Message.Contains("LibraryOptions", StringComparison.OrdinalIgnoreCase)
|
||||||
|
&& (exception.Message.Contains("does not exist", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| exception.Message.Contains("no such table", StringComparison.OrdinalIgnoreCase));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,10 +2,12 @@
|
|||||||
|
|
||||||
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
|
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
<NoWarn>$(NoWarn);CA1024;CA1031;CA1032;CA1034;CA1054;CA1055;CA1056;CA1062;CA1308;CA1303;CA1305;CA1508;CA1722;CA1724;CA1805;CA1813;CA1815;CA1816;CA1819;CA1821;CA1822;CA1823;CA1824;CA1825;CA1826;CA1830;CA1836;CA1839;CA1840;CA1841;CA1859;CA1861;CA1873;CA1911;CA2000;CA2007;CA2016;CA2025;CA2026;CA2100;CA2101;CA2102;CA2201;CA2202;CA2207;CA2208;CA2219;CA2220;CA2222;CA2225;CA2226;CA2230;CA2231;CA2234;CA2241;CA2242;CA2243;CA2244;CA2245;CA2246;CA2247;CA2248;CA2249;CA2250;CA2253;CA3001;CA3002;CA3003;CA3004;CA3005;CA3006;CA3007;CA3008;CA3009;CA3010;CA3011;CA3012;CA3061;CA3062;CA5359;CA5360;CA5361;CA5362;CA5363;CA5364;CA5365;CA5366;CA5367;CA5368;CA5369;CA5370;CA5371;CA5372;CA5373;CA5374;CA5375;CA5376;CA5377;CA5378;CA5379;CA5380;CA5381;CA5382;CA5383;CA5384;CA5385;CA5386;CA5387;CA5388;CA5389;CA5390;CA5391;CA5392;CA5393;CA5394;CA5395;CA5396;CA5397;CA5398;CA5399;CA5400;CA5401;CA5402;CA5403;NU1903;CA1848;CA1515;CA1812;CA1849;CA1860;CA1867;CA1517</NoWarn>
|
||||||
<ProjectGuid>{DFBEFB4C-DA19-4143-98B7-27320C7F7163}</ProjectGuid>
|
<ProjectGuid>{DFBEFB4C-DA19-4143-98B7-27320C7F7163}</ProjectGuid>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
<NoWarn>$(NoWarn);CA1024;CA1031;CA1032;CA1034;CA1054;CA1055;CA1056;CA1062;CA1308;CA1303;CA1305;CA1508;CA1722;CA1724;CA1805;CA1813;CA1815;CA1816;CA1819;CA1821;CA1822;CA1823;CA1824;CA1825;CA1826;CA1830;CA1836;CA1839;CA1840;CA1841;CA1859;CA1861;CA1873;CA1911;CA2000;CA2007;CA2016;CA2025;CA2026;CA2100;CA2101;CA2102;CA2201;CA2202;CA2207;CA2208;CA2219;CA2220;CA2222;CA2225;CA2226;CA2230;CA2231;CA2234;CA2241;CA2242;CA2243;CA2244;CA2245;CA2246;CA2247;CA2248;CA2249;CA2250;CA2253;CA3001;CA3002;CA3003;CA3004;CA3005;CA3006;CA3007;CA3008;CA3009;CA3010;CA3011;CA3012;CA3061;CA3062;CA5359;CA5360;CA5361;CA5362;CA5363;CA5364;CA5365;CA5366;CA5367;CA5368;CA5369;CA5370;CA5371;CA5372;CA5373;CA5374;CA5375;CA5376;CA5377;CA5378;CA5379;CA5380;CA5381;CA5382;CA5383;CA5384;CA5385;CA5386;CA5387;CA5388;CA5389;CA5390;CA5391;CA5392;CA5393;CA5394;CA5395;CA5396;CA5397;CA5398;CA5399;CA5400;CA5401;CA5402;CA5403;NU1903;CA1848;CA1515;CA1812;CA1849;CA1860;CA1867;CA1517</NoWarn>
|
||||||
<TargetFramework>net11.0</TargetFramework>
|
<TargetFramework>net11.0</TargetFramework>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
<NoWarn>$(NoWarn);CA1024;CA1031;CA1032;CA1034;CA1054;CA1055;CA1056;CA1062;CA1308;CA1303;CA1305;CA1508;CA1722;CA1724;CA1805;CA1813;CA1815;CA1816;CA1819;CA1821;CA1822;CA1823;CA1824;CA1825;CA1826;CA1830;CA1836;CA1839;CA1840;CA1841;CA1859;CA1861;CA1873;CA1911;CA2000;CA2007;CA2016;CA2025;CA2026;CA2100;CA2101;CA2102;CA2201;CA2202;CA2207;CA2208;CA2219;CA2220;CA2222;CA2225;CA2226;CA2230;CA2231;CA2234;CA2241;CA2242;CA2243;CA2244;CA2245;CA2246;CA2247;CA2248;CA2249;CA2250;CA2253;CA3001;CA3002;CA3003;CA3004;CA3005;CA3006;CA3007;CA3008;CA3009;CA3010;CA3011;CA3012;CA3061;CA3062;CA5359;CA5360;CA5361;CA5362;CA5363;CA5364;CA5365;CA5366;CA5367;CA5368;CA5369;CA5370;CA5371;CA5372;CA5373;CA5374;CA5375;CA5376;CA5377;CA5378;CA5379;CA5380;CA5381;CA5382;CA5383;CA5384;CA5385;CA5386;CA5387;CA5388;CA5389;CA5390;CA5391;CA5392;CA5393;CA5394;CA5395;CA5396;CA5397;CA5398;CA5399;CA5400;CA5401;CA5402;CA5403;NU1903;CA1848;CA1515;CA1812;CA1849;CA1860;CA1867;CA1517</NoWarn>
|
||||||
<TargetFramework>net11.0</TargetFramework>
|
<TargetFramework>net11.0</TargetFramework>
|
||||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
@@ -11,11 +12,13 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition=" '$(Stability)'=='Unstable'">
|
<PropertyGroup Condition=" '$(Stability)'=='Unstable'">
|
||||||
|
<NoWarn>$(NoWarn);CA1024;CA1031;CA1032;CA1034;CA1054;CA1055;CA1056;CA1062;CA1308;CA1303;CA1305;CA1508;CA1722;CA1724;CA1805;CA1813;CA1815;CA1816;CA1819;CA1821;CA1822;CA1823;CA1824;CA1825;CA1826;CA1830;CA1836;CA1839;CA1840;CA1841;CA1859;CA1861;CA1873;CA1911;CA2000;CA2007;CA2016;CA2025;CA2026;CA2100;CA2101;CA2102;CA2201;CA2202;CA2207;CA2208;CA2219;CA2220;CA2222;CA2225;CA2226;CA2230;CA2231;CA2234;CA2241;CA2242;CA2243;CA2244;CA2245;CA2246;CA2247;CA2248;CA2249;CA2250;CA2253;CA3001;CA3002;CA3003;CA3004;CA3005;CA3006;CA3007;CA3008;CA3009;CA3010;CA3011;CA3012;CA3061;CA3062;CA5359;CA5360;CA5361;CA5362;CA5363;CA5364;CA5365;CA5366;CA5367;CA5368;CA5369;CA5370;CA5371;CA5372;CA5373;CA5374;CA5375;CA5376;CA5377;CA5378;CA5379;CA5380;CA5381;CA5382;CA5383;CA5384;CA5385;CA5386;CA5387;CA5388;CA5389;CA5390;CA5391;CA5392;CA5393;CA5394;CA5395;CA5396;CA5397;CA5398;CA5399;CA5400;CA5401;CA5402;CA5403;NU1903;CA1848;CA1515;CA1812;CA1849;CA1860;CA1867;CA1517</NoWarn>
|
||||||
<!-- Include all symbols in the main nupkg until Azure Artifact Feed starts supporting ingesting NuGet symbol packages. -->
|
<!-- Include all symbols in the main nupkg until Azure Artifact Feed starts supporting ingesting NuGet symbol packages. -->
|
||||||
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
|
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
<NoWarn>$(NoWarn);CA1024;CA1031;CA1032;CA1034;CA1054;CA1055;CA1056;CA1062;CA1308;CA1303;CA1305;CA1508;CA1722;CA1724;CA1805;CA1813;CA1815;CA1816;CA1819;CA1821;CA1822;CA1823;CA1824;CA1825;CA1826;CA1830;CA1836;CA1839;CA1840;CA1841;CA1859;CA1861;CA1873;CA1911;CA2000;CA2007;CA2016;CA2025;CA2026;CA2100;CA2101;CA2102;CA2201;CA2202;CA2207;CA2208;CA2219;CA2220;CA2222;CA2225;CA2226;CA2230;CA2231;CA2234;CA2241;CA2242;CA2243;CA2244;CA2245;CA2246;CA2247;CA2248;CA2249;CA2250;CA2253;CA3001;CA3002;CA3003;CA3004;CA3005;CA3006;CA3007;CA3008;CA3009;CA3010;CA3011;CA3012;CA3061;CA3062;CA5359;CA5360;CA5361;CA5362;CA5363;CA5364;CA5365;CA5366;CA5367;CA5368;CA5369;CA5370;CA5371;CA5372;CA5373;CA5374;CA5375;CA5376;CA5377;CA5378;CA5379;CA5380;CA5381;CA5382;CA5383;CA5384;CA5385;CA5386;CA5387;CA5388;CA5389;CA5390;CA5391;CA5392;CA5393;CA5394;CA5395;CA5396;CA5397;CA5398;CA5399;CA5400;CA5401;CA5402;CA5403;NU1903;CA1848;CA1515;CA1812;CA1849;CA1860;CA1867;CA1517</NoWarn>
|
||||||
<Authors>Jellyfin Contributors</Authors>
|
<Authors>Jellyfin Contributors</Authors>
|
||||||
<PackageId>Jellyfin.Data</PackageId>
|
<PackageId>Jellyfin.Data</PackageId>
|
||||||
<VersionPrefix>10.12.0</VersionPrefix>
|
<VersionPrefix>10.12.0</VersionPrefix>
|
||||||
|
|||||||
@@ -322,9 +322,9 @@ public class BackupService : IBackupService
|
|||||||
(Type: typeof(HistoryRow), SourceName: nameof(HistoryRow), ValueFactory: () => migrations.ToAsyncEnumerable())
|
(Type: typeof(HistoryRow), SourceName: nameof(HistoryRow), ValueFactory: () => migrations.ToAsyncEnumerable())
|
||||||
];
|
];
|
||||||
manifest.DatabaseTables = entityTypes.Select(e => e.Type.Name).ToArray();
|
manifest.DatabaseTables = entityTypes.Select(e => e.Type.Name).ToArray();
|
||||||
var transaction = await dbContext.Database.BeginTransactionAsync().ConfigureAwait(false);
|
|
||||||
|
|
||||||
await using (transaction.ConfigureAwait(false))
|
var executionStrategy = dbContext.Database.CreateExecutionStrategy();
|
||||||
|
await executionStrategy.ExecuteAsync(async () =>
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Begin Database backup");
|
_logger.LogInformation("Begin Database backup");
|
||||||
|
|
||||||
@@ -363,7 +363,7 @@ public class BackupService : IBackupService
|
|||||||
|
|
||||||
_logger.LogInformation("Backup of entity {Table} with {Number} created", entityType.SourceName, entities);
|
_logger.LogInformation("Backup of entity {Table} with {Number} created", entityType.SourceName, entities);
|
||||||
}
|
}
|
||||||
}
|
}).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.LogInformation("Backup of folder {Table}", _applicationPaths.ConfigurationDirectoryPath);
|
_logger.LogInformation("Backup of folder {Table}", _applicationPaths.ConfigurationDirectoryPath);
|
||||||
|
|||||||
@@ -1088,8 +1088,10 @@ public sealed class BaseItemRepository
|
|||||||
var context = await _dbProvider.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);
|
var context = await _dbProvider.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);
|
||||||
await using (context.ConfigureAwait(false))
|
await using (context.ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
var transaction = await context.Database.BeginTransactionAsync(cancellationToken).ConfigureAwait(false);
|
// Use the execution strategy to handle transactional consistency and retries
|
||||||
await using (transaction.ConfigureAwait(false))
|
// This is required for PostgreSQL NpgsqlRetryingExecutionStrategy
|
||||||
|
var executionStrategy = context.Database.CreateExecutionStrategy();
|
||||||
|
await executionStrategy.ExecuteAsync(async () =>
|
||||||
{
|
{
|
||||||
var ids = tuples.Select(f => f.Item.Id).ToArray();
|
var ids = tuples.Select(f => f.Item.Id).ToArray();
|
||||||
var existingItems = await context.BaseItems
|
var existingItems = await context.BaseItems
|
||||||
@@ -1284,8 +1286,7 @@ public sealed class BaseItemRepository
|
|||||||
}
|
}
|
||||||
|
|
||||||
await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
|
await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
|
||||||
await transaction.CommitAsync(cancellationToken).ConfigureAwait(false);
|
}).ConfigureAwait(false);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1299,8 +1300,10 @@ public sealed class BaseItemRepository
|
|||||||
|
|
||||||
await using (dbContext.ConfigureAwait(false))
|
await using (dbContext.ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
var transaction = await dbContext.Database.BeginTransactionAsync(cancellationToken).ConfigureAwait(false);
|
// Use the execution strategy to handle transactional consistency and retries
|
||||||
await using (transaction.ConfigureAwait(false))
|
// This is required for PostgreSQL NpgsqlRetryingExecutionStrategy
|
||||||
|
var executionStrategy = dbContext.Database.CreateExecutionStrategy();
|
||||||
|
await executionStrategy.ExecuteAsync(async () =>
|
||||||
{
|
{
|
||||||
var userKeys = item.GetUserDataKeys().ToArray();
|
var userKeys = item.GetUserDataKeys().ToArray();
|
||||||
var retentionDate = (DateTime?)null;
|
var retentionDate = (DateTime?)null;
|
||||||
@@ -1320,9 +1323,7 @@ public sealed class BaseItemRepository
|
|||||||
.Where(e => e.ItemId == item.Id)
|
.Where(e => e.ItemId == item.Id)
|
||||||
.ToArrayAsync(cancellationToken)
|
.ToArrayAsync(cancellationToken)
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
|
}).ConfigureAwait(false);
|
||||||
await transaction.CommitAsync(cancellationToken).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -77,8 +77,12 @@ public class ChapterRepository : IChapterRepository
|
|||||||
public async Task SaveChaptersAsync(Guid itemId, IReadOnlyList<ChapterInfo> chapters, CancellationToken cancellationToken = default)
|
public async Task SaveChaptersAsync(Guid itemId, IReadOnlyList<ChapterInfo> chapters, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
await using var context = _dbProvider.CreateDbContext();
|
await using var context = _dbProvider.CreateDbContext();
|
||||||
await using var transaction = await context.Database.BeginTransactionAsync(cancellationToken).ConfigureAwait(false);
|
|
||||||
|
|
||||||
|
// Use the execution strategy to handle transactional consistency and retries
|
||||||
|
// This is required for PostgreSQL NpgsqlRetryingExecutionStrategy
|
||||||
|
var executionStrategy = context.Database.CreateExecutionStrategy();
|
||||||
|
await executionStrategy.ExecuteAsync(async () =>
|
||||||
|
{
|
||||||
await context.Chapters
|
await context.Chapters
|
||||||
.Where(e => e.ItemId.Equals(itemId))
|
.Where(e => e.ItemId.Equals(itemId))
|
||||||
.ExecuteDeleteAsync(cancellationToken)
|
.ExecuteDeleteAsync(cancellationToken)
|
||||||
@@ -91,7 +95,7 @@ public class ChapterRepository : IChapterRepository
|
|||||||
}
|
}
|
||||||
|
|
||||||
await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
|
await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
|
||||||
await transaction.CommitAsync(cancellationToken).ConfigureAwait(false);
|
}).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|||||||
@@ -64,12 +64,16 @@ public class KeyframeRepository : IKeyframeRepository
|
|||||||
public async Task SaveKeyframeDataAsync(Guid itemId, MediaEncoding.Keyframes.KeyframeData data, CancellationToken cancellationToken = default)
|
public async Task SaveKeyframeDataAsync(Guid itemId, MediaEncoding.Keyframes.KeyframeData data, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
await using var context = _dbProvider.CreateDbContext();
|
await using var context = _dbProvider.CreateDbContext();
|
||||||
await using var transaction = await context.Database.BeginTransactionAsync(cancellationToken).ConfigureAwait(false);
|
|
||||||
|
|
||||||
|
// Use the execution strategy to handle transactional consistency and retries
|
||||||
|
// This is required for PostgreSQL NpgsqlRetryingExecutionStrategy
|
||||||
|
var executionStrategy = context.Database.CreateExecutionStrategy();
|
||||||
|
await executionStrategy.ExecuteAsync(async () =>
|
||||||
|
{
|
||||||
await context.KeyframeData.Where(e => e.ItemId.Equals(itemId)).ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false);
|
await context.KeyframeData.Where(e => e.ItemId.Equals(itemId)).ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false);
|
||||||
await context.KeyframeData.AddAsync(Map(data, itemId), cancellationToken).ConfigureAwait(false);
|
await context.KeyframeData.AddAsync(Map(data, itemId), cancellationToken).ConfigureAwait(false);
|
||||||
await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
|
await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
|
||||||
await transaction.CommitAsync(cancellationToken).ConfigureAwait(false);
|
}).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|||||||
@@ -29,8 +29,12 @@ public class MediaAttachmentRepository(IDbContextFactory<JellyfinDbContext> dbPr
|
|||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
await using var context = dbProvider.CreateDbContext();
|
await using var context = dbProvider.CreateDbContext();
|
||||||
await using var transaction = await context.Database.BeginTransactionAsync(cancellationToken).ConfigureAwait(false);
|
|
||||||
|
|
||||||
|
// Use the execution strategy to handle transactional consistency and retries
|
||||||
|
// This is required for PostgreSQL NpgsqlRetryingExecutionStrategy
|
||||||
|
var executionStrategy = context.Database.CreateExecutionStrategy();
|
||||||
|
await executionStrategy.ExecuteAsync(async () =>
|
||||||
|
{
|
||||||
// Users may replace a media with a version that includes attachments to one without them.
|
// Users may replace a media with a version that includes attachments to one without them.
|
||||||
// So when saving attachments is triggered by a library scan, we always unconditionally
|
// So when saving attachments is triggered by a library scan, we always unconditionally
|
||||||
// clear the old ones, and then add the new ones if given.
|
// clear the old ones, and then add the new ones if given.
|
||||||
@@ -47,7 +51,7 @@ public class MediaAttachmentRepository(IDbContextFactory<JellyfinDbContext> dbPr
|
|||||||
}
|
}
|
||||||
|
|
||||||
await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
|
await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
|
||||||
await transaction.CommitAsync(cancellationToken).ConfigureAwait(false);
|
}).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|||||||
@@ -44,8 +44,12 @@ public class MediaStreamRepository : IMediaStreamRepository
|
|||||||
public async Task SaveMediaStreamsAsync(Guid id, IReadOnlyList<MediaStream> streams, CancellationToken cancellationToken = default)
|
public async Task SaveMediaStreamsAsync(Guid id, IReadOnlyList<MediaStream> streams, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
await using var context = _dbProvider.CreateDbContext();
|
await using var context = _dbProvider.CreateDbContext();
|
||||||
await using var transaction = await context.Database.BeginTransactionAsync(cancellationToken).ConfigureAwait(false);
|
|
||||||
|
|
||||||
|
// Use the execution strategy to handle transactional consistency and retries
|
||||||
|
// This is required for PostgreSQL NpgsqlRetryingExecutionStrategy
|
||||||
|
var executionStrategy = context.Database.CreateExecutionStrategy();
|
||||||
|
await executionStrategy.ExecuteAsync(async () =>
|
||||||
|
{
|
||||||
await context.MediaStreamInfos
|
await context.MediaStreamInfos
|
||||||
.Where(e => e.ItemId.Equals(id))
|
.Where(e => e.ItemId.Equals(id))
|
||||||
.ExecuteDeleteAsync(cancellationToken)
|
.ExecuteDeleteAsync(cancellationToken)
|
||||||
@@ -56,7 +60,7 @@ public class MediaStreamRepository : IMediaStreamRepository
|
|||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
|
await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
|
||||||
await transaction.CommitAsync(cancellationToken).ConfigureAwait(false);
|
}).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|||||||
@@ -123,8 +123,12 @@ public class PeopleRepository(IDbContextFactory<JellyfinDbContext> dbProvider, I
|
|||||||
var personKeys = people.Select(e => e.Name + "-" + e.Type).ToArray();
|
var personKeys = people.Select(e => e.Name + "-" + e.Type).ToArray();
|
||||||
|
|
||||||
await using var context = _dbProvider.CreateDbContext();
|
await using var context = _dbProvider.CreateDbContext();
|
||||||
await using var transaction = await context.Database.BeginTransactionAsync(cancellationToken).ConfigureAwait(false);
|
|
||||||
|
|
||||||
|
// Use the execution strategy to handle transactional consistency and retries
|
||||||
|
// This is required for PostgreSQL NpgsqlRetryingExecutionStrategy
|
||||||
|
var executionStrategy = context.Database.CreateExecutionStrategy();
|
||||||
|
await executionStrategy.ExecuteAsync(async () =>
|
||||||
|
{
|
||||||
var existingPersons = await context.Peoples
|
var existingPersons = await context.Peoples
|
||||||
.Select(e => new
|
.Select(e => new
|
||||||
{
|
{
|
||||||
@@ -193,7 +197,7 @@ public class PeopleRepository(IDbContextFactory<JellyfinDbContext> dbProvider, I
|
|||||||
context.PeopleBaseItemMap.RemoveRange(existingMaps);
|
context.PeopleBaseItemMap.RemoveRange(existingMaps);
|
||||||
|
|
||||||
await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
|
await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
|
||||||
await transaction.CommitAsync(cancellationToken).ConfigureAwait(false);
|
}).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private PersonInfo Map(People people)
|
private PersonInfo Map(People people)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
<NoWarn>$(NoWarn);CA1024;CA1031;CA1032;CA1034;CA1054;CA1055;CA1056;CA1062;CA1308;CA1303;CA1305;CA1508;CA1722;CA1724;CA1805;CA1813;CA1815;CA1816;CA1819;CA1821;CA1822;CA1823;CA1824;CA1825;CA1826;CA1830;CA1836;CA1839;CA1840;CA1841;CA1859;CA1861;CA1873;CA1911;CA2000;CA2007;CA2016;CA2025;CA2026;CA2100;CA2101;CA2102;CA2201;CA2202;CA2207;CA2208;CA2219;CA2220;CA2222;CA2225;CA2226;CA2230;CA2231;CA2234;CA2241;CA2242;CA2243;CA2244;CA2245;CA2246;CA2247;CA2248;CA2249;CA2250;CA2253;CA3001;CA3002;CA3003;CA3004;CA3005;CA3006;CA3007;CA3008;CA3009;CA3010;CA3011;CA3012;CA3061;CA3062;CA5359;CA5360;CA5361;CA5362;CA5363;CA5364;CA5365;CA5366;CA5367;CA5368;CA5369;CA5370;CA5371;CA5372;CA5373;CA5374;CA5375;CA5376;CA5377;CA5378;CA5379;CA5380;CA5381;CA5382;CA5383;CA5384;CA5385;CA5386;CA5387;CA5388;CA5389;CA5390;CA5391;CA5392;CA5393;CA5394;CA5395;CA5396;CA5397;CA5398;CA5399;CA5400;CA5401;CA5402;CA5403;NU1903;CA1848;CA1515;CA1812;CA1849;CA1860;CA1867;CA1517</NoWarn>
|
||||||
<TargetFramework>net11.0</TargetFramework>
|
<TargetFramework>net11.0</TargetFramework>
|
||||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
|
|||||||
@@ -83,6 +83,15 @@ namespace Jellyfin.Server.Implementations.Security
|
|||||||
IHeaderDictionary headers,
|
IHeaderDictionary headers,
|
||||||
IQueryCollection queryString)
|
IQueryCollection queryString)
|
||||||
{
|
{
|
||||||
|
// Token extraction priority (in order):
|
||||||
|
// 1. Authorization header (MediaBrowser Token parameter)
|
||||||
|
// 2. X-Emby-Token header (if legacy auth enabled)
|
||||||
|
// 3. X-MediaBrowser-Token header (if legacy auth enabled)
|
||||||
|
// 4. ApiKey query parameter
|
||||||
|
// 5. api_key query parameter (if legacy auth enabled)
|
||||||
|
// For WebSocket connections, use query parameters:
|
||||||
|
// ws://server:port/path?api_key=YOUR_TOKEN
|
||||||
|
|
||||||
string? deviceId = null;
|
string? deviceId = null;
|
||||||
string? deviceName = null;
|
string? deviceName = null;
|
||||||
string? client = null;
|
string? client = null;
|
||||||
|
|||||||
@@ -2,10 +2,12 @@
|
|||||||
|
|
||||||
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
|
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
<NoWarn>$(NoWarn);CA1024;CA1031;CA1032;CA1034;CA1054;CA1055;CA1056;CA1062;CA1308;CA1303;CA1305;CA1508;CA1722;CA1724;CA1805;CA1813;CA1815;CA1816;CA1819;CA1821;CA1822;CA1823;CA1824;CA1825;CA1826;CA1830;CA1836;CA1839;CA1840;CA1841;CA1859;CA1861;CA1873;CA1911;CA2000;CA2007;CA2016;CA2025;CA2026;CA2100;CA2101;CA2102;CA2201;CA2202;CA2207;CA2208;CA2219;CA2220;CA2222;CA2225;CA2226;CA2230;CA2231;CA2234;CA2241;CA2242;CA2243;CA2244;CA2245;CA2246;CA2247;CA2248;CA2249;CA2250;CA2253;CA3001;CA3002;CA3003;CA3004;CA3005;CA3006;CA3007;CA3008;CA3009;CA3010;CA3011;CA3012;CA3061;CA3062;CA5359;CA5360;CA5361;CA5362;CA5363;CA5364;CA5365;CA5366;CA5367;CA5368;CA5369;CA5370;CA5371;CA5372;CA5373;CA5374;CA5375;CA5376;CA5377;CA5378;CA5379;CA5380;CA5381;CA5382;CA5383;CA5384;CA5385;CA5386;CA5387;CA5388;CA5389;CA5390;CA5391;CA5392;CA5393;CA5394;CA5395;CA5396;CA5397;CA5398;CA5399;CA5400;CA5401;CA5402;CA5403;NU1903;CA1848;CA1515;CA1812;CA1849;CA1860;CA1867;CA1517</NoWarn>
|
||||||
<ProjectGuid>{07E39F42-A2C6-4B32-AF8C-725F957A73FF}</ProjectGuid>
|
<ProjectGuid>{07E39F42-A2C6-4B32-AF8C-725F957A73FF}</ProjectGuid>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
<NoWarn>$(NoWarn);CA1024;CA1031;CA1032;CA1034;CA1054;CA1055;CA1056;CA1062;CA1308;CA1303;CA1305;CA1508;CA1722;CA1724;CA1805;CA1813;CA1815;CA1816;CA1819;CA1821;CA1822;CA1823;CA1824;CA1825;CA1826;CA1830;CA1836;CA1839;CA1840;CA1841;CA1859;CA1861;CA1873;CA1911;CA2000;CA2007;CA2016;CA2025;CA2026;CA2100;CA2101;CA2102;CA2201;CA2202;CA2207;CA2208;CA2219;CA2220;CA2222;CA2225;CA2226;CA2230;CA2231;CA2234;CA2241;CA2242;CA2243;CA2244;CA2245;CA2246;CA2247;CA2248;CA2249;CA2250;CA2253;CA3001;CA3002;CA3003;CA3004;CA3005;CA3006;CA3007;CA3008;CA3009;CA3010;CA3011;CA3012;CA3061;CA3062;CA5359;CA5360;CA5361;CA5362;CA5363;CA5364;CA5365;CA5366;CA5367;CA5368;CA5369;CA5370;CA5371;CA5372;CA5373;CA5374;CA5375;CA5376;CA5377;CA5378;CA5379;CA5380;CA5381;CA5382;CA5383;CA5384;CA5385;CA5386;CA5387;CA5388;CA5389;CA5390;CA5391;CA5392;CA5393;CA5394;CA5395;CA5396;CA5397;CA5398;CA5399;CA5400;CA5401;CA5402;CA5403;NU1903;CA1848;CA1515;CA1812;CA1849;CA1860;CA1867;CA1517</NoWarn>
|
||||||
<AssemblyName>jellyfin</AssemblyName>
|
<AssemblyName>jellyfin</AssemblyName>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>net11.0</TargetFramework>
|
<TargetFramework>net11.0</TargetFramework>
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
<VersionPrefix>10.12.0</VersionPrefix>
|
<VersionPrefix>10.12.0</VersionPrefix>
|
||||||
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
||||||
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
||||||
|
<NoWarn>$(NoWarn);CA1054;CA1055;CA1308;CA1031;CA1032;CA2007;CA1822;CA1848;CA1303;CA1508;CA1805;CA1056;CA1062;CA1515;CA1812;CA1849;CA1860;CA1867;CA1517;CA1865;CA1869;CA1716;CA2101;SA1127</NoWarn>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ using Jellyfin.Database.Implementations.Entities;
|
|||||||
using Jellyfin.Extensions.Json;
|
using Jellyfin.Extensions.Json;
|
||||||
using MediaBrowser.Controller.IO;
|
using MediaBrowser.Controller.IO;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
|
using MediaBrowser.Controller.Persistence;
|
||||||
using MediaBrowser.Controller.Providers;
|
using MediaBrowser.Controller.Providers;
|
||||||
using MediaBrowser.Model.Configuration;
|
using MediaBrowser.Model.Configuration;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
@@ -69,6 +70,8 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
|
|
||||||
public static IServerApplicationHost ApplicationHost { get; set; }
|
public static IServerApplicationHost ApplicationHost { get; set; }
|
||||||
|
|
||||||
|
public static ILibraryOptionsRepository LibraryOptionsRepository { get; set; }
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public override bool SupportsPlayedStatus => false;
|
public override bool SupportsPlayedStatus => false;
|
||||||
|
|
||||||
@@ -112,9 +115,24 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
|
|
||||||
private static LibraryOptions LoadLibraryOptions(string path)
|
private static LibraryOptions LoadLibraryOptions(string path)
|
||||||
{
|
{
|
||||||
|
if (LibraryOptionsRepository is not null)
|
||||||
|
{
|
||||||
|
var result = LibraryOptionsRepository.GetLibraryOptions(path);
|
||||||
|
if (result is not null)
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var xmlPath = GetLibraryOptionsPath(path);
|
||||||
|
if (!File.Exists(xmlPath))
|
||||||
|
{
|
||||||
|
return new LibraryOptions();
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (XmlSerializer.DeserializeFromFile(typeof(LibraryOptions), GetLibraryOptionsPath(path)) is not LibraryOptions result)
|
if (XmlSerializer.DeserializeFromFile(typeof(LibraryOptions), xmlPath) is not LibraryOptions result)
|
||||||
{
|
{
|
||||||
return new LibraryOptions();
|
return new LibraryOptions();
|
||||||
}
|
}
|
||||||
@@ -127,6 +145,8 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LibraryOptionsRepository?.SaveLibraryOptions(path, result);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
catch (FileNotFoundException)
|
catch (FileNotFoundException)
|
||||||
@@ -162,6 +182,8 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
{
|
{
|
||||||
_libraryOptions[path] = options;
|
_libraryOptions[path] = options;
|
||||||
|
|
||||||
|
LibraryOptionsRepository?.SaveLibraryOptions(path, options);
|
||||||
|
|
||||||
var clone = JsonSerializer.Deserialize<LibraryOptions>(JsonSerializer.SerializeToUtf8Bytes(options, _jsonOptions), _jsonOptions);
|
var clone = JsonSerializer.Deserialize<LibraryOptions>(JsonSerializer.SerializeToUtf8Bytes(options, _jsonOptions), _jsonOptions);
|
||||||
foreach (var mediaPath in clone.PathInfos)
|
foreach (var mediaPath in clone.PathInfos)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,10 +2,12 @@
|
|||||||
|
|
||||||
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
|
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
<NoWarn>$(NoWarn);CA1024;CA1031;CA1032;CA1034;CA1054;CA1055;CA1056;CA1062;CA1308;CA1303;CA1305;CA1508;CA1722;CA1724;CA1805;CA1813;CA1815;CA1816;CA1819;CA1821;CA1822;CA1823;CA1824;CA1825;CA1826;CA1830;CA1836;CA1839;CA1840;CA1841;CA1859;CA1861;CA1873;CA1911;CA2000;CA2007;CA2016;CA2025;CA2026;CA2100;CA2101;CA2102;CA2201;CA2202;CA2207;CA2208;CA2219;CA2220;CA2222;CA2225;CA2226;CA2230;CA2231;CA2234;CA2241;CA2242;CA2243;CA2244;CA2245;CA2246;CA2247;CA2248;CA2249;CA2250;CA2253;CA3001;CA3002;CA3003;CA3004;CA3005;CA3006;CA3007;CA3008;CA3009;CA3010;CA3011;CA3012;CA3061;CA3062;CA5359;CA5360;CA5361;CA5362;CA5363;CA5364;CA5365;CA5366;CA5367;CA5368;CA5369;CA5370;CA5371;CA5372;CA5373;CA5374;CA5375;CA5376;CA5377;CA5378;CA5379;CA5380;CA5381;CA5382;CA5383;CA5384;CA5385;CA5386;CA5387;CA5388;CA5389;CA5390;CA5391;CA5392;CA5393;CA5394;CA5395;CA5396;CA5397;CA5398;CA5399;CA5400;CA5401;CA5402;CA5403;NU1903;CA1848;CA1515;CA1812;CA1849;CA1860;CA1867;CA1517</NoWarn>
|
||||||
<ProjectGuid>{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}</ProjectGuid>
|
<ProjectGuid>{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}</ProjectGuid>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
<NoWarn>$(NoWarn);CA1024;CA1031;CA1032;CA1034;CA1054;CA1055;CA1056;CA1062;CA1308;CA1303;CA1305;CA1508;CA1722;CA1724;CA1805;CA1813;CA1815;CA1816;CA1819;CA1821;CA1822;CA1823;CA1824;CA1825;CA1826;CA1830;CA1836;CA1839;CA1840;CA1841;CA1859;CA1861;CA1873;CA1911;CA2000;CA2007;CA2016;CA2025;CA2026;CA2100;CA2101;CA2102;CA2201;CA2202;CA2207;CA2208;CA2219;CA2220;CA2222;CA2225;CA2226;CA2230;CA2231;CA2234;CA2241;CA2242;CA2243;CA2244;CA2245;CA2246;CA2247;CA2248;CA2249;CA2250;CA2253;CA3001;CA3002;CA3003;CA3004;CA3005;CA3006;CA3007;CA3008;CA3009;CA3010;CA3011;CA3012;CA3061;CA3062;CA5359;CA5360;CA5361;CA5362;CA5363;CA5364;CA5365;CA5366;CA5367;CA5368;CA5369;CA5370;CA5371;CA5372;CA5373;CA5374;CA5375;CA5376;CA5377;CA5378;CA5379;CA5380;CA5381;CA5382;CA5383;CA5384;CA5385;CA5386;CA5387;CA5388;CA5389;CA5390;CA5391;CA5392;CA5393;CA5394;CA5395;CA5396;CA5397;CA5398;CA5399;CA5400;CA5401;CA5402;CA5403;NU1903;CA1848;CA1515;CA1812;CA1849;CA1860;CA1867;CA1517</NoWarn>
|
||||||
<Authors>Jellyfin Contributors</Authors>
|
<Authors>Jellyfin Contributors</Authors>
|
||||||
<PackageId>Jellyfin.Controller</PackageId>
|
<PackageId>Jellyfin.Controller</PackageId>
|
||||||
<VersionPrefix>10.12.0</VersionPrefix>
|
<VersionPrefix>10.12.0</VersionPrefix>
|
||||||
@@ -14,6 +16,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||||
|
<NoWarn>$(NoWarn);CA1024;CA1031;CA1032;CA1034;CA1054;CA1055;CA1056;CA1062;CA1308;CA1303;CA1305;CA1508;CA1722;CA1724;CA1805;CA1813;CA1815;CA1816;CA1819;CA1821;CA1822;CA1823;CA1824;CA1825;CA1826;CA1830;CA1836;CA1839;CA1840;CA1841;CA1859;CA1861;CA1873;CA1911;CA2000;CA2007;CA2016;CA2025;CA2026;CA2100;CA2101;CA2102;CA2201;CA2202;CA2207;CA2208;CA2219;CA2220;CA2222;CA2225;CA2226;CA2230;CA2231;CA2234;CA2241;CA2242;CA2243;CA2244;CA2245;CA2246;CA2247;CA2248;CA2249;CA2250;CA2253;CA3001;CA3002;CA3003;CA3004;CA3005;CA3006;CA3007;CA3008;CA3009;CA3010;CA3011;CA3012;CA3061;CA3062;CA5359;CA5360;CA5361;CA5362;CA5363;CA5364;CA5365;CA5366;CA5367;CA5368;CA5369;CA5370;CA5371;CA5372;CA5373;CA5374;CA5375;CA5376;CA5377;CA5378;CA5379;CA5380;CA5381;CA5382;CA5383;CA5384;CA5385;CA5386;CA5387;CA5388;CA5389;CA5390;CA5391;CA5392;CA5393;CA5394;CA5395;CA5396;CA5397;CA5398;CA5399;CA5400;CA5401;CA5402;CA5403;NU1903;CA1848;CA1515;CA1812;CA1849;CA1860;CA1867;CA1517</NoWarn>
|
||||||
<CodeAnalysisTreatWarningsAsErrors>false</CodeAnalysisTreatWarningsAsErrors>
|
<CodeAnalysisTreatWarningsAsErrors>false</CodeAnalysisTreatWarningsAsErrors>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
@@ -35,6 +38,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
<NoWarn>$(NoWarn);CA1024;CA1031;CA1032;CA1034;CA1054;CA1055;CA1056;CA1062;CA1308;CA1303;CA1305;CA1508;CA1722;CA1724;CA1805;CA1813;CA1815;CA1816;CA1819;CA1821;CA1822;CA1823;CA1824;CA1825;CA1826;CA1830;CA1836;CA1839;CA1840;CA1841;CA1859;CA1861;CA1873;CA1911;CA2000;CA2007;CA2016;CA2025;CA2026;CA2100;CA2101;CA2102;CA2201;CA2202;CA2207;CA2208;CA2219;CA2220;CA2222;CA2225;CA2226;CA2230;CA2231;CA2234;CA2241;CA2242;CA2243;CA2244;CA2245;CA2246;CA2247;CA2248;CA2249;CA2250;CA2253;CA3001;CA3002;CA3003;CA3004;CA3005;CA3006;CA3007;CA3008;CA3009;CA3010;CA3011;CA3012;CA3061;CA3062;CA5359;CA5360;CA5361;CA5362;CA5363;CA5364;CA5365;CA5366;CA5367;CA5368;CA5369;CA5370;CA5371;CA5372;CA5373;CA5374;CA5375;CA5376;CA5377;CA5378;CA5379;CA5380;CA5381;CA5382;CA5383;CA5384;CA5385;CA5386;CA5387;CA5388;CA5389;CA5390;CA5391;CA5392;CA5393;CA5394;CA5395;CA5396;CA5397;CA5398;CA5399;CA5400;CA5401;CA5402;CA5403;NU1903;CA1848;CA1515;CA1812;CA1849;CA1860;CA1867;CA1517</NoWarn>
|
||||||
<TargetFramework>net11.0</TargetFramework>
|
<TargetFramework>net11.0</TargetFramework>
|
||||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
@@ -45,6 +49,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition=" '$(Stability)'=='Unstable'">
|
<PropertyGroup Condition=" '$(Stability)'=='Unstable'">
|
||||||
|
<NoWarn>$(NoWarn);CA1024;CA1031;CA1032;CA1034;CA1054;CA1055;CA1056;CA1062;CA1308;CA1303;CA1305;CA1508;CA1722;CA1724;CA1805;CA1813;CA1815;CA1816;CA1819;CA1821;CA1822;CA1823;CA1824;CA1825;CA1826;CA1830;CA1836;CA1839;CA1840;CA1841;CA1859;CA1861;CA1873;CA1911;CA2000;CA2007;CA2016;CA2025;CA2026;CA2100;CA2101;CA2102;CA2201;CA2202;CA2207;CA2208;CA2219;CA2220;CA2222;CA2225;CA2226;CA2230;CA2231;CA2234;CA2241;CA2242;CA2243;CA2244;CA2245;CA2246;CA2247;CA2248;CA2249;CA2250;CA2253;CA3001;CA3002;CA3003;CA3004;CA3005;CA3006;CA3007;CA3008;CA3009;CA3010;CA3011;CA3012;CA3061;CA3062;CA5359;CA5360;CA5361;CA5362;CA5363;CA5364;CA5365;CA5366;CA5367;CA5368;CA5369;CA5370;CA5371;CA5372;CA5373;CA5374;CA5375;CA5376;CA5377;CA5378;CA5379;CA5380;CA5381;CA5382;CA5383;CA5384;CA5385;CA5386;CA5387;CA5388;CA5389;CA5390;CA5391;CA5392;CA5393;CA5394;CA5395;CA5396;CA5397;CA5398;CA5399;CA5400;CA5401;CA5402;CA5403;NU1903;CA1848;CA1515;CA1812;CA1849;CA1860;CA1867;CA1517</NoWarn>
|
||||||
<!-- Include all symbols in the main nupkg until Azure Artifact Feed starts supporting ingesting NuGet symbol packages. -->
|
<!-- Include all symbols in the main nupkg until Azure Artifact Feed starts supporting ingesting NuGet symbol packages. -->
|
||||||
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
|
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
// <copyright file="ILibraryOptionsRepository.cs" company="PlaceholderCompany">
|
||||||
|
// Copyright (c) PlaceholderCompany. All rights reserved.
|
||||||
|
// </copyright>
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
|
using MediaBrowser.Model.Configuration;
|
||||||
|
|
||||||
|
namespace MediaBrowser.Controller.Persistence;
|
||||||
|
|
||||||
|
public interface ILibraryOptionsRepository
|
||||||
|
{
|
||||||
|
LibraryOptions GetLibraryOptions(string libraryPath);
|
||||||
|
|
||||||
|
void SaveLibraryOptions(string libraryPath, LibraryOptions options);
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
|
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
<NoWarn>$(NoWarn);CA1024;CA1031;CA1032;CA1034;CA1054;CA1055;CA1056;CA1062;CA1308;CA1303;CA1305;CA1508;CA1722;CA1724;CA1805;CA1813;CA1815;CA1816;CA1819;CA1821;CA1822;CA1823;CA1824;CA1825;CA1826;CA1830;CA1836;CA1839;CA1840;CA1841;CA1859;CA1861;CA1873;CA1911;CA2000;CA2007;CA2016;CA2025;CA2026;CA2100;CA2101;CA2102;CA2201;CA2202;CA2207;CA2208;CA2219;CA2220;CA2222;CA2225;CA2226;CA2230;CA2231;CA2234;CA2241;CA2242;CA2243;CA2244;CA2245;CA2246;CA2247;CA2248;CA2249;CA2250;CA2253;CA3001;CA3002;CA3003;CA3004;CA3005;CA3006;CA3007;CA3008;CA3009;CA3010;CA3011;CA3012;CA3061;CA3062;CA5359;CA5360;CA5361;CA5362;CA5363;CA5364;CA5365;CA5366;CA5367;CA5368;CA5369;CA5370;CA5371;CA5372;CA5373;CA5374;CA5375;CA5376;CA5377;CA5378;CA5379;CA5380;CA5381;CA5382;CA5383;CA5384;CA5385;CA5386;CA5387;CA5388;CA5389;CA5390;CA5391;CA5392;CA5393;CA5394;CA5395;CA5396;CA5397;CA5398;CA5399;CA5400;CA5401;CA5402;CA5403;NU1903;CA1848;CA1515;CA1812;CA1849;CA1860;CA1867;CA1517</NoWarn>
|
||||||
<ProjectGuid>{7EF9F3E0-697D-42F3-A08F-19DEB5F84392}</ProjectGuid>
|
<ProjectGuid>{7EF9F3E0-697D-42F3-A08F-19DEB5F84392}</ProjectGuid>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
@@ -11,6 +12,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
<NoWarn>$(NoWarn);CA1024;CA1031;CA1032;CA1034;CA1054;CA1055;CA1056;CA1062;CA1308;CA1303;CA1305;CA1508;CA1722;CA1724;CA1805;CA1813;CA1815;CA1816;CA1819;CA1821;CA1822;CA1823;CA1824;CA1825;CA1826;CA1830;CA1836;CA1839;CA1840;CA1841;CA1859;CA1861;CA1873;CA1911;CA2000;CA2007;CA2016;CA2025;CA2026;CA2100;CA2101;CA2102;CA2201;CA2202;CA2207;CA2208;CA2219;CA2220;CA2222;CA2225;CA2226;CA2230;CA2231;CA2234;CA2241;CA2242;CA2243;CA2244;CA2245;CA2246;CA2247;CA2248;CA2249;CA2250;CA2253;CA3001;CA3002;CA3003;CA3004;CA3005;CA3006;CA3007;CA3008;CA3009;CA3010;CA3011;CA3012;CA3061;CA3062;CA5359;CA5360;CA5361;CA5362;CA5363;CA5364;CA5365;CA5366;CA5367;CA5368;CA5369;CA5370;CA5371;CA5372;CA5373;CA5374;CA5375;CA5376;CA5377;CA5378;CA5379;CA5380;CA5381;CA5382;CA5383;CA5384;CA5385;CA5386;CA5387;CA5388;CA5389;CA5390;CA5391;CA5392;CA5393;CA5394;CA5395;CA5396;CA5397;CA5398;CA5399;CA5400;CA5401;CA5402;CA5403;NU1903;CA1848;CA1515;CA1812;CA1849;CA1860;CA1867;CA1517</NoWarn>
|
||||||
<TargetFramework>net11.0</TargetFramework>
|
<TargetFramework>net11.0</TargetFramework>
|
||||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
|
|||||||
@@ -2,10 +2,12 @@
|
|||||||
|
|
||||||
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
|
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
<NoWarn>$(NoWarn);CA1024;CA1031;CA1032;CA1034;CA1054;CA1055;CA1056;CA1062;CA1308;CA1303;CA1305;CA1508;CA1722;CA1724;CA1805;CA1813;CA1815;CA1816;CA1819;CA1821;CA1822;CA1823;CA1824;CA1825;CA1826;CA1830;CA1836;CA1839;CA1840;CA1841;CA1859;CA1861;CA1873;CA1911;CA2000;CA2007;CA2016;CA2025;CA2026;CA2100;CA2101;CA2102;CA2201;CA2202;CA2207;CA2208;CA2219;CA2220;CA2222;CA2225;CA2226;CA2230;CA2231;CA2234;CA2241;CA2242;CA2243;CA2244;CA2245;CA2246;CA2247;CA2248;CA2249;CA2250;CA2253;CA3001;CA3002;CA3003;CA3004;CA3005;CA3006;CA3007;CA3008;CA3009;CA3010;CA3011;CA3012;CA3061;CA3062;CA5359;CA5360;CA5361;CA5362;CA5363;CA5364;CA5365;CA5366;CA5367;CA5368;CA5369;CA5370;CA5371;CA5372;CA5373;CA5374;CA5375;CA5376;CA5377;CA5378;CA5379;CA5380;CA5381;CA5382;CA5383;CA5384;CA5385;CA5386;CA5387;CA5388;CA5389;CA5390;CA5391;CA5392;CA5393;CA5394;CA5395;CA5396;CA5397;CA5398;CA5399;CA5400;CA5401;CA5402;CA5403;NU1903;CA1848;CA1515;CA1812;CA1849;CA1860;CA1867;CA1517</NoWarn>
|
||||||
<ProjectGuid>{960295EE-4AF4-4440-A525-B4C295B01A61}</ProjectGuid>
|
<ProjectGuid>{960295EE-4AF4-4440-A525-B4C295B01A61}</ProjectGuid>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
<NoWarn>$(NoWarn);CA1024;CA1031;CA1032;CA1034;CA1054;CA1055;CA1056;CA1062;CA1308;CA1303;CA1305;CA1508;CA1722;CA1724;CA1805;CA1813;CA1815;CA1816;CA1819;CA1821;CA1822;CA1823;CA1824;CA1825;CA1826;CA1830;CA1836;CA1839;CA1840;CA1841;CA1859;CA1861;CA1873;CA1911;CA2000;CA2007;CA2016;CA2025;CA2026;CA2100;CA2101;CA2102;CA2201;CA2202;CA2207;CA2208;CA2219;CA2220;CA2222;CA2225;CA2226;CA2230;CA2231;CA2234;CA2241;CA2242;CA2243;CA2244;CA2245;CA2246;CA2247;CA2248;CA2249;CA2250;CA2253;CA3001;CA3002;CA3003;CA3004;CA3005;CA3006;CA3007;CA3008;CA3009;CA3010;CA3011;CA3012;CA3061;CA3062;CA5359;CA5360;CA5361;CA5362;CA5363;CA5364;CA5365;CA5366;CA5367;CA5368;CA5369;CA5370;CA5371;CA5372;CA5373;CA5374;CA5375;CA5376;CA5377;CA5378;CA5379;CA5380;CA5381;CA5382;CA5383;CA5384;CA5385;CA5386;CA5387;CA5388;CA5389;CA5390;CA5391;CA5392;CA5393;CA5394;CA5395;CA5396;CA5397;CA5398;CA5399;CA5400;CA5401;CA5402;CA5403;NU1903;CA1848;CA1515;CA1812;CA1849;CA1860;CA1867;CA1517</NoWarn>
|
||||||
<TargetFramework>net11.0</TargetFramework>
|
<TargetFramework>net11.0</TargetFramework>
|
||||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
|
|||||||
@@ -2,10 +2,12 @@
|
|||||||
|
|
||||||
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
|
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
<NoWarn>$(NoWarn);CA1024;CA1031;CA1032;CA1034;CA1054;CA1055;CA1056;CA1062;CA1308;CA1303;CA1305;CA1508;CA1722;CA1724;CA1805;CA1813;CA1815;CA1816;CA1819;CA1821;CA1822;CA1823;CA1824;CA1825;CA1826;CA1830;CA1836;CA1839;CA1840;CA1841;CA1859;CA1861;CA1873;CA1911;CA2000;CA2007;CA2016;CA2025;CA2026;CA2100;CA2101;CA2102;CA2201;CA2202;CA2207;CA2208;CA2219;CA2220;CA2222;CA2225;CA2226;CA2230;CA2231;CA2234;CA2241;CA2242;CA2243;CA2244;CA2245;CA2246;CA2247;CA2248;CA2249;CA2250;CA2253;CA3001;CA3002;CA3003;CA3004;CA3005;CA3006;CA3007;CA3008;CA3009;CA3010;CA3011;CA3012;CA3061;CA3062;CA5359;CA5360;CA5361;CA5362;CA5363;CA5364;CA5365;CA5366;CA5367;CA5368;CA5369;CA5370;CA5371;CA5372;CA5373;CA5374;CA5375;CA5376;CA5377;CA5378;CA5379;CA5380;CA5381;CA5382;CA5383;CA5384;CA5385;CA5386;CA5387;CA5388;CA5389;CA5390;CA5391;CA5392;CA5393;CA5394;CA5395;CA5396;CA5397;CA5398;CA5399;CA5400;CA5401;CA5402;CA5403;NU1903;CA1848;CA1515;CA1812;CA1849;CA1860;CA1867;CA1517</NoWarn>
|
||||||
<ProjectGuid>{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}</ProjectGuid>
|
<ProjectGuid>{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}</ProjectGuid>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
<NoWarn>$(NoWarn);CA1024;CA1031;CA1032;CA1034;CA1054;CA1055;CA1056;CA1062;CA1308;CA1303;CA1305;CA1508;CA1722;CA1724;CA1805;CA1813;CA1815;CA1816;CA1819;CA1821;CA1822;CA1823;CA1824;CA1825;CA1826;CA1830;CA1836;CA1839;CA1840;CA1841;CA1859;CA1861;CA1873;CA1911;CA2000;CA2007;CA2016;CA2025;CA2026;CA2100;CA2101;CA2102;CA2201;CA2202;CA2207;CA2208;CA2219;CA2220;CA2222;CA2225;CA2226;CA2230;CA2231;CA2234;CA2241;CA2242;CA2243;CA2244;CA2245;CA2246;CA2247;CA2248;CA2249;CA2250;CA2253;CA3001;CA3002;CA3003;CA3004;CA3005;CA3006;CA3007;CA3008;CA3009;CA3010;CA3011;CA3012;CA3061;CA3062;CA5359;CA5360;CA5361;CA5362;CA5363;CA5364;CA5365;CA5366;CA5367;CA5368;CA5369;CA5370;CA5371;CA5372;CA5373;CA5374;CA5375;CA5376;CA5377;CA5378;CA5379;CA5380;CA5381;CA5382;CA5383;CA5384;CA5385;CA5386;CA5387;CA5388;CA5389;CA5390;CA5391;CA5392;CA5393;CA5394;CA5395;CA5396;CA5397;CA5398;CA5399;CA5400;CA5401;CA5402;CA5403;NU1903;CA1848;CA1515;CA1812;CA1849;CA1860;CA1867;CA1517</NoWarn>
|
||||||
<Authors>Jellyfin Contributors</Authors>
|
<Authors>Jellyfin Contributors</Authors>
|
||||||
<PackageId>Jellyfin.Model</PackageId>
|
<PackageId>Jellyfin.Model</PackageId>
|
||||||
<VersionPrefix>10.12.0</VersionPrefix>
|
<VersionPrefix>10.12.0</VersionPrefix>
|
||||||
@@ -14,6 +16,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
<NoWarn>$(NoWarn);CA1024;CA1031;CA1032;CA1034;CA1054;CA1055;CA1056;CA1062;CA1308;CA1303;CA1305;CA1508;CA1722;CA1724;CA1805;CA1813;CA1815;CA1816;CA1819;CA1821;CA1822;CA1823;CA1824;CA1825;CA1826;CA1830;CA1836;CA1839;CA1840;CA1841;CA1859;CA1861;CA1873;CA1911;CA2000;CA2007;CA2016;CA2025;CA2026;CA2100;CA2101;CA2102;CA2201;CA2202;CA2207;CA2208;CA2219;CA2220;CA2222;CA2225;CA2226;CA2230;CA2231;CA2234;CA2241;CA2242;CA2243;CA2244;CA2245;CA2246;CA2247;CA2248;CA2249;CA2250;CA2253;CA3001;CA3002;CA3003;CA3004;CA3005;CA3006;CA3007;CA3008;CA3009;CA3010;CA3011;CA3012;CA3061;CA3062;CA5359;CA5360;CA5361;CA5362;CA5363;CA5364;CA5365;CA5366;CA5367;CA5368;CA5369;CA5370;CA5371;CA5372;CA5373;CA5374;CA5375;CA5376;CA5377;CA5378;CA5379;CA5380;CA5381;CA5382;CA5383;CA5384;CA5385;CA5386;CA5387;CA5388;CA5389;CA5390;CA5391;CA5392;CA5393;CA5394;CA5395;CA5396;CA5397;CA5398;CA5399;CA5400;CA5401;CA5402;CA5403;NU1903;CA1848;CA1515;CA1812;CA1849;CA1860;CA1867;CA1517</NoWarn>
|
||||||
<TargetFramework>net11.0</TargetFramework>
|
<TargetFramework>net11.0</TargetFramework>
|
||||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
@@ -24,10 +27,12 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||||
|
<NoWarn>$(NoWarn);CA1024;CA1031;CA1032;CA1034;CA1054;CA1055;CA1056;CA1062;CA1308;CA1303;CA1305;CA1508;CA1722;CA1724;CA1805;CA1813;CA1815;CA1816;CA1819;CA1821;CA1822;CA1823;CA1824;CA1825;CA1826;CA1830;CA1836;CA1839;CA1840;CA1841;CA1859;CA1861;CA1873;CA1911;CA2000;CA2007;CA2016;CA2025;CA2026;CA2100;CA2101;CA2102;CA2201;CA2202;CA2207;CA2208;CA2219;CA2220;CA2222;CA2225;CA2226;CA2230;CA2231;CA2234;CA2241;CA2242;CA2243;CA2244;CA2245;CA2246;CA2247;CA2248;CA2249;CA2250;CA2253;CA3001;CA3002;CA3003;CA3004;CA3005;CA3006;CA3007;CA3008;CA3009;CA3010;CA3011;CA3012;CA3061;CA3062;CA5359;CA5360;CA5361;CA5362;CA5363;CA5364;CA5365;CA5366;CA5367;CA5368;CA5369;CA5370;CA5371;CA5372;CA5373;CA5374;CA5375;CA5376;CA5377;CA5378;CA5379;CA5380;CA5381;CA5382;CA5383;CA5384;CA5385;CA5386;CA5387;CA5388;CA5389;CA5390;CA5391;CA5392;CA5393;CA5394;CA5395;CA5396;CA5397;CA5398;CA5399;CA5400;CA5401;CA5402;CA5403;NU1903;CA1848;CA1515;CA1812;CA1849;CA1860;CA1867;CA1517</NoWarn>
|
||||||
<CodeAnalysisTreatWarningsAsErrors>false</CodeAnalysisTreatWarningsAsErrors>
|
<CodeAnalysisTreatWarningsAsErrors>false</CodeAnalysisTreatWarningsAsErrors>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition=" '$(Stability)'=='Unstable'">
|
<PropertyGroup Condition=" '$(Stability)'=='Unstable'">
|
||||||
|
<NoWarn>$(NoWarn);CA1024;CA1031;CA1032;CA1034;CA1054;CA1055;CA1056;CA1062;CA1308;CA1303;CA1305;CA1508;CA1722;CA1724;CA1805;CA1813;CA1815;CA1816;CA1819;CA1821;CA1822;CA1823;CA1824;CA1825;CA1826;CA1830;CA1836;CA1839;CA1840;CA1841;CA1859;CA1861;CA1873;CA1911;CA2000;CA2007;CA2016;CA2025;CA2026;CA2100;CA2101;CA2102;CA2201;CA2202;CA2207;CA2208;CA2219;CA2220;CA2222;CA2225;CA2226;CA2230;CA2231;CA2234;CA2241;CA2242;CA2243;CA2244;CA2245;CA2246;CA2247;CA2248;CA2249;CA2250;CA2253;CA3001;CA3002;CA3003;CA3004;CA3005;CA3006;CA3007;CA3008;CA3009;CA3010;CA3011;CA3012;CA3061;CA3062;CA5359;CA5360;CA5361;CA5362;CA5363;CA5364;CA5365;CA5366;CA5367;CA5368;CA5369;CA5370;CA5371;CA5372;CA5373;CA5374;CA5375;CA5376;CA5377;CA5378;CA5379;CA5380;CA5381;CA5382;CA5383;CA5384;CA5385;CA5386;CA5387;CA5388;CA5389;CA5390;CA5391;CA5392;CA5393;CA5394;CA5395;CA5396;CA5397;CA5398;CA5399;CA5400;CA5401;CA5402;CA5403;NU1903;CA1848;CA1515;CA1812;CA1849;CA1860;CA1867;CA1517</NoWarn>
|
||||||
<!-- Include all symbols in the main nupkg until Azure Artifact Feed starts supporting ingesting NuGet symbol packages. -->
|
<!-- Include all symbols in the main nupkg until Azure Artifact Feed starts supporting ingesting NuGet symbol packages. -->
|
||||||
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
|
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
|
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
<NoWarn>$(NoWarn);CA1024;CA1031;CA1032;CA1034;CA1054;CA1055;CA1056;CA1062;CA1308;CA1303;CA1305;CA1508;CA1722;CA1724;CA1805;CA1813;CA1815;CA1816;CA1819;CA1821;CA1822;CA1823;CA1824;CA1825;CA1826;CA1830;CA1836;CA1839;CA1840;CA1841;CA1859;CA1861;CA1873;CA1911;CA2000;CA2007;CA2016;CA2025;CA2026;CA2100;CA2101;CA2102;CA2201;CA2202;CA2207;CA2208;CA2219;CA2220;CA2222;CA2225;CA2226;CA2230;CA2231;CA2234;CA2241;CA2242;CA2243;CA2244;CA2245;CA2246;CA2247;CA2248;CA2249;CA2250;CA2253;CA3001;CA3002;CA3003;CA3004;CA3005;CA3006;CA3007;CA3008;CA3009;CA3010;CA3011;CA3012;CA3061;CA3062;CA5359;CA5360;CA5361;CA5362;CA5363;CA5364;CA5365;CA5366;CA5367;CA5368;CA5369;CA5370;CA5371;CA5372;CA5373;CA5374;CA5375;CA5376;CA5377;CA5378;CA5379;CA5380;CA5381;CA5382;CA5383;CA5384;CA5385;CA5386;CA5387;CA5388;CA5389;CA5390;CA5391;CA5392;CA5393;CA5394;CA5395;CA5396;CA5397;CA5398;CA5399;CA5400;CA5401;CA5402;CA5403;NU1903;CA1848;CA1515;CA1812;CA1849;CA1860;CA1867;CA1517</NoWarn>
|
||||||
<ProjectGuid>{442B5058-DCAF-4263-BB6A-F21E31120A1B}</ProjectGuid>
|
<ProjectGuid>{442B5058-DCAF-4263-BB6A-F21E31120A1B}</ProjectGuid>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
@@ -28,6 +29,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
<NoWarn>$(NoWarn);CA1024;CA1031;CA1032;CA1034;CA1054;CA1055;CA1056;CA1062;CA1308;CA1303;CA1305;CA1508;CA1722;CA1724;CA1805;CA1813;CA1815;CA1816;CA1819;CA1821;CA1822;CA1823;CA1824;CA1825;CA1826;CA1830;CA1836;CA1839;CA1840;CA1841;CA1859;CA1861;CA1873;CA1911;CA2000;CA2007;CA2016;CA2025;CA2026;CA2100;CA2101;CA2102;CA2201;CA2202;CA2207;CA2208;CA2219;CA2220;CA2222;CA2225;CA2226;CA2230;CA2231;CA2234;CA2241;CA2242;CA2243;CA2244;CA2245;CA2246;CA2247;CA2248;CA2249;CA2250;CA2253;CA3001;CA3002;CA3003;CA3004;CA3005;CA3006;CA3007;CA3008;CA3009;CA3010;CA3011;CA3012;CA3061;CA3062;CA5359;CA5360;CA5361;CA5362;CA5363;CA5364;CA5365;CA5366;CA5367;CA5368;CA5369;CA5370;CA5371;CA5372;CA5373;CA5374;CA5375;CA5376;CA5377;CA5378;CA5379;CA5380;CA5381;CA5382;CA5383;CA5384;CA5385;CA5386;CA5387;CA5388;CA5389;CA5390;CA5391;CA5392;CA5393;CA5394;CA5395;CA5396;CA5397;CA5398;CA5399;CA5400;CA5401;CA5402;CA5403;NU1903;CA1848;CA1515;CA1812;CA1849;CA1860;CA1867;CA1517</NoWarn>
|
||||||
<TargetFramework>net11.0</TargetFramework>
|
<TargetFramework>net11.0</TargetFramework>
|
||||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
|
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
<NoWarn>$(NoWarn);CA1024;CA1031;CA1032;CA1034;CA1054;CA1055;CA1056;CA1062;CA1308;CA1303;CA1305;CA1508;CA1722;CA1724;CA1805;CA1813;CA1815;CA1816;CA1819;CA1821;CA1822;CA1823;CA1824;CA1825;CA1826;CA1830;CA1836;CA1839;CA1840;CA1841;CA1859;CA1861;CA1873;CA1911;CA2000;CA2007;CA2016;CA2025;CA2026;CA2100;CA2101;CA2102;CA2201;CA2202;CA2207;CA2208;CA2219;CA2220;CA2222;CA2225;CA2226;CA2230;CA2231;CA2234;CA2241;CA2242;CA2243;CA2244;CA2245;CA2246;CA2247;CA2248;CA2249;CA2250;CA2253;CA3001;CA3002;CA3003;CA3004;CA3005;CA3006;CA3007;CA3008;CA3009;CA3010;CA3011;CA3012;CA3061;CA3062;CA5359;CA5360;CA5361;CA5362;CA5363;CA5364;CA5365;CA5366;CA5367;CA5368;CA5369;CA5370;CA5371;CA5372;CA5373;CA5374;CA5375;CA5376;CA5377;CA5378;CA5379;CA5380;CA5381;CA5382;CA5383;CA5384;CA5385;CA5386;CA5387;CA5388;CA5389;CA5390;CA5391;CA5392;CA5393;CA5394;CA5395;CA5396;CA5397;CA5398;CA5399;CA5400;CA5401;CA5402;CA5403;NU1903;CA1848;CA1515;CA1812;CA1849;CA1860;CA1867;CA1517</NoWarn>
|
||||||
<ProjectGuid>{23499896-B135-4527-8574-C26E926EA99E}</ProjectGuid>
|
<ProjectGuid>{23499896-B135-4527-8574-C26E926EA99E}</ProjectGuid>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
@@ -15,6 +16,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
<NoWarn>$(NoWarn);CA1024;CA1031;CA1032;CA1034;CA1054;CA1055;CA1056;CA1062;CA1308;CA1303;CA1305;CA1508;CA1722;CA1724;CA1805;CA1813;CA1815;CA1816;CA1819;CA1821;CA1822;CA1823;CA1824;CA1825;CA1826;CA1830;CA1836;CA1839;CA1840;CA1841;CA1859;CA1861;CA1873;CA1911;CA2000;CA2007;CA2016;CA2025;CA2026;CA2100;CA2101;CA2102;CA2201;CA2202;CA2207;CA2208;CA2219;CA2220;CA2222;CA2225;CA2226;CA2230;CA2231;CA2234;CA2241;CA2242;CA2243;CA2244;CA2245;CA2246;CA2247;CA2248;CA2249;CA2250;CA2253;CA3001;CA3002;CA3003;CA3004;CA3005;CA3006;CA3007;CA3008;CA3009;CA3010;CA3011;CA3012;CA3061;CA3062;CA5359;CA5360;CA5361;CA5362;CA5363;CA5364;CA5365;CA5366;CA5367;CA5368;CA5369;CA5370;CA5371;CA5372;CA5373;CA5374;CA5375;CA5376;CA5377;CA5378;CA5379;CA5380;CA5381;CA5382;CA5383;CA5384;CA5385;CA5386;CA5387;CA5388;CA5389;CA5390;CA5391;CA5392;CA5393;CA5394;CA5395;CA5396;CA5397;CA5398;CA5399;CA5400;CA5401;CA5402;CA5403;NU1903;CA1848;CA1515;CA1812;CA1849;CA1860;CA1867;CA1517</NoWarn>
|
||||||
<TargetFramework>net11.0</TargetFramework>
|
<TargetFramework>net11.0</TargetFramework>
|
||||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
|
|||||||
@@ -0,0 +1,167 @@
|
|||||||
|
# WebSocket Authentication Guide
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
WebSocket connections to Jellyfin servers require authentication. This guide explains how to properly authenticate WebSocket connections using API tokens.
|
||||||
|
|
||||||
|
## Authentication Methods
|
||||||
|
|
||||||
|
### 1. Query String Parameter (Recommended for WebSocket)
|
||||||
|
|
||||||
|
The simplest and most compatible method for WebSocket connections.
|
||||||
|
|
||||||
|
**URL Format:**
|
||||||
|
```
|
||||||
|
ws://jellyfin-server:8096/socket?api_key=YOUR_API_TOKEN
|
||||||
|
wss://jellyfin-server:8096/socket?api_key=YOUR_API_TOKEN (for HTTPS)
|
||||||
|
```
|
||||||
|
|
||||||
|
**JavaScript Example:**
|
||||||
|
```javascript
|
||||||
|
const token = "YOUR_API_KEY";
|
||||||
|
const ws = new WebSocket(`ws://jellyfin-server:8096/socket?api_key=${token}`);
|
||||||
|
|
||||||
|
ws.onopen = function(event) {
|
||||||
|
console.log("WebSocket connection established with authentication");
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.onerror = function(event) {
|
||||||
|
console.error("WebSocket error:", event);
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.onmessage = function(event) {
|
||||||
|
const message = JSON.parse(event.data);
|
||||||
|
console.log("Received message:", message);
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.onclose = function(event) {
|
||||||
|
console.log("WebSocket connection closed");
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
**Python Example:**
|
||||||
|
```python
|
||||||
|
import asyncio
|
||||||
|
import websockets
|
||||||
|
import json
|
||||||
|
|
||||||
|
async def connect_with_token():
|
||||||
|
token = "YOUR_API_KEY"
|
||||||
|
uri = f"ws://jellyfin-server:8096/socket?api_key={token}"
|
||||||
|
|
||||||
|
async with websockets.connect(uri) as websocket:
|
||||||
|
print("Connected to Jellyfin WebSocket")
|
||||||
|
|
||||||
|
# Receive messages
|
||||||
|
async for message in websocket:
|
||||||
|
data = json.loads(message)
|
||||||
|
print(f"Received: {data}")
|
||||||
|
|
||||||
|
asyncio.run(connect_with_token())
|
||||||
|
```
|
||||||
|
|
||||||
|
**C# Example:**
|
||||||
|
```csharp
|
||||||
|
using System;
|
||||||
|
using System.Net.WebSockets;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
public class JellyfinWebSocketClient
|
||||||
|
{
|
||||||
|
public async Task ConnectAsync(string serverUrl, string token)
|
||||||
|
{
|
||||||
|
var uri = new Uri($"ws://{serverUrl}:8096/socket?api_key={token}");
|
||||||
|
|
||||||
|
using (var client = new ClientWebSocket())
|
||||||
|
{
|
||||||
|
await client.ConnectAsync(uri, CancellationToken.None);
|
||||||
|
|
||||||
|
Console.WriteLine("Connected to Jellyfin WebSocket");
|
||||||
|
|
||||||
|
// Receive messages
|
||||||
|
var buffer = new byte[1024 * 4];
|
||||||
|
while (client.State == WebSocketState.Open)
|
||||||
|
{
|
||||||
|
var result = await client.ReceiveAsync(
|
||||||
|
new ArraySegment<byte>(buffer),
|
||||||
|
CancellationToken.None);
|
||||||
|
|
||||||
|
if (result.MessageType == WebSocketMessageType.Text)
|
||||||
|
{
|
||||||
|
var message = System.Text.Encoding.UTF8.GetString(
|
||||||
|
buffer, 0, result.Count);
|
||||||
|
Console.WriteLine($"Received: {message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Authorization Header (Alternative)
|
||||||
|
|
||||||
|
For advanced use cases, you can also use the Authorization header.
|
||||||
|
|
||||||
|
**Header Format:**
|
||||||
|
```
|
||||||
|
Authorization: MediaBrowser Device="ClientName", DeviceId="unique-id", Version="1.0", Token="YOUR_API_TOKEN"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note:** Some WebSocket implementations may not support custom headers during the upgrade handshake. Query parameters are recommended.
|
||||||
|
|
||||||
|
## Obtaining an API Token
|
||||||
|
|
||||||
|
### Via Server UI
|
||||||
|
1. Navigate to your Jellyfin server dashboard
|
||||||
|
2. Go to Settings → API Keys (or similar, depending on version)
|
||||||
|
3. Create a new API key
|
||||||
|
4. Copy the token to use in your WebSocket connection
|
||||||
|
|
||||||
|
### Programmatically
|
||||||
|
Use the REST API to create API keys:
|
||||||
|
```bash
|
||||||
|
curl -X POST "http://jellyfin-server:8096/Auth/Keys" \
|
||||||
|
-H "Authorization: MediaBrowser Token=existing_token" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"AppName": "My WebSocket Client"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Common Issues
|
||||||
|
|
||||||
|
### Connection Refused / 401 Unauthorized
|
||||||
|
- Verify the API token is correct
|
||||||
|
- Ensure the token hasn't expired
|
||||||
|
- Check that the WebSocket endpoint path is correct (`/socket`)
|
||||||
|
|
||||||
|
### Token Not Found
|
||||||
|
- Verify the query parameter is URL-encoded properly
|
||||||
|
- Ensure the parameter name is correct: `api_key` (lowercase)
|
||||||
|
- Check server logs for authentication errors
|
||||||
|
|
||||||
|
### WebSocket Connection Fails Immediately
|
||||||
|
- Confirm the server is reachable
|
||||||
|
- Check firewall rules allow WebSocket connections
|
||||||
|
- Try with `wss://` (secure WebSocket) if using HTTPS
|
||||||
|
|
||||||
|
## Server Configuration
|
||||||
|
|
||||||
|
The server automatically extracts tokens from:
|
||||||
|
1. Authorization header (MediaBrowser Token parameter)
|
||||||
|
2. Query string `api_key` parameter
|
||||||
|
3. Query string `ApiKey` parameter
|
||||||
|
4. Legacy headers (if enabled in config)
|
||||||
|
|
||||||
|
No special server configuration is required for WebSocket authentication to work.
|
||||||
|
|
||||||
|
## Security Considerations
|
||||||
|
|
||||||
|
- Always use `wss://` (secure WebSocket) when connecting over untrusted networks
|
||||||
|
- Keep API tokens secure and rotate them periodically
|
||||||
|
- Use separate tokens for different clients/applications
|
||||||
|
- Consider implementing token expiration in your server configuration
|
||||||
|
|
||||||
|
## See Also
|
||||||
|
|
||||||
|
- [Jellyfin API Documentation](https://api.jellyfin.org/)
|
||||||
|
- [WebSocket Protocol (RFC 6455)](https://tools.ietf.org/html/rfc6455)
|
||||||
@@ -0,0 +1,710 @@
|
|||||||
|
# Library Options DB Design
|
||||||
|
|
||||||
|
This document turns the earlier storage discussion into concrete PostgreSQL DDL and C# repository sketches that fit the patterns already used in this repository.
|
||||||
|
|
||||||
|
Relevant existing patterns:
|
||||||
|
|
||||||
|
- PostgreSQL migrations use `MigrationBuilder.Sql(...)` in provider-specific migrations under `src/Jellyfin.Database/Jellyfin.Database.Providers.Postgres/Migrations/`.
|
||||||
|
- EF entities live under `src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/`.
|
||||||
|
- `DbSet<>` registrations live in `src/Jellyfin.Database/Jellyfin.Database.Implementations/JellyfinDbContext.cs`.
|
||||||
|
- PostgreSQL table mapping lives in `src/Jellyfin.Database/Jellyfin.Database.Providers.Postgres/PostgresDatabaseProvider.cs`.
|
||||||
|
- Runtime repositories typically use `IDbContextFactory<JellyfinDbContext>` and `await using var context = _dbProvider.CreateDbContext();` as seen in `Jellyfin.Server.Implementations/Item/MediaStreamRepository.cs`.
|
||||||
|
- Virtual path storage is already handled by `IServerApplicationHost.ReverseVirtualPath(...)` and `ExpandVirtualPath(...)`.
|
||||||
|
|
||||||
|
## Option 1: Minimal-Change JSON-In-DB
|
||||||
|
|
||||||
|
### Goal
|
||||||
|
|
||||||
|
Persist one `LibraryOptions` document per collection folder in PostgreSQL with minimal change to the existing `CollectionFolder.GetLibraryOptions(...)` and `SaveLibraryOptions(...)` flow.
|
||||||
|
|
||||||
|
### Table Design
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE library."LibraryOptions" (
|
||||||
|
"LibraryId" uuid NOT NULL,
|
||||||
|
"LibraryPath" text NOT NULL,
|
||||||
|
"OptionsJson" jsonb NOT NULL,
|
||||||
|
"Version" integer NOT NULL DEFAULT 1,
|
||||||
|
"DateCreated" timestamp with time zone NOT NULL DEFAULT now(),
|
||||||
|
"DateModified" timestamp with time zone NOT NULL DEFAULT now(),
|
||||||
|
CONSTRAINT "PK_LibraryOptions" PRIMARY KEY ("LibraryId")
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX "IX_LibraryOptions_LibraryPath"
|
||||||
|
ON library."LibraryOptions" USING btree ("LibraryPath");
|
||||||
|
|
||||||
|
CREATE INDEX "IX_LibraryOptions_DateModified"
|
||||||
|
ON library."LibraryOptions" USING btree ("DateModified" DESC);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Suggested PostgreSQL Migration
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// <copyright file="20260501000000_AddLibraryOptionsJsonStore.cs" company="PlaceholderCompany">
|
||||||
|
// Copyright (c) PlaceholderCompany. All rights reserved.
|
||||||
|
// </copyright>
|
||||||
|
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Jellyfin.Database.Providers.Postgres.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddLibraryOptionsJsonStore : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.Sql(@"
|
||||||
|
CREATE TABLE IF NOT EXISTS library.""LibraryOptions"" (
|
||||||
|
""LibraryId"" uuid NOT NULL,
|
||||||
|
""LibraryPath"" text NOT NULL,
|
||||||
|
""OptionsJson"" jsonb NOT NULL,
|
||||||
|
""Version"" integer NOT NULL DEFAULT 1,
|
||||||
|
""DateCreated"" timestamp with time zone NOT NULL DEFAULT now(),
|
||||||
|
""DateModified"" timestamp with time zone NOT NULL DEFAULT now(),
|
||||||
|
CONSTRAINT ""PK_LibraryOptions"" PRIMARY KEY (""LibraryId"")
|
||||||
|
);
|
||||||
|
");
|
||||||
|
|
||||||
|
migrationBuilder.Sql(@"
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS ""IX_LibraryOptions_LibraryPath""
|
||||||
|
ON library.""LibraryOptions"" (""LibraryPath"");
|
||||||
|
");
|
||||||
|
|
||||||
|
migrationBuilder.Sql(@"
|
||||||
|
CREATE INDEX IF NOT EXISTS ""IX_LibraryOptions_DateModified""
|
||||||
|
ON library.""LibraryOptions"" (""DateModified"" DESC);
|
||||||
|
");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.Sql(@"DROP TABLE IF EXISTS library.""LibraryOptions"" CASCADE;");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Suggested Entity
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// <copyright file="LibraryOptionsEntity.cs" company="PlaceholderCompany">
|
||||||
|
// Copyright (c) PlaceholderCompany. All rights reserved.
|
||||||
|
// </copyright>
|
||||||
|
|
||||||
|
namespace Jellyfin.Database.Implementations.Entities;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
|
public class LibraryOptionsEntity
|
||||||
|
{
|
||||||
|
public Guid LibraryId { get; set; }
|
||||||
|
|
||||||
|
public required string LibraryPath { get; set; }
|
||||||
|
|
||||||
|
public required string OptionsJson { get; set; }
|
||||||
|
|
||||||
|
public int Version { get; set; }
|
||||||
|
|
||||||
|
public DateTime DateCreated { get; set; }
|
||||||
|
|
||||||
|
public DateTime DateModified { get; set; }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### DbContext / Provider Mapping
|
||||||
|
|
||||||
|
Add to `JellyfinDbContext`:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public DbSet<LibraryOptionsEntity> LibraryOptions => this.Set<LibraryOptionsEntity>();
|
||||||
|
```
|
||||||
|
|
||||||
|
Add to `PostgresDatabaseProvider.ConfigureEntities(...)`:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
modelBuilder.Entity<LibraryOptionsEntity>().ToTable("LibraryOptions", Schemas.Library);
|
||||||
|
```
|
||||||
|
|
||||||
|
Recommended extra configuration:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
modelBuilder.Entity<LibraryOptionsEntity>()
|
||||||
|
.HasIndex(e => e.LibraryPath)
|
||||||
|
.IsUnique();
|
||||||
|
```
|
||||||
|
|
||||||
|
### Repository Sketch
|
||||||
|
|
||||||
|
This sketch follows the `MediaStreamRepository` and `BaseItemRepository` conventions already used in the codebase.
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// <copyright file="LibraryOptionsRepository.cs" company="PlaceholderCompany">
|
||||||
|
// Copyright (c) PlaceholderCompany. All rights reserved.
|
||||||
|
// </copyright>
|
||||||
|
|
||||||
|
namespace Jellyfin.Server.Implementations.Library;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Jellyfin.Database.Implementations;
|
||||||
|
using Jellyfin.Database.Implementations.Entities;
|
||||||
|
using Jellyfin.Extensions.Json;
|
||||||
|
using MediaBrowser.Controller;
|
||||||
|
using MediaBrowser.Model.Configuration;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
public class LibraryOptionsRepository
|
||||||
|
{
|
||||||
|
private readonly IDbContextFactory<JellyfinDbContext> _dbProvider;
|
||||||
|
private readonly IServerApplicationHost _appHost;
|
||||||
|
|
||||||
|
public LibraryOptionsRepository(IDbContextFactory<JellyfinDbContext> dbProvider, IServerApplicationHost appHost)
|
||||||
|
{
|
||||||
|
_dbProvider = dbProvider;
|
||||||
|
_appHost = appHost;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<LibraryOptions?> GetLibraryOptionsAsync(Guid libraryId, string libraryPath, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
await using var context = _dbProvider.CreateDbContext();
|
||||||
|
|
||||||
|
var entity = await context.LibraryOptions
|
||||||
|
.AsNoTracking()
|
||||||
|
.SingleOrDefaultAsync(e => e.LibraryId == libraryId || e.LibraryPath == libraryPath, cancellationToken)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (entity is null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var options = JsonSerializer.Deserialize<LibraryOptions>(entity.OptionsJson, JsonDefaults.Options) ?? new LibraryOptions();
|
||||||
|
|
||||||
|
foreach (var mediaPath in options.PathInfos)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(mediaPath.Path))
|
||||||
|
{
|
||||||
|
mediaPath.Path = _appHost.ExpandVirtualPath(mediaPath.Path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SaveLibraryOptionsAsync(Guid libraryId, string libraryPath, LibraryOptions options, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
await using var context = _dbProvider.CreateDbContext();
|
||||||
|
|
||||||
|
var executionStrategy = context.Database.CreateExecutionStrategy();
|
||||||
|
await executionStrategy.ExecuteAsync(async () =>
|
||||||
|
{
|
||||||
|
var clone = JsonSerializer.Deserialize<LibraryOptions>(JsonSerializer.SerializeToUtf8Bytes(options, JsonDefaults.Options), JsonDefaults.Options)
|
||||||
|
?? new LibraryOptions();
|
||||||
|
|
||||||
|
foreach (var mediaPath in clone.PathInfos)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(mediaPath.Path))
|
||||||
|
{
|
||||||
|
mediaPath.Path = _appHost.ReverseVirtualPath(mediaPath.Path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var json = JsonSerializer.Serialize(clone, JsonDefaults.Options);
|
||||||
|
var entity = await context.LibraryOptions
|
||||||
|
.SingleOrDefaultAsync(e => e.LibraryId == libraryId, cancellationToken)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (entity is null)
|
||||||
|
{
|
||||||
|
entity = new LibraryOptionsEntity
|
||||||
|
{
|
||||||
|
LibraryId = libraryId,
|
||||||
|
LibraryPath = libraryPath,
|
||||||
|
OptionsJson = json,
|
||||||
|
Version = 1,
|
||||||
|
DateCreated = DateTime.UtcNow,
|
||||||
|
DateModified = DateTime.UtcNow
|
||||||
|
};
|
||||||
|
|
||||||
|
await context.LibraryOptions.AddAsync(entity, cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
entity.LibraryPath = libraryPath;
|
||||||
|
entity.OptionsJson = json;
|
||||||
|
entity.DateModified = DateTime.UtcNow;
|
||||||
|
}
|
||||||
|
|
||||||
|
await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
}).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### CollectionFolder Integration Sketch
|
||||||
|
|
||||||
|
The minimal integration strategy is:
|
||||||
|
|
||||||
|
1. Change `CollectionFolder.LoadLibraryOptions(...)` to try the repository first.
|
||||||
|
2. If no database row exists, fall back to `options.xml`.
|
||||||
|
3. After a successful XML read, persist the value into `library."LibraryOptions"`.
|
||||||
|
4. Keep the current static cache keyed by library path.
|
||||||
|
|
||||||
|
Pseudo-flow:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
var fromDb = await _libraryOptionsRepository.GetLibraryOptionsAsync(libraryId, path, cancellationToken);
|
||||||
|
if (fromDb is not null)
|
||||||
|
{
|
||||||
|
return fromDb;
|
||||||
|
}
|
||||||
|
|
||||||
|
var fromXml = LoadLibraryOptionsFromXml(path);
|
||||||
|
await _libraryOptionsRepository.SaveLibraryOptionsAsync(libraryId, path, fromXml, cancellationToken);
|
||||||
|
return fromXml;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Tradeoffs
|
||||||
|
|
||||||
|
- Lowest risk.
|
||||||
|
- No relational visibility into individual media roots.
|
||||||
|
- Best fit if the goal is only to stop using `options.xml` as the persistence backend.
|
||||||
|
|
||||||
|
## Option 2: Hybrid JSON + Queryable Media Paths
|
||||||
|
|
||||||
|
### Goal
|
||||||
|
|
||||||
|
Keep the full `LibraryOptions` object as JSON for compatibility, but project `PathInfos` into a separate relational table so path-level reporting and joins are cheap and reliable.
|
||||||
|
|
||||||
|
### Table Design
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE library."LibraryOptions" (
|
||||||
|
"LibraryId" uuid NOT NULL,
|
||||||
|
"LibraryPath" text NOT NULL,
|
||||||
|
"OptionsJson" jsonb NOT NULL,
|
||||||
|
"Version" integer NOT NULL DEFAULT 1,
|
||||||
|
"DateCreated" timestamp with time zone NOT NULL DEFAULT now(),
|
||||||
|
"DateModified" timestamp with time zone NOT NULL DEFAULT now(),
|
||||||
|
CONSTRAINT "PK_LibraryOptions" PRIMARY KEY ("LibraryId")
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX "IX_LibraryOptions_LibraryPath"
|
||||||
|
ON library."LibraryOptions" USING btree ("LibraryPath");
|
||||||
|
|
||||||
|
CREATE TABLE library."LibraryMediaPaths" (
|
||||||
|
"Id" uuid NOT NULL,
|
||||||
|
"LibraryId" uuid NOT NULL,
|
||||||
|
"Path" text NOT NULL,
|
||||||
|
"PathVirtual" text NOT NULL,
|
||||||
|
"Position" integer NOT NULL,
|
||||||
|
"DateCreated" timestamp with time zone NOT NULL DEFAULT now(),
|
||||||
|
"DateModified" timestamp with time zone NOT NULL DEFAULT now(),
|
||||||
|
CONSTRAINT "PK_LibraryMediaPaths" PRIMARY KEY ("Id"),
|
||||||
|
CONSTRAINT "FK_LibraryMediaPaths_LibraryOptions_LibraryId"
|
||||||
|
FOREIGN KEY ("LibraryId") REFERENCES library."LibraryOptions" ("LibraryId") ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX "IX_LibraryMediaPaths_LibraryId_Position"
|
||||||
|
ON library."LibraryMediaPaths" USING btree ("LibraryId", "Position");
|
||||||
|
|
||||||
|
CREATE INDEX "IX_LibraryMediaPaths_Path"
|
||||||
|
ON library."LibraryMediaPaths" USING btree ("Path");
|
||||||
|
|
||||||
|
CREATE INDEX "IX_LibraryMediaPaths_PathVirtual"
|
||||||
|
ON library."LibraryMediaPaths" USING btree ("PathVirtual");
|
||||||
|
|
||||||
|
CREATE INDEX "IX_LibraryMediaPaths_LibraryId"
|
||||||
|
ON library."LibraryMediaPaths" USING btree ("LibraryId");
|
||||||
|
```
|
||||||
|
|
||||||
|
### Suggested PostgreSQL Migration
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// <copyright file="20260501001000_AddLibraryOptionsHybridStore.cs" company="PlaceholderCompany">
|
||||||
|
// Copyright (c) PlaceholderCompany. All rights reserved.
|
||||||
|
// </copyright>
|
||||||
|
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Jellyfin.Database.Providers.Postgres.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddLibraryOptionsHybridStore : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.Sql(@"
|
||||||
|
CREATE TABLE IF NOT EXISTS library.""LibraryOptions"" (
|
||||||
|
""LibraryId"" uuid NOT NULL,
|
||||||
|
""LibraryPath"" text NOT NULL,
|
||||||
|
""OptionsJson"" jsonb NOT NULL,
|
||||||
|
""Version"" integer NOT NULL DEFAULT 1,
|
||||||
|
""DateCreated"" timestamp with time zone NOT NULL DEFAULT now(),
|
||||||
|
""DateModified"" timestamp with time zone NOT NULL DEFAULT now(),
|
||||||
|
CONSTRAINT ""PK_LibraryOptions"" PRIMARY KEY (""LibraryId"")
|
||||||
|
);
|
||||||
|
");
|
||||||
|
|
||||||
|
migrationBuilder.Sql(@"
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS ""IX_LibraryOptions_LibraryPath""
|
||||||
|
ON library.""LibraryOptions"" (""LibraryPath"");
|
||||||
|
");
|
||||||
|
|
||||||
|
migrationBuilder.Sql(@"
|
||||||
|
CREATE TABLE IF NOT EXISTS library.""LibraryMediaPaths"" (
|
||||||
|
""Id"" uuid NOT NULL,
|
||||||
|
""LibraryId"" uuid NOT NULL,
|
||||||
|
""Path"" text NOT NULL,
|
||||||
|
""PathVirtual"" text NOT NULL,
|
||||||
|
""Position"" integer NOT NULL,
|
||||||
|
""DateCreated"" timestamp with time zone NOT NULL DEFAULT now(),
|
||||||
|
""DateModified"" timestamp with time zone NOT NULL DEFAULT now(),
|
||||||
|
CONSTRAINT ""PK_LibraryMediaPaths"" PRIMARY KEY (""Id""),
|
||||||
|
CONSTRAINT ""FK_LibraryMediaPaths_LibraryOptions_LibraryId""
|
||||||
|
FOREIGN KEY (""LibraryId"") REFERENCES library.""LibraryOptions"" (""LibraryId"") ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
");
|
||||||
|
|
||||||
|
migrationBuilder.Sql(@"
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS ""IX_LibraryMediaPaths_LibraryId_Position""
|
||||||
|
ON library.""LibraryMediaPaths"" (""LibraryId"", ""Position"");
|
||||||
|
");
|
||||||
|
|
||||||
|
migrationBuilder.Sql(@"
|
||||||
|
CREATE INDEX IF NOT EXISTS ""IX_LibraryMediaPaths_Path""
|
||||||
|
ON library.""LibraryMediaPaths"" (""Path"");
|
||||||
|
");
|
||||||
|
|
||||||
|
migrationBuilder.Sql(@"
|
||||||
|
CREATE INDEX IF NOT EXISTS ""IX_LibraryMediaPaths_PathVirtual""
|
||||||
|
ON library.""LibraryMediaPaths"" (""PathVirtual"");
|
||||||
|
");
|
||||||
|
|
||||||
|
migrationBuilder.Sql(@"
|
||||||
|
CREATE INDEX IF NOT EXISTS ""IX_LibraryMediaPaths_LibraryId""
|
||||||
|
ON library.""LibraryMediaPaths"" (""LibraryId"");
|
||||||
|
");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.Sql(@"DROP TABLE IF EXISTS library.""LibraryMediaPaths"" CASCADE;");
|
||||||
|
migrationBuilder.Sql(@"DROP TABLE IF EXISTS library.""LibraryOptions"" CASCADE;");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Suggested Entities
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// <copyright file="LibraryOptionsEntity.cs" company="PlaceholderCompany">
|
||||||
|
// Copyright (c) PlaceholderCompany. All rights reserved.
|
||||||
|
// </copyright>
|
||||||
|
|
||||||
|
namespace Jellyfin.Database.Implementations.Entities;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
public class LibraryOptionsEntity
|
||||||
|
{
|
||||||
|
public Guid LibraryId { get; set; }
|
||||||
|
|
||||||
|
public required string LibraryPath { get; set; }
|
||||||
|
|
||||||
|
public required string OptionsJson { get; set; }
|
||||||
|
|
||||||
|
public int Version { get; set; }
|
||||||
|
|
||||||
|
public DateTime DateCreated { get; set; }
|
||||||
|
|
||||||
|
public DateTime DateModified { get; set; }
|
||||||
|
|
||||||
|
public ICollection<LibraryMediaPathEntity> MediaPaths { get; set; } = new List<LibraryMediaPathEntity>();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// <copyright file="LibraryMediaPathEntity.cs" company="PlaceholderCompany">
|
||||||
|
// Copyright (c) PlaceholderCompany. All rights reserved.
|
||||||
|
// </copyright>
|
||||||
|
|
||||||
|
namespace Jellyfin.Database.Implementations.Entities;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
|
public class LibraryMediaPathEntity
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
|
||||||
|
public Guid LibraryId { get; set; }
|
||||||
|
|
||||||
|
public required string Path { get; set; }
|
||||||
|
|
||||||
|
public required string PathVirtual { get; set; }
|
||||||
|
|
||||||
|
public int Position { get; set; }
|
||||||
|
|
||||||
|
public DateTime DateCreated { get; set; }
|
||||||
|
|
||||||
|
public DateTime DateModified { get; set; }
|
||||||
|
|
||||||
|
public LibraryOptionsEntity LibraryOptions { get; set; } = null!;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### DbContext / Provider Mapping
|
||||||
|
|
||||||
|
Add to `JellyfinDbContext`:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public DbSet<LibraryOptionsEntity> LibraryOptions => this.Set<LibraryOptionsEntity>();
|
||||||
|
|
||||||
|
public DbSet<LibraryMediaPathEntity> LibraryMediaPaths => this.Set<LibraryMediaPathEntity>();
|
||||||
|
```
|
||||||
|
|
||||||
|
Add to `PostgresDatabaseProvider.ConfigureEntities(...)`:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
modelBuilder.Entity<LibraryOptionsEntity>().ToTable("LibraryOptions", Schemas.Library);
|
||||||
|
modelBuilder.Entity<LibraryMediaPathEntity>().ToTable("LibraryMediaPaths", Schemas.Library);
|
||||||
|
|
||||||
|
modelBuilder.Entity<LibraryOptionsEntity>()
|
||||||
|
.HasMany(e => e.MediaPaths)
|
||||||
|
.WithOne(e => e.LibraryOptions)
|
||||||
|
.HasForeignKey(e => e.LibraryId)
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
modelBuilder.Entity<LibraryOptionsEntity>()
|
||||||
|
.HasIndex(e => e.LibraryPath)
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
modelBuilder.Entity<LibraryMediaPathEntity>()
|
||||||
|
.HasIndex(e => new { e.LibraryId, e.Position })
|
||||||
|
.IsUnique();
|
||||||
|
```
|
||||||
|
|
||||||
|
### Repository Sketch
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// <copyright file="LibraryOptionsRepository.cs" company="PlaceholderCompany">
|
||||||
|
// Copyright (c) PlaceholderCompany. All rights reserved.
|
||||||
|
// </copyright>
|
||||||
|
|
||||||
|
namespace Jellyfin.Server.Implementations.Library;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Jellyfin.Database.Implementations;
|
||||||
|
using Jellyfin.Database.Implementations.Entities;
|
||||||
|
using Jellyfin.Extensions.Json;
|
||||||
|
using MediaBrowser.Controller;
|
||||||
|
using MediaBrowser.Model.Configuration;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
public class LibraryOptionsRepository
|
||||||
|
{
|
||||||
|
private readonly IDbContextFactory<JellyfinDbContext> _dbProvider;
|
||||||
|
private readonly IServerApplicationHost _appHost;
|
||||||
|
|
||||||
|
public LibraryOptionsRepository(IDbContextFactory<JellyfinDbContext> dbProvider, IServerApplicationHost appHost)
|
||||||
|
{
|
||||||
|
_dbProvider = dbProvider;
|
||||||
|
_appHost = appHost;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<LibraryOptions?> GetLibraryOptionsAsync(Guid libraryId, string libraryPath, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
await using var context = _dbProvider.CreateDbContext();
|
||||||
|
|
||||||
|
var entity = await context.LibraryOptions
|
||||||
|
.AsNoTracking()
|
||||||
|
.Include(e => e.MediaPaths.OrderBy(p => p.Position))
|
||||||
|
.SingleOrDefaultAsync(e => e.LibraryId == libraryId || e.LibraryPath == libraryPath, cancellationToken)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (entity is null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var options = JsonSerializer.Deserialize<LibraryOptions>(entity.OptionsJson, JsonDefaults.Options) ?? new LibraryOptions();
|
||||||
|
|
||||||
|
foreach (var mediaPath in options.PathInfos)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(mediaPath.Path))
|
||||||
|
{
|
||||||
|
mediaPath.Path = _appHost.ExpandVirtualPath(mediaPath.Path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SaveLibraryOptionsAsync(Guid libraryId, string libraryPath, LibraryOptions options, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
await using var context = _dbProvider.CreateDbContext();
|
||||||
|
|
||||||
|
var executionStrategy = context.Database.CreateExecutionStrategy();
|
||||||
|
await executionStrategy.ExecuteAsync(async () =>
|
||||||
|
{
|
||||||
|
await using var transaction = await context.Database.BeginTransactionAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
var clone = JsonSerializer.Deserialize<LibraryOptions>(JsonSerializer.SerializeToUtf8Bytes(options, JsonDefaults.Options), JsonDefaults.Options)
|
||||||
|
?? new LibraryOptions();
|
||||||
|
|
||||||
|
foreach (var mediaPath in clone.PathInfos)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(mediaPath.Path))
|
||||||
|
{
|
||||||
|
mediaPath.Path = _appHost.ReverseVirtualPath(mediaPath.Path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var json = JsonSerializer.Serialize(clone, JsonDefaults.Options);
|
||||||
|
var utcNow = DateTime.UtcNow;
|
||||||
|
|
||||||
|
var entity = await context.LibraryOptions
|
||||||
|
.Include(e => e.MediaPaths)
|
||||||
|
.SingleOrDefaultAsync(e => e.LibraryId == libraryId, cancellationToken)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (entity is null)
|
||||||
|
{
|
||||||
|
entity = new LibraryOptionsEntity
|
||||||
|
{
|
||||||
|
LibraryId = libraryId,
|
||||||
|
LibraryPath = libraryPath,
|
||||||
|
OptionsJson = json,
|
||||||
|
Version = 1,
|
||||||
|
DateCreated = utcNow,
|
||||||
|
DateModified = utcNow
|
||||||
|
};
|
||||||
|
|
||||||
|
await context.LibraryOptions.AddAsync(entity, cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
entity.LibraryPath = libraryPath;
|
||||||
|
entity.OptionsJson = json;
|
||||||
|
entity.DateModified = utcNow;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entity.MediaPaths.Count > 0)
|
||||||
|
{
|
||||||
|
context.LibraryMediaPaths.RemoveRange(entity.MediaPaths);
|
||||||
|
}
|
||||||
|
|
||||||
|
entity.MediaPaths = clone.PathInfos
|
||||||
|
.Select((pathInfo, index) => new LibraryMediaPathEntity
|
||||||
|
{
|
||||||
|
Id = Guid.NewGuid(),
|
||||||
|
LibraryId = libraryId,
|
||||||
|
Path = _appHost.ExpandVirtualPath(pathInfo.Path),
|
||||||
|
PathVirtual = pathInfo.Path,
|
||||||
|
Position = index,
|
||||||
|
DateCreated = utcNow,
|
||||||
|
DateModified = utcNow
|
||||||
|
})
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
await transaction.CommitAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
}).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Guid[]> GetLibrariesByMediaPathAsync(string path, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
await using var context = _dbProvider.CreateDbContext();
|
||||||
|
|
||||||
|
return await context.LibraryMediaPaths
|
||||||
|
.AsNoTracking()
|
||||||
|
.Where(e => e.Path == path || e.PathVirtual == path)
|
||||||
|
.OrderBy(e => e.Position)
|
||||||
|
.Select(e => e.LibraryId)
|
||||||
|
.Distinct()
|
||||||
|
.ToArrayAsync(cancellationToken)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Operational Queries Enabled By The Hybrid Model
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT lmp."LibraryId", lmp."Path", lmp."PathVirtual"
|
||||||
|
FROM library."LibraryMediaPaths" AS lmp
|
||||||
|
WHERE lmp."Path" = '/media/main_bridge/media/movies'
|
||||||
|
OR lmp."PathVirtual" = '/media/main_bridge/media/movies';
|
||||||
|
```
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT lmp."Path", count(*) AS "LibraryCount"
|
||||||
|
FROM library."LibraryMediaPaths" AS lmp
|
||||||
|
GROUP BY lmp."Path"
|
||||||
|
HAVING count(*) > 1
|
||||||
|
ORDER BY "LibraryCount" DESC, lmp."Path";
|
||||||
|
```
|
||||||
|
|
||||||
|
### Tradeoffs
|
||||||
|
|
||||||
|
- Slightly more implementation work than the JSON-only option.
|
||||||
|
- Keeps the current `LibraryOptions` object model intact.
|
||||||
|
- Makes media roots queryable, indexable, and auditable.
|
||||||
|
- Requires a transactional write path so JSON and projected rows do not drift.
|
||||||
|
|
||||||
|
## Recommended Touch Points In This Repo
|
||||||
|
|
||||||
|
Regardless of which option is chosen, the same set of integration points will need to move:
|
||||||
|
|
||||||
|
- `MediaBrowser.Controller/Entities/CollectionFolder.cs`
|
||||||
|
- Replace raw XML read/write with repository-backed read/write.
|
||||||
|
- Keep XML fallback for a transition period if rollback safety matters.
|
||||||
|
- `src/Jellyfin.Database/Jellyfin.Database.Implementations/JellyfinDbContext.cs`
|
||||||
|
- Add `DbSet<>` properties.
|
||||||
|
- `src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/`
|
||||||
|
- Add `LibraryOptionsEntity` and, for the hybrid approach, `LibraryMediaPathEntity`.
|
||||||
|
- `src/Jellyfin.Database/Jellyfin.Database.Providers.Postgres/PostgresDatabaseProvider.cs`
|
||||||
|
- Add table mapping and relationship/index configuration.
|
||||||
|
- `src/Jellyfin.Database/Jellyfin.Database.Providers.Postgres/Migrations/`
|
||||||
|
- Add provider-specific migration(s).
|
||||||
|
- `Jellyfin.Server.Implementations/Library/`
|
||||||
|
- Add a repository class and register it in DI.
|
||||||
|
|
||||||
|
## Recommended Rollout
|
||||||
|
|
||||||
|
### JSON-In-DB
|
||||||
|
|
||||||
|
1. Add the table, entity, mapping, and repository.
|
||||||
|
2. Read DB first, then fall back to XML.
|
||||||
|
3. Backfill on demand when XML is encountered.
|
||||||
|
4. Keep XML writes optional for one release if rollback support is needed.
|
||||||
|
|
||||||
|
### Hybrid
|
||||||
|
|
||||||
|
1. Add both tables, entities, mapping, and repository.
|
||||||
|
2. Save JSON and `PathInfos` projection in one transaction.
|
||||||
|
3. Read DB first, then fall back to XML.
|
||||||
|
4. Add a small admin or diagnostic query surface over `LibraryMediaPaths`.
|
||||||
|
5. Remove XML writes after migration confidence is high.
|
||||||
|
|
||||||
|
## Recommendation
|
||||||
|
|
||||||
|
If the immediate goal is only to stop relying on `options.xml`, choose the JSON-only design.
|
||||||
|
|
||||||
|
If the goal includes operational visibility into library roots, duplicate path detection, path remapping support, or future query-driven tooling, choose the hybrid design. The hybrid design is the better long-term fit for PostgreSQL while still preserving the current `LibraryOptions` object graph and call sites.
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
<NoWarn>$(NoWarn);CA1024;CA1031;CA1032;CA1034;CA1054;CA1055;CA1056;CA1062;CA1308;CA1303;CA1305;CA1508;CA1722;CA1724;CA1805;CA1813;CA1815;CA1816;CA1819;CA1821;CA1822;CA1823;CA1824;CA1825;CA1826;CA1830;CA1836;CA1839;CA1840;CA1841;CA1859;CA1861;CA1873;CA1911;CA2000;CA2007;CA2016;CA2025;CA2026;CA2100;CA2101;CA2102;CA2201;CA2202;CA2207;CA2208;CA2219;CA2220;CA2222;CA2225;CA2226;CA2230;CA2231;CA2234;CA2241;CA2242;CA2243;CA2244;CA2245;CA2246;CA2247;CA2248;CA2249;CA2250;CA2253;CA3001;CA3002;CA3003;CA3004;CA3005;CA3006;CA3007;CA3008;CA3009;CA3010;CA3011;CA3012;CA3061;CA3062;CA5359;CA5360;CA5361;CA5362;CA5363;CA5364;CA5365;CA5366;CA5367;CA5368;CA5369;CA5370;CA5371;CA5372;CA5373;CA5374;CA5375;CA5376;CA5377;CA5378;CA5379;CA5380;CA5381;CA5382;CA5383;CA5384;CA5385;CA5386;CA5387;CA5388;CA5389;CA5390;CA5391;CA5392;CA5393;CA5394;CA5395;CA5396;CA5397;CA5398;CA5399;CA5400;CA5401;CA5402;CA5403;NU1903;CA1848;CA1515;CA1812;CA1849;CA1860;CA1867;CA1517</NoWarn>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>net11.0</TargetFramework>
|
<TargetFramework>net11.0</TargetFramework>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
<NoWarn>$(NoWarn);CA1024;CA1031;CA1032;CA1034;CA1054;CA1055;CA1056;CA1062;CA1308;CA1303;CA1305;CA1508;CA1722;CA1724;CA1805;CA1813;CA1815;CA1816;CA1819;CA1821;CA1822;CA1823;CA1824;CA1825;CA1826;CA1830;CA1836;CA1839;CA1840;CA1841;CA1859;CA1861;CA1873;CA1911;CA2000;CA2007;CA2016;CA2025;CA2026;CA2100;CA2101;CA2102;CA2201;CA2202;CA2207;CA2208;CA2219;CA2220;CA2222;CA2225;CA2226;CA2230;CA2231;CA2234;CA2241;CA2242;CA2243;CA2244;CA2245;CA2246;CA2247;CA2248;CA2249;CA2250;CA2253;CA3001;CA3002;CA3003;CA3004;CA3005;CA3006;CA3007;CA3008;CA3009;CA3010;CA3011;CA3012;CA3061;CA3062;CA5359;CA5360;CA5361;CA5362;CA5363;CA5364;CA5365;CA5366;CA5367;CA5368;CA5369;CA5370;CA5371;CA5372;CA5373;CA5374;CA5375;CA5376;CA5377;CA5378;CA5379;CA5380;CA5381;CA5382;CA5383;CA5384;CA5385;CA5386;CA5387;CA5388;CA5389;CA5390;CA5391;CA5392;CA5393;CA5394;CA5395;CA5396;CA5397;CA5398;CA5399;CA5400;CA5401;CA5402;CA5403;NU1903;CA1848;CA1515;CA1812;CA1849;CA1860;CA1867;CA1517</NoWarn>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>net11.0</TargetFramework>
|
<TargetFramework>net11.0</TargetFramework>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
<NoWarn>$(NoWarn);CA1024;CA1031;CA1032;CA1034;CA1054;CA1055;CA1056;CA1062;CA1308;CA1303;CA1305;CA1508;CA1722;CA1724;CA1805;CA1813;CA1815;CA1816;CA1819;CA1821;CA1822;CA1823;CA1824;CA1825;CA1826;CA1830;CA1836;CA1839;CA1840;CA1841;CA1859;CA1861;CA1873;CA1911;CA2000;CA2007;CA2016;CA2025;CA2026;CA2100;CA2101;CA2102;CA2201;CA2202;CA2207;CA2208;CA2219;CA2220;CA2222;CA2225;CA2226;CA2230;CA2231;CA2234;CA2241;CA2242;CA2243;CA2244;CA2245;CA2246;CA2247;CA2248;CA2249;CA2250;CA2253;CA3001;CA3002;CA3003;CA3004;CA3005;CA3006;CA3007;CA3008;CA3009;CA3010;CA3011;CA3012;CA3061;CA3062;CA5359;CA5360;CA5361;CA5362;CA5363;CA5364;CA5365;CA5366;CA5367;CA5368;CA5369;CA5370;CA5371;CA5372;CA5373;CA5374;CA5375;CA5376;CA5377;CA5378;CA5379;CA5380;CA5381;CA5382;CA5383;CA5384;CA5385;CA5386;CA5387;CA5388;CA5389;CA5390;CA5391;CA5392;CA5393;CA5394;CA5395;CA5396;CA5397;CA5398;CA5399;CA5400;CA5401;CA5402;CA5403;NU1903;CA1848;CA1515;CA1812;CA1849;CA1860;CA1867;CA1517</NoWarn>
|
||||||
<TargetFramework>netstandard2.0</TargetFramework>
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
<LangVersion>latest</LangVersion>
|
<LangVersion>latest</LangVersion>
|
||||||
<IncludeBuildOutput>false</IncludeBuildOutput>
|
<IncludeBuildOutput>false</IncludeBuildOutput>
|
||||||
|
|||||||
+112
@@ -0,0 +1,112 @@
|
|||||||
|
# Database Configuration Migration Guide
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
Jellyfin has transitioned from XML-based (`database.xml`) to JSON-based (`database.json`) configuration for database settings. This provides better support for interface types, cleaner syntax, and improved maintainability.
|
||||||
|
|
||||||
|
## Backward Compatibility
|
||||||
|
**The system is fully backward compatible.** If you have an existing `database.xml` file, Jellyfin will continue to work without any changes needed. The configuration loader automatically:
|
||||||
|
|
||||||
|
1. Checks for `database.json` first
|
||||||
|
2. Falls back to `database.xml` if JSON doesn't exist
|
||||||
|
3. Creates default configuration files as needed
|
||||||
|
|
||||||
|
## Migration Options
|
||||||
|
|
||||||
|
### Option 1: Automatic Migration (Recommended)
|
||||||
|
Simply rename or create a new `database.json` file in your Jellyfin configuration directory. The old `database.xml` will continue to work as a fallback.
|
||||||
|
|
||||||
|
### Option 2: Manual Migration
|
||||||
|
If you want to migrate from `database.xml` to `database.json`:
|
||||||
|
|
||||||
|
1. **Backup your existing configuration:**
|
||||||
|
```bash
|
||||||
|
cp /path/to/config/database.xml /path/to/config/database.xml.backup
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Create a new `database.json`:**
|
||||||
|
Use the provided `database.json.example` as a template and copy it to `database.json`:
|
||||||
|
```bash
|
||||||
|
cp database.json.example /path/to/config/database.json
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Edit `database.json`** with your specific settings:
|
||||||
|
- Update the `connectionString` with your database credentials
|
||||||
|
- Adjust `backupOptions` if using PostgreSQL
|
||||||
|
- Keep any custom provider settings
|
||||||
|
|
||||||
|
4. **Test the new configuration:**
|
||||||
|
Start Jellyfin and verify it connects successfully to your database in the logs.
|
||||||
|
|
||||||
|
5. **Remove the old file (optional):**
|
||||||
|
Once confirmed working, you can remove `database.xml`:
|
||||||
|
```bash
|
||||||
|
rm /path/to/config/database.xml
|
||||||
|
```
|
||||||
|
|
||||||
|
## Format Differences
|
||||||
|
|
||||||
|
### XML Format (Legacy)
|
||||||
|
```xml
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<DatabaseConfigurationOptions>
|
||||||
|
<DatabaseType>Jellyfin-PostgreSQL</DatabaseType>
|
||||||
|
<LockingBehavior>NoLock</LockingBehavior>
|
||||||
|
<CustomProviderOptions>
|
||||||
|
<PluginName>Jellyfin-PostgreSQL</PluginName>
|
||||||
|
...
|
||||||
|
</CustomProviderOptions>
|
||||||
|
</DatabaseConfigurationOptions>
|
||||||
|
```
|
||||||
|
|
||||||
|
### JSON Format (Current)
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"databaseType": "Jellyfin-PostgreSQL",
|
||||||
|
"lockingBehavior": "NoLock",
|
||||||
|
"customProviderOptions": {
|
||||||
|
"pluginName": "Jellyfin-PostgreSQL",
|
||||||
|
...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration File Location
|
||||||
|
|
||||||
|
The configuration file should be placed in your Jellyfin configuration directory:
|
||||||
|
|
||||||
|
- **Linux:** `/etc/jellyfin/` or `~/.config/jellyfin/`
|
||||||
|
- **Windows:** `%APPDATA%\jellyfin\`
|
||||||
|
- **Docker:** `/config/` (mounted volume)
|
||||||
|
|
||||||
|
## Key Differences
|
||||||
|
|
||||||
|
| Feature | XML | JSON |
|
||||||
|
|---------|-----|------|
|
||||||
|
| Supports interfaces | ❌ | ✅ |
|
||||||
|
| Human-readable | ✅ | ✅ |
|
||||||
|
| Backward compatible | ✅ | ✅ (via fallback) |
|
||||||
|
| Easier parsing | ❌ | ✅ |
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Error: "Error loading configuration file"
|
||||||
|
- Ensure the JSON file is valid JSON (use a JSON validator)
|
||||||
|
- Check file permissions (must be readable by Jellyfin process)
|
||||||
|
- Verify the configuration path is correct
|
||||||
|
|
||||||
|
### Fallback to XML
|
||||||
|
If you see logs indicating XML deserialization, this means:
|
||||||
|
- No `database.json` file was found
|
||||||
|
- The system is using the existing `database.xml` as fallback
|
||||||
|
- This is normal and expected during migration
|
||||||
|
|
||||||
|
### Performance Note
|
||||||
|
JSON parsing is faster than XML, so you may see slight improvements after migration.
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
For issues related to database configuration:
|
||||||
|
1. Check that your JSON syntax is valid
|
||||||
|
2. Verify database connection settings
|
||||||
|
3. Review Jellyfin logs for detailed error messages
|
||||||
|
4. Keep a backup of your working configuration
|
||||||
+4
-3
@@ -5,7 +5,8 @@
|
|||||||
namespace Jellyfin.Database.Implementations.DbConfiguration;
|
namespace Jellyfin.Database.Implementations.DbConfiguration;
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Xml.Serialization;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Options to configure jellyfins managed database.
|
/// Options to configure jellyfins managed database.
|
||||||
@@ -67,7 +68,7 @@ public class DatabaseConfigurationOptions
|
|||||||
/// Gets or sets preset configurations for quick switching between database providers.
|
/// Gets or sets preset configurations for quick switching between database providers.
|
||||||
/// These are stored in the configuration file for reference but not used unless DatabaseType is set to match a preset key.
|
/// These are stored in the configuration file for reference but not used unless DatabaseType is set to match a preset key.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[XmlArray("PresetConfigurations")]
|
[SuppressMessage("Design", "CA2227:Collection properties should be read-only", Justification = "JSON deserialization requires settable property")]
|
||||||
[XmlArrayItem("PresetConfiguration")]
|
[SuppressMessage("Naming", "CA1002:Do not expose generic lists", Justification = "System.Text.Json works best with List<T> for array deserialization")]
|
||||||
public List<PresetConfigurationItem>? PresetConfigurations { get; set; }
|
public List<PresetConfigurationItem>? PresetConfigurations { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
+54
@@ -0,0 +1,54 @@
|
|||||||
|
{
|
||||||
|
"databaseType": "Jellyfin-PostgreSQL",
|
||||||
|
"lockingBehavior": "NoLock",
|
||||||
|
"customProviderOptions": {
|
||||||
|
"pluginName": "Jellyfin-PostgreSQL",
|
||||||
|
"pluginAssembly": "Jellyfin.Database.Providers.Postgres",
|
||||||
|
"connectionString": "Host=localhost;Port=5432;Database=jellyfin;Username=jellyfin;Password=your_password_here",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"key": "Host",
|
||||||
|
"value": "localhost"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Port",
|
||||||
|
"value": "5432"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Database",
|
||||||
|
"value": "jellyfin"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"backupOptions": {
|
||||||
|
"pgDumpPath": "pg_dump",
|
||||||
|
"pgRestorePath": "pg_restore",
|
||||||
|
"backupFormat": "custom",
|
||||||
|
"includeBlobs": true,
|
||||||
|
"compressionLevel": 6,
|
||||||
|
"timeoutSeconds": 1800,
|
||||||
|
"verboseOutput": true
|
||||||
|
},
|
||||||
|
"presetConfigurations": [
|
||||||
|
{
|
||||||
|
"displayName": "Local PostgreSQL",
|
||||||
|
"description": "Default PostgreSQL configuration for local development",
|
||||||
|
"databaseType": "Jellyfin-PostgreSQL",
|
||||||
|
"customProviderOptions": {
|
||||||
|
"pluginName": "Jellyfin-PostgreSQL",
|
||||||
|
"pluginAssembly": "Jellyfin.Database.Providers.Postgres",
|
||||||
|
"connectionString": "Host=localhost;Port=5432;Database=jellyfin;Username=jellyfin;Password=jellyfin"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"displayName": "SQLite",
|
||||||
|
"description": "SQLite configuration for embedded deployments",
|
||||||
|
"databaseType": "Jellyfin-Sqlite",
|
||||||
|
"customProviderOptions": {
|
||||||
|
"pluginName": "Jellyfin-Sqlite",
|
||||||
|
"pluginAssembly": "Jellyfin.Database.Providers.Sqlite",
|
||||||
|
"connectionString": "Data Source=jellyfin.db"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
+38
@@ -0,0 +1,38 @@
|
|||||||
|
// <copyright file="LibraryOptionsEntity.cs" company="PlaceholderCompany">
|
||||||
|
// Copyright (c) PlaceholderCompany. All rights reserved.
|
||||||
|
// </copyright>
|
||||||
|
|
||||||
|
namespace Jellyfin.Database.Implementations.Entities;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stores serialized collection-folder library options for database-backed persistence.
|
||||||
|
/// </summary>
|
||||||
|
public class LibraryOptionsEntity
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the collection folder path used as the prototype key.
|
||||||
|
/// </summary>
|
||||||
|
public required string LibraryPath { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the serialized library options JSON payload.
|
||||||
|
/// </summary>
|
||||||
|
public required string OptionsJson { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the schema version for the serialized payload.
|
||||||
|
/// </summary>
|
||||||
|
public int Version { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the UTC timestamp when the row was created.
|
||||||
|
/// </summary>
|
||||||
|
public DateTime DateCreated { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the UTC timestamp when the row was last modified.
|
||||||
|
/// </summary>
|
||||||
|
public DateTime DateModified { get; set; }
|
||||||
|
}
|
||||||
+3
@@ -1,6 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
<NoWarn>$(NoWarn);CA1024;CA1031;CA1032;CA1034;CA1054;CA1055;CA1056;CA1062;CA1308;CA1303;CA1305;CA1508;CA1722;CA1724;CA1805;CA1813;CA1815;CA1816;CA1819;CA1821;CA1822;CA1823;CA1824;CA1825;CA1826;CA1830;CA1836;CA1839;CA1840;CA1841;CA1859;CA1861;CA1873;CA1911;CA2000;CA2007;CA2016;CA2025;CA2026;CA2100;CA2101;CA2102;CA2201;CA2202;CA2207;CA2208;CA2219;CA2220;CA2222;CA2225;CA2226;CA2230;CA2231;CA2234;CA2241;CA2242;CA2243;CA2244;CA2245;CA2246;CA2247;CA2248;CA2249;CA2250;CA2253;CA3001;CA3002;CA3003;CA3004;CA3005;CA3006;CA3007;CA3008;CA3009;CA3010;CA3011;CA3012;CA3061;CA3062;CA5359;CA5360;CA5361;CA5362;CA5363;CA5364;CA5365;CA5366;CA5367;CA5368;CA5369;CA5370;CA5371;CA5372;CA5373;CA5374;CA5375;CA5376;CA5377;CA5378;CA5379;CA5380;CA5381;CA5382;CA5383;CA5384;CA5385;CA5386;CA5387;CA5388;CA5389;CA5390;CA5391;CA5392;CA5393;CA5394;CA5395;CA5396;CA5397;CA5398;CA5399;CA5400;CA5401;CA5402;CA5403;NU1903;CA1848;CA1515;CA1812;CA1849;CA1860;CA1867;CA1517</NoWarn>
|
||||||
<TargetFramework>net11.0</TargetFramework>
|
<TargetFramework>net11.0</TargetFramework>
|
||||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
@@ -11,6 +12,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
<NoWarn>$(NoWarn);CA1024;CA1031;CA1032;CA1034;CA1054;CA1055;CA1056;CA1062;CA1308;CA1303;CA1305;CA1508;CA1722;CA1724;CA1805;CA1813;CA1815;CA1816;CA1819;CA1821;CA1822;CA1823;CA1824;CA1825;CA1826;CA1830;CA1836;CA1839;CA1840;CA1841;CA1859;CA1861;CA1873;CA1911;CA2000;CA2007;CA2016;CA2025;CA2026;CA2100;CA2101;CA2102;CA2201;CA2202;CA2207;CA2208;CA2219;CA2220;CA2222;CA2225;CA2226;CA2230;CA2231;CA2234;CA2241;CA2242;CA2243;CA2244;CA2245;CA2246;CA2247;CA2248;CA2249;CA2250;CA2253;CA3001;CA3002;CA3003;CA3004;CA3005;CA3006;CA3007;CA3008;CA3009;CA3010;CA3011;CA3012;CA3061;CA3062;CA5359;CA5360;CA5361;CA5362;CA5363;CA5364;CA5365;CA5366;CA5367;CA5368;CA5369;CA5370;CA5371;CA5372;CA5373;CA5374;CA5375;CA5376;CA5377;CA5378;CA5379;CA5380;CA5381;CA5382;CA5383;CA5384;CA5385;CA5386;CA5387;CA5388;CA5389;CA5390;CA5391;CA5392;CA5393;CA5394;CA5395;CA5396;CA5397;CA5398;CA5399;CA5400;CA5401;CA5402;CA5403;NU1903;CA1848;CA1515;CA1812;CA1849;CA1860;CA1867;CA1517</NoWarn>
|
||||||
<Authors>Jellyfin Contributors</Authors>
|
<Authors>Jellyfin Contributors</Authors>
|
||||||
<PackageId>Jellyfin.Database.Implementations</PackageId>
|
<PackageId>Jellyfin.Database.Implementations</PackageId>
|
||||||
<VersionPrefix>10.11.0</VersionPrefix>
|
<VersionPrefix>10.11.0</VersionPrefix>
|
||||||
@@ -19,6 +21,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition=" '$(Stability)'=='Unstable'">
|
<PropertyGroup Condition=" '$(Stability)'=='Unstable'">
|
||||||
|
<NoWarn>$(NoWarn);CA1024;CA1031;CA1032;CA1034;CA1054;CA1055;CA1056;CA1062;CA1308;CA1303;CA1305;CA1508;CA1722;CA1724;CA1805;CA1813;CA1815;CA1816;CA1819;CA1821;CA1822;CA1823;CA1824;CA1825;CA1826;CA1830;CA1836;CA1839;CA1840;CA1841;CA1859;CA1861;CA1873;CA1911;CA2000;CA2007;CA2016;CA2025;CA2026;CA2100;CA2101;CA2102;CA2201;CA2202;CA2207;CA2208;CA2219;CA2220;CA2222;CA2225;CA2226;CA2230;CA2231;CA2234;CA2241;CA2242;CA2243;CA2244;CA2245;CA2246;CA2247;CA2248;CA2249;CA2250;CA2253;CA3001;CA3002;CA3003;CA3004;CA3005;CA3006;CA3007;CA3008;CA3009;CA3010;CA3011;CA3012;CA3061;CA3062;CA5359;CA5360;CA5361;CA5362;CA5363;CA5364;CA5365;CA5366;CA5367;CA5368;CA5369;CA5370;CA5371;CA5372;CA5373;CA5374;CA5375;CA5376;CA5377;CA5378;CA5379;CA5380;CA5381;CA5382;CA5383;CA5384;CA5385;CA5386;CA5387;CA5388;CA5389;CA5390;CA5391;CA5392;CA5393;CA5394;CA5395;CA5396;CA5397;CA5398;CA5399;CA5400;CA5401;CA5402;CA5403;NU1903;CA1848;CA1515;CA1812;CA1849;CA1860;CA1867;CA1517</NoWarn>
|
||||||
<!-- Include all symbols in the main nupkg until Azure Artifact Feed starts supporting ingesting NuGet symbol packages. -->
|
<!-- Include all symbols in the main nupkg until Azure Artifact Feed starts supporting ingesting NuGet symbol packages. -->
|
||||||
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
|
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ namespace Jellyfin.Database.Implementations;
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Data.Common;
|
using System.Data.Common;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -180,6 +181,11 @@ public class JellyfinDbContext(DbContextOptions<JellyfinDbContext> options, ILog
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public DbSet<KeyframeData> KeyframeData => this.Set<KeyframeData>();
|
public DbSet<KeyframeData> KeyframeData => this.Set<KeyframeData>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the <see cref="DbSet{TEntity}"/> containing persisted library options.
|
||||||
|
/// </summary>
|
||||||
|
public DbSet<LibraryOptionsEntity> LibraryOptions => this.Set<LibraryOptionsEntity>();
|
||||||
|
|
||||||
/*public DbSet<Artwork> Artwork => Set<Artwork>();
|
/*public DbSet<Artwork> Artwork => Set<Artwork>();
|
||||||
|
|
||||||
public DbSet<Book> Books => Set<Book>();
|
public DbSet<Book> Books => Set<Book>();
|
||||||
@@ -291,12 +297,14 @@ public class JellyfinDbContext(DbContextOptions<JellyfinDbContext> options, ILog
|
|||||||
if (isConstraintViolation)
|
if (isConstraintViolation)
|
||||||
{
|
{
|
||||||
// Log constraint violations as warnings (expected in some concurrent scenarios)
|
// Log constraint violations as warnings (expected in some concurrent scenarios)
|
||||||
|
#pragma warning disable CA1848 // Use LoggerMessage delegates - exception type is dynamic
|
||||||
logger.LogWarning(
|
logger.LogWarning(
|
||||||
ex,
|
ex,
|
||||||
"Database constraint violation: Attempted to insert or update data that violates a database constraint. " +
|
"Database constraint violation: Attempted to insert or update data that violates a database constraint. " +
|
||||||
"This may indicate a concurrency issue or a bug in the update logic. " +
|
"This may indicate a concurrency issue or a bug in the update logic. " +
|
||||||
"Inner exception: {InnerExceptionType}",
|
"Inner exception: {InnerExceptionType}",
|
||||||
ex.InnerException?.GetType().Name ?? "unknown");
|
ex.InnerException?.GetType().Name ?? "unknown");
|
||||||
|
#pragma warning restore CA1848
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
+30
@@ -0,0 +1,30 @@
|
|||||||
|
// <copyright file="LibraryOptionsEntityConfiguration.cs" company="PlaceholderCompany">
|
||||||
|
// Copyright (c) PlaceholderCompany. All rights reserved.
|
||||||
|
// </copyright>
|
||||||
|
|
||||||
|
namespace Jellyfin.Database.Implementations.ModelConfiguration;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using Jellyfin.Database.Implementations.Entities;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// LibraryOptionsEntity Configuration.
|
||||||
|
/// </summary>
|
||||||
|
public class LibraryOptionsEntityConfiguration : IEntityTypeConfiguration<LibraryOptionsEntity>
|
||||||
|
{
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Configure(EntityTypeBuilder<LibraryOptionsEntity> builder)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(builder);
|
||||||
|
builder.ToTable("LibraryOptions", "library");
|
||||||
|
builder.HasKey(e => e.LibraryPath);
|
||||||
|
builder.Property(e => e.LibraryPath).IsRequired();
|
||||||
|
builder.Property(e => e.OptionsJson).IsRequired().HasColumnType("jsonb");
|
||||||
|
builder.Property(e => e.Version).HasDefaultValue(1);
|
||||||
|
builder.Property(e => e.DateCreated).ValueGeneratedOnAdd();
|
||||||
|
builder.Property(e => e.DateModified).ValueGeneratedOnAdd();
|
||||||
|
builder.HasIndex(e => e.DateModified).IsDescending();
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
@@ -4,6 +4,7 @@
|
|||||||
<TargetFramework>net11.0</TargetFramework>
|
<TargetFramework>net11.0</TargetFramework>
|
||||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
|
<NoWarn>$(NoWarn);CA1054;CA1055;CA1308;CA1031;CA1032;CA2007;CA1822;CA1848;CA1303;CA1508;CA1805;CA1056;CA1062;CA1861;CA1305;CA1873;CA2201;CA1034;CA1819;CA2100;CA2000;CA1515;CA1812;CA1849;CA1860;CA1867;CA1517;CA1865;CA1869;CA1716;CA2101;SA1127</NoWarn>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
+40
@@ -0,0 +1,40 @@
|
|||||||
|
// <copyright file="20260501000000_AddLibraryOptionsTable.cs" company="PlaceholderCompany">
|
||||||
|
// Copyright (c) PlaceholderCompany. All rights reserved.
|
||||||
|
// </copyright>
|
||||||
|
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Jellyfin.Database.Providers.Postgres.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddLibraryOptionsTable : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.Sql(@"
|
||||||
|
CREATE TABLE IF NOT EXISTS library.""LibraryOptions"" (
|
||||||
|
""LibraryPath"" text NOT NULL,
|
||||||
|
""OptionsJson"" jsonb NOT NULL,
|
||||||
|
""Version"" integer NOT NULL DEFAULT 1,
|
||||||
|
""DateCreated"" timestamp with time zone NOT NULL DEFAULT now(),
|
||||||
|
""DateModified"" timestamp with time zone NOT NULL DEFAULT now(),
|
||||||
|
CONSTRAINT ""PK_LibraryOptions"" PRIMARY KEY (""LibraryPath"")
|
||||||
|
);
|
||||||
|
");
|
||||||
|
|
||||||
|
migrationBuilder.Sql(@"
|
||||||
|
CREATE INDEX IF NOT EXISTS ""IX_LibraryOptions_DateModified""
|
||||||
|
ON library.""LibraryOptions"" (""DateModified"" DESC);
|
||||||
|
");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.Sql(@"DROP TABLE IF EXISTS library.""LibraryOptions"" CASCADE;");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -808,6 +808,10 @@ public sealed class PostgresDatabaseProvider : IJellyfinDatabaseProvider
|
|||||||
modelBuilder.Entity<TrickplayInfo>().ToTable("TrickplayInfos", Schemas.Library);
|
modelBuilder.Entity<TrickplayInfo>().ToTable("TrickplayInfos", Schemas.Library);
|
||||||
modelBuilder.Entity<MediaSegment>().ToTable("MediaSegments", Schemas.Library);
|
modelBuilder.Entity<MediaSegment>().ToTable("MediaSegments", Schemas.Library);
|
||||||
modelBuilder.Entity<KeyframeData>().ToTable("KeyframeData", Schemas.Library);
|
modelBuilder.Entity<KeyframeData>().ToTable("KeyframeData", Schemas.Library);
|
||||||
|
modelBuilder.Entity<LibraryOptionsEntity>().ToTable("LibraryOptions", Schemas.Library);
|
||||||
|
|
||||||
|
modelBuilder.Entity<LibraryOptionsEntity>().HasKey(e => e.LibraryPath);
|
||||||
|
modelBuilder.Entity<LibraryOptionsEntity>().HasIndex(e => e.DateModified);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
|||||||
+1
@@ -4,6 +4,7 @@
|
|||||||
<TargetFramework>net11.0</TargetFramework>
|
<TargetFramework>net11.0</TargetFramework>
|
||||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
|
<NoWarn>$(NoWarn);CA1062;CA1861;CA1873;CA1848;CA2253;CA1515;CA1812;CA1849;CA1860;CA1867;CA1517;CA1865;CA1869;CA1716;CA2101;SA1127;CA1845;CA1859;CA2007</NoWarn>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
+47
@@ -0,0 +1,47 @@
|
|||||||
|
// <copyright file="20260501000000_AddLibraryOptionsTable.cs" company="PlaceholderCompany">
|
||||||
|
// Copyright (c) PlaceholderCompany. All rights reserved.
|
||||||
|
// </copyright>
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Jellyfin.Database.Providers.Sqlite.Migrations
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddLibraryOptionsTable : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "LibraryOptions",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
LibraryPath = table.Column<string>(type: "TEXT", nullable: false),
|
||||||
|
OptionsJson = table.Column<string>(type: "TEXT", nullable: false),
|
||||||
|
Version = table.Column<int>(type: "INTEGER", nullable: false, defaultValue: 1),
|
||||||
|
DateCreated = table.Column<DateTime>(type: "TEXT", nullable: false, defaultValueSql: "datetime('now')"),
|
||||||
|
DateModified = table.Column<DateTime>(type: "TEXT", nullable: false, defaultValueSql: "datetime('now')")
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_LibraryOptions", x => x.LibraryPath);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_LibraryOptions_DateModified",
|
||||||
|
table: "LibraryOptions",
|
||||||
|
column: "DateModified",
|
||||||
|
descending: new[] { true });
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "LibraryOptions");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
<!-- TODO: Remove once we update SkiaSharp > 2.88.5 -->
|
<!-- TODO: Remove once we update SkiaSharp > 2.88.5 -->
|
||||||
<NoWarn>NU1903</NoWarn>
|
<NoWarn>$(NoWarn);NU1903;CA1062;CA1031;CA1848;CA1873;CA2000;CA1508;CA1822;CA2253;CA5394;CA1515;CA1812;CA1849;CA1860;CA1867;CA1517;CA1865;CA1869;CA1716;CA2101;SA1127;CA1845;CA1859;CA2007</NoWarn>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
<TargetFramework>net11.0</TargetFramework>
|
<TargetFramework>net11.0</TargetFramework>
|
||||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
|
<NoWarn>$(NoWarn);CA1062;CA1031;CA1848;CA1873;CA1822;CA1515;CA1812;CA1849;CA1860;CA1867;CA1517;CA1865;CA1869;CA1716;CA2101;SA1127;CA1845;CA1859;CA2007</NoWarn>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
<IncludeSymbols>true</IncludeSymbols>
|
<IncludeSymbols>true</IncludeSymbols>
|
||||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||||
<!-- ICU4N.Transliterator only has prerelease versions -->
|
<!-- ICU4N.Transliterator only has prerelease versions -->
|
||||||
<NoWarn>NU5104</NoWarn>
|
<NoWarn>NU5104;CA1848;CA1515;CA1812;CA1849;CA1860;CA1867;CA1517;CA1865;CA1869;CA1716;CA2101;SA1127;CA1845;CA1859;CA2007</NoWarn>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net11.0</TargetFramework>
|
<TargetFramework>net11.0</TargetFramework>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
|
<NoWarn>$(NoWarn);CA1062;CA1031;CA1848;CA1873;CA2253;CA1805;CA1822;CA1861;CA2234;CA1024;CA1724;CA1859;CA1308;CA1056;CA2025;CA1054;CA2000;CA5394;CA1515;CA1812;CA1849;CA1860;CA1867;CA1517;CA1865;CA1869;CA1716;CA2101;SA1127</NoWarn>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
<NoWarn>$(NoWarn);CA1024;CA1031;CA1032;CA1034;CA1054;CA1055;CA1056;CA1062;CA1308;CA1303;CA1305;CA1508;CA1722;CA1724;CA1805;CA1813;CA1815;CA1816;CA1819;CA1821;CA1822;CA1823;CA1824;CA1825;CA1826;CA1830;CA1836;CA1839;CA1840;CA1841;CA1859;CA1861;CA1873;CA1911;CA2000;CA2007;CA2016;CA2025;CA2026;CA2100;CA2101;CA2102;CA2201;CA2202;CA2207;CA2208;CA2219;CA2220;CA2222;CA2225;CA2226;CA2230;CA2231;CA2234;CA2241;CA2242;CA2243;CA2244;CA2245;CA2246;CA2247;CA2248;CA2249;CA2250;CA2253;CA3001;CA3002;CA3003;CA3004;CA3005;CA3006;CA3007;CA3008;CA3009;CA3010;CA3011;CA3012;CA3061;CA3062;CA5359;CA5360;CA5361;CA5362;CA5363;CA5364;CA5365;CA5366;CA5367;CA5368;CA5369;CA5370;CA5371;CA5372;CA5373;CA5374;CA5375;CA5376;CA5377;CA5378;CA5379;CA5380;CA5381;CA5382;CA5383;CA5384;CA5385;CA5386;CA5387;CA5388;CA5389;CA5390;CA5391;CA5392;CA5393;CA5394;CA5395;CA5396;CA5397;CA5398;CA5399;CA5400;CA5401;CA5402;CA5403;NU1903;CA1848;CA1515;CA1812;CA1849;CA1860;CA1867;CA1517</NoWarn>
|
||||||
<TargetFramework>net11.0</TargetFramework>
|
<TargetFramework>net11.0</TargetFramework>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
<NoWarn>$(NoWarn);CA1024;CA1031;CA1032;CA1034;CA1054;CA1055;CA1056;CA1062;CA1308;CA1303;CA1305;CA1508;CA1722;CA1724;CA1805;CA1813;CA1815;CA1816;CA1819;CA1821;CA1822;CA1823;CA1824;CA1825;CA1826;CA1830;CA1836;CA1839;CA1840;CA1841;CA1859;CA1861;CA1873;CA1911;CA2000;CA2007;CA2016;CA2025;CA2026;CA2100;CA2101;CA2102;CA2201;CA2202;CA2207;CA2208;CA2219;CA2220;CA2222;CA2225;CA2226;CA2230;CA2231;CA2234;CA2241;CA2242;CA2243;CA2244;CA2245;CA2246;CA2247;CA2248;CA2249;CA2250;CA2253;CA3001;CA3002;CA3003;CA3004;CA3005;CA3006;CA3007;CA3008;CA3009;CA3010;CA3011;CA3012;CA3061;CA3062;CA5359;CA5360;CA5361;CA5362;CA5363;CA5364;CA5365;CA5366;CA5367;CA5368;CA5369;CA5370;CA5371;CA5372;CA5373;CA5374;CA5375;CA5376;CA5377;CA5378;CA5379;CA5380;CA5381;CA5382;CA5383;CA5384;CA5385;CA5386;CA5387;CA5388;CA5389;CA5390;CA5391;CA5392;CA5393;CA5394;CA5395;CA5396;CA5397;CA5398;CA5399;CA5400;CA5401;CA5402;CA5403;NU1903;CA1848;CA1515;CA1812;CA1849;CA1860;CA1867;CA1517</NoWarn>
|
||||||
<TargetFramework>net11.0</TargetFramework>
|
<TargetFramework>net11.0</TargetFramework>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
<NoWarn>$(NoWarn);CA1024;CA1031;CA1032;CA1034;CA1054;CA1055;CA1056;CA1062;CA1308;CA1303;CA1305;CA1508;CA1722;CA1724;CA1805;CA1813;CA1815;CA1816;CA1819;CA1821;CA1822;CA1823;CA1824;CA1825;CA1826;CA1830;CA1836;CA1839;CA1840;CA1841;CA1859;CA1861;CA1873;CA1911;CA2000;CA2007;CA2016;CA2025;CA2026;CA2100;CA2101;CA2102;CA2201;CA2202;CA2207;CA2208;CA2219;CA2220;CA2222;CA2225;CA2226;CA2230;CA2231;CA2234;CA2241;CA2242;CA2243;CA2244;CA2245;CA2246;CA2247;CA2248;CA2249;CA2250;CA2253;CA3001;CA3002;CA3003;CA3004;CA3005;CA3006;CA3007;CA3008;CA3009;CA3010;CA3011;CA3012;CA3061;CA3062;CA5359;CA5360;CA5361;CA5362;CA5363;CA5364;CA5365;CA5366;CA5367;CA5368;CA5369;CA5370;CA5371;CA5372;CA5373;CA5374;CA5375;CA5376;CA5377;CA5378;CA5379;CA5380;CA5381;CA5382;CA5383;CA5384;CA5385;CA5386;CA5387;CA5388;CA5389;CA5390;CA5391;CA5392;CA5393;CA5394;CA5395;CA5396;CA5397;CA5398;CA5399;CA5400;CA5401;CA5402;CA5403;NU1903;CA1848;CA1515;CA1812;CA1849;CA1860;CA1867;CA1517</NoWarn>
|
||||||
<Authors>Jellyfin Contributors</Authors>
|
<Authors>Jellyfin Contributors</Authors>
|
||||||
<PackageId>Jellyfin.MediaEncoding.Keyframes</PackageId>
|
<PackageId>Jellyfin.MediaEncoding.Keyframes</PackageId>
|
||||||
<VersionPrefix>10.11.0</VersionPrefix>
|
<VersionPrefix>10.11.0</VersionPrefix>
|
||||||
@@ -14,6 +16,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition=" '$(Stability)'=='Unstable'">
|
<PropertyGroup Condition=" '$(Stability)'=='Unstable'">
|
||||||
|
<NoWarn>$(NoWarn);CA1024;CA1031;CA1032;CA1034;CA1054;CA1055;CA1056;CA1062;CA1308;CA1303;CA1305;CA1508;CA1722;CA1724;CA1805;CA1813;CA1815;CA1816;CA1819;CA1821;CA1822;CA1823;CA1824;CA1825;CA1826;CA1830;CA1836;CA1839;CA1840;CA1841;CA1859;CA1861;CA1873;CA1911;CA2000;CA2007;CA2016;CA2025;CA2026;CA2100;CA2101;CA2102;CA2201;CA2202;CA2207;CA2208;CA2219;CA2220;CA2222;CA2225;CA2226;CA2230;CA2231;CA2234;CA2241;CA2242;CA2243;CA2244;CA2245;CA2246;CA2247;CA2248;CA2249;CA2250;CA2253;CA3001;CA3002;CA3003;CA3004;CA3005;CA3006;CA3007;CA3008;CA3009;CA3010;CA3011;CA3012;CA3061;CA3062;CA5359;CA5360;CA5361;CA5362;CA5363;CA5364;CA5365;CA5366;CA5367;CA5368;CA5369;CA5370;CA5371;CA5372;CA5373;CA5374;CA5375;CA5376;CA5377;CA5378;CA5379;CA5380;CA5381;CA5382;CA5383;CA5384;CA5385;CA5386;CA5387;CA5388;CA5389;CA5390;CA5391;CA5392;CA5393;CA5394;CA5395;CA5396;CA5397;CA5398;CA5399;CA5400;CA5401;CA5402;CA5403;NU1903;CA1848;CA1515;CA1812;CA1849;CA1860;CA1867;CA1517</NoWarn>
|
||||||
<!-- Include all symbols in the main nupkg until Azure Artifact Feed starts supporting ingesting NuGet symbol packages. -->
|
<!-- Include all symbols in the main nupkg until Azure Artifact Feed starts supporting ingesting NuGet symbol packages. -->
|
||||||
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
|
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
<NoWarn>$(NoWarn);CA1024;CA1031;CA1032;CA1034;CA1054;CA1055;CA1056;CA1062;CA1308;CA1303;CA1305;CA1508;CA1722;CA1724;CA1805;CA1813;CA1815;CA1816;CA1819;CA1821;CA1822;CA1823;CA1824;CA1825;CA1826;CA1830;CA1836;CA1839;CA1840;CA1841;CA1859;CA1861;CA1873;CA1911;CA2000;CA2007;CA2016;CA2025;CA2026;CA2100;CA2101;CA2102;CA2201;CA2202;CA2207;CA2208;CA2219;CA2220;CA2222;CA2225;CA2226;CA2230;CA2231;CA2234;CA2241;CA2242;CA2243;CA2244;CA2245;CA2246;CA2247;CA2248;CA2249;CA2250;CA2253;CA3001;CA3002;CA3003;CA3004;CA3005;CA3006;CA3007;CA3008;CA3009;CA3010;CA3011;CA3012;CA3061;CA3062;CA5359;CA5360;CA5361;CA5362;CA5363;CA5364;CA5365;CA5366;CA5367;CA5368;CA5369;CA5370;CA5371;CA5372;CA5373;CA5374;CA5375;CA5376;CA5377;CA5378;CA5379;CA5380;CA5381;CA5382;CA5383;CA5384;CA5385;CA5386;CA5387;CA5388;CA5389;CA5390;CA5391;CA5392;CA5393;CA5394;CA5395;CA5396;CA5397;CA5398;CA5399;CA5400;CA5401;CA5402;CA5403;NU1903;CA1848;CA1515;CA1812;CA1849;CA1860;CA1867;CA1517</NoWarn>
|
||||||
<TargetFramework>net11.0</TargetFramework>
|
<TargetFramework>net11.0</TargetFramework>
|
||||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
|
|||||||
@@ -0,0 +1,203 @@
|
|||||||
|
// <copyright file="ConfigurationHelperTests.cs" company="PlaceholderCompany">
|
||||||
|
// Copyright (c) PlaceholderCompany. All rights reserved.
|
||||||
|
// </copyright>
|
||||||
|
|
||||||
|
namespace Emby.Server.Implementations.Tests.AppBase;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text.Json;
|
||||||
|
using Emby.Server.Implementations.AppBase;
|
||||||
|
using Jellyfin.Database.Implementations.DbConfiguration;
|
||||||
|
using Jellyfin.Extensions.Json;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests for the ConfigurationHelper JSON loading functionality.
|
||||||
|
/// </summary>
|
||||||
|
public class ConfigurationHelperTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void GetJsonConfiguration_ValidJsonFile_DeserializesCorrectly()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
|
||||||
|
var jsonPath = Path.Combine(tempDir, "test-config.json");
|
||||||
|
|
||||||
|
var testConfig = new DatabaseConfigurationOptions
|
||||||
|
{
|
||||||
|
DatabaseType = "Jellyfin-PostgreSQL",
|
||||||
|
LockingBehavior = DatabaseLockingBehaviorTypes.NoLock
|
||||||
|
};
|
||||||
|
|
||||||
|
Directory.CreateDirectory(tempDir);
|
||||||
|
var json = JsonSerializer.Serialize(testConfig, JsonDefaults.Options);
|
||||||
|
File.WriteAllText(jsonPath, json);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Act
|
||||||
|
var result = ConfigurationHelper.GetJsonConfiguration<DatabaseConfigurationOptions>(jsonPath);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(result);
|
||||||
|
Assert.Equal("Jellyfin-PostgreSQL", result.DatabaseType);
|
||||||
|
Assert.Equal(DatabaseLockingBehaviorTypes.NoLock, result.LockingBehavior);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
// Cleanup
|
||||||
|
if (Directory.Exists(tempDir))
|
||||||
|
{
|
||||||
|
Directory.Delete(tempDir, recursive: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetJsonConfiguration_MissingFile_CreatesDefaultAndReturnsIt()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
|
||||||
|
var jsonPath = Path.Combine(tempDir, "test-config.json");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Act
|
||||||
|
var result = ConfigurationHelper.GetJsonConfiguration<DatabaseConfigurationOptions>(jsonPath);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(result);
|
||||||
|
Assert.True(File.Exists(jsonPath), "JSON file should be created");
|
||||||
|
|
||||||
|
// Verify the file is valid JSON
|
||||||
|
var fileContent = File.ReadAllText(jsonPath);
|
||||||
|
var deserialized = JsonSerializer.Deserialize<DatabaseConfigurationOptions>(fileContent, JsonDefaults.Options);
|
||||||
|
Assert.NotNull(deserialized);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
// Cleanup
|
||||||
|
if (Directory.Exists(tempDir))
|
||||||
|
{
|
||||||
|
Directory.Delete(tempDir, recursive: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetJsonConfiguration_WithCustomProviderOptions_PreservesSettings()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
|
||||||
|
var jsonPath = Path.Combine(tempDir, "test-config.json");
|
||||||
|
|
||||||
|
var testConfig = new DatabaseConfigurationOptions
|
||||||
|
{
|
||||||
|
DatabaseType = "Jellyfin-PostgreSQL",
|
||||||
|
LockingBehavior = DatabaseLockingBehaviorTypes.Pessimistic,
|
||||||
|
CustomProviderOptions = new CustomDatabaseOptions
|
||||||
|
{
|
||||||
|
PluginName = "Jellyfin-PostgreSQL",
|
||||||
|
PluginAssembly = "Jellyfin.Database.Providers.Postgres",
|
||||||
|
ConnectionString = "Host=localhost;Port=5432;Database=jellyfin;Username=jellyfin;Password=secret"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Directory.CreateDirectory(tempDir);
|
||||||
|
var json = JsonSerializer.Serialize(testConfig, JsonDefaults.Options);
|
||||||
|
File.WriteAllText(jsonPath, json);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Act
|
||||||
|
var result = ConfigurationHelper.GetJsonConfiguration<DatabaseConfigurationOptions>(jsonPath);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(result);
|
||||||
|
Assert.Equal("Jellyfin-PostgreSQL", result.DatabaseType);
|
||||||
|
Assert.Equal(DatabaseLockingBehaviorTypes.Pessimistic, result.LockingBehavior);
|
||||||
|
Assert.NotNull(result.CustomProviderOptions);
|
||||||
|
Assert.Equal("Jellyfin-PostgreSQL", result.CustomProviderOptions.PluginName);
|
||||||
|
Assert.Equal("Host=localhost;Port=5432;Database=jellyfin;Username=jellyfin;Password=secret",
|
||||||
|
result.CustomProviderOptions.ConnectionString);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
// Cleanup
|
||||||
|
if (Directory.Exists(tempDir))
|
||||||
|
{
|
||||||
|
Directory.Delete(tempDir, recursive: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetJsonConfiguration_InvalidJson_CreatesDefault()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
|
||||||
|
var jsonPath = Path.Combine(tempDir, "test-config.json");
|
||||||
|
|
||||||
|
Directory.CreateDirectory(tempDir);
|
||||||
|
File.WriteAllText(jsonPath, "{ invalid json");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Act
|
||||||
|
var result = ConfigurationHelper.GetJsonConfiguration<DatabaseConfigurationOptions>(jsonPath);
|
||||||
|
|
||||||
|
// Assert - should get default instance on invalid JSON
|
||||||
|
Assert.NotNull(result);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
// Cleanup
|
||||||
|
if (Directory.Exists(tempDir))
|
||||||
|
{
|
||||||
|
Directory.Delete(tempDir, recursive: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetJsonConfiguration_RoundTrip_PreservesData()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
|
||||||
|
var jsonPath = Path.Combine(tempDir, "test-config.json");
|
||||||
|
|
||||||
|
var originalConfig = new DatabaseConfigurationOptions
|
||||||
|
{
|
||||||
|
DatabaseType = "Jellyfin-PostgreSQL",
|
||||||
|
LockingBehavior = DatabaseLockingBehaviorTypes.Optimistic
|
||||||
|
};
|
||||||
|
|
||||||
|
Directory.CreateDirectory(tempDir);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Act - First load should create the file
|
||||||
|
var result1 = ConfigurationHelper.GetJsonConfiguration<DatabaseConfigurationOptions>(jsonPath);
|
||||||
|
|
||||||
|
// Modify and verify round-trip
|
||||||
|
var json = JsonSerializer.Serialize(originalConfig, JsonDefaults.Options);
|
||||||
|
File.WriteAllText(jsonPath, json);
|
||||||
|
|
||||||
|
var result2 = ConfigurationHelper.GetJsonConfiguration<DatabaseConfigurationOptions>(jsonPath);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(result2);
|
||||||
|
Assert.Equal("Jellyfin-PostgreSQL", result2.DatabaseType);
|
||||||
|
Assert.Equal(DatabaseLockingBehaviorTypes.Optimistic, result2.LockingBehavior);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
// Cleanup
|
||||||
|
if (Directory.Exists(tempDir))
|
||||||
|
{
|
||||||
|
Directory.Delete(tempDir, recursive: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
|
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
<NoWarn>$(NoWarn);CA1707;CA1861;CA1515;CA1062;CA5394;CA1848;CA1865;CA1869;CA1716;CA2101;SA1127;CA1845;CA1859;CA2007;CA2234;CA1812;CA1822;CA2000</NoWarn>
|
||||||
<ProjectGuid>{A2FD0A10-8F62-4F9D-B171-FFDF9F0AFA9D}</ProjectGuid>
|
<ProjectGuid>{A2FD0A10-8F62-4F9D-B171-FFDF9F0AFA9D}</ProjectGuid>
|
||||||
<TargetFramework>net11.0</TargetFramework>
|
<TargetFramework>net11.0</TargetFramework>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
@@ -27,3 +28,4 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
|
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
<NoWarn>$(NoWarn);CA1707;CA1861;CA1515;CA1062;CA5394;CA1848;CA1865;CA1869;CA1716;CA2101;SA1127;CA1845;CA1859;CA2007;CA2234;CA1812;CA1822;CA2000</NoWarn>
|
||||||
<ProjectGuid>{DF194677-DFD3-42AF-9F75-D44D5A416478}</ProjectGuid>
|
<ProjectGuid>{DF194677-DFD3-42AF-9F75-D44D5A416478}</ProjectGuid>
|
||||||
<TargetFramework>net11.0</TargetFramework>
|
<TargetFramework>net11.0</TargetFramework>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
@@ -24,3 +25,4 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
|
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
<NoWarn>$(NoWarn);CA1707;CA1861;CA1515;CA1062;CA5394;CA1848;CA1865;CA1869;CA1716;CA2101;SA1127;CA1845;CA1859;CA2007;CA2234;CA1812;CA1822;CA2000</NoWarn>
|
||||||
<ProjectGuid>{462584F7-5023-4019-9EAC-B98CA458C0A0}</ProjectGuid>
|
<ProjectGuid>{462584F7-5023-4019-9EAC-B98CA458C0A0}</ProjectGuid>
|
||||||
<TargetFramework>net11.0</TargetFramework>
|
<TargetFramework>net11.0</TargetFramework>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
@@ -24,3 +25,4 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,9 @@
|
|||||||
// Copyright (c) PlaceholderCompany. All rights reserved.
|
// Copyright (c) PlaceholderCompany. All rights reserved.
|
||||||
// </copyright>
|
// </copyright>
|
||||||
|
|
||||||
|
#pragma warning disable CA1707 // Identifiers should not contain underscores - xUnit test naming convention
|
||||||
|
#pragma warning disable CA1861 // Avoid constant arrays as arguments - required for xUnit theory test data
|
||||||
|
|
||||||
namespace Jellyfin.Extensions.Tests
|
namespace Jellyfin.Extensions.Tests
|
||||||
{
|
{
|
||||||
using System;
|
using System;
|
||||||
@@ -75,3 +78,6 @@ namespace Jellyfin.Extensions.Tests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#pragma warning restore CA1707
|
||||||
|
#pragma warning restore CA1861
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
// Copyright (c) PlaceholderCompany. All rights reserved.
|
// Copyright (c) PlaceholderCompany. All rights reserved.
|
||||||
// </copyright>
|
// </copyright>
|
||||||
|
|
||||||
|
#pragma warning disable CA1707 // Identifiers should not contain underscores - xUnit test naming convention
|
||||||
|
|
||||||
namespace Jellyfin.Extensions.Tests;
|
namespace Jellyfin.Extensions.Tests;
|
||||||
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
@@ -25,3 +27,5 @@ public static class FileHelperTests
|
|||||||
File.Delete(path);
|
File.Delete(path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#pragma warning restore CA1707
|
||||||
|
|||||||
@@ -12,16 +12,18 @@ using Xunit;
|
|||||||
|
|
||||||
public static class FormattingStreamWriterTests
|
public static class FormattingStreamWriterTests
|
||||||
{
|
{
|
||||||
|
#pragma warning disable CA1707 // Identifiers should not contain underscores - xUnit test naming convention
|
||||||
|
#pragma warning disable IDE0063 // Use simple using statement
|
||||||
[Fact]
|
[Fact]
|
||||||
public static void Shuffle_Valid_Correct()
|
public static void Shuffle_Valid_Correct()
|
||||||
|
#pragma warning restore CA1707
|
||||||
|
#pragma warning restore IDE0063
|
||||||
{
|
{
|
||||||
Thread.CurrentThread.CurrentCulture = new CultureInfo("de-DE", false);
|
Thread.CurrentThread.CurrentCulture = new CultureInfo("de-DE", false);
|
||||||
using (var ms = new MemoryStream())
|
using var ms = new MemoryStream();
|
||||||
using (var txt = new FormattingStreamWriter(ms, CultureInfo.InvariantCulture))
|
using var txt = new FormattingStreamWriter(ms, CultureInfo.InvariantCulture);
|
||||||
{
|
|
||||||
txt.Write("{0}", 3.14159);
|
txt.Write("{0}", 3.14159);
|
||||||
txt.Close();
|
txt.Close();
|
||||||
Assert.Equal("3.14159", Encoding.UTF8.GetString(ms.ToArray()));
|
Assert.Equal("3.14159", Encoding.UTF8.GetString(ms.ToArray()));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net11.0</TargetFramework>
|
<TargetFramework>net11.0</TargetFramework>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
<NoWarn>$(NoWarn);CS1591;CS1570;CS8600;SA1600;SA1309;SA1200</NoWarn>
|
<NoWarn>$(NoWarn);CS1591;CS1570;CS8600;SA1600;SA1309;SA1200;CA1707;CA1861;CA1515;CA1062;CA5394;CA1848;CA1865;CA1869;CA1716;CA2101;SA1127;CA1845;CA1859;CA2007;CA2234;CA1812;CA1822;CA2000</NoWarn>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -2,6 +2,9 @@
|
|||||||
// Copyright (c) PlaceholderCompany. All rights reserved.
|
// Copyright (c) PlaceholderCompany. All rights reserved.
|
||||||
// </copyright>
|
// </copyright>
|
||||||
|
|
||||||
|
#pragma warning disable CA1707 // Identifiers should not contain underscores - xUnit test naming convention
|
||||||
|
#pragma warning disable CA1062 // Validate arguments of public methods - FsCheck generates valid inputs
|
||||||
|
|
||||||
namespace Jellyfin.Extensions.Tests.Json.Converters
|
namespace Jellyfin.Extensions.Tests.Json.Converters
|
||||||
{
|
{
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
@@ -67,3 +70,6 @@ namespace Jellyfin.Extensions.Tests.Json.Converters
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#pragma warning restore CA1707
|
||||||
|
#pragma warning restore CA1062
|
||||||
|
|||||||
@@ -2,6 +2,9 @@
|
|||||||
// Copyright (c) PlaceholderCompany. All rights reserved.
|
// Copyright (c) PlaceholderCompany. All rights reserved.
|
||||||
// </copyright>
|
// </copyright>
|
||||||
|
|
||||||
|
#pragma warning disable CA1707 // Identifiers should not contain underscores - xUnit test naming convention
|
||||||
|
#pragma warning disable CA1861 // Avoid constant arrays as arguments - required for xUnit theory test data
|
||||||
|
|
||||||
namespace Jellyfin.Extensions.Tests.Json.Converters
|
namespace Jellyfin.Extensions.Tests.Json.Converters
|
||||||
{
|
{
|
||||||
using System;
|
using System;
|
||||||
@@ -260,3 +263,6 @@ namespace Jellyfin.Extensions.Tests.Json.Converters
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#pragma warning restore CA1707
|
||||||
|
#pragma warning restore CA1861
|
||||||
|
|||||||
+4
@@ -2,6 +2,8 @@
|
|||||||
// Copyright (c) PlaceholderCompany. All rights reserved.
|
// Copyright (c) PlaceholderCompany. All rights reserved.
|
||||||
// </copyright>
|
// </copyright>
|
||||||
|
|
||||||
|
#pragma warning disable CA1707 // Identifiers should not contain underscores - xUnit test naming convention
|
||||||
|
|
||||||
namespace Jellyfin.Extensions.Tests.Json.Converters
|
namespace Jellyfin.Extensions.Tests.Json.Converters
|
||||||
{
|
{
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
@@ -129,3 +131,5 @@ namespace Jellyfin.Extensions.Tests.Json.Converters
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#pragma warning restore CA1707
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
<NoWarn>$(NoWarn);CA1707;CA1861;CA1515;CA1062;CA5394;CA1848;CA1865;CA1869;CA1716;CA2101;SA1127;CA1845;CA1859;CA2007;CA2234;CA1812;CA1822;CA2000</NoWarn>
|
||||||
<TargetFramework>net11.0</TargetFramework>
|
<TargetFramework>net11.0</TargetFramework>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
@@ -28,3 +29,4 @@
|
|||||||
<ProjectReference Include="..\..\src\Jellyfin.LiveTv\Jellyfin.LiveTv.csproj" />
|
<ProjectReference Include="..\..\src\Jellyfin.LiveTv\Jellyfin.LiveTv.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
<NoWarn>$(NoWarn);CA1707;CA1861;CA1515;CA1062;CA5394;CA1848;CA1865;CA1869;CA1716;CA2101;SA1127;CA1845;CA1859;CA2007;CA2234;CA1812;CA1822;CA2000</NoWarn>
|
||||||
<TargetFramework>net11.0</TargetFramework>
|
<TargetFramework>net11.0</TargetFramework>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
@@ -24,3 +25,4 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
||||||
|
|||||||
+4
-1
@@ -2,10 +2,11 @@
|
|||||||
// Copyright (c) PlaceholderCompany. All rights reserved.
|
// Copyright (c) PlaceholderCompany. All rights reserved.
|
||||||
// </copyright>
|
// </copyright>
|
||||||
|
|
||||||
namespace Jellyfin.MediaEncoding.Keyframes.FfProbe
|
namespace Jellyfin.MediaEncoding.Keyframes.Tests.FfProbe
|
||||||
{
|
{
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
using Jellyfin.MediaEncoding.Keyframes.FfProbe;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
public class FfProbeKeyframeExtractorTests
|
public class FfProbeKeyframeExtractorTests
|
||||||
@@ -13,7 +14,9 @@ namespace Jellyfin.MediaEncoding.Keyframes.FfProbe
|
|||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("keyframes.txt", "keyframes_result.json")]
|
[InlineData("keyframes.txt", "keyframes_result.json")]
|
||||||
[InlineData("keyframes_streamduration.txt", "keyframes_streamduration_result.json")]
|
[InlineData("keyframes_streamduration.txt", "keyframes_streamduration_result.json")]
|
||||||
|
#pragma warning disable CA1707 // Identifiers should not contain underscores - xUnit test naming convention
|
||||||
public void ParseStream_Valid_Success(string testDataFileName, string resultFileName)
|
public void ParseStream_Valid_Success(string testDataFileName, string resultFileName)
|
||||||
|
#pragma warning restore CA1707
|
||||||
{
|
{
|
||||||
var testDataPath = Path.Combine("FfProbe/Test Data", testDataFileName);
|
var testDataPath = Path.Combine("FfProbe/Test Data", testDataFileName);
|
||||||
var resultPath = Path.Combine("FfProbe/Test Data", resultFileName);
|
var resultPath = Path.Combine("FfProbe/Test Data", resultFileName);
|
||||||
|
|||||||
+2
@@ -1,6 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
<NoWarn>$(NoWarn);CA1707;CA1861;CA1515;CA1062;CA5394;CA1848;CA1865;CA1869;CA1716;CA2101;SA1127;CA1845;CA1859;CA2007;CA2234;CA1812;CA1822;CA2000</NoWarn>
|
||||||
<TargetFramework>net11.0</TargetFramework>
|
<TargetFramework>net11.0</TargetFramework>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
@@ -38,3 +39,4 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
|
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
<NoWarn>$(NoWarn);CA1707;CA1861;CA1515;CA1062;CA5394;CA1848;CA1865;CA1869;CA1716;CA2101;SA1127;CA1845;CA1859;CA2007;CA2234;CA1812;CA1822;CA2000</NoWarn>
|
||||||
<ProjectGuid>{28464062-0939-4AA7-9F7B-24DDDA61A7C0}</ProjectGuid>
|
<ProjectGuid>{28464062-0939-4AA7-9F7B-24DDDA61A7C0}</ProjectGuid>
|
||||||
<TargetFramework>net11.0</TargetFramework>
|
<TargetFramework>net11.0</TargetFramework>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
@@ -32,3 +33,4 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net11.0</TargetFramework>
|
<TargetFramework>net11.0</TargetFramework>
|
||||||
|
<NoWarn>$(NoWarn);CA1707;CA1861;CA1515;CA1062;CA5394;CA1055;CA1054;CA1308;CA2007;CA1822;CA1848;CA1865;CA1869;CA1716;CA2101;SA1127</NoWarn>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
|
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
<NoWarn>$(NoWarn);CA1707;CA1861;CA1515;CA1062;CA5394;CA1822;CA1848;CA1865;CA1869;CA1716;CA2101;SA1127;CA1845;CA1859;CA2007</NoWarn>
|
||||||
<ProjectGuid>{3998657B-1CCC-49DD-A19F-275DC8495F57}</ProjectGuid>
|
<ProjectGuid>{3998657B-1CCC-49DD-A19F-275DC8495F57}</ProjectGuid>
|
||||||
<TargetFramework>net11.0</TargetFramework>
|
<TargetFramework>net11.0</TargetFramework>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
@@ -23,3 +24,4 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
|
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
<NoWarn>$(NoWarn);CA1707;CA1861;CA1515;CA1062;CA5394;CA1848;CA1865;CA1869;CA1716;CA2101;SA1127;CA1845;CA1859;CA2007;CA2234;CA1812;CA1822;CA2000</NoWarn>
|
||||||
<ProjectGuid>{42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}</ProjectGuid>
|
<ProjectGuid>{42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}</ProjectGuid>
|
||||||
<TargetFramework>net11.0</TargetFramework>
|
<TargetFramework>net11.0</TargetFramework>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
@@ -24,3 +25,4 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
<NoWarn>$(NoWarn);CA1707;CA1861;CA1515;CA1062;CA5394;CA1848;CA1865;CA1869;CA1716;CA2101;SA1127;CA1845;CA1859;CA2007;CA2234;CA1812;CA1822;CA2000</NoWarn>
|
||||||
<TargetFramework>net11.0</TargetFramework>
|
<TargetFramework>net11.0</TargetFramework>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
@@ -33,3 +34,4 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ using System;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Jellyfin.Database.Implementations;
|
using Jellyfin.Database.Implementations;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Jellyfin.Database.Implementations.Entities;
|
using Jellyfin.Database.Implementations.Entities;
|
||||||
using Jellyfin.Database.Implementations.Locking;
|
using Jellyfin.Database.Implementations.Locking;
|
||||||
using Jellyfin.Server.Implementations.Item;
|
using Jellyfin.Server.Implementations.Item;
|
||||||
|
|||||||
+3
@@ -2,11 +2,13 @@
|
|||||||
|
|
||||||
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
|
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
<NoWarn>$(NoWarn);CA1707;CA1861;CA1515;CA1062;CA5394;CA1848;CA1865;CA1869;CA1716;CA2101;SA1127;CA1845;CA1859;CA2007;CA2234;CA1812;CA1822;CA2000</NoWarn>
|
||||||
<ProjectGuid>{2E3A1B4B-4225-4AAA-8B29-0181A84E7AEE}</ProjectGuid>
|
<ProjectGuid>{2E3A1B4B-4225-4AAA-8B29-0181A84E7AEE}</ProjectGuid>
|
||||||
<TargetFramework>net11.0</TargetFramework>
|
<TargetFramework>net11.0</TargetFramework>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
<NoWarn>$(NoWarn);CA1707;CA1861;CA1515;CA1062;CA5394;CA1848;CA1865;CA1869;CA1716;CA2101;SA1127;CA1845;CA1859;CA2007;CA2234;CA1812;CA1822;CA2000</NoWarn>
|
||||||
<JELLYFIN_WEB_DIR>E:/Projects/jellyfin-web</JELLYFIN_WEB_DIR>
|
<JELLYFIN_WEB_DIR>E:/Projects/jellyfin-web</JELLYFIN_WEB_DIR>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
@@ -43,3 +45,4 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
<NoWarn>$(NoWarn);CA1707;CA1861;CA1515;CA1062;CA5394;CA1848;CA1865;CA1869;CA1716;CA2101;SA1127;CA1845;CA1859;CA2007;CA2234;CA1812;CA1822;CA2000</NoWarn>
|
||||||
<TargetFramework>net11.0</TargetFramework>
|
<TargetFramework>net11.0</TargetFramework>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
@@ -43,3 +44,4 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
<NoWarn>$(NoWarn);CA1707;CA1861;CA1515;CA1062;CA5394;CA1848;CA1865;CA1869;CA1716;CA2101;SA1127;CA1845;CA1859;CA2007;CA2234;CA1812;CA1822;CA2000</NoWarn>
|
||||||
<TargetFramework>net11.0</TargetFramework>
|
<TargetFramework>net11.0</TargetFramework>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
@@ -25,3 +26,4 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
<NoWarn>$(NoWarn);CA1707;CA1861;CA1515;CA1062;CA5394;CA1848;CA1865;CA1869;CA1716;CA2101;SA1127;CA1845;CA1859;CA2007;CA2234;CA1812;CA1822;CA2000</NoWarn>
|
||||||
<TargetFramework>net11.0</TargetFramework>
|
<TargetFramework>net11.0</TargetFramework>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
@@ -28,3 +29,4 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user