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>
|
||||
<WarningsAsErrors></WarningsAsErrors>
|
||||
<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 Condition=" '$(Stability)'=='Unstable'">
|
||||
@@ -62,7 +62,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<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>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
|
||||
<PropertyGroup>
|
||||
<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>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -10,6 +10,7 @@ namespace Emby.Server.Implementations.AppBase
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Events;
|
||||
@@ -306,6 +307,32 @@ namespace Emby.Server.Implementations.AppBase
|
||||
|
||||
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
|
||||
{
|
||||
if (File.Exists(path))
|
||||
|
||||
@@ -6,6 +6,8 @@ namespace Emby.Server.Implementations.AppBase
|
||||
{
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using Jellyfin.Extensions.Json;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
|
||||
/// <summary>
|
||||
@@ -54,10 +56,54 @@ namespace Emby.Server.Implementations.AppBase
|
||||
Directory.CreateDirectory(directory);
|
||||
|
||||
// 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))
|
||||
{
|
||||
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;
|
||||
|
||||
@@ -517,6 +517,7 @@ namespace Emby.Server.Implementations
|
||||
serviceCollection.AddSingleton<IMediaAttachmentRepository, MediaAttachmentRepository>();
|
||||
serviceCollection.AddSingleton<IMediaStreamRepository, MediaStreamRepository>();
|
||||
serviceCollection.AddSingleton<IKeyframeRepository, KeyframeRepository>();
|
||||
serviceCollection.AddSingleton<ILibraryOptionsRepository, LibraryOptionsRepository>();
|
||||
serviceCollection.AddSingleton<IItemTypeLookup, ItemTypeLookup>();
|
||||
|
||||
serviceCollection.AddSingleton<IMediaEncoder, MediaBrowser.MediaEncoding.Encoder.MediaEncoder>();
|
||||
@@ -600,11 +601,37 @@ namespace Emby.Server.Implementations
|
||||
|
||||
FindParts();
|
||||
|
||||
BackfillLibraryOptionsFromXml();
|
||||
|
||||
// Ensure at least one user exists
|
||||
var userManager = Resolve<IUserManager>();
|
||||
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)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
@@ -658,6 +685,7 @@ namespace Emby.Server.Implementations
|
||||
BaseItem.UserDataManager = Resolve<IUserDataManager>();
|
||||
CollectionFolder.XmlSerializer = _xmlSerializer;
|
||||
CollectionFolder.ApplicationHost = this;
|
||||
CollectionFolder.LibraryOptionsRepository = Resolve<ILibraryOptionsRepository>();
|
||||
Folder.UserViewManager = Resolve<IUserViewManager>();
|
||||
Folder.CollectionManager = Resolve<ICollectionManager>();
|
||||
Folder.LimitedConcurrencyLibraryScheduler = Resolve<ILimitedConcurrencyLibraryScheduler>();
|
||||
|
||||
@@ -108,14 +108,15 @@ public class CleanDatabaseScheduledTask : ILibraryPostScanTask
|
||||
var context = await _dbProvider.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);
|
||||
await using (context.ConfigureAwait(false))
|
||||
{
|
||||
var transaction = await context.Database.BeginTransactionAsync(cancellationToken).ConfigureAwait(false);
|
||||
await using (transaction.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.ItemValues.Where(e => e.BaseItemsMap!.Count == 0).ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false);
|
||||
subProgress.Report(50);
|
||||
await transaction.CommitAsync(cancellationToken).ConfigureAwait(false);
|
||||
subProgress.Report(100);
|
||||
}
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
progress.Report(100);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
|
||||
<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>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -38,12 +39,14 @@
|
||||
</ItemGroup>
|
||||
|
||||
<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>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<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>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
@@ -22,6 +22,15 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
|
||||
/// <summary>
|
||||
/// 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>
|
||||
public class WebSocketConnection : IWebSocketConnection
|
||||
{
|
||||
@@ -47,7 +56,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
/// </summary>
|
||||
/// <param name="logger">The logger.</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>
|
||||
public WebSocketConnection(
|
||||
ILogger<WebSocketConnection> logger,
|
||||
|
||||
@@ -18,6 +18,25 @@ using Microsoft.Extensions.Logging;
|
||||
|
||||
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
|
||||
{
|
||||
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 -->
|
||||
<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>
|
||||
</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>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<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>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
@@ -11,11 +12,13 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<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. -->
|
||||
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
|
||||
</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>
|
||||
<PackageId>Jellyfin.Data</PackageId>
|
||||
<VersionPrefix>10.12.0</VersionPrefix>
|
||||
|
||||
@@ -322,9 +322,9 @@ public class BackupService : IBackupService
|
||||
(Type: typeof(HistoryRow), SourceName: nameof(HistoryRow), ValueFactory: () => migrations.ToAsyncEnumerable())
|
||||
];
|
||||
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");
|
||||
|
||||
@@ -363,7 +363,7 @@ public class BackupService : IBackupService
|
||||
|
||||
_logger.LogInformation("Backup of entity {Table} with {Number} created", entityType.SourceName, entities);
|
||||
}
|
||||
}
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
_logger.LogInformation("Backup of folder {Table}", _applicationPaths.ConfigurationDirectoryPath);
|
||||
|
||||
@@ -1088,8 +1088,10 @@ public sealed class BaseItemRepository
|
||||
var context = await _dbProvider.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);
|
||||
await using (context.ConfigureAwait(false))
|
||||
{
|
||||
var transaction = await context.Database.BeginTransactionAsync(cancellationToken).ConfigureAwait(false);
|
||||
await using (transaction.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 ids = tuples.Select(f => f.Item.Id).ToArray();
|
||||
var existingItems = await context.BaseItems
|
||||
@@ -1284,8 +1286,7 @@ public sealed class BaseItemRepository
|
||||
}
|
||||
|
||||
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))
|
||||
{
|
||||
var transaction = await dbContext.Database.BeginTransactionAsync(cancellationToken).ConfigureAwait(false);
|
||||
await using (transaction.ConfigureAwait(false))
|
||||
// Use the execution strategy to handle transactional consistency and retries
|
||||
// This is required for PostgreSQL NpgsqlRetryingExecutionStrategy
|
||||
var executionStrategy = dbContext.Database.CreateExecutionStrategy();
|
||||
await executionStrategy.ExecuteAsync(async () =>
|
||||
{
|
||||
var userKeys = item.GetUserDataKeys().ToArray();
|
||||
var retentionDate = (DateTime?)null;
|
||||
@@ -1320,9 +1323,7 @@ public sealed class BaseItemRepository
|
||||
.Where(e => e.ItemId == item.Id)
|
||||
.ToArrayAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
await transaction.CommitAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -77,8 +77,12 @@ public class ChapterRepository : IChapterRepository
|
||||
public async Task SaveChaptersAsync(Guid itemId, IReadOnlyList<ChapterInfo> chapters, CancellationToken cancellationToken = default)
|
||||
{
|
||||
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
|
||||
.Where(e => e.ItemId.Equals(itemId))
|
||||
.ExecuteDeleteAsync(cancellationToken)
|
||||
@@ -91,7 +95,7 @@ public class ChapterRepository : IChapterRepository
|
||||
}
|
||||
|
||||
await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
|
||||
await transaction.CommitAsync(cancellationToken).ConfigureAwait(false);
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -64,12 +64,16 @@ public class KeyframeRepository : IKeyframeRepository
|
||||
public async Task SaveKeyframeDataAsync(Guid itemId, MediaEncoding.Keyframes.KeyframeData data, CancellationToken cancellationToken = default)
|
||||
{
|
||||
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.AddAsync(Map(data, itemId), cancellationToken).ConfigureAwait(false);
|
||||
await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
|
||||
await transaction.CommitAsync(cancellationToken).ConfigureAwait(false);
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -29,8 +29,12 @@ public class MediaAttachmentRepository(IDbContextFactory<JellyfinDbContext> dbPr
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
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.
|
||||
// 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.
|
||||
@@ -47,7 +51,7 @@ public class MediaAttachmentRepository(IDbContextFactory<JellyfinDbContext> dbPr
|
||||
}
|
||||
|
||||
await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
|
||||
await transaction.CommitAsync(cancellationToken).ConfigureAwait(false);
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -44,8 +44,12 @@ public class MediaStreamRepository : IMediaStreamRepository
|
||||
public async Task SaveMediaStreamsAsync(Guid id, IReadOnlyList<MediaStream> streams, CancellationToken cancellationToken = default)
|
||||
{
|
||||
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
|
||||
.Where(e => e.ItemId.Equals(id))
|
||||
.ExecuteDeleteAsync(cancellationToken)
|
||||
@@ -56,7 +60,7 @@ public class MediaStreamRepository : IMediaStreamRepository
|
||||
.ConfigureAwait(false);
|
||||
|
||||
await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
|
||||
await transaction.CommitAsync(cancellationToken).ConfigureAwait(false);
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -123,8 +123,12 @@ public class PeopleRepository(IDbContextFactory<JellyfinDbContext> dbProvider, I
|
||||
var personKeys = people.Select(e => e.Name + "-" + e.Type).ToArray();
|
||||
|
||||
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
|
||||
.Select(e => new
|
||||
{
|
||||
@@ -193,7 +197,7 @@ public class PeopleRepository(IDbContextFactory<JellyfinDbContext> dbProvider, I
|
||||
context.PeopleBaseItemMap.RemoveRange(existingMaps);
|
||||
|
||||
await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
|
||||
await transaction.CommitAsync(cancellationToken).ConfigureAwait(false);
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private PersonInfo Map(People people)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<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>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
|
||||
@@ -83,6 +83,15 @@ namespace Jellyfin.Server.Implementations.Security
|
||||
IHeaderDictionary headers,
|
||||
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? deviceName = null;
|
||||
string? client = null;
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
|
||||
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
|
||||
<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>
|
||||
</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>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net11.0</TargetFramework>
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
<VersionPrefix>10.12.0</VersionPrefix>
|
||||
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
||||
<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>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -20,6 +20,7 @@ using Jellyfin.Database.Implementations.Entities;
|
||||
using Jellyfin.Extensions.Json;
|
||||
using MediaBrowser.Controller.IO;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Persistence;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
using MediaBrowser.Model.IO;
|
||||
@@ -69,6 +70,8 @@ namespace MediaBrowser.Controller.Entities
|
||||
|
||||
public static IServerApplicationHost ApplicationHost { get; set; }
|
||||
|
||||
public static ILibraryOptionsRepository LibraryOptionsRepository { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public override bool SupportsPlayedStatus => false;
|
||||
|
||||
@@ -112,9 +115,24 @@ namespace MediaBrowser.Controller.Entities
|
||||
|
||||
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
|
||||
{
|
||||
if (XmlSerializer.DeserializeFromFile(typeof(LibraryOptions), GetLibraryOptionsPath(path)) is not LibraryOptions result)
|
||||
if (XmlSerializer.DeserializeFromFile(typeof(LibraryOptions), xmlPath) is not LibraryOptions result)
|
||||
{
|
||||
return new LibraryOptions();
|
||||
}
|
||||
@@ -127,6 +145,8 @@ namespace MediaBrowser.Controller.Entities
|
||||
}
|
||||
}
|
||||
|
||||
LibraryOptionsRepository?.SaveLibraryOptions(path, result);
|
||||
|
||||
return result;
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
@@ -162,6 +182,8 @@ namespace MediaBrowser.Controller.Entities
|
||||
{
|
||||
_libraryOptions[path] = options;
|
||||
|
||||
LibraryOptionsRepository?.SaveLibraryOptions(path, options);
|
||||
|
||||
var clone = JsonSerializer.Deserialize<LibraryOptions>(JsonSerializer.SerializeToUtf8Bytes(options, _jsonOptions), _jsonOptions);
|
||||
foreach (var mediaPath in clone.PathInfos)
|
||||
{
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
|
||||
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
|
||||
<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>
|
||||
</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>
|
||||
<PackageId>Jellyfin.Controller</PackageId>
|
||||
<VersionPrefix>10.12.0</VersionPrefix>
|
||||
@@ -14,6 +16,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<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>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -35,6 +38,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<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>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
@@ -45,6 +49,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<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. -->
|
||||
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
|
||||
</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 -->
|
||||
<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>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -11,6 +12,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<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>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
|
||||
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
|
||||
<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>
|
||||
</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>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
|
||||
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
|
||||
<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>
|
||||
</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>
|
||||
<PackageId>Jellyfin.Model</PackageId>
|
||||
<VersionPrefix>10.12.0</VersionPrefix>
|
||||
@@ -14,6 +16,7 @@
|
||||
</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>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
@@ -24,10 +27,12 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<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>
|
||||
</PropertyGroup>
|
||||
|
||||
<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. -->
|
||||
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
|
||||
<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>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -28,6 +29,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<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>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
|
||||
<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>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -15,6 +16,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<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>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<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">
|
||||
|
||||
<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>
|
||||
<TargetFramework>net11.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<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>
|
||||
<TargetFramework>net11.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<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>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<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;
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Serialization;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
/// <summary>
|
||||
/// Options to configure jellyfins managed database.
|
||||
@@ -67,7 +68,7 @@ public class DatabaseConfigurationOptions
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[XmlArray("PresetConfigurations")]
|
||||
[XmlArrayItem("PresetConfiguration")]
|
||||
[SuppressMessage("Design", "CA2227:Collection properties should be read-only", Justification = "JSON deserialization requires settable property")]
|
||||
[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; }
|
||||
}
|
||||
|
||||
+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">
|
||||
|
||||
<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>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
@@ -11,6 +12,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<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>
|
||||
<PackageId>Jellyfin.Database.Implementations</PackageId>
|
||||
<VersionPrefix>10.11.0</VersionPrefix>
|
||||
@@ -19,6 +21,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<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. -->
|
||||
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -8,6 +8,7 @@ namespace Jellyfin.Database.Implementations;
|
||||
|
||||
using System;
|
||||
using System.Data.Common;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -180,6 +181,11 @@ public class JellyfinDbContext(DbContextOptions<JellyfinDbContext> options, ILog
|
||||
/// </summary>
|
||||
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<Book> Books => Set<Book>();
|
||||
@@ -291,12 +297,14 @@ public class JellyfinDbContext(DbContextOptions<JellyfinDbContext> options, ILog
|
||||
if (isConstraintViolation)
|
||||
{
|
||||
// Log constraint violations as warnings (expected in some concurrent scenarios)
|
||||
#pragma warning disable CA1848 // Use LoggerMessage delegates - exception type is dynamic
|
||||
logger.LogWarning(
|
||||
ex,
|
||||
"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. " +
|
||||
"Inner exception: {InnerExceptionType}",
|
||||
ex.InnerException?.GetType().Name ?? "unknown");
|
||||
#pragma warning restore CA1848
|
||||
}
|
||||
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>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<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>
|
||||
|
||||
<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<MediaSegment>().ToTable("MediaSegments", 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/>
|
||||
|
||||
+1
@@ -4,6 +4,7 @@
|
||||
<TargetFramework>net11.0</TargetFramework>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<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>
|
||||
|
||||
<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>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<!-- 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>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
<TargetFramework>net11.0</TargetFramework>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<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>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<IncludeSymbols>true</IncludeSymbols>
|
||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||
<!-- 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>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net11.0</TargetFramework>
|
||||
<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>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<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>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<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>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
</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>
|
||||
<PackageId>Jellyfin.MediaEncoding.Keyframes</PackageId>
|
||||
<VersionPrefix>10.11.0</VersionPrefix>
|
||||
@@ -14,6 +16,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<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. -->
|
||||
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<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>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<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 -->
|
||||
<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>
|
||||
<TargetFramework>net11.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
@@ -27,3 +28,4 @@
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
|
||||
<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>
|
||||
<TargetFramework>net11.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
@@ -24,3 +25,4 @@
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
|
||||
<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>
|
||||
<TargetFramework>net11.0</TargetFramework>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
@@ -24,3 +25,4 @@
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
// Copyright (c) PlaceholderCompany. All rights reserved.
|
||||
// </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
|
||||
{
|
||||
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>
|
||||
|
||||
#pragma warning disable CA1707 // Identifiers should not contain underscores - xUnit test naming convention
|
||||
|
||||
namespace Jellyfin.Extensions.Tests;
|
||||
|
||||
using System.IO;
|
||||
@@ -25,3 +27,5 @@ public static class FileHelperTests
|
||||
File.Delete(path);
|
||||
}
|
||||
}
|
||||
|
||||
#pragma warning restore CA1707
|
||||
|
||||
@@ -12,16 +12,18 @@ using Xunit;
|
||||
|
||||
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]
|
||||
public static void Shuffle_Valid_Correct()
|
||||
#pragma warning restore CA1707
|
||||
#pragma warning restore IDE0063
|
||||
{
|
||||
Thread.CurrentThread.CurrentCulture = new CultureInfo("de-DE", false);
|
||||
using (var ms = new MemoryStream())
|
||||
using (var txt = new FormattingStreamWriter(ms, CultureInfo.InvariantCulture))
|
||||
{
|
||||
using var ms = new MemoryStream();
|
||||
using var txt = new FormattingStreamWriter(ms, CultureInfo.InvariantCulture);
|
||||
txt.Write("{0}", 3.14159);
|
||||
txt.Close();
|
||||
Assert.Equal("3.14159", Encoding.UTF8.GetString(ms.ToArray()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net11.0</TargetFramework>
|
||||
<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>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
// Copyright (c) PlaceholderCompany. All rights reserved.
|
||||
// </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
|
||||
{
|
||||
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>
|
||||
|
||||
#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
|
||||
{
|
||||
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>
|
||||
|
||||
#pragma warning disable CA1707 // Identifiers should not contain underscores - xUnit test naming convention
|
||||
|
||||
namespace Jellyfin.Extensions.Tests.Json.Converters
|
||||
{
|
||||
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">
|
||||
<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>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -28,3 +29,4 @@
|
||||
<ProjectReference Include="..\..\src\Jellyfin.LiveTv\Jellyfin.LiveTv.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<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>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -24,3 +25,4 @@
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
|
||||
+4
-1
@@ -2,10 +2,11 @@
|
||||
// Copyright (c) PlaceholderCompany. All rights reserved.
|
||||
// </copyright>
|
||||
|
||||
namespace Jellyfin.MediaEncoding.Keyframes.FfProbe
|
||||
namespace Jellyfin.MediaEncoding.Keyframes.Tests.FfProbe
|
||||
{
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using Jellyfin.MediaEncoding.Keyframes.FfProbe;
|
||||
using Xunit;
|
||||
|
||||
public class FfProbeKeyframeExtractorTests
|
||||
@@ -13,7 +14,9 @@ namespace Jellyfin.MediaEncoding.Keyframes.FfProbe
|
||||
[Theory]
|
||||
[InlineData("keyframes.txt", "keyframes_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)
|
||||
#pragma warning restore CA1707
|
||||
{
|
||||
var testDataPath = Path.Combine("FfProbe/Test Data", testDataFileName);
|
||||
var resultPath = Path.Combine("FfProbe/Test Data", resultFileName);
|
||||
|
||||
+2
@@ -1,6 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<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>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -38,3 +39,4 @@
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
|
||||
<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>
|
||||
<TargetFramework>net11.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
@@ -32,3 +33,4 @@
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net11.0</TargetFramework>
|
||||
<NoWarn>$(NoWarn);CA1707;CA1861;CA1515;CA1062;CA5394;CA1055;CA1054;CA1308;CA2007;CA1822;CA1848;CA1865;CA1869;CA1716;CA2101;SA1127</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
|
||||
<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>
|
||||
<TargetFramework>net11.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
@@ -23,3 +24,4 @@
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
|
||||
<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>
|
||||
<TargetFramework>net11.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
@@ -24,3 +25,4 @@
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<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>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -33,3 +34,4 @@
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Database.Implementations;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Jellyfin.Database.Implementations.Entities;
|
||||
using Jellyfin.Database.Implementations.Locking;
|
||||
using Jellyfin.Server.Implementations.Item;
|
||||
|
||||
+3
@@ -2,11 +2,13 @@
|
||||
|
||||
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
|
||||
<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>
|
||||
<TargetFramework>net11.0</TargetFramework>
|
||||
</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>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -43,3 +45,4 @@
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<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>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -43,3 +44,4 @@
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<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>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -25,3 +26,4 @@
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<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>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -28,3 +29,4 @@
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user