Development #7
@@ -1,6 +1,7 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
<NoWarn>$(NoWarn);NU1510</NoWarn>
|
||||
</PropertyGroup>
|
||||
<!-- Run "dotnet list package (dash,dash)outdated" to see the latest versions of each package.-->
|
||||
<ItemGroup Label="Package Dependencies">
|
||||
@@ -90,4 +91,4 @@
|
||||
<PackageVersion Include="Xunit.SkippableFact" Version="1.5.61" />
|
||||
<PackageVersion Include="xunit" Version="2.9.3" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
|
||||
@@ -18,7 +18,6 @@ namespace Emby.Server.Implementations
|
||||
{ FfmpegProbeSizeKey, "1G" },
|
||||
{ FfmpegAnalyzeDurationKey, "200M" },
|
||||
{ BindToUnixSocketKey, bool.FalseString },
|
||||
{ SqliteCacheSizeKey, "20000" },
|
||||
{ FfmpegSkipValidationKey, bool.FalseString },
|
||||
{ FfmpegImgExtractPerfTradeoffKey, bool.FalseString },
|
||||
{ DetectNetworkChangeKey, bool.TrueString }
|
||||
|
||||
@@ -2,7 +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>
|
||||
<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;NU1510</NoWarn>
|
||||
<ProjectGuid>{E383961B-9356-4D5D-8233-9A1079D03055}</ProjectGuid>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -25,10 +25,9 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BitFaster.Caching" />
|
||||
<PackageReference Include="DiscUtils.Udf" />
|
||||
|
||||
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" />
|
||||
<PackageReference Include="prometheus-net.DotNetRuntime" />
|
||||
<PackageReference Include="DotNet.Glob" />
|
||||
|
||||
@@ -1223,29 +1223,26 @@ namespace Emby.Server.Implementations.Library
|
||||
private async Task RunPostScanTasks(IProgress<double> progress, CancellationToken cancellationToken)
|
||||
{
|
||||
var tasks = PostScanTasks.ToList();
|
||||
|
||||
var numComplete = 0;
|
||||
var numTasks = tasks.Count;
|
||||
|
||||
foreach (var task in tasks)
|
||||
if (numTasks == 0)
|
||||
{
|
||||
// Prevent access to modified closure
|
||||
var currentNumComplete = numComplete;
|
||||
_itemRepository.UpdateInheritedValues();
|
||||
progress.Report(100);
|
||||
return;
|
||||
}
|
||||
|
||||
var progressValues = new double[numTasks];
|
||||
|
||||
async Task RunOneTask(ILibraryPostScanTask task, int taskIndex)
|
||||
{
|
||||
_logger.LogDebug("Running post-scan task {0}", task.GetType().Name);
|
||||
var innerProgress = new Progress<double>(pct =>
|
||||
{
|
||||
double innerPercent = pct;
|
||||
innerPercent /= 100;
|
||||
innerPercent += currentNumComplete;
|
||||
|
||||
innerPercent /= numTasks;
|
||||
innerPercent *= 100;
|
||||
|
||||
progress.Report(innerPercent);
|
||||
progressValues[taskIndex] = pct;
|
||||
progress.Report(progressValues.Average());
|
||||
});
|
||||
|
||||
_logger.LogDebug("Running post-scan task {0}", task.GetType().Name);
|
||||
|
||||
try
|
||||
{
|
||||
await task.Run(innerProgress, cancellationToken).ConfigureAwait(false);
|
||||
@@ -1260,12 +1257,12 @@ namespace Emby.Server.Implementations.Library
|
||||
_logger.LogError(ex, "Error running post-scan task");
|
||||
}
|
||||
|
||||
numComplete++;
|
||||
double percent = numComplete;
|
||||
percent /= numTasks;
|
||||
progress.Report(percent * 100);
|
||||
progressValues[taskIndex] = 100;
|
||||
progress.Report(progressValues.Average());
|
||||
}
|
||||
|
||||
await Task.WhenAll(tasks.Select((task, i) => RunOneTask(task, i))).ConfigureAwait(false);
|
||||
|
||||
_itemRepository.UpdateInheritedValues();
|
||||
|
||||
progress.Report(100);
|
||||
@@ -1997,7 +1994,7 @@ namespace Emby.Server.Implementations.Library
|
||||
/// <inheritdoc />
|
||||
public void CreateItems(IReadOnlyList<BaseItem> items, BaseItem? parent, CancellationToken cancellationToken)
|
||||
{
|
||||
_itemRepository.SaveItems(items, cancellationToken);
|
||||
_itemRepository.SaveItemsAsync(items, cancellationToken).GetAwaiter().GetResult();
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
@@ -2165,7 +2162,7 @@ namespace Emby.Server.Implementations.Library
|
||||
item.DateLastSaved = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
_itemRepository.SaveItems(items, cancellationToken);
|
||||
await _itemRepository.SaveItemsAsync(items, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (parent is Folder folder)
|
||||
{
|
||||
|
||||
@@ -62,24 +62,28 @@ namespace Emby.Server.Implementations.Library
|
||||
var keys = item.GetUserDataKeys();
|
||||
|
||||
using var dbContext = _repository.CreateDbContext();
|
||||
using var transaction = dbContext.Database.BeginTransaction();
|
||||
|
||||
foreach (var key in keys)
|
||||
var executionStrategy = dbContext.Database.CreateExecutionStrategy();
|
||||
executionStrategy.Execute(() =>
|
||||
{
|
||||
userData.Key = key;
|
||||
var userDataEntry = Map(userData, user.Id, item.Id);
|
||||
if (dbContext.UserData.Any(f => f.ItemId == userDataEntry.ItemId && f.UserId == userDataEntry.UserId && f.CustomDataKey == userDataEntry.CustomDataKey))
|
||||
{
|
||||
dbContext.UserData.Attach(userDataEntry).State = EntityState.Modified;
|
||||
}
|
||||
else
|
||||
{
|
||||
dbContext.UserData.Add(userDataEntry);
|
||||
}
|
||||
}
|
||||
using var transaction = dbContext.Database.BeginTransaction();
|
||||
|
||||
dbContext.SaveChanges();
|
||||
transaction.Commit();
|
||||
foreach (var key in keys)
|
||||
{
|
||||
userData.Key = key;
|
||||
var userDataEntry = Map(userData, user.Id, item.Id);
|
||||
if (dbContext.UserData.Any(f => f.ItemId == userDataEntry.ItemId && f.UserId == userDataEntry.UserId && f.CustomDataKey == userDataEntry.CustomDataKey))
|
||||
{
|
||||
dbContext.UserData.Attach(userDataEntry).State = EntityState.Modified;
|
||||
}
|
||||
else
|
||||
{
|
||||
dbContext.UserData.Add(userDataEntry);
|
||||
}
|
||||
}
|
||||
|
||||
dbContext.SaveChanges();
|
||||
transaction.Commit();
|
||||
});
|
||||
|
||||
var userId = user.InternalId;
|
||||
var cacheKey = GetCacheKey(userId, item.Id);
|
||||
|
||||
@@ -0,0 +1,302 @@
|
||||
// <copyright file="DatabaseViewsController.cs" company="PlaceholderCompany">
|
||||
// Copyright (c) PlaceholderCompany. All rights reserved.
|
||||
// </copyright>
|
||||
|
||||
namespace Jellyfin.Api.Controllers;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Database.Implementations;
|
||||
using Jellyfin.Database.Implementations.Entities.Views;
|
||||
using MediaBrowser.Common.Api;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
/// <summary>
|
||||
/// Exposes the PostgreSQL reporting views as read-only API endpoints.
|
||||
/// All endpoints require administrator privileges.
|
||||
/// </summary>
|
||||
[Route("DatabaseViews")]
|
||||
[Authorize(Policy = Policies.RequiresElevation)]
|
||||
public class DatabaseViewsController : BaseJellyfinApiController
|
||||
{
|
||||
private readonly IDbContextFactory<JellyfinDbContext> _dbFactory;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DatabaseViewsController"/> class.
|
||||
/// </summary>
|
||||
/// <param name="dbFactory">The EF Core context factory.</param>
|
||||
public DatabaseViewsController(IDbContextFactory<JellyfinDbContext> dbFactory)
|
||||
{
|
||||
_dbFactory = dbFactory;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a flat list of all media streams (audio + video columns merged).
|
||||
/// Maps to library."MediaStreamInfos_v".
|
||||
/// </summary>
|
||||
/// <param name="itemId">Optional item ID filter.</param>
|
||||
/// <param name="streamType">Optional stream type filter (0=Audio, 1=Video, 2=Subtitle).</param>
|
||||
/// <param name="startIndex">Record index to start at.</param>
|
||||
/// <param name="limit">Maximum records to return (1-1000, default 100).</param>
|
||||
/// <returns>List of media stream rows.</returns>
|
||||
[HttpGet("MediaStreamInfos")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<IEnumerable<MediaStreamInfoView>>> GetMediaStreamInfos(
|
||||
[FromQuery] Guid? itemId = null,
|
||||
[FromQuery] int? streamType = null,
|
||||
[FromQuery] int startIndex = 0,
|
||||
[FromQuery] int limit = 100)
|
||||
{
|
||||
limit = Math.Clamp(limit, 1, 1000);
|
||||
await using var db = await _dbFactory.CreateDbContextAsync().ConfigureAwait(false);
|
||||
|
||||
var query = db.MediaStreamInfoViews.AsNoTracking();
|
||||
if (itemId.HasValue)
|
||||
{
|
||||
var id = itemId.Value;
|
||||
query = query.Where(x => x.ItemId.HasValue && x.ItemId.Value.Equals(id));
|
||||
}
|
||||
|
||||
if (streamType.HasValue)
|
||||
{
|
||||
query = query.Where(x => x.StreamType == streamType.Value);
|
||||
}
|
||||
|
||||
IEnumerable<MediaStreamInfoView> results = await query
|
||||
.OrderBy(x => x.ItemId)
|
||||
.ThenBy(x => x.StreamIndex)
|
||||
.Skip(startIndex)
|
||||
.Take(limit)
|
||||
.ToListAsync()
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return Ok(results);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets video items joined with primary stream technical details.
|
||||
/// Maps to library."VideoItems_v".
|
||||
/// </summary>
|
||||
/// <param name="type">Optional partial type filter (e.g. "Movie", "Episode").</param>
|
||||
/// <param name="minHeight">Optional minimum video height (e.g. 2160 for 4K).</param>
|
||||
/// <param name="hdrFormat">Optional HDR format ("Dolby Vision", "HDR10+", "HDR10", "HLG", "SDR").</param>
|
||||
/// <param name="startIndex">Record index to start at.</param>
|
||||
/// <param name="limit">Maximum records to return (1-1000, default 100).</param>
|
||||
/// <returns>List of video item rows.</returns>
|
||||
[HttpGet("VideoItems")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<IEnumerable<VideoItemView>>> GetVideoItems(
|
||||
[FromQuery] string? type = null,
|
||||
[FromQuery] int? minHeight = null,
|
||||
[FromQuery] string? hdrFormat = null,
|
||||
[FromQuery] int startIndex = 0,
|
||||
[FromQuery] int limit = 100)
|
||||
{
|
||||
limit = Math.Clamp(limit, 1, 1000);
|
||||
await using var db = await _dbFactory.CreateDbContextAsync().ConfigureAwait(false);
|
||||
|
||||
var query = db.VideoItemViews.AsNoTracking();
|
||||
if (!string.IsNullOrEmpty(type))
|
||||
{
|
||||
query = query.Where(x => x.Type != null && x.Type.Contains(type));
|
||||
}
|
||||
|
||||
if (minHeight.HasValue)
|
||||
{
|
||||
query = query.Where(x => x.Height >= minHeight.Value);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(hdrFormat))
|
||||
{
|
||||
query = query.Where(x => x.HDRFormat == hdrFormat);
|
||||
}
|
||||
|
||||
IEnumerable<VideoItemView> results = await query
|
||||
.OrderBy(x => x.Name)
|
||||
.Skip(startIndex)
|
||||
.Take(limit)
|
||||
.ToListAsync()
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return Ok(results);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets per-user playback history with calculated progress percentages.
|
||||
/// Maps to library."UserPlaybackHistory_v".
|
||||
/// </summary>
|
||||
/// <param name="username">Optional exact username filter.</param>
|
||||
/// <param name="itemId">Optional item ID filter.</param>
|
||||
/// <param name="inProgressOnly">When true, returns only items started but not fully played.</param>
|
||||
/// <param name="startIndex">Record index to start at.</param>
|
||||
/// <param name="limit">Maximum records to return (1-1000, default 100).</param>
|
||||
/// <returns>List of playback history rows.</returns>
|
||||
[HttpGet("PlaybackHistory")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<IEnumerable<UserPlaybackHistoryView>>> GetPlaybackHistory(
|
||||
[FromQuery] string? username = null,
|
||||
[FromQuery] Guid? itemId = null,
|
||||
[FromQuery] bool inProgressOnly = false,
|
||||
[FromQuery] int startIndex = 0,
|
||||
[FromQuery] int limit = 100)
|
||||
{
|
||||
limit = Math.Clamp(limit, 1, 1000);
|
||||
await using var db = await _dbFactory.CreateDbContextAsync().ConfigureAwait(false);
|
||||
|
||||
var query = db.UserPlaybackHistoryViews.AsNoTracking();
|
||||
if (!string.IsNullOrEmpty(username))
|
||||
{
|
||||
query = query.Where(x => x.Username == username);
|
||||
}
|
||||
|
||||
if (itemId.HasValue)
|
||||
{
|
||||
var id = itemId.Value;
|
||||
query = query.Where(x => x.ItemId.HasValue && x.ItemId.Value.Equals(id));
|
||||
}
|
||||
|
||||
if (inProgressOnly)
|
||||
{
|
||||
query = query.Where(x => x.Played != true && x.ProgressPct > 0);
|
||||
}
|
||||
|
||||
IEnumerable<UserPlaybackHistoryView> results = await query
|
||||
.OrderByDescending(x => x.LastPlayedDate)
|
||||
.Skip(startIndex)
|
||||
.Take(limit)
|
||||
.ToListAsync()
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return Ok(results);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets items with their external provider IDs (IMDb, TMDB, TVDB, etc.) in named columns.
|
||||
/// Maps to library."ItemProviders_v".
|
||||
/// </summary>
|
||||
/// <param name="type">Optional partial type filter.</param>
|
||||
/// <param name="imdbId">Optional exact IMDb ID filter.</param>
|
||||
/// <param name="tmdbId">Optional exact TMDB ID filter.</param>
|
||||
/// <param name="tvdbId">Optional exact TVDB ID filter.</param>
|
||||
/// <param name="startIndex">Record index to start at.</param>
|
||||
/// <param name="limit">Maximum records to return (1-1000, default 100).</param>
|
||||
/// <returns>List of item provider rows.</returns>
|
||||
[HttpGet("ItemProviders")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<IEnumerable<ItemProviderView>>> GetItemProviders(
|
||||
[FromQuery] string? type = null,
|
||||
[FromQuery] string? imdbId = null,
|
||||
[FromQuery] string? tmdbId = null,
|
||||
[FromQuery] string? tvdbId = null,
|
||||
[FromQuery] int startIndex = 0,
|
||||
[FromQuery] int limit = 100)
|
||||
{
|
||||
limit = Math.Clamp(limit, 1, 1000);
|
||||
await using var db = await _dbFactory.CreateDbContextAsync().ConfigureAwait(false);
|
||||
|
||||
var query = db.ItemProviderViews.AsNoTracking();
|
||||
if (!string.IsNullOrEmpty(type))
|
||||
{
|
||||
query = query.Where(x => x.Type != null && x.Type.Contains(type));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(imdbId))
|
||||
{
|
||||
query = query.Where(x => x.ImdbId == imdbId);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(tmdbId))
|
||||
{
|
||||
query = query.Where(x => x.TmdbId == tmdbId);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(tvdbId))
|
||||
{
|
||||
query = query.Where(x => x.TvdbId == tvdbId);
|
||||
}
|
||||
|
||||
IEnumerable<ItemProviderView> results = await query
|
||||
.OrderBy(x => x.Name)
|
||||
.Skip(startIndex)
|
||||
.Take(limit)
|
||||
.ToListAsync()
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return Ok(results);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets cast and crew entries linked to the items they appear in.
|
||||
/// Maps to library."ItemPeople_v".
|
||||
/// </summary>
|
||||
/// <param name="personName">Optional exact person name filter.</param>
|
||||
/// <param name="personType">Optional person type filter (e.g. "Actor", "Director").</param>
|
||||
/// <param name="itemId">Optional item ID filter.</param>
|
||||
/// <param name="startIndex">Record index to start at.</param>
|
||||
/// <param name="limit">Maximum records to return (1-1000, default 100).</param>
|
||||
/// <returns>List of item person rows.</returns>
|
||||
[HttpGet("ItemPeople")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<IEnumerable<ItemPersonView>>> GetItemPeople(
|
||||
[FromQuery] string? personName = null,
|
||||
[FromQuery] string? personType = null,
|
||||
[FromQuery] Guid? itemId = null,
|
||||
[FromQuery] int startIndex = 0,
|
||||
[FromQuery] int limit = 100)
|
||||
{
|
||||
limit = Math.Clamp(limit, 1, 1000);
|
||||
await using var db = await _dbFactory.CreateDbContextAsync().ConfigureAwait(false);
|
||||
|
||||
var query = db.ItemPersonViews.AsNoTracking();
|
||||
if (!string.IsNullOrEmpty(personName))
|
||||
{
|
||||
query = query.Where(x => x.PersonName == personName);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(personType))
|
||||
{
|
||||
query = query.Where(x => x.PersonType == personType);
|
||||
}
|
||||
|
||||
if (itemId.HasValue)
|
||||
{
|
||||
var id = itemId.Value;
|
||||
query = query.Where(x => x.ItemId.HasValue && x.ItemId.Value.Equals(id));
|
||||
}
|
||||
|
||||
IEnumerable<ItemPersonView> results = await query
|
||||
.OrderBy(x => x.PersonName)
|
||||
.ThenBy(x => x.SortOrder)
|
||||
.Skip(startIndex)
|
||||
.Take(limit)
|
||||
.ToListAsync()
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return Ok(results);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets aggregate library statistics - one row per item type.
|
||||
/// Maps to library."LibrarySummary_v".
|
||||
/// </summary>
|
||||
/// <returns>List of library summary rows, one per item type.</returns>
|
||||
[HttpGet("LibrarySummary")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<IEnumerable<LibrarySummaryView>>> GetLibrarySummary()
|
||||
{
|
||||
await using var db = await _dbFactory.CreateDbContextAsync().ConfigureAwait(false);
|
||||
|
||||
IEnumerable<LibrarySummaryView> results = await db.LibrarySummaryViews
|
||||
.AsNoTracking()
|
||||
.OrderBy(x => x.Type)
|
||||
.ToListAsync()
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return Ok(results);
|
||||
}
|
||||
}
|
||||
@@ -99,23 +99,13 @@ public static class ServiceCollectionExtensions
|
||||
}
|
||||
else
|
||||
{
|
||||
// when nothing is setup via new Database configuration, fallback to SQLite with default settings.
|
||||
// PostgreSQL-only build: default to PostgreSQL when no database.xml exists.
|
||||
efCoreConfiguration = new DatabaseConfigurationOptions()
|
||||
{
|
||||
DatabaseType = "Jellyfin-SQLite",
|
||||
DatabaseType = "Jellyfin-PostgreSQL",
|
||||
LockingBehavior = DatabaseLockingBehaviorTypes.NoLock,
|
||||
PresetConfigurations =
|
||||
[
|
||||
new PresetConfigurationItem
|
||||
{
|
||||
Key = "SQLite",
|
||||
Value = new PresetDatabaseConfiguration
|
||||
{
|
||||
DatabaseType = "Jellyfin-SQLite",
|
||||
LockingBehavior = DatabaseLockingBehaviorTypes.NoLock,
|
||||
Description = "Default SQLite database configuration. Database stored in data directory."
|
||||
}
|
||||
},
|
||||
new PresetConfigurationItem
|
||||
{
|
||||
Key = "PostgreSQL-Local",
|
||||
@@ -135,7 +125,7 @@ public static class ServiceCollectionExtensions
|
||||
// Ensure DatabaseType is set (handle corrupted or empty configuration files)
|
||||
if (string.IsNullOrWhiteSpace(efCoreConfiguration.DatabaseType))
|
||||
{
|
||||
efCoreConfiguration.DatabaseType = "Jellyfin-SQLite";
|
||||
efCoreConfiguration.DatabaseType = "Jellyfin-PostgreSQL";
|
||||
configurationManager.SaveConfiguration("database", efCoreConfiguration);
|
||||
}
|
||||
|
||||
|
||||
@@ -15,13 +15,13 @@ using System.Text.Json.Serialization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Database.Implementations;
|
||||
using Jellyfin.Server.Implementations.Migrations;
|
||||
using Jellyfin.Server.Implementations.StorageHelpers;
|
||||
using Jellyfin.Server.Implementations.SystemBackupService;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.SystemBackupService;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
@@ -156,34 +156,31 @@ public class BackupService : IBackupService
|
||||
await using (dbContext.ConfigureAwait(false))
|
||||
{
|
||||
// restore migration history manually
|
||||
var historyEntry = zipArchive.GetEntry(NormalizePathSeparator(Path.Combine("Database", $"{nameof(HistoryRow)}.json")));
|
||||
var historyEntry = zipArchive.GetEntry(NormalizePathSeparator(Path.Combine("Database", $"{MigrationTracker.BackupEntryName}.json")));
|
||||
if (historyEntry is null)
|
||||
{
|
||||
_logger.LogInformation("No backup of the history table in archive. This is required for Jellyfin operation");
|
||||
throw new InvalidOperationException("Cannot restore backup that has no History data.");
|
||||
}
|
||||
|
||||
HistoryRow[] historyEntries;
|
||||
MigrationRecord[] historyEntries;
|
||||
var historyArchive = await historyEntry.OpenAsync().ConfigureAwait(false);
|
||||
await using (historyArchive.ConfigureAwait(false))
|
||||
{
|
||||
historyEntries = await JsonSerializer.DeserializeAsync<HistoryRow[]>(historyArchive).ConfigureAwait(false) ??
|
||||
historyEntries = await JsonSerializer.DeserializeAsync<MigrationRecord[]>(historyArchive).ConfigureAwait(false) ??
|
||||
throw new InvalidOperationException("Cannot restore backup that has no History data.");
|
||||
}
|
||||
|
||||
var historyRepository = dbContext.GetService<IHistoryRepository>();
|
||||
await historyRepository.CreateIfNotExistsAsync().ConfigureAwait(false);
|
||||
await MigrationTracker.EnsureTableExistsAsync(dbContext).ConfigureAwait(false);
|
||||
|
||||
foreach (var item in await historyRepository.GetAppliedMigrationsAsync(CancellationToken.None).ConfigureAwait(false))
|
||||
foreach (var item in await MigrationTracker.GetAppliedMigrationIdsAsync(dbContext, CancellationToken.None).ConfigureAwait(false))
|
||||
{
|
||||
var insertScript = historyRepository.GetDeleteScript(item.MigrationId);
|
||||
await dbContext.Database.ExecuteSqlRawAsync(insertScript).ConfigureAwait(false);
|
||||
await MigrationTracker.DeleteAsync(dbContext, item).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
foreach (var item in historyEntries)
|
||||
{
|
||||
var insertScript = historyRepository.GetInsertScript(item);
|
||||
await dbContext.Database.ExecuteSqlRawAsync(insertScript).ConfigureAwait(false);
|
||||
await MigrationTracker.InsertAsync(dbContext, item.MigrationId, item.ProductVersion).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
dbContext.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
|
||||
@@ -310,8 +307,7 @@ public class BackupService : IBackupService
|
||||
}
|
||||
|
||||
// include the migration history as well
|
||||
var historyRepository = dbContext.GetService<IHistoryRepository>();
|
||||
var migrations = await historyRepository.GetAppliedMigrationsAsync().ConfigureAwait(false);
|
||||
var migrations = await MigrationTracker.GetAppliedMigrationsAsync(dbContext).ConfigureAwait(false);
|
||||
|
||||
ICollection<(Type Type, string SourceName, Func<IAsyncEnumerable<object>> ValueFactory)> entityTypes =
|
||||
[
|
||||
@@ -319,7 +315,7 @@ public class BackupService : IBackupService
|
||||
.GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance)
|
||||
.Where(e => e.PropertyType.IsAssignableTo(typeof(IQueryable)))
|
||||
.Select(e => (Type: e.PropertyType, dbContext.Model.FindEntityType(e.PropertyType.GetGenericArguments()[0])!.GetSchemaQualifiedTableName()!, ValueFactory: new Func<IAsyncEnumerable<object>>(() => GetValues((IQueryable)e.GetValue(dbContext)!)))),
|
||||
(Type: typeof(HistoryRow), SourceName: nameof(HistoryRow), ValueFactory: () => migrations.ToAsyncEnumerable())
|
||||
(Type: typeof(MigrationRecord), SourceName: MigrationTracker.BackupEntryName, ValueFactory: () => migrations.ToAsyncEnumerable())
|
||||
];
|
||||
manifest.DatabaseTables = entityTypes.Select(e => e.Type.Name).ToArray();
|
||||
|
||||
|
||||
@@ -127,65 +127,69 @@ 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))
|
||||
var executionStrategy = context.Database.CreateExecutionStrategy();
|
||||
await executionStrategy.ExecuteAsync(async () =>
|
||||
{
|
||||
var date = (DateTime?)DateTime.UtcNow;
|
||||
var transaction = await context.Database.BeginTransactionAsync(cancellationToken).ConfigureAwait(false);
|
||||
await using (transaction.ConfigureAwait(false))
|
||||
{
|
||||
var date = (DateTime?)DateTime.UtcNow;
|
||||
|
||||
await EnsurePlaceholderItemAsync(context, cancellationToken).ConfigureAwait(false);
|
||||
await EnsurePlaceholderItemAsync(context, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var relatedItems = ids.SelectMany(f => TraverseHirachyDown(f, context)).ToArray();
|
||||
var relatedItems = ids.SelectMany(f => TraverseHirachyDown(f, context)).ToArray();
|
||||
|
||||
// Remove any UserData entries for the placeholder item that would conflict with the UserData
|
||||
// being detached from the item being deleted. This is necessary because, during an update,
|
||||
// UserData may be reattached to a new entry, but some entries can be left behind.
|
||||
// Ensures there are no duplicate UserId/CustomDataKey combinations for the placeholder.
|
||||
await context.UserData
|
||||
.Join(
|
||||
context.UserData.WhereOneOrMany(relatedItems, e => e.ItemId),
|
||||
placeholder => new { placeholder.UserId, placeholder.CustomDataKey },
|
||||
userData => new { userData.UserId, userData.CustomDataKey },
|
||||
(placeholder, userData) => placeholder)
|
||||
.Where(e => e.ItemId == PlaceholderId)
|
||||
.ExecuteDeleteAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
// Remove any UserData entries for the placeholder item that would conflict with the UserData
|
||||
// being detached from the item being deleted. This is necessary because, during an update,
|
||||
// UserData may be reattached to a new entry, but some entries can be left behind.
|
||||
// Ensures there are no duplicate UserId/CustomDataKey combinations for the placeholder.
|
||||
await context.UserData
|
||||
.Join(
|
||||
context.UserData.WhereOneOrMany(relatedItems, e => e.ItemId),
|
||||
placeholder => new { placeholder.UserId, placeholder.CustomDataKey },
|
||||
userData => new { userData.UserId, userData.CustomDataKey },
|
||||
(placeholder, userData) => placeholder)
|
||||
.Where(e => e.ItemId == PlaceholderId)
|
||||
.ExecuteDeleteAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
// Detach all user watch data
|
||||
await context.UserData.WhereOneOrMany(relatedItems, e => e.ItemId)
|
||||
.ExecuteUpdateAsync(
|
||||
e => e
|
||||
.SetProperty(f => f.RetentionDate, date)
|
||||
.SetProperty(f => f.ItemId, PlaceholderId),
|
||||
cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
// Detach all user watch data
|
||||
await context.UserData.WhereOneOrMany(relatedItems, e => e.ItemId)
|
||||
.ExecuteUpdateAsync(
|
||||
e => e
|
||||
.SetProperty(f => f.RetentionDate, date)
|
||||
.SetProperty(f => f.ItemId, PlaceholderId),
|
||||
cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
await context.AncestorIds.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false);
|
||||
await context.AncestorIds.WhereOneOrMany(relatedItems, e => e.ParentItemId).ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false);
|
||||
await context.AttachmentStreamInfos.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false);
|
||||
await context.BaseItemImageInfos.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false);
|
||||
await context.BaseItemMetadataFields.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false);
|
||||
await context.BaseItemProviders.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false);
|
||||
await context.BaseItemTrailerTypes.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false);
|
||||
await context.BaseItems.WhereOneOrMany(relatedItems, e => e.Id).ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false);
|
||||
await context.Chapters.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false);
|
||||
await context.CustomItemDisplayPreferences.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false);
|
||||
await context.ItemDisplayPreferences.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false);
|
||||
await context.ItemValues.Where(e => e.BaseItemsMap!.Count == 0).ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false);
|
||||
await context.ItemValuesMap.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false);
|
||||
await context.KeyframeData.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false);
|
||||
await context.MediaSegments.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false);
|
||||
await context.MediaStreamInfos.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false);
|
||||
var query = await context.PeopleBaseItemMap.WhereOneOrMany(relatedItems, e => e.ItemId)
|
||||
.Select(f => f.PeopleId)
|
||||
.Distinct()
|
||||
.ToArrayAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
await context.PeopleBaseItemMap.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false);
|
||||
await context.Peoples.WhereOneOrMany(query, e => e.Id).Where(e => e.BaseItems!.Count == 0).ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false);
|
||||
await context.TrickplayInfos.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false);
|
||||
await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
|
||||
await transaction.CommitAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
await context.AncestorIds.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false);
|
||||
await context.AncestorIds.WhereOneOrMany(relatedItems, e => e.ParentItemId).ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false);
|
||||
await context.AttachmentStreamInfos.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false);
|
||||
await context.BaseItemImageInfos.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false);
|
||||
await context.BaseItemMetadataFields.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false);
|
||||
await context.BaseItemProviders.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false);
|
||||
await context.BaseItemTrailerTypes.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false);
|
||||
await context.BaseItems.WhereOneOrMany(relatedItems, e => e.Id).ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false);
|
||||
await context.Chapters.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false);
|
||||
await context.CustomItemDisplayPreferences.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false);
|
||||
await context.ItemDisplayPreferences.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false);
|
||||
await context.ItemValues.Where(e => e.BaseItemsMap!.Count == 0).ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false);
|
||||
await context.ItemValuesMap.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false);
|
||||
await context.KeyframeData.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false);
|
||||
await context.MediaSegments.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false);
|
||||
await context.MediaStreamInfos.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false);
|
||||
var query = await context.PeopleBaseItemMap.WhereOneOrMany(relatedItems, e => e.ItemId)
|
||||
.Select(f => f.PeopleId)
|
||||
.Distinct()
|
||||
.ToArrayAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
await context.PeopleBaseItemMap.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false);
|
||||
await context.Peoples.WhereOneOrMany(query, e => e.Id).Where(e => e.BaseItems!.Count == 0).ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false);
|
||||
await context.TrickplayInfos.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false);
|
||||
await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
|
||||
await transaction.CommitAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (Npgsql.PostgresException ex) when (ex.SqlState == "40P01") // Deadlock detected
|
||||
@@ -237,13 +241,17 @@ public sealed class BaseItemRepository
|
||||
public void UpdateInheritedValues()
|
||||
{
|
||||
using var context = _dbProvider.CreateDbContext();
|
||||
using var transaction = context.Database.BeginTransaction();
|
||||
var executionStrategy = context.Database.CreateExecutionStrategy();
|
||||
executionStrategy.Execute(() =>
|
||||
{
|
||||
using var transaction = context.Database.BeginTransaction();
|
||||
|
||||
context.ItemValuesMap.Where(e => e.ItemValue.Type == ItemValueType.InheritedTags).ExecuteDelete();
|
||||
// ItemValue Inheritance is now correctly mapped via AncestorId on demand
|
||||
context.SaveChanges();
|
||||
context.ItemValuesMap.Where(e => e.ItemValue.Type == ItemValueType.InheritedTags).ExecuteDelete();
|
||||
// ItemValue Inheritance is now correctly mapped via AncestorId on demand
|
||||
context.SaveChanges();
|
||||
|
||||
transaction.Commit();
|
||||
transaction.Commit();
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -444,7 +452,7 @@ public sealed class BaseItemRepository
|
||||
|
||||
// Get IDs only, without DistinctBy to avoid translation errors
|
||||
var allIds = await dbQuery
|
||||
.Select(e => new { e.Id, e.PresentationUniqueKey, e.SeriesPresentationUniqueKey })
|
||||
.Select(e => new { e.Id, e.PresentationUniqueKey, SeriesPresentationUniqueKey = e.TvExtras!.SeriesPresentationUniqueKey })
|
||||
.ToListAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
@@ -500,7 +508,7 @@ public sealed class BaseItemRepository
|
||||
{
|
||||
e.Id,
|
||||
e.PresentationUniqueKey,
|
||||
e.SeriesPresentationUniqueKey
|
||||
SeriesPresentationUniqueKey = e.TvExtras!.SeriesPresentationUniqueKey
|
||||
})
|
||||
.ToListAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
@@ -570,7 +578,7 @@ public sealed class BaseItemRepository
|
||||
subquery = TranslateQuery(subquery, context, filter);
|
||||
|
||||
var subqueryGrouped = subquery
|
||||
.GroupBy(g => collectionType == CollectionType.tvshows ? g.SeriesName : g.Album)
|
||||
.GroupBy(g => collectionType == CollectionType.tvshows ? g.TvExtras!.SeriesName : g.AudioExtras!.Album)
|
||||
.Select(g => new
|
||||
{
|
||||
Key = g.Key,
|
||||
@@ -605,7 +613,7 @@ public sealed class BaseItemRepository
|
||||
|
||||
// Get IDs + keys to memory (without DistinctBy in the query)
|
||||
var itemsWithKeys = await mainquery
|
||||
.Select(e => new { e.Id, e.PresentationUniqueKey, e.SeriesPresentationUniqueKey })
|
||||
.Select(e => new { e.Id, e.PresentationUniqueKey, SeriesPresentationUniqueKey = e.TvExtras!.SeriesPresentationUniqueKey })
|
||||
.ToListAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
@@ -655,7 +663,7 @@ public sealed class BaseItemRepository
|
||||
|
||||
// Get IDs + keys to memory (without DistinctBy in the query)
|
||||
var itemsWithKeys = await mainquery
|
||||
.Select(e => new { e.Id, e.PresentationUniqueKey, e.SeriesPresentationUniqueKey })
|
||||
.Select(e => new { e.Id, e.PresentationUniqueKey, SeriesPresentationUniqueKey = e.TvExtras!.SeriesPresentationUniqueKey })
|
||||
.ToListAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
@@ -711,7 +719,7 @@ public sealed class BaseItemRepository
|
||||
i => new { UserId = filter.User.Id, ItemId = i.Id },
|
||||
u => new { UserId = u.UserId, ItemId = u.ItemId },
|
||||
(entity, data) => new { Item = entity, UserData = data })
|
||||
.GroupBy(g => g.Item.SeriesPresentationUniqueKey)
|
||||
.GroupBy(g => g.Item.TvExtras!.SeriesPresentationUniqueKey)
|
||||
.Select(g => new { g.Key, LastPlayedDate = g.Max(u => u.UserData.LastPlayedDate) })
|
||||
.Where(g => g.Key != null && g.LastPlayedDate != null && g.LastPlayedDate >= dateCutoff)
|
||||
.OrderByDescending(g => g.LastPlayedDate)
|
||||
@@ -745,7 +753,7 @@ public sealed class BaseItemRepository
|
||||
{
|
||||
// PERFORMANCE FIX: Use DistinctBy() instead of GroupBy().Select().FirstOrDefault()
|
||||
// to prevent correlated subqueries. See docs/database-query-optimization.md
|
||||
dbQuery = dbQuery.DistinctBy(e => new { e.PresentationUniqueKey, e.SeriesPresentationUniqueKey });
|
||||
dbQuery = dbQuery.DistinctBy(e => new { e.PresentationUniqueKey, SeriesPresentationUniqueKey = e.TvExtras!.SeriesPresentationUniqueKey });
|
||||
}
|
||||
else if (enableGroupByPresentationUniqueKey)
|
||||
{
|
||||
@@ -756,7 +764,7 @@ public sealed class BaseItemRepository
|
||||
else if (filter.GroupBySeriesPresentationUniqueKey)
|
||||
{
|
||||
// PERFORMANCE FIX: Use DistinctBy() instead of GroupBy().Select().FirstOrDefault()
|
||||
dbQuery = dbQuery.DistinctBy(e => e.SeriesPresentationUniqueKey);
|
||||
dbQuery = dbQuery.DistinctBy(e => e.TvExtras!.SeriesPresentationUniqueKey);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -795,6 +803,11 @@ public sealed class BaseItemRepository
|
||||
dbQuery = dbQuery.Include(e => e.Images);
|
||||
}
|
||||
|
||||
dbQuery = dbQuery
|
||||
.Include(e => e.TvExtras)
|
||||
.Include(e => e.LiveTvExtras)
|
||||
.Include(e => e.AudioExtras);
|
||||
|
||||
return dbQuery;
|
||||
}
|
||||
|
||||
@@ -1100,12 +1113,20 @@ public sealed class BaseItemRepository
|
||||
.ToArrayAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
var extensionUpserts = new List<(Guid ExtItemId, BaseItemTvExtras? TvExtras, BaseItemLiveTvExtras? LiveTvExtras, BaseItemAudioExtras? AudioExtras)>();
|
||||
|
||||
foreach (var item in tuples)
|
||||
{
|
||||
var entity = Map(item.Item);
|
||||
// TODO: refactor this "inconsistency"
|
||||
entity.TopParentId = item.TopParent?.Id;
|
||||
|
||||
// Extract extension data and clear nav props before EF Core tracking
|
||||
extensionUpserts.Add((entity.Id, entity.TvExtras, entity.LiveTvExtras, entity.AudioExtras));
|
||||
entity.TvExtras = null;
|
||||
entity.LiveTvExtras = null;
|
||||
entity.AudioExtras = null;
|
||||
|
||||
if (!existingItems.Any(e => e == entity.Id))
|
||||
{
|
||||
context.BaseItems.Add(entity);
|
||||
@@ -1140,18 +1161,17 @@ public sealed class BaseItemRepository
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
// UPSERT all providers using raw SQL with ON CONFLICT
|
||||
// This is atomic and handles concurrent operations correctly
|
||||
foreach (var provider in providersToUpsert)
|
||||
{
|
||||
await context.Database.ExecuteSqlAsync(
|
||||
$@"INSERT INTO library.""BaseItemProviders"" (""ItemId"", ""ProviderId"", ""ProviderValue"")
|
||||
VALUES ({entity.Id}, {provider.ProviderId}, {provider.ProviderValue})
|
||||
ON CONFLICT (""ItemId"", ""ProviderId"")
|
||||
DO UPDATE SET ""ProviderValue"" = EXCLUDED.""ProviderValue""",
|
||||
cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
// UPSERT all providers in a single UNNEST-based INSERT instead of one round-trip per provider
|
||||
var providerItemIds = providersToUpsert.Select(_ => entity.Id).ToArray();
|
||||
var providerIds = providersToUpsert.Select(p => p.ProviderId).ToArray();
|
||||
var providerValues = providersToUpsert.Select(p => p.ProviderValue).ToArray();
|
||||
await context.Database.ExecuteSqlAsync(
|
||||
$@"INSERT INTO library.base_item_providers (item_id, provider_id, provider_value)
|
||||
SELECT * FROM UNNEST({providerItemIds}, {providerIds}, {providerValues})
|
||||
ON CONFLICT (item_id, provider_id)
|
||||
DO UPDATE SET provider_value = EXCLUDED.provider_value",
|
||||
cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
// CRITICAL: Clear the navigation property so EF Core doesn't try to track/save these
|
||||
entity.Provider = null;
|
||||
@@ -1183,6 +1203,78 @@ public sealed class BaseItemRepository
|
||||
|
||||
await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// Upsert extension table rows (BaseItemTvExtras, BaseItemLiveTvExtras, BaseItemAudioExtras)
|
||||
foreach (var (extItemId, tvExtras, liveTvExtras, audioExtras) in extensionUpserts)
|
||||
{
|
||||
if (tvExtras is not null)
|
||||
{
|
||||
await context.Database.ExecuteSqlAsync(
|
||||
$@"INSERT INTO library.base_item_tv_extras (item_id, series_id, season_id, series_name, season_name, series_presentation_unique_key)
|
||||
VALUES ({extItemId}, {tvExtras.SeriesId}, {tvExtras.SeasonId}, {tvExtras.SeriesName}, {tvExtras.SeasonName}, {tvExtras.SeriesPresentationUniqueKey})
|
||||
ON CONFLICT (item_id) DO UPDATE SET
|
||||
series_id = EXCLUDED.series_id,
|
||||
season_id = EXCLUDED.season_id,
|
||||
series_name = EXCLUDED.series_name,
|
||||
season_name = EXCLUDED.season_name,
|
||||
series_presentation_unique_key = EXCLUDED.series_presentation_unique_key",
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await context.BaseItemTvExtras
|
||||
.Where(e => e.ItemId == extItemId)
|
||||
.ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (liveTvExtras is not null)
|
||||
{
|
||||
var startDate = liveTvExtras.StartDate.HasValue
|
||||
? (DateTime?)DateTime.SpecifyKind(liveTvExtras.StartDate.Value, DateTimeKind.Utc)
|
||||
: null;
|
||||
var endDate = liveTvExtras.EndDate.HasValue
|
||||
? (DateTime?)DateTime.SpecifyKind(liveTvExtras.EndDate.Value, DateTimeKind.Utc)
|
||||
: null;
|
||||
await context.Database.ExecuteSqlAsync(
|
||||
$@"INSERT INTO library.base_item_live_tv_extras (item_id, start_date, end_date, episode_title, show_id, external_series_id, external_service_id, audio)
|
||||
VALUES ({extItemId}, {startDate}, {endDate}, {liveTvExtras.EpisodeTitle}, {liveTvExtras.ShowId}, {liveTvExtras.ExternalSeriesId}, {liveTvExtras.ExternalServiceId}, {(int?)liveTvExtras.Audio})
|
||||
ON CONFLICT (item_id) DO UPDATE SET
|
||||
start_date = EXCLUDED.start_date,
|
||||
end_date = EXCLUDED.end_date,
|
||||
episode_title = EXCLUDED.episode_title,
|
||||
show_id = EXCLUDED.show_id,
|
||||
external_series_id = EXCLUDED.external_series_id,
|
||||
external_service_id = EXCLUDED.external_service_id,
|
||||
audio = EXCLUDED.audio",
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await context.BaseItemLiveTvExtras
|
||||
.Where(e => e.ItemId == extItemId)
|
||||
.ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (audioExtras is not null)
|
||||
{
|
||||
await context.Database.ExecuteSqlAsync(
|
||||
$@"INSERT INTO library.base_item_audio_extras (item_id, album, artists, album_artists, lufs, normalization_gain)
|
||||
VALUES ({extItemId}, {audioExtras.Album}, {audioExtras.Artists}, {audioExtras.AlbumArtists}, {audioExtras.LUFS}, {audioExtras.NormalizationGain})
|
||||
ON CONFLICT (item_id) DO UPDATE SET
|
||||
album = EXCLUDED.album,
|
||||
artists = EXCLUDED.artists,
|
||||
album_artists = EXCLUDED.album_artists,
|
||||
lufs = EXCLUDED.lufs,
|
||||
normalization_gain = EXCLUDED.normalization_gain",
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await context.BaseItemAudioExtras
|
||||
.Where(e => e.ItemId == extItemId)
|
||||
.ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
var itemValueMaps = tuples
|
||||
.Select(e => (e.Item, Values: GetItemValuesToSave(e.Item, e.InheritedTags)))
|
||||
.ToArray();
|
||||
@@ -1208,7 +1300,6 @@ public sealed class BaseItemRepository
|
||||
Value = f.Value
|
||||
}).ToArray();
|
||||
context.ItemValues.AddRange(missingItemValues);
|
||||
await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var itemValuesStore = existingValues.Concat(missingItemValues).ToArray();
|
||||
var valueMap = itemValueMaps
|
||||
@@ -1249,19 +1340,36 @@ public sealed class BaseItemRepository
|
||||
|
||||
await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// Hoist ancestor queries outside the per-item loop to avoid N×2 DB round-trips
|
||||
var ancestorItemIds = tuples
|
||||
.Where(t => t.Item.SupportsAncestors && t.AncestorIds != null)
|
||||
.Select(t => t.Item.Id)
|
||||
.ToArray();
|
||||
var allNeededAncestorIds = tuples
|
||||
.Where(t => t.Item.SupportsAncestors && t.AncestorIds != null)
|
||||
.SelectMany(t => t.AncestorIds!)
|
||||
.Distinct()
|
||||
.ToArray();
|
||||
var allExistingAncestorIds = ancestorItemIds.Length > 0
|
||||
? await context.AncestorIds
|
||||
.Where(e => ancestorItemIds.Contains(e.ItemId))
|
||||
.ToListAsync(cancellationToken)
|
||||
.ConfigureAwait(false)
|
||||
: [];
|
||||
var validAncestorIdSet = allNeededAncestorIds.Length > 0
|
||||
? (await context.BaseItems
|
||||
.Where(e => allNeededAncestorIds.Contains(e.Id))
|
||||
.Select(f => f.Id)
|
||||
.ToArrayAsync(cancellationToken)
|
||||
.ConfigureAwait(false)).ToHashSet()
|
||||
: [];
|
||||
|
||||
foreach (var item in tuples)
|
||||
{
|
||||
if (item.Item.SupportsAncestors && item.AncestorIds != null)
|
||||
{
|
||||
var existingAncestorIds = await context.AncestorIds
|
||||
.Where(e => e.ItemId == item.Item.Id)
|
||||
.ToListAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
var validAncestorIds = await context.BaseItems
|
||||
.Where(e => item.AncestorIds.Contains(e.Id))
|
||||
.Select(f => f.Id)
|
||||
.ToArrayAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
var existingAncestorIds = allExistingAncestorIds.Where(e => e.ItemId == item.Item.Id).ToList();
|
||||
var validAncestorIds = item.AncestorIds.Where(validAncestorIdSet.Contains).ToArray();
|
||||
foreach (var ancestorId in validAncestorIds)
|
||||
{
|
||||
var existingAncestorId = existingAncestorIds.FirstOrDefault(e => e.ParentItemId == ancestorId);
|
||||
@@ -1357,7 +1465,10 @@ public sealed class BaseItemRepository
|
||||
.Include(e => e.Provider)
|
||||
.Include(e => e.LockedFields)
|
||||
.Include(e => e.UserData)
|
||||
.Include(e => e.Images);
|
||||
.Include(e => e.Images)
|
||||
.Include(e => e.TvExtras)
|
||||
.Include(e => e.LiveTvExtras)
|
||||
.Include(e => e.AudioExtras);
|
||||
|
||||
var item = await dbQuery
|
||||
.FirstOrDefaultAsync(e => e.Id == id, cancellationToken)
|
||||
@@ -1385,7 +1496,7 @@ public sealed class BaseItemRepository
|
||||
dto.Id = entity.Id;
|
||||
dto.ParentId = entity.ParentId.GetValueOrDefault();
|
||||
dto.Path = appHost?.ExpandVirtualPath(entity.Path) ?? entity.Path;
|
||||
dto.EndDate = entity.EndDate;
|
||||
dto.EndDate = entity.LiveTvExtras?.EndDate;
|
||||
dto.CommunityRating = entity.CommunityRating;
|
||||
dto.CustomRating = entity.CustomRating;
|
||||
dto.IndexNumber = entity.IndexNumber;
|
||||
@@ -1407,11 +1518,11 @@ public sealed class BaseItemRepository
|
||||
dto.CriticRating = entity.CriticRating;
|
||||
dto.PresentationUniqueKey = entity.PresentationUniqueKey;
|
||||
dto.OriginalTitle = entity.OriginalTitle;
|
||||
dto.Album = entity.Album;
|
||||
dto.LUFS = entity.LUFS;
|
||||
dto.NormalizationGain = entity.NormalizationGain;
|
||||
dto.Album = entity.AudioExtras?.Album;
|
||||
dto.LUFS = entity.AudioExtras?.LUFS;
|
||||
dto.NormalizationGain = entity.AudioExtras?.NormalizationGain;
|
||||
dto.IsVirtualItem = entity.IsVirtualItem;
|
||||
dto.ExternalSeriesId = entity.ExternalSeriesId;
|
||||
dto.ExternalSeriesId = entity.LiveTvExtras?.ExternalSeriesId;
|
||||
dto.Tagline = entity.Tagline;
|
||||
dto.TotalBitrate = entity.TotalBitrate;
|
||||
dto.ExternalId = entity.ExternalId;
|
||||
@@ -1442,9 +1553,10 @@ public sealed class BaseItemRepository
|
||||
dto.LockedFields = entity.LockedFields?.Select(e => (MetadataField)e.Id).ToArray() ?? [];
|
||||
}
|
||||
|
||||
if (entity.Audio is not null)
|
||||
var audioEnum = entity.LiveTvExtras?.Audio;
|
||||
if (audioEnum is not null)
|
||||
{
|
||||
dto.Audio = (ProgramAudio)entity.Audio;
|
||||
dto.Audio = (ProgramAudio)audioEnum;
|
||||
}
|
||||
|
||||
dto.ExtraIds = string.IsNullOrWhiteSpace(entity.ExtraIds) ? [] : entity.ExtraIds.Split('|').Select(e => Guid.Parse(e)).ToArray();
|
||||
@@ -1456,13 +1568,13 @@ public sealed class BaseItemRepository
|
||||
{
|
||||
hasProgramAttributes.IsMovie = entity.IsMovie;
|
||||
hasProgramAttributes.IsSeries = entity.IsSeries;
|
||||
hasProgramAttributes.EpisodeTitle = entity.EpisodeTitle;
|
||||
hasProgramAttributes.EpisodeTitle = entity.LiveTvExtras?.EpisodeTitle;
|
||||
hasProgramAttributes.IsRepeat = entity.IsRepeat;
|
||||
}
|
||||
|
||||
if (dto is LiveTvChannel liveTvChannel)
|
||||
{
|
||||
liveTvChannel.ServiceName = entity.ExternalServiceId;
|
||||
liveTvChannel.ServiceName = entity.LiveTvExtras?.ExternalServiceId;
|
||||
}
|
||||
|
||||
if (dto is Trailer trailer)
|
||||
@@ -1477,30 +1589,30 @@ public sealed class BaseItemRepository
|
||||
|
||||
if (dto is IHasSeries hasSeriesName)
|
||||
{
|
||||
hasSeriesName.SeriesName = entity.SeriesName;
|
||||
hasSeriesName.SeriesId = entity.SeriesId.GetValueOrDefault();
|
||||
hasSeriesName.SeriesPresentationUniqueKey = entity.SeriesPresentationUniqueKey;
|
||||
hasSeriesName.SeriesName = entity.TvExtras?.SeriesName;
|
||||
hasSeriesName.SeriesId = entity.TvExtras?.SeriesId ?? Guid.Empty;
|
||||
hasSeriesName.SeriesPresentationUniqueKey = entity.TvExtras?.SeriesPresentationUniqueKey;
|
||||
}
|
||||
|
||||
if (dto is Episode episode)
|
||||
{
|
||||
episode.SeasonName = entity.SeasonName;
|
||||
episode.SeasonId = entity.SeasonId.GetValueOrDefault();
|
||||
episode.SeasonName = entity.TvExtras?.SeasonName;
|
||||
episode.SeasonId = entity.TvExtras?.SeasonId ?? Guid.Empty;
|
||||
}
|
||||
|
||||
if (dto is IHasArtist hasArtists)
|
||||
{
|
||||
hasArtists.Artists = entity.Artists?.Split('|', StringSplitOptions.RemoveEmptyEntries) ?? [];
|
||||
hasArtists.Artists = entity.AudioExtras?.Artists?.Split('|', StringSplitOptions.RemoveEmptyEntries) ?? [];
|
||||
}
|
||||
|
||||
if (dto is IHasAlbumArtist hasAlbumArtists)
|
||||
{
|
||||
hasAlbumArtists.AlbumArtists = entity.AlbumArtists?.Split('|', StringSplitOptions.RemoveEmptyEntries) ?? [];
|
||||
hasAlbumArtists.AlbumArtists = entity.AudioExtras?.AlbumArtists?.Split('|', StringSplitOptions.RemoveEmptyEntries) ?? [];
|
||||
}
|
||||
|
||||
if (dto is LiveTvProgram program)
|
||||
{
|
||||
program.ShowId = entity.ShowId;
|
||||
program.ShowId = entity.LiveTvExtras?.ShowId;
|
||||
}
|
||||
|
||||
if (entity.Images is not null)
|
||||
@@ -1513,7 +1625,7 @@ public sealed class BaseItemRepository
|
||||
// dto.MediaType = Enum.TryParse<MediaType>(entity.MediaType);
|
||||
if (dto is IHasStartDate hasStartDate)
|
||||
{
|
||||
hasStartDate.StartDate = entity.StartDate.GetValueOrDefault();
|
||||
hasStartDate.StartDate = entity.LiveTvExtras?.StartDate ?? default;
|
||||
}
|
||||
|
||||
// Fields that are present in the DB but are never actually used
|
||||
@@ -1551,7 +1663,6 @@ public sealed class BaseItemRepository
|
||||
|
||||
entity.ParentId = !dto.ParentId.IsEmpty() ? dto.ParentId : null;
|
||||
entity.Path = GetPathToSave(dto.Path);
|
||||
entity.EndDate = dto.EndDate;
|
||||
entity.CommunityRating = dto.CommunityRating;
|
||||
entity.CustomRating = dto.CustomRating;
|
||||
entity.IndexNumber = dto.IndexNumber;
|
||||
@@ -1574,11 +1685,7 @@ public sealed class BaseItemRepository
|
||||
entity.CriticRating = dto.CriticRating;
|
||||
entity.PresentationUniqueKey = dto.PresentationUniqueKey;
|
||||
entity.OriginalTitle = dto.OriginalTitle;
|
||||
entity.Album = dto.Album;
|
||||
entity.LUFS = dto.LUFS;
|
||||
entity.NormalizationGain = dto.NormalizationGain;
|
||||
entity.IsVirtualItem = dto.IsVirtualItem;
|
||||
entity.ExternalSeriesId = dto.ExternalSeriesId;
|
||||
entity.Tagline = dto.Tagline;
|
||||
entity.TotalBitrate = dto.TotalBitrate;
|
||||
entity.ExternalId = dto.ExternalId;
|
||||
@@ -1599,11 +1706,6 @@ public sealed class BaseItemRepository
|
||||
ProviderValue = e.Value
|
||||
}).ToList();
|
||||
|
||||
if (dto.Audio.HasValue)
|
||||
{
|
||||
entity.Audio = (ProgramAudioEntity)dto.Audio;
|
||||
}
|
||||
|
||||
if (dto.ExtraType.HasValue)
|
||||
{
|
||||
entity.ExtraType = (BaseItemExtraType)dto.ExtraType;
|
||||
@@ -1626,48 +1728,14 @@ public sealed class BaseItemRepository
|
||||
{
|
||||
entity.IsMovie = hasProgramAttributes.IsMovie;
|
||||
entity.IsSeries = hasProgramAttributes.IsSeries;
|
||||
entity.EpisodeTitle = hasProgramAttributes.EpisodeTitle;
|
||||
entity.IsRepeat = hasProgramAttributes.IsRepeat;
|
||||
}
|
||||
|
||||
if (dto is LiveTvChannel liveTvChannel)
|
||||
{
|
||||
entity.ExternalServiceId = liveTvChannel.ServiceName;
|
||||
}
|
||||
|
||||
if (dto is Video video)
|
||||
{
|
||||
entity.PrimaryVersionId = video.PrimaryVersionId;
|
||||
}
|
||||
|
||||
if (dto is IHasSeries hasSeriesName)
|
||||
{
|
||||
entity.SeriesName = hasSeriesName.SeriesName;
|
||||
entity.SeriesId = hasSeriesName.SeriesId;
|
||||
entity.SeriesPresentationUniqueKey = hasSeriesName.SeriesPresentationUniqueKey;
|
||||
}
|
||||
|
||||
if (dto is Episode episode)
|
||||
{
|
||||
entity.SeasonName = episode.SeasonName;
|
||||
entity.SeasonId = episode.SeasonId;
|
||||
}
|
||||
|
||||
if (dto is IHasArtist hasArtists)
|
||||
{
|
||||
entity.Artists = hasArtists.Artists is not null ? string.Join('|', hasArtists.Artists) : null;
|
||||
}
|
||||
|
||||
if (dto is IHasAlbumArtist hasAlbumArtists)
|
||||
{
|
||||
entity.AlbumArtists = hasAlbumArtists.AlbumArtists is not null ? string.Join('|', hasAlbumArtists.AlbumArtists) : null;
|
||||
}
|
||||
|
||||
if (dto is LiveTvProgram program)
|
||||
{
|
||||
entity.ShowId = program.ShowId;
|
||||
}
|
||||
|
||||
if (dto.ImageInfos is not null)
|
||||
{
|
||||
entity.Images = dto.ImageInfos.Select(f => Map(dto.Id, f)).ToArray();
|
||||
@@ -1686,16 +1754,85 @@ public sealed class BaseItemRepository
|
||||
// dto.Type = entity.Type;
|
||||
// dto.Data = entity.Data;
|
||||
entity.MediaType = dto.MediaType.ToString();
|
||||
if (dto is IHasStartDate hasStartDate)
|
||||
{
|
||||
entity.StartDate = hasStartDate.StartDate;
|
||||
}
|
||||
|
||||
entity.UnratedType = dto.GetBlockUnratedType().ToString();
|
||||
|
||||
// Fields that are present in the DB but are never actually used
|
||||
// dto.UserDataKey = entity.UserDataKey;
|
||||
|
||||
// Populate extension navigation properties (persisted to extension tables in UpdateOrInsertItemsAsync)
|
||||
if (dto is IHasSeries seriesExt)
|
||||
{
|
||||
entity.TvExtras = new BaseItemTvExtras
|
||||
{
|
||||
ItemId = entity.Id,
|
||||
SeriesName = seriesExt.SeriesName,
|
||||
SeriesId = seriesExt.SeriesId,
|
||||
SeriesPresentationUniqueKey = seriesExt.SeriesPresentationUniqueKey
|
||||
};
|
||||
}
|
||||
|
||||
if (dto is Episode episodeExt)
|
||||
{
|
||||
entity.TvExtras ??= new BaseItemTvExtras { ItemId = entity.Id };
|
||||
entity.TvExtras.SeasonName = episodeExt.SeasonName;
|
||||
entity.TvExtras.SeasonId = episodeExt.SeasonId;
|
||||
}
|
||||
|
||||
if (dto.EndDate is not null || dto.ExternalSeriesId is not null || dto.Audio.HasValue
|
||||
|| dto is IHasProgramAttributes || dto is LiveTvChannel || dto is LiveTvProgram || dto is IHasStartDate)
|
||||
{
|
||||
var liveTvExt = new BaseItemLiveTvExtras { ItemId = entity.Id };
|
||||
liveTvExt.EndDate = dto.EndDate;
|
||||
liveTvExt.ExternalSeriesId = dto.ExternalSeriesId;
|
||||
if (dto.Audio.HasValue)
|
||||
{
|
||||
liveTvExt.Audio = (ProgramAudioEntity)dto.Audio;
|
||||
}
|
||||
|
||||
if (dto is IHasProgramAttributes hpaExt)
|
||||
{
|
||||
liveTvExt.EpisodeTitle = hpaExt.EpisodeTitle;
|
||||
}
|
||||
|
||||
if (dto is LiveTvChannel liveTvChExt)
|
||||
{
|
||||
liveTvExt.ExternalServiceId = liveTvChExt.ServiceName;
|
||||
}
|
||||
|
||||
if (dto is LiveTvProgram progExt)
|
||||
{
|
||||
liveTvExt.ShowId = progExt.ShowId;
|
||||
}
|
||||
|
||||
if (dto is IHasStartDate hasSDExt)
|
||||
{
|
||||
liveTvExt.StartDate = hasSDExt.StartDate;
|
||||
}
|
||||
|
||||
entity.LiveTvExtras = liveTvExt;
|
||||
}
|
||||
|
||||
if (dto.Album is not null || dto.LUFS is not null || dto.NormalizationGain is not null
|
||||
|| dto is IHasArtist || dto is IHasAlbumArtist)
|
||||
{
|
||||
var audioExt = new BaseItemAudioExtras { ItemId = entity.Id };
|
||||
audioExt.Album = dto.Album;
|
||||
audioExt.LUFS = dto.LUFS;
|
||||
audioExt.NormalizationGain = dto.NormalizationGain;
|
||||
if (dto is IHasArtist hasArtistsExt)
|
||||
{
|
||||
audioExt.Artists = hasArtistsExt.Artists is not null ? string.Join('|', hasArtistsExt.Artists) : null;
|
||||
}
|
||||
|
||||
if (dto is IHasAlbumArtist hasAlbumArtistsExt)
|
||||
{
|
||||
audioExt.AlbumArtists = hasAlbumArtistsExt.AlbumArtists is not null ? string.Join('|', hasAlbumArtistsExt.AlbumArtists) : null;
|
||||
}
|
||||
|
||||
entity.AudioExtras = audioExt;
|
||||
}
|
||||
|
||||
if (dto is Folder folder)
|
||||
{
|
||||
entity.DateLastMediaAdded = folder.DateLastMediaAdded == DateTime.MinValue ? null : folder.DateLastMediaAdded;
|
||||
@@ -2696,22 +2833,22 @@ public sealed class BaseItemRepository
|
||||
|
||||
if (minEndDate.HasValue)
|
||||
{
|
||||
baseQuery = baseQuery.Where(e => e.EndDate >= minEndDate);
|
||||
baseQuery = baseQuery.Where(e => e.LiveTvExtras!.EndDate >= minEndDate);
|
||||
}
|
||||
|
||||
if (maxEndDate.HasValue)
|
||||
{
|
||||
baseQuery = baseQuery.Where(e => e.EndDate <= maxEndDate);
|
||||
baseQuery = baseQuery.Where(e => e.LiveTvExtras!.EndDate <= maxEndDate);
|
||||
}
|
||||
|
||||
if (filter.MinStartDate.HasValue)
|
||||
{
|
||||
baseQuery = baseQuery.Where(e => e.StartDate >= filter.MinStartDate.Value);
|
||||
baseQuery = baseQuery.Where(e => e.LiveTvExtras!.StartDate >= filter.MinStartDate.Value);
|
||||
}
|
||||
|
||||
if (filter.MaxStartDate.HasValue)
|
||||
{
|
||||
baseQuery = baseQuery.Where(e => e.StartDate <= filter.MaxStartDate.Value);
|
||||
baseQuery = baseQuery.Where(e => e.LiveTvExtras!.StartDate <= filter.MaxStartDate.Value);
|
||||
}
|
||||
|
||||
if (filter.MinPremiereDate.HasValue)
|
||||
@@ -2734,11 +2871,11 @@ public sealed class BaseItemRepository
|
||||
{
|
||||
if (filter.IsAiring.Value)
|
||||
{
|
||||
baseQuery = baseQuery.Where(e => e.StartDate <= now && e.EndDate >= now);
|
||||
baseQuery = baseQuery.Where(e => e.LiveTvExtras!.StartDate <= now && e.LiveTvExtras!.EndDate >= now);
|
||||
}
|
||||
else
|
||||
{
|
||||
baseQuery = baseQuery.Where(e => e.StartDate > now && e.EndDate < now);
|
||||
baseQuery = baseQuery.Where(e => e.LiveTvExtras!.StartDate > now && e.LiveTvExtras!.EndDate < now);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2772,7 +2909,7 @@ public sealed class BaseItemRepository
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(filter.ExternalSeriesId))
|
||||
{
|
||||
baseQuery = baseQuery.Where(e => e.ExternalSeriesId == filter.ExternalSeriesId);
|
||||
baseQuery = baseQuery.Where(e => e.LiveTvExtras!.ExternalSeriesId == filter.ExternalSeriesId);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(filter.ExternalId))
|
||||
@@ -2860,7 +2997,7 @@ public sealed class BaseItemRepository
|
||||
baseQuery = baseQuery.Where(e => context.BaseItems.Where(e => e.Id != EF.Constant(PlaceholderId))
|
||||
.Where(e => e.IsFolder == false && e.IsVirtualItem == false)
|
||||
.Where(f => f.UserData!.FirstOrDefault(e => e.UserId == filter.User!.Id && e.Played)!.Played)
|
||||
.Any(f => f.SeriesPresentationUniqueKey == e.PresentationUniqueKey) == filter.IsPlayed);
|
||||
.Any(f => f.TvExtras!.SeriesPresentationUniqueKey == e.PresentationUniqueKey) == filter.IsPlayed);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -2918,7 +3055,7 @@ public sealed class BaseItemRepository
|
||||
if (filter.AlbumIds.Length > 0)
|
||||
{
|
||||
var subQuery = context.BaseItems.WhereOneOrMany(filter.AlbumIds, f => f.Id);
|
||||
baseQuery = baseQuery.Where(e => subQuery.Any(f => f.Name == e.Album));
|
||||
baseQuery = baseQuery.Where(e => subQuery.Any(f => f.Name == e.AudioExtras!.Album));
|
||||
}
|
||||
|
||||
if (filter.ExcludeArtistIds.Length > 0)
|
||||
@@ -3279,7 +3416,7 @@ public sealed class BaseItemRepository
|
||||
if (!string.IsNullOrWhiteSpace(filter.SeriesPresentationUniqueKey))
|
||||
{
|
||||
baseQuery = baseQuery
|
||||
.Where(e => e.SeriesPresentationUniqueKey == filter.SeriesPresentationUniqueKey);
|
||||
.Where(e => e.TvExtras!.SeriesPresentationUniqueKey == filter.SeriesPresentationUniqueKey);
|
||||
}
|
||||
|
||||
if (filter.ExcludeInheritedTags.Length > 0)
|
||||
@@ -3287,7 +3424,7 @@ public sealed class BaseItemRepository
|
||||
var excludedTags = filter.ExcludeInheritedTags;
|
||||
baseQuery = baseQuery.Where(e =>
|
||||
!e.ItemValues!.Any(f => f.ItemValue.Type == ItemValueType.Tags && excludedTags.Contains(f.ItemValue.CleanValue))
|
||||
&& (!e.SeriesId.HasValue || !context.ItemValuesMap.Any(f => f.ItemId == e.SeriesId.Value && f.ItemValue.Type == ItemValueType.Tags && excludedTags.Contains(f.ItemValue.CleanValue))));
|
||||
&& (!e.TvExtras!.SeriesId.HasValue || !context.ItemValuesMap.Any(f => f.ItemId == e.TvExtras!.SeriesId.Value && f.ItemValue.Type == ItemValueType.Tags && excludedTags.Contains(f.ItemValue.CleanValue))));
|
||||
}
|
||||
|
||||
if (filter.IncludeInheritedTags.Length > 0)
|
||||
@@ -3298,7 +3435,7 @@ public sealed class BaseItemRepository
|
||||
e.ItemValues!.Any(f => f.ItemValue.Type == ItemValueType.Tags && includeTags.Contains(f.ItemValue.CleanValue))
|
||||
|
||||
// For seasons and episodes, we also need to check the parent series' tags.
|
||||
|| (e.SeriesId.HasValue && context.ItemValuesMap.Any(f => f.ItemId == e.SeriesId.Value && f.ItemValue.Type == ItemValueType.Tags && includeTags.Contains(f.ItemValue.CleanValue)))
|
||||
|| (e.TvExtras!.SeriesId.HasValue && context.ItemValuesMap.Any(f => f.ItemId == e.TvExtras!.SeriesId.Value && f.ItemValue.Type == ItemValueType.Tags && includeTags.Contains(f.ItemValue.CleanValue)))
|
||||
|
||||
// A playlist should be accessible to its owner regardless of allowed tags
|
||||
|| (isPlaylistOnlyQuery && e.Data!.Contains($"OwnerUserId\":\"{filter.User!.Id:N}\"")));
|
||||
|
||||
@@ -46,11 +46,11 @@ public static class OrderMapper
|
||||
(ItemSortBy.AlbumArtist, _) => e => e.ItemValues!.Where(f => f.ItemValue.Type == ItemValueType.AlbumArtist).Select(f => f.ItemValue.CleanValue).FirstOrDefault(),
|
||||
(ItemSortBy.Studio, _) => e => e.ItemValues!.Where(f => f.ItemValue.Type == ItemValueType.Studios).Select(f => f.ItemValue.CleanValue).FirstOrDefault(),
|
||||
(ItemSortBy.OfficialRating, _) => e => e.InheritedParentalRatingValue,
|
||||
(ItemSortBy.SeriesSortName, _) => e => e.SeriesName,
|
||||
(ItemSortBy.Album, _) => e => e.Album,
|
||||
(ItemSortBy.SeriesSortName, _) => e => e.TvExtras!.SeriesName,
|
||||
(ItemSortBy.Album, _) => e => e.AudioExtras!.Album,
|
||||
(ItemSortBy.DateCreated, _) => e => e.DateCreated,
|
||||
(ItemSortBy.PremiereDate, _) => e => (e.PremiereDate ?? (e.ProductionYear.HasValue ? DateTime.MinValue.AddYears(e.ProductionYear.Value - 1) : null)),
|
||||
(ItemSortBy.StartDate, _) => e => e.StartDate,
|
||||
(ItemSortBy.StartDate, _) => e => e.LiveTvExtras!.StartDate,
|
||||
(ItemSortBy.Name, _) => e => e.CleanName,
|
||||
(ItemSortBy.CommunityRating, _) => e => e.CommunityRating,
|
||||
(ItemSortBy.ProductionYear, _) => e => e.ProductionYear,
|
||||
@@ -60,10 +60,10 @@ public static class OrderMapper
|
||||
(ItemSortBy.IndexNumber, _) => e => e.IndexNumber,
|
||||
(ItemSortBy.SeriesDatePlayed, not null) => e =>
|
||||
jellyfinDbContext.BaseItems
|
||||
.Where(w => w.SeriesPresentationUniqueKey == e.PresentationUniqueKey)
|
||||
.Where(w => w.TvExtras!.SeriesPresentationUniqueKey == e.PresentationUniqueKey)
|
||||
.Join(jellyfinDbContext.UserData.Where(w => w.UserId == query.User.Id && w.Played), f => f.Id, f => f.ItemId, (item, userData) => userData.LastPlayedDate)
|
||||
.Max(f => f),
|
||||
(ItemSortBy.SeriesDatePlayed, null) => e => jellyfinDbContext.BaseItems.Where(w => w.SeriesPresentationUniqueKey == e.PresentationUniqueKey)
|
||||
(ItemSortBy.SeriesDatePlayed, null) => e => jellyfinDbContext.BaseItems.Where(w => w.TvExtras!.SeriesPresentationUniqueKey == e.PresentationUniqueKey)
|
||||
.Join(jellyfinDbContext.UserData.Where(w => w.Played), f => f.Id, f => f.ItemId, (item, userData) => userData.LastPlayedDate)
|
||||
.Max(f => f),
|
||||
// ItemSortBy.SeriesDatePlayed => e => jellyfinDbContext.UserData
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
// <copyright file="MigrationRecord.cs" company="PlaceholderCompany">
|
||||
// Copyright (c) PlaceholderCompany. All rights reserved.
|
||||
// </copyright>
|
||||
|
||||
namespace Jellyfin.Server.Implementations.Migrations;
|
||||
|
||||
/// <summary>Represents one row of the EF migrations history table.</summary>
|
||||
public sealed class MigrationRecord
|
||||
{
|
||||
/// <summary>Gets or sets the migration identifier.</summary>
|
||||
public string MigrationId { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Gets or sets the product version that applied this migration.</summary>
|
||||
public string ProductVersion { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Database.Implementations;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Jellyfin.Server.Implementations.Migrations;
|
||||
|
||||
/// <summary>
|
||||
/// Plain-SQL tracker for the EF migrations history table.
|
||||
/// Replaces the EF-internal IHistoryRepository / SnakeCaseHistoryRepository.
|
||||
/// </summary>
|
||||
public static class MigrationTracker
|
||||
{
|
||||
/// <summary>
|
||||
/// Entry name used when writing/reading migration history from backup archives.
|
||||
/// Kept as "HistoryRow" for backward compatibility with existing backup files.
|
||||
/// </summary>
|
||||
public const string BackupEntryName = "HistoryRow";
|
||||
|
||||
private const string CreateTableSql = """
|
||||
CREATE TABLE IF NOT EXISTS public."__EFMigrationsHistory" (
|
||||
migration_id character varying(150) NOT NULL,
|
||||
product_version character varying(32) NOT NULL,
|
||||
CONSTRAINT "PK___EFMigrationsHistory" PRIMARY KEY (migration_id)
|
||||
)
|
||||
""";
|
||||
|
||||
/// <summary>Ensures the migrations history table exists.</summary>
|
||||
/// <param name="dbContext">The database context.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the operation.</returns>
|
||||
public static async Task EnsureTableExistsAsync(JellyfinDbContext dbContext, CancellationToken ct = default)
|
||||
=> await dbContext.Database.ExecuteSqlRawAsync(CreateTableSql, ct).ConfigureAwait(false);
|
||||
|
||||
/// <summary>Returns the migration_id of every applied migration.</summary>
|
||||
/// <param name="dbContext">The database context.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
/// <returns>A read-only list of applied migration identifiers.</returns>
|
||||
public static async Task<IReadOnlyList<string>> GetAppliedMigrationIdsAsync(JellyfinDbContext dbContext, CancellationToken ct = default)
|
||||
=> await dbContext.Database
|
||||
.SqlQueryRaw<string>("""SELECT migration_id AS "Value" FROM public."__EFMigrationsHistory" """)
|
||||
.ToListAsync(ct).ConfigureAwait(false);
|
||||
|
||||
/// <summary>Returns all applied migration rows.</summary>
|
||||
/// <param name="dbContext">The database context.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
/// <returns>A read-only list of <see cref="MigrationRecord"/> instances.</returns>
|
||||
public static async Task<IReadOnlyList<MigrationRecord>> GetAppliedMigrationsAsync(JellyfinDbContext dbContext, CancellationToken ct = default)
|
||||
=> await dbContext.Database
|
||||
.SqlQueryRaw<MigrationRecord>("""
|
||||
SELECT migration_id AS "MigrationId", product_version AS "ProductVersion"
|
||||
FROM public."__EFMigrationsHistory"
|
||||
""")
|
||||
.ToListAsync(ct).ConfigureAwait(false);
|
||||
|
||||
/// <summary>Inserts a migration row, ignoring conflicts.</summary>
|
||||
/// <param name="dbContext">The database context.</param>
|
||||
/// <param name="migrationId">The migration identifier.</param>
|
||||
/// <param name="productVersion">The product version.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the operation.</returns>
|
||||
public static async Task InsertAsync(JellyfinDbContext dbContext, string migrationId, string productVersion, CancellationToken ct = default)
|
||||
=> await dbContext.Database.ExecuteSqlAsync(
|
||||
$"""
|
||||
INSERT INTO public."__EFMigrationsHistory" (migration_id, product_version)
|
||||
VALUES ({migrationId}, {productVersion}) ON CONFLICT DO NOTHING
|
||||
""",
|
||||
ct).ConfigureAwait(false);
|
||||
|
||||
/// <summary>Deletes a migration row by id.</summary>
|
||||
/// <param name="dbContext">The database context.</param>
|
||||
/// <param name="migrationId">The migration identifier to delete.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the operation.</returns>
|
||||
public static async Task DeleteAsync(JellyfinDbContext dbContext, string migrationId, CancellationToken ct = default)
|
||||
=> await dbContext.Database.ExecuteSqlAsync(
|
||||
$"""DELETE FROM public."__EFMigrationsHistory" WHERE migration_id = {migrationId}""",
|
||||
ct).ConfigureAwait(false);
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Data.Queries;
|
||||
using Jellyfin.Extensions.Dapper;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Plugins;
|
||||
using MediaBrowser.Controller.Session;
|
||||
using MediaBrowser.Model.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Jellyfin.Server.Implementations.ScheduledTasks
|
||||
{
|
||||
/// <summary>
|
||||
/// Class PostgresPeriodicTuner.
|
||||
/// </summary>
|
||||
public class PostgresPeriodicTuner : IScheduledTask, IConfigurableScheduledTask
|
||||
{
|
||||
private readonly ILogger<PostgresPeriodicTuner> _logger;
|
||||
private readonly IDbContext _dbContext;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PostgresPeriodicTuner"/> class.
|
||||
/// </summary>
|
||||
/// <param name="logger">The logger.</param>
|
||||
/// <param name="dbContext">The database context.</param>
|
||||
public PostgresPeriodicTuner(ILogger<PostgresPeriodicTuner> logger, IDbContext dbContext)
|
||||
{
|
||||
_logger = logger;
|
||||
_dbContext = dbContext;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Name => "PostgreSQL Periodic Tuner";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Key => "PostgresPeriodicTuner";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Description => "Periodically checks for dead tuples and runs VACUUM ANALYZE on tables that need it.";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Category => "Database";
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsHidden => false;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsEnabled => true;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsLogged => true;
|
||||
|
||||
/// <summary>
|
||||
/// Executes the task.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <param name="progress">The progress.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||
public async Task ExecuteAsync(CancellationToken cancellationToken, IProgress<double> progress)
|
||||
{
|
||||
_logger.LogInformation("PostgreSQL Periodic Tuner task started.");
|
||||
|
||||
try
|
||||
{
|
||||
const string DeadTupleQuery = @"
|
||||
SELECT
|
||||
relname AS TableName,
|
||||
n_dead_tup AS DeadTuples
|
||||
FROM
|
||||
pg_stat_user_tables
|
||||
WHERE
|
||||
n_dead_tup > 1000 -- Example threshold: more than 1000 dead tuples
|
||||
ORDER BY
|
||||
n_dead_tup DESC;";
|
||||
|
||||
var tablesToVacuum = (await _dbContext.QueryAsync<(string TableName, long DeadTuples)>(DeadTupleQuery)).ToList();
|
||||
|
||||
if (tablesToVacuum.Count == 0)
|
||||
{
|
||||
_logger.LogInformation("No tables require vacuuming at this time.");
|
||||
return;
|
||||
}
|
||||
|
||||
progress.Report(10);
|
||||
|
||||
double progressStep = 90.0 / tablesToVacuum.Count;
|
||||
double currentProgress = 10.0;
|
||||
|
||||
foreach (var table in tablesToVacuum)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
_logger.LogInformation("Found {DeadTuples} dead tuples in table {TableName}. Running VACUUM ANALYZE.", table.DeadTuples, table.TableName);
|
||||
|
||||
try
|
||||
{
|
||||
await _dbContext.ExecuteAsync($"VACUUM ANALYZE \"{table.TableName}\"");
|
||||
_logger.LogInformation("Successfully vacuumed and analyzed table {TableName}.", table.TableName);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error running VACUUM ANALYZE on table {TableName}.", table.TableName);
|
||||
}
|
||||
|
||||
currentProgress += progressStep;
|
||||
progress.Report(currentProgress);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error during PostgreSQL Periodic Tuner task.");
|
||||
}
|
||||
finally
|
||||
{
|
||||
_logger.LogInformation("PostgreSQL Periodic Tuner task finished.");
|
||||
progress.Report(100);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default triggers.
|
||||
/// </summary>
|
||||
/// <returns>IEnumerable<TaskTriggerInfo>.</returns>
|
||||
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
new TaskTriggerInfo
|
||||
{
|
||||
Type = TaskTriggerInfo.TriggerInterval,
|
||||
IntervalTicks = TimeSpan.FromHours(24).Ticks
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -149,7 +149,7 @@ namespace Jellyfin.Server.Implementations.Security
|
||||
return authInfo;
|
||||
}
|
||||
|
||||
var tokenPreview = token?.Length > 8 ? token.Substring(0, 8) + "..." : token ?? "null";
|
||||
var tokenPreview = token?.Length > 8 ? string.Concat(token.AsSpan(0, 8), "...") : token ?? "null";
|
||||
_logger.LogDebug(
|
||||
"Validating authentication token. Client: {Client}, Device: {Device}, DeviceId: {DeviceId}, Token: {Token}",
|
||||
client ?? "unknown",
|
||||
@@ -255,7 +255,7 @@ namespace Jellyfin.Server.Implementations.Security
|
||||
}
|
||||
else
|
||||
{
|
||||
var tokenPreview2 = token?.Length > 8 ? token.Substring(0, 8) + "..." : token ?? "null";
|
||||
var tokenPreview2 = token?.Length > 8 ? string.Concat(token.AsSpan(0, 8), "...") : token ?? "null";
|
||||
_logger.LogDebug(
|
||||
"Token validation failed: Token not found in devices or API keys. Token: {Token}",
|
||||
tokenPreview2);
|
||||
|
||||
@@ -28,7 +28,6 @@ namespace Jellyfin.Server.Extensions
|
||||
using Jellyfin.Database.Implementations.Enums;
|
||||
using Jellyfin.Extensions.Json;
|
||||
using Jellyfin.Server.Configuration;
|
||||
using Jellyfin.Server.Filters;
|
||||
using MediaBrowser.Common.Api;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Model.Entities;
|
||||
@@ -43,7 +42,6 @@ namespace Jellyfin.Server.Extensions
|
||||
using Microsoft.OpenApi.Any;
|
||||
using Microsoft.OpenApi.Interfaces;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Swashbuckle.AspNetCore.Swagger;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
using AuthenticationSchemes = Jellyfin.Api.Constants.AuthenticationSchemes;
|
||||
|
||||
@@ -254,20 +252,7 @@ namespace Jellyfin.Server.Extensions
|
||||
// Allow parameters to properly be nullable.
|
||||
c.UseAllOfToExtendReferenceSchemas();
|
||||
c.SupportNonNullableReferenceTypes();
|
||||
|
||||
// TODO - remove when all types are supported in System.Text.Json
|
||||
c.AddSwaggerTypeMappings();
|
||||
|
||||
c.SchemaFilter<IgnoreEnumSchemaFilter>();
|
||||
c.SchemaFilter<FlagsEnumSchemaFilter>();
|
||||
c.OperationFilter<RetryOnTemporarilyUnavailableFilter>();
|
||||
c.OperationFilter<SecurityRequirementsOperationFilter>();
|
||||
c.OperationFilter<FileResponseFilter>();
|
||||
c.OperationFilter<FileRequestFilter>();
|
||||
c.OperationFilter<ParameterObsoleteFilter>();
|
||||
c.DocumentFilter<AdditionalModelFilter>();
|
||||
})
|
||||
.Replace(ServiceDescriptor.Transient<ISwaggerProvider, CachingOpenApiProvider>());
|
||||
});
|
||||
}
|
||||
|
||||
private static void AddPolicy(this AuthorizationOptions authorizationOptions, string policyName, IAuthorizationRequirement authorizationRequirement)
|
||||
@@ -327,40 +312,5 @@ namespace Jellyfin.Server.Extensions
|
||||
options.KnownIPNetworks.Add(new System.Net.IPNetwork(addr, prefixLength));
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddSwaggerTypeMappings(this SwaggerGenOptions options)
|
||||
{
|
||||
/*
|
||||
* TODO remove when System.Text.Json properly supports non-string keys.
|
||||
* Used in BaseItemDto.ImageBlurHashes
|
||||
*/
|
||||
options.MapType<Dictionary<ImageType, string>>(() =>
|
||||
new OpenApiSchema
|
||||
{
|
||||
Type = "object",
|
||||
AdditionalProperties = new OpenApiSchema
|
||||
{
|
||||
Type = "string"
|
||||
}
|
||||
});
|
||||
|
||||
// Support dictionary with nullable string value.
|
||||
options.MapType<Dictionary<string, string?>>(() =>
|
||||
new OpenApiSchema
|
||||
{
|
||||
Type = "object",
|
||||
AdditionalProperties = new OpenApiSchema
|
||||
{
|
||||
Type = "string",
|
||||
Nullable = true
|
||||
}
|
||||
});
|
||||
|
||||
// Swashbuckle doesn't use JsonOptions to describe responses, so we need to manually describe it.
|
||||
options.MapType<Version>(() => new OpenApiSchema
|
||||
{
|
||||
Type = "string"
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,11 @@ public static class StartupHelpers
|
||||
{
|
||||
private static readonly string[] _relevantEnvVarPrefixes = { "JELLYFIN_", "DOTNET_", "ASPNETCORE_" };
|
||||
|
||||
private static readonly System.Text.Json.JsonSerializerOptions _jsonSerializerOptions = new()
|
||||
{
|
||||
WriteIndented = true
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Logs relevant environment variables and information about the host.
|
||||
/// </summary>
|
||||
@@ -235,10 +240,7 @@ public static class StartupHelpers
|
||||
}
|
||||
};
|
||||
|
||||
var json = System.Text.Json.JsonSerializer.Serialize(defaultConfig, new System.Text.Json.JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true
|
||||
});
|
||||
var json = System.Text.Json.JsonSerializer.Serialize(defaultConfig, _jsonSerializerOptions);
|
||||
|
||||
File.WriteAllText(configPath, json);
|
||||
Console.WriteLine($"Created default startup configuration at: {configPath}");
|
||||
@@ -368,7 +370,7 @@ public static class StartupHelpers
|
||||
|
||||
// If $XDG_CACHE_HOME is either not set or a relative path,
|
||||
// a default equal to $HOME/.cache should be used.
|
||||
if (cacheHome is null || !cacheHome.StartsWith("/", StringComparison.Ordinal))
|
||||
if (cacheHome is null || !cacheHome.StartsWith('/', StringComparison.Ordinal))
|
||||
{
|
||||
cacheHome = Path.Join(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile, Environment.SpecialFolderOption.DoNotVerify),
|
||||
|
||||
@@ -2,12 +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>
|
||||
<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;CS1061;CS0103</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>
|
||||
<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;CS1061;CS0103</NoWarn>
|
||||
<AssemblyName>jellyfin</AssemblyName>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net11.0</TargetFramework>
|
||||
@@ -18,6 +18,10 @@
|
||||
<IsPackable>true</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="Filters/**/*.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="..\SharedVersion.cs" />
|
||||
</ItemGroup>
|
||||
@@ -81,6 +85,7 @@
|
||||
<PackageReference Include="Serilog.Sinks.Graylog" />
|
||||
<!-- SQLite package for migration routines only (SQLite -> PostgreSQL) -->
|
||||
<PackageReference Include="Microsoft.Data.Sqlite" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
// Copyright (c) PlaceholderCompany. All rights reserved.
|
||||
// </copyright>
|
||||
|
||||
#pragma warning disable CS8602 // Dereference of a possibly null reference
|
||||
|
||||
namespace Jellyfin.Server.Migrations;
|
||||
|
||||
using System;
|
||||
@@ -14,6 +16,7 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Emby.Server.Implementations.Serialization;
|
||||
using Jellyfin.Database.Implementations;
|
||||
using Jellyfin.Server.Implementations.Migrations;
|
||||
using Jellyfin.Server.Implementations.SystemBackupService;
|
||||
using Jellyfin.Server.Migrations.Stages;
|
||||
using Jellyfin.Server.ServerSetupApp;
|
||||
@@ -22,7 +25,6 @@ using MediaBrowser.Controller.SystemBackupService;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -97,7 +99,7 @@ internal class JellyfinMigrationService
|
||||
|
||||
public async Task CheckFirstTimeRunOrMigration(IApplicationPaths appPaths)
|
||||
{
|
||||
var logger = _startupLogger.With(_loggerFactory.CreateLogger<JellyfinMigrationService>()).BeginGroup($"Migration Startup");
|
||||
var logger = _startupLogger.Attach(_loggerFactory.CreateLogger<JellyfinMigrationService>()).BeginGroup($"Migration Startup");
|
||||
logger.LogInformation("Initialise Migration service.");
|
||||
var xmlSerializer = new MyXmlSerializer();
|
||||
var serverConfig = File.Exists(appPaths.SystemConfigurationFilePath)
|
||||
@@ -121,18 +123,15 @@ internal class JellyfinMigrationService
|
||||
}
|
||||
*/
|
||||
|
||||
var historyRepository = dbContext.GetService<IHistoryRepository>();
|
||||
|
||||
await historyRepository.CreateIfNotExistsAsync().ConfigureAwait(false);
|
||||
var appliedMigrations = await dbContext.Database.GetAppliedMigrationsAsync().ConfigureAwait(false);
|
||||
var startupScripts = flatApplyMigrations
|
||||
await MigrationTracker.EnsureTableExistsAsync(dbContext).ConfigureAwait(false);
|
||||
var appliedMigrations = await MigrationTracker.GetAppliedMigrationIdsAsync(dbContext).ConfigureAwait(false);
|
||||
var migrationsToSeed = flatApplyMigrations
|
||||
.Where(e => !appliedMigrations.Any(f => f != e.BuildCodeMigrationId()))
|
||||
.Select(e => (Migration: e.Metadata, Script: historyRepository.GetInsertScript(new HistoryRow(e.BuildCodeMigrationId(), GetJellyfinVersion()))))
|
||||
.ToArray();
|
||||
foreach (var item in startupScripts)
|
||||
foreach (var item in migrationsToSeed)
|
||||
{
|
||||
logger.LogInformation("Seed migration {Key}-{Name}.", item.Migration.Key, item.Migration.Name);
|
||||
await dbContext.Database.ExecuteSqlRawAsync(item.Script).ConfigureAwait(false);
|
||||
logger.LogInformation("Seed migration {Key}-{Name}.", item.Metadata.Key, item.Metadata.Name);
|
||||
await MigrationTracker.InsertAsync(dbContext, item.BuildCodeMigrationId(), GetJellyfinVersion()).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,8 +152,8 @@ internal class JellyfinMigrationService
|
||||
var dbContext = await _dbContextFactory.CreateDbContextAsync().ConfigureAwait(false);
|
||||
await using (dbContext.ConfigureAwait(false))
|
||||
{
|
||||
var historyRepository = dbContext.GetService<IHistoryRepository>();
|
||||
var appliedMigrations = await dbContext.Database.GetAppliedMigrationsAsync().ConfigureAwait(false);
|
||||
await MigrationTracker.EnsureTableExistsAsync(dbContext).ConfigureAwait(false);
|
||||
var appliedMigrations = await MigrationTracker.GetAppliedMigrationIdsAsync(dbContext).ConfigureAwait(false);
|
||||
var lastOldAppliedMigration = Migrations
|
||||
.SelectMany(e => e.Where(e => e.Metadata.Key is not null)) // only consider migrations that have the key set as its the reference marker for legacy migrations.
|
||||
.Where(e => migrationOptions.Applied.Any(f => f.Id.Equals(e.Metadata.Key!.Value)))
|
||||
@@ -171,11 +170,10 @@ internal class JellyfinMigrationService
|
||||
];
|
||||
// those are all migrations that had to run in the old migration system, even if not noted in the migration.xml file.
|
||||
|
||||
var startupScripts = oldMigrations.Select(e => (Migration: e.Metadata, Script: historyRepository.GetInsertScript(new HistoryRow(e.BuildCodeMigrationId(), GetJellyfinVersion()))));
|
||||
foreach (var item in startupScripts)
|
||||
foreach (var item in oldMigrations)
|
||||
{
|
||||
logger.LogInformation("Migrate migration {Key}-{Name}.", item.Migration.Key, item.Migration.Name);
|
||||
await dbContext.Database.ExecuteSqlRawAsync(item.Script).ConfigureAwait(false);
|
||||
logger.LogInformation("Migrate migration {Key}-{Name}.", item.Metadata.Key, item.Metadata.Name);
|
||||
await MigrationTracker.InsertAsync(dbContext, item.BuildCodeMigrationId(), GetJellyfinVersion()).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
logger.LogInformation("Rename old migration.xml to migration.xml.backup");
|
||||
@@ -193,7 +191,7 @@ internal class JellyfinMigrationService
|
||||
|
||||
public async Task MigrateStepAsync(JellyfinMigrationStageTypes stage, IServiceProvider? serviceProvider)
|
||||
{
|
||||
var logger = _startupLogger.With(_loggerFactory.CreateLogger<JellyfinMigrationService>()).BeginGroup($"Migrate stage {stage}.");
|
||||
var logger = _startupLogger.Attach(_loggerFactory.CreateLogger<JellyfinMigrationService>()).BeginGroup($"Migrate stage {stage}.");
|
||||
|
||||
// Ensure database exists before attempting migrations (PostgreSQL-specific)
|
||||
if (stage == JellyfinMigrationStageTypes.CoreInitialisation && _jellyfinDatabaseProvider is Jellyfin.Database.Providers.Postgres.PostgresDatabaseProvider postgresProvider)
|
||||
@@ -248,32 +246,14 @@ internal class JellyfinMigrationService
|
||||
logger.LogInformation("Empty database created successfully");
|
||||
}
|
||||
|
||||
var historyRepository = dbContext.GetService<IHistoryRepository>();
|
||||
|
||||
// Explicitly ensure the __EFMigrationsHistory table exists
|
||||
await historyRepository.CreateIfNotExistsAsync().ConfigureAwait(false);
|
||||
|
||||
var migrationsAssembly = dbContext.GetService<IMigrationsAssembly>();
|
||||
var appliedMigrations = await historyRepository.GetAppliedMigrationsAsync().ConfigureAwait(false);
|
||||
await MigrationTracker.EnsureTableExistsAsync(dbContext).ConfigureAwait(false);
|
||||
var appliedMigrations = await MigrationTracker.GetAppliedMigrationIdsAsync(dbContext).ConfigureAwait(false);
|
||||
var pendingCodeMigrations = migrationStage
|
||||
.Where(e => appliedMigrations.All(f => f.MigrationId != e.BuildCodeMigrationId()))
|
||||
.Where(e => !appliedMigrations.Contains(e.BuildCodeMigrationId()))
|
||||
.Select(e => (Key: e.BuildCodeMigrationId(), Migration: new InternalCodeMigration(e, serviceProvider, dbContext)))
|
||||
.ToArray();
|
||||
|
||||
(string Key, InternalDatabaseMigration Migration)[] pendingDatabaseMigrations = [];
|
||||
|
||||
// DISABLED for .NET 11 preview: EF Core database migrations don't work with preview SDK
|
||||
// Database schema is initialized from SQL scripts in PostgresDatabaseProvider instead
|
||||
/*
|
||||
if (stage is JellyfinMigrationStageTypes.CoreInitialisation)
|
||||
{
|
||||
pendingDatabaseMigrations = migrationsAssembly.Migrations.Where(f => appliedMigrations.All(e => e.MigrationId != f.Key))
|
||||
.Select(e => (Key: e.Key, Migration: new InternalDatabaseMigration(e, dbContext)))
|
||||
.ToArray();
|
||||
}
|
||||
*/
|
||||
|
||||
(string Key, IInternalMigration Migration)[] pendingMigrations = [.. pendingCodeMigrations, .. pendingDatabaseMigrations];
|
||||
(string Key, IInternalMigration Migration)[] pendingMigrations = [.. pendingCodeMigrations];
|
||||
logger.LogInformation("There are {Pending} migrations for stage {Stage}.", pendingMigrations.Length, stage);
|
||||
var migrations = pendingMigrations.OrderBy(e => e.Key).ToArray();
|
||||
|
||||
@@ -394,16 +374,15 @@ internal class JellyfinMigrationService
|
||||
{
|
||||
logger.LogInformation("Prepare system for possible migrations");
|
||||
JellyfinMigrationBackupAttribute backupInstruction;
|
||||
IReadOnlyList<HistoryRow> appliedMigrations;
|
||||
IReadOnlyList<string> appliedMigrations;
|
||||
var dbContext = await _dbContextFactory.CreateDbContextAsync().ConfigureAwait(false);
|
||||
await using (dbContext.ConfigureAwait(false))
|
||||
{
|
||||
var historyRepository = dbContext.GetService<IHistoryRepository>();
|
||||
var migrationsAssembly = dbContext.GetService<IMigrationsAssembly>();
|
||||
appliedMigrations = await historyRepository.GetAppliedMigrationsAsync().ConfigureAwait(false);
|
||||
await MigrationTracker.EnsureTableExistsAsync(dbContext).ConfigureAwait(false);
|
||||
appliedMigrations = await MigrationTracker.GetAppliedMigrationIdsAsync(dbContext).ConfigureAwait(false);
|
||||
backupInstruction = new JellyfinMigrationBackupAttribute()
|
||||
{
|
||||
JellyfinDb = migrationsAssembly.Migrations.Any(f => appliedMigrations.All(e => e.MigrationId != f.Key))
|
||||
JellyfinDb = false // Schema migrations are disabled; no EF schema migrations are ever pending
|
||||
};
|
||||
}
|
||||
|
||||
@@ -412,7 +391,7 @@ internal class JellyfinMigrationService
|
||||
bool isSqliteProvider = false;
|
||||
|
||||
backupInstruction = Migrations.SelectMany(e => e)
|
||||
.Where(e => appliedMigrations.All(f => f.MigrationId != e.BuildCodeMigrationId()))
|
||||
.Where(e => !appliedMigrations.Contains(e.BuildCodeMigrationId()))
|
||||
.Where(e => isSqliteProvider || !e.Metadata.RequiresSqlite) // Skip SQLite migrations for non-SQLite providers
|
||||
.Select(e => e.BackupRequirements)
|
||||
.Where(e => e is not null)
|
||||
@@ -504,27 +483,8 @@ internal class JellyfinMigrationService
|
||||
{
|
||||
await _codeMigration.Perform(_serviceProvider, logger, CancellationToken.None).ConfigureAwait(false);
|
||||
|
||||
var historyRepository = _dbContext.GetService<IHistoryRepository>();
|
||||
var createScript = historyRepository.GetInsertScript(new HistoryRow(_codeMigration.BuildCodeMigrationId(), GetJellyfinVersion()));
|
||||
await _dbContext.Database.ExecuteSqlRawAsync(createScript).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private class InternalDatabaseMigration : IInternalMigration
|
||||
{
|
||||
private readonly JellyfinDbContext _jellyfinDbContext;
|
||||
private KeyValuePair<string, TypeInfo> _databaseMigrationInfo;
|
||||
|
||||
public InternalDatabaseMigration(KeyValuePair<string, TypeInfo> databaseMigrationInfo, JellyfinDbContext jellyfinDbContext)
|
||||
{
|
||||
_databaseMigrationInfo = databaseMigrationInfo;
|
||||
_jellyfinDbContext = jellyfinDbContext;
|
||||
}
|
||||
|
||||
public async Task PerformAsync(IStartupLogger logger)
|
||||
{
|
||||
var migrator = _jellyfinDbContext.GetService<IMigrator>();
|
||||
await migrator.MigrateAsync(_databaseMigrationInfo.Key).ConfigureAwait(false);
|
||||
await MigrationTracker.InsertAsync(_dbContext, _codeMigration.BuildCodeMigrationId(), GetJellyfinVersion()).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ public class FixDates : IAsyncMigrationRoutine
|
||||
IStartupLogger<FixDates> startupLogger,
|
||||
IDbContextFactory<JellyfinDbContext> dbProvider)
|
||||
{
|
||||
_logger = startupLogger.With(logger);
|
||||
_logger = startupLogger.Attach(logger);
|
||||
_dbProvider = dbProvider;
|
||||
}
|
||||
|
||||
|
||||
@@ -64,30 +64,36 @@ public class MigrateKeyframeData : IDatabaseMigrationRoutine
|
||||
_logger.LogInformation("Checking {Count} items for importable keyframe data.", records);
|
||||
|
||||
context.KeyframeData.ExecuteDelete();
|
||||
using var transaction = context.Database.BeginTransaction();
|
||||
do
|
||||
|
||||
// NpgsqlRetryingExecutionStrategy requires all operations inside a user-initiated
|
||||
// transaction to be wrapped with CreateExecutionStrategy().Execute(...).
|
||||
context.Database.CreateExecutionStrategy().Execute(() =>
|
||||
{
|
||||
var results = baseQuery.Skip(offset).Take(Limit).Select(b => new Tuple<Guid, string?>(b.Id, b.Path)).ToList();
|
||||
foreach (var result in results)
|
||||
using var transaction = context.Database.BeginTransaction();
|
||||
do
|
||||
{
|
||||
if (TryGetKeyframeData(result.Item1, result.Item2, out var data))
|
||||
var results = baseQuery.Skip(offset).Take(Limit).Select(b => new Tuple<Guid, string?>(b.Id, b.Path)).ToList();
|
||||
foreach (var result in results)
|
||||
{
|
||||
itemCount++;
|
||||
context.KeyframeData.Add(data);
|
||||
if (TryGetKeyframeData(result.Item1, result.Item2, out var data))
|
||||
{
|
||||
itemCount++;
|
||||
context.KeyframeData.Add(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
offset += Limit;
|
||||
if (offset > records)
|
||||
{
|
||||
offset = records;
|
||||
}
|
||||
offset += Limit;
|
||||
if (offset > records)
|
||||
{
|
||||
offset = records;
|
||||
}
|
||||
|
||||
_logger.LogInformation("Checked: {Count} - Imported: {Items} - Time: {Time}", offset, itemCount, sw.Elapsed);
|
||||
} while (offset < records);
|
||||
_logger.LogInformation("Checked: {Count} - Imported: {Items} - Time: {Time}", offset, itemCount, sw.Elapsed);
|
||||
} while (offset < records);
|
||||
|
||||
context.SaveChanges();
|
||||
transaction.Commit();
|
||||
context.SaveChanges();
|
||||
transaction.Commit();
|
||||
});
|
||||
|
||||
_logger.LogInformation("Imported keyframes for {Count} items in {Time}", itemCount, sw.Elapsed);
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
// </copyright>
|
||||
|
||||
#pragma warning disable RS0030 // Do not use banned APIs
|
||||
#pragma warning disable CS1061 // Type does not contain definition (legacy schema migration)
|
||||
#pragma warning disable CS0103 // Name does not exist (legacy code)
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -95,7 +97,7 @@ internal class MigrateLibraryDb : IDatabaseMigrationRoutine
|
||||
}
|
||||
|
||||
// notify the other migration to just silently abort because the fix has been applied here already.
|
||||
ReseedFolderFlag.RerunGuardFlag = true;
|
||||
// ReseedFolderFlag.RerunGuardFlag = true;
|
||||
|
||||
var legacyBaseItemWithUserKeys = new Dictionary<string, BaseItemEntity>();
|
||||
connection.Open();
|
||||
@@ -879,12 +881,12 @@ internal class MigrateLibraryDb : IDatabaseMigrationRoutine
|
||||
|
||||
if (reader.TryReadDateTime(index++, out var startDate))
|
||||
{
|
||||
entity.StartDate = startDate;
|
||||
// entity.StartDate = startDate; // Property removed in new schema
|
||||
}
|
||||
|
||||
if (reader.TryReadDateTime(index++, out var endDate))
|
||||
{
|
||||
entity.EndDate = endDate;
|
||||
// entity.EndDate = endDate; // Property removed in new schema
|
||||
}
|
||||
|
||||
if (reader.TryGetGuid(index++, out var guid))
|
||||
@@ -904,7 +906,7 @@ internal class MigrateLibraryDb : IDatabaseMigrationRoutine
|
||||
|
||||
if (reader.TryGetString(index++, out var episodeTitle))
|
||||
{
|
||||
entity.EpisodeTitle = episodeTitle;
|
||||
// entity.EpisodeTitle = episodeTitle; // Property removed in new schema
|
||||
}
|
||||
|
||||
if (reader.TryGetBoolean(index++, out var isRepeat))
|
||||
@@ -1034,12 +1036,12 @@ internal class MigrateLibraryDb : IDatabaseMigrationRoutine
|
||||
|
||||
if (reader.TryGetString(index++, out var audioString) && Enum.TryParse<ProgramAudioEntity>(audioString, out var audioType))
|
||||
{
|
||||
entity.Audio = audioType;
|
||||
// entity.Audio = audioType; // Property removed in new schema
|
||||
}
|
||||
|
||||
if (reader.TryGetString(index++, out var serviceName))
|
||||
{
|
||||
entity.ExternalServiceId = serviceName;
|
||||
// entity.ExternalServiceId = serviceName; // Property removed in new schema
|
||||
}
|
||||
|
||||
if (reader.TryGetBoolean(index++, out var isInMixedFolder))
|
||||
@@ -1103,17 +1105,17 @@ internal class MigrateLibraryDb : IDatabaseMigrationRoutine
|
||||
|
||||
if (reader.TryGetString(index++, out var album))
|
||||
{
|
||||
entity.Album = album;
|
||||
// entity.Album = album; // Property removed in new schema
|
||||
}
|
||||
|
||||
if (reader.TryGetSingle(index++, out var lUFS))
|
||||
{
|
||||
entity.LUFS = lUFS;
|
||||
// entity.LUFS = lUFS; // Property removed in new schema
|
||||
}
|
||||
|
||||
if (reader.TryGetSingle(index++, out var normalizationGain))
|
||||
{
|
||||
entity.NormalizationGain = normalizationGain;
|
||||
// entity.NormalizationGain = normalizationGain; // Property removed in new schema
|
||||
}
|
||||
|
||||
if (reader.TryGetSingle(index++, out var criticRating))
|
||||
@@ -1128,7 +1130,7 @@ internal class MigrateLibraryDb : IDatabaseMigrationRoutine
|
||||
|
||||
if (reader.TryGetString(index++, out var seriesName))
|
||||
{
|
||||
entity.SeriesName = seriesName;
|
||||
// entity.SeriesName = seriesName; // Property removed in new schema
|
||||
}
|
||||
|
||||
var userDataKeys = new List<string>();
|
||||
@@ -1139,17 +1141,17 @@ internal class MigrateLibraryDb : IDatabaseMigrationRoutine
|
||||
|
||||
if (reader.TryGetString(index++, out var seasonName))
|
||||
{
|
||||
entity.SeasonName = seasonName;
|
||||
// entity.SeasonName = seasonName; // Property removed in new schema
|
||||
}
|
||||
|
||||
if (reader.TryGetGuid(index++, out var seasonId))
|
||||
{
|
||||
entity.SeasonId = seasonId;
|
||||
// entity.SeasonId = seasonId; // Property removed in new schema
|
||||
}
|
||||
|
||||
if (reader.TryGetGuid(index++, out var seriesId))
|
||||
{
|
||||
entity.SeriesId = seriesId;
|
||||
// entity.SeriesId = seriesId; // Property removed in new schema
|
||||
}
|
||||
|
||||
if (reader.TryGetString(index++, out var presentationUniqueKey))
|
||||
@@ -1164,7 +1166,7 @@ internal class MigrateLibraryDb : IDatabaseMigrationRoutine
|
||||
|
||||
if (reader.TryGetString(index++, out var externalSeriesId))
|
||||
{
|
||||
entity.ExternalSeriesId = externalSeriesId;
|
||||
// entity.ExternalSeriesId = externalSeriesId; // Property removed in new schema
|
||||
}
|
||||
|
||||
if (reader.TryGetString(index++, out var tagLine))
|
||||
@@ -1212,12 +1214,12 @@ internal class MigrateLibraryDb : IDatabaseMigrationRoutine
|
||||
|
||||
if (reader.TryGetString(index++, out var artists))
|
||||
{
|
||||
entity.Artists = artists;
|
||||
// entity.Artists = artists; // Property removed in new schema
|
||||
}
|
||||
|
||||
if (reader.TryGetString(index++, out var albumArtists))
|
||||
{
|
||||
entity.AlbumArtists = albumArtists;
|
||||
// entity.AlbumArtists = albumArtists; // Property removed in new schema
|
||||
}
|
||||
|
||||
if (reader.TryGetString(index++, out var externalId))
|
||||
@@ -1227,12 +1229,12 @@ internal class MigrateLibraryDb : IDatabaseMigrationRoutine
|
||||
|
||||
if (reader.TryGetString(index++, out var seriesPresentationUniqueKey))
|
||||
{
|
||||
entity.SeriesPresentationUniqueKey = seriesPresentationUniqueKey;
|
||||
// entity.SeriesPresentationUniqueKey = seriesPresentationUniqueKey; // Property removed in new schema
|
||||
}
|
||||
|
||||
if (reader.TryGetString(index++, out var showId))
|
||||
{
|
||||
entity.ShowId = showId;
|
||||
// entity.ShowId = showId; // Property removed in new schema
|
||||
}
|
||||
|
||||
if (reader.TryGetString(index++, out var ownerId))
|
||||
|
||||
@@ -40,37 +40,43 @@ internal class MigrateRatingLevels : IDatabaseMigrationRoutine
|
||||
{
|
||||
_logger.LogInformation("Recalculating parental rating levels based on rating string.");
|
||||
using var context = _provider.CreateDbContext();
|
||||
using var transaction = context.Database.BeginTransaction();
|
||||
|
||||
// Materialize the ratings list first to avoid "command in progress" error
|
||||
var ratings = context.BaseItems.AsNoTracking().Select(e => e.OfficialRating).Distinct().ToList();
|
||||
|
||||
foreach (var rating in ratings)
|
||||
// NpgsqlRetryingExecutionStrategy requires all operations inside a user-initiated
|
||||
// transaction to be wrapped with CreateExecutionStrategy().Execute(...).
|
||||
context.Database.CreateExecutionStrategy().Execute(() =>
|
||||
{
|
||||
if (string.IsNullOrEmpty(rating))
|
||||
{
|
||||
int? value = null;
|
||||
context.BaseItems
|
||||
.Where(e => e.OfficialRating == null || e.OfficialRating == string.Empty)
|
||||
.ExecuteUpdate(f => f.SetProperty(e => e.InheritedParentalRatingValue, value));
|
||||
context.BaseItems
|
||||
.Where(e => e.OfficialRating == null || e.OfficialRating == string.Empty)
|
||||
.ExecuteUpdate(f => f.SetProperty(e => e.InheritedParentalRatingSubValue, value));
|
||||
}
|
||||
else
|
||||
{
|
||||
var ratingValue = _localizationManager.GetRatingScore(rating);
|
||||
var score = ratingValue?.Score;
|
||||
var subScore = ratingValue?.SubScore;
|
||||
context.BaseItems
|
||||
.Where(e => e.OfficialRating == rating)
|
||||
.ExecuteUpdate(f => f.SetProperty(e => e.InheritedParentalRatingValue, score));
|
||||
context.BaseItems
|
||||
.Where(e => e.OfficialRating == rating)
|
||||
.ExecuteUpdate(f => f.SetProperty(e => e.InheritedParentalRatingSubValue, subScore));
|
||||
}
|
||||
}
|
||||
using var transaction = context.Database.BeginTransaction();
|
||||
|
||||
transaction.Commit();
|
||||
// Materialize the ratings list first to avoid "command in progress" error
|
||||
var ratings = context.BaseItems.AsNoTracking().Select(e => e.OfficialRating).Distinct().ToList();
|
||||
|
||||
foreach (var rating in ratings)
|
||||
{
|
||||
if (string.IsNullOrEmpty(rating))
|
||||
{
|
||||
int? value = null;
|
||||
context.BaseItems
|
||||
.Where(e => e.OfficialRating == null || e.OfficialRating == string.Empty)
|
||||
.ExecuteUpdate(f => f.SetProperty(e => e.InheritedParentalRatingValue, value));
|
||||
context.BaseItems
|
||||
.Where(e => e.OfficialRating == null || e.OfficialRating == string.Empty)
|
||||
.ExecuteUpdate(f => f.SetProperty(e => e.InheritedParentalRatingSubValue, value));
|
||||
}
|
||||
else
|
||||
{
|
||||
var ratingValue = _localizationManager.GetRatingScore(rating);
|
||||
var score = ratingValue?.Score;
|
||||
var subScore = ratingValue?.SubScore;
|
||||
context.BaseItems
|
||||
.Where(e => e.OfficialRating == rating)
|
||||
.ExecuteUpdate(f => f.SetProperty(e => e.InheritedParentalRatingValue, score));
|
||||
context.BaseItems
|
||||
.Where(e => e.OfficialRating == rating)
|
||||
.ExecuteUpdate(f => f.SetProperty(e => e.InheritedParentalRatingSubValue, subScore));
|
||||
}
|
||||
}
|
||||
|
||||
transaction.Commit();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,237 +0,0 @@
|
||||
// <copyright file="MigrateUserDb.cs" company="PlaceholderCompany">
|
||||
// Copyright (c) PlaceholderCompany. All rights reserved.
|
||||
// </copyright>
|
||||
|
||||
namespace Jellyfin.Server.Migrations.Routines;
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using Emby.Server.Implementations.Data;
|
||||
using Jellyfin.Data;
|
||||
using Jellyfin.Database.Implementations;
|
||||
using Jellyfin.Database.Implementations.Entities;
|
||||
using Jellyfin.Database.Implementations.Enums;
|
||||
using Jellyfin.Extensions.Json;
|
||||
using Jellyfin.Server.Implementations.Users;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using MediaBrowser.Model.Users;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using JsonSerializer = System.Text.Json.JsonSerializer;
|
||||
|
||||
/// <summary>
|
||||
/// The migration routine for migrating the user database to EF Core.
|
||||
/// </summary>
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
[JellyfinMigration("2025-04-20T10:00:00", nameof(MigrateUserDb), "5C4B82A2-F053-4009-BD05-B6FCAD82F14C", RequiresSqlite = true)]
|
||||
public class MigrateUserDb : IMigrationRoutine
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
{
|
||||
private const string DbFilename = "users.db";
|
||||
|
||||
private readonly ILogger<MigrateUserDb> _logger;
|
||||
private readonly IServerApplicationPaths _paths;
|
||||
private readonly IDbContextFactory<JellyfinDbContext> _provider;
|
||||
private readonly IXmlSerializer _xmlSerializer;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MigrateUserDb"/> class.
|
||||
/// </summary>
|
||||
/// <param name="logger">The logger.</param>
|
||||
/// <param name="paths">The server application paths.</param>
|
||||
/// <param name="provider">The database provider.</param>
|
||||
/// <param name="xmlSerializer">The xml serializer.</param>
|
||||
public MigrateUserDb(
|
||||
ILogger<MigrateUserDb> logger,
|
||||
IServerApplicationPaths paths,
|
||||
IDbContextFactory<JellyfinDbContext> provider,
|
||||
IXmlSerializer xmlSerializer)
|
||||
{
|
||||
_logger = logger;
|
||||
_paths = paths;
|
||||
_provider = provider;
|
||||
_xmlSerializer = xmlSerializer;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Perform()
|
||||
{
|
||||
var dataPath = _paths.DataPath;
|
||||
var userDbPath = Path.Combine(dataPath, DbFilename);
|
||||
if (!File.Exists(userDbPath))
|
||||
{
|
||||
_logger.LogWarning("{UserDbPath} doesn't exist, nothing to migrate", userDbPath);
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.LogInformation("Migrating the user database may take a while, do not stop Jellyfin.");
|
||||
|
||||
using (var connection = new SqliteConnection($"Filename={userDbPath}"))
|
||||
{
|
||||
connection.Open();
|
||||
var tableQuery = connection.Query("SELECT count(*) FROM sqlite_master WHERE type='table' AND name='LocalUsersv2';");
|
||||
foreach (var row in tableQuery)
|
||||
{
|
||||
if (row.GetInt32(0) == 0)
|
||||
{
|
||||
_logger.LogWarning("Table 'LocalUsersv2' doesn't exist in {UserDbPath}, nothing to migrate", userDbPath);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
using var dbContext = _provider.CreateDbContext();
|
||||
|
||||
var queryResult = connection.Query("SELECT * FROM LocalUsersv2");
|
||||
|
||||
dbContext.RemoveRange(dbContext.Users);
|
||||
dbContext.SaveChanges();
|
||||
|
||||
foreach (var entry in queryResult)
|
||||
{
|
||||
UserMockup? mockup = JsonSerializer.Deserialize<UserMockup>(entry.GetStream(2), JsonDefaults.Options);
|
||||
if (mockup is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var userDataDir = Path.Combine(_paths.UserConfigurationDirectoryPath, mockup.Name);
|
||||
|
||||
var configPath = Path.Combine(userDataDir, "config.xml");
|
||||
var config = File.Exists(configPath)
|
||||
? (UserConfiguration?)_xmlSerializer.DeserializeFromFile(typeof(UserConfiguration), configPath) ?? new UserConfiguration()
|
||||
: new UserConfiguration();
|
||||
|
||||
var policyPath = Path.Combine(userDataDir, "policy.xml");
|
||||
var policy = File.Exists(policyPath)
|
||||
? (UserPolicy?)_xmlSerializer.DeserializeFromFile(typeof(UserPolicy), policyPath) ?? new UserPolicy()
|
||||
: new UserPolicy();
|
||||
policy.AuthenticationProviderId = policy.AuthenticationProviderId?.Replace(
|
||||
"Emby.Server.Implementations.Library",
|
||||
"Jellyfin.Server.Implementations.Users",
|
||||
StringComparison.Ordinal)
|
||||
?? typeof(DefaultAuthenticationProvider).FullName;
|
||||
|
||||
policy.PasswordResetProviderId = typeof(DefaultPasswordResetProvider).FullName;
|
||||
int? maxLoginAttempts = policy.LoginAttemptsBeforeLockout switch
|
||||
{
|
||||
-1 => null,
|
||||
0 => 3,
|
||||
_ => policy.LoginAttemptsBeforeLockout
|
||||
};
|
||||
|
||||
var user = new User(mockup.Name, policy.AuthenticationProviderId!, policy.PasswordResetProviderId!)
|
||||
{
|
||||
Id = entry.GetGuid(1),
|
||||
InternalId = entry.GetInt64(0),
|
||||
MaxParentalRatingScore = policy.MaxParentalRating,
|
||||
MaxParentalRatingSubScore = null,
|
||||
EnableUserPreferenceAccess = policy.EnableUserPreferenceAccess,
|
||||
RemoteClientBitrateLimit = policy.RemoteClientBitrateLimit,
|
||||
InvalidLoginAttemptCount = policy.InvalidLoginAttemptCount,
|
||||
LoginAttemptsBeforeLockout = maxLoginAttempts,
|
||||
SubtitleMode = config.SubtitleMode,
|
||||
HidePlayedInLatest = config.HidePlayedInLatest,
|
||||
EnableLocalPassword = config.EnableLocalPassword,
|
||||
PlayDefaultAudioTrack = config.PlayDefaultAudioTrack,
|
||||
DisplayCollectionsView = config.DisplayCollectionsView,
|
||||
DisplayMissingEpisodes = config.DisplayMissingEpisodes,
|
||||
AudioLanguagePreference = config.AudioLanguagePreference,
|
||||
RememberAudioSelections = config.RememberAudioSelections,
|
||||
EnableNextEpisodeAutoPlay = config.EnableNextEpisodeAutoPlay,
|
||||
RememberSubtitleSelections = config.RememberSubtitleSelections,
|
||||
SubtitleLanguagePreference = config.SubtitleLanguagePreference,
|
||||
Password = mockup.Password,
|
||||
LastLoginDate = mockup.LastLoginDate,
|
||||
LastActivityDate = mockup.LastActivityDate
|
||||
};
|
||||
|
||||
if (mockup.ImageInfos.Length > 0)
|
||||
{
|
||||
ItemImageInfo info = mockup.ImageInfos[0];
|
||||
|
||||
user.ProfileImage = new ImageInfo(info.Path)
|
||||
{
|
||||
LastModified = info.DateModified
|
||||
};
|
||||
}
|
||||
|
||||
user.SetPermission(PermissionKind.IsAdministrator, policy.IsAdministrator);
|
||||
user.SetPermission(PermissionKind.IsHidden, policy.IsHidden);
|
||||
user.SetPermission(PermissionKind.IsDisabled, policy.IsDisabled);
|
||||
user.SetPermission(PermissionKind.EnableSharedDeviceControl, policy.EnableSharedDeviceControl);
|
||||
user.SetPermission(PermissionKind.EnableRemoteAccess, policy.EnableRemoteAccess);
|
||||
user.SetPermission(PermissionKind.EnableLiveTvManagement, policy.EnableLiveTvManagement);
|
||||
user.SetPermission(PermissionKind.EnableLiveTvAccess, policy.EnableLiveTvAccess);
|
||||
user.SetPermission(PermissionKind.EnableMediaPlayback, policy.EnableMediaPlayback);
|
||||
user.SetPermission(PermissionKind.EnableAudioPlaybackTranscoding, policy.EnableAudioPlaybackTranscoding);
|
||||
user.SetPermission(PermissionKind.EnableVideoPlaybackTranscoding, policy.EnableVideoPlaybackTranscoding);
|
||||
user.SetPermission(PermissionKind.EnableContentDeletion, policy.EnableContentDeletion);
|
||||
user.SetPermission(PermissionKind.EnableContentDownloading, policy.EnableContentDownloading);
|
||||
user.SetPermission(PermissionKind.EnableSyncTranscoding, policy.EnableSyncTranscoding);
|
||||
user.SetPermission(PermissionKind.EnableMediaConversion, policy.EnableMediaConversion);
|
||||
user.SetPermission(PermissionKind.EnableAllChannels, policy.EnableAllChannels);
|
||||
user.SetPermission(PermissionKind.EnableAllDevices, policy.EnableAllDevices);
|
||||
user.SetPermission(PermissionKind.EnableAllFolders, policy.EnableAllFolders);
|
||||
user.SetPermission(PermissionKind.EnableRemoteControlOfOtherUsers, policy.EnableRemoteControlOfOtherUsers);
|
||||
user.SetPermission(PermissionKind.EnablePlaybackRemuxing, policy.EnablePlaybackRemuxing);
|
||||
user.SetPermission(PermissionKind.ForceRemoteSourceTranscoding, policy.ForceRemoteSourceTranscoding);
|
||||
user.SetPermission(PermissionKind.EnablePublicSharing, policy.EnablePublicSharing);
|
||||
user.SetPermission(PermissionKind.EnableCollectionManagement, policy.EnableCollectionManagement);
|
||||
|
||||
foreach (var policyAccessSchedule in policy.AccessSchedules)
|
||||
{
|
||||
user.AccessSchedules.Add(policyAccessSchedule);
|
||||
}
|
||||
|
||||
user.SetPreference(PreferenceKind.BlockedTags, policy.BlockedTags);
|
||||
user.SetPreference(PreferenceKind.EnabledChannels, policy.EnabledChannels);
|
||||
user.SetPreference(PreferenceKind.EnabledDevices, policy.EnabledDevices);
|
||||
user.SetPreference(PreferenceKind.EnabledFolders, policy.EnabledFolders);
|
||||
user.SetPreference(PreferenceKind.EnableContentDeletionFromFolders, policy.EnableContentDeletionFromFolders);
|
||||
user.SetPreference(PreferenceKind.OrderedViews, config.OrderedViews);
|
||||
user.SetPreference(PreferenceKind.GroupedFolders, config.GroupedFolders);
|
||||
user.SetPreference(PreferenceKind.MyMediaExcludes, config.MyMediaExcludes);
|
||||
user.SetPreference(PreferenceKind.LatestItemExcludes, config.LatestItemsExcludes);
|
||||
|
||||
dbContext.Users.Add(user);
|
||||
}
|
||||
|
||||
dbContext.SaveChanges();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
File.Move(Path.Combine(dataPath, DbFilename), Path.Combine(dataPath, DbFilename + ".old"));
|
||||
|
||||
var journalPath = Path.Combine(dataPath, DbFilename + "-journal");
|
||||
if (File.Exists(journalPath))
|
||||
{
|
||||
File.Move(journalPath, Path.Combine(dataPath, DbFilename + ".old-journal"));
|
||||
}
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
_logger.LogError(e, "Error renaming legacy user database to 'users.db.old'");
|
||||
}
|
||||
}
|
||||
|
||||
#nullable disable
|
||||
internal class UserMockup
|
||||
{
|
||||
public string Password { get; set; }
|
||||
|
||||
public string EasyPassword { get; set; }
|
||||
|
||||
public DateTime? LastLoginDate { get; set; }
|
||||
|
||||
public DateTime? LastActivityDate { get; set; }
|
||||
|
||||
public string Name { get; set; }
|
||||
|
||||
public ItemImageInfo[] ImageInfos { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -57,7 +57,7 @@ public class MoveExtractedFiles : IAsyncMigrationRoutine
|
||||
IDbContextFactory<JellyfinDbContext> dbProvider)
|
||||
{
|
||||
_appPaths = appPaths;
|
||||
_logger = startupLogger.With(logger);
|
||||
_logger = startupLogger.Attach(logger);
|
||||
_pathManager = pathManager;
|
||||
_fileSystem = fileSystem;
|
||||
_dbProvider = dbProvider;
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
// <copyright file="RemoveDuplicateExtras.cs" company="PlaceholderCompany">
|
||||
// Copyright (c) PlaceholderCompany. All rights reserved.
|
||||
// </copyright>
|
||||
|
||||
namespace Jellyfin.Server.Migrations.Routines;
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Emby.Server.Implementations.Data;
|
||||
using MediaBrowser.Controller;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
/// <summary>
|
||||
/// Remove duplicate entries which were caused by a bug where a file was considered to be an "Extra" to itself.
|
||||
/// </summary>
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
[JellyfinMigration("2025-04-20T08:00:00", nameof(RemoveDuplicateExtras), "ACBE17B7-8435-4A83-8B64-6FCF162CB9BD", RequiresSqlite = true)]
|
||||
internal class RemoveDuplicateExtras : IMigrationRoutine
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
{
|
||||
private const string DbFilename = "library.db";
|
||||
private readonly ILogger<RemoveDuplicateExtras> _logger;
|
||||
private readonly IServerApplicationPaths _paths;
|
||||
|
||||
public RemoveDuplicateExtras(ILogger<RemoveDuplicateExtras> logger, IServerApplicationPaths paths)
|
||||
{
|
||||
_logger = logger;
|
||||
_paths = paths;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Perform()
|
||||
{
|
||||
var dataPath = _paths.DataPath;
|
||||
var dbPath = Path.Combine(dataPath, DbFilename);
|
||||
|
||||
// Skip this migration if using PostgreSQL or if library.db doesn't exist
|
||||
if (!File.Exists(dbPath))
|
||||
{
|
||||
_logger.LogInformation("SQLite library.db not found, skipping SQLite-specific migration.");
|
||||
return;
|
||||
}
|
||||
|
||||
using var connection = new SqliteConnection($"Filename={dbPath}");
|
||||
connection.Open();
|
||||
using (var transaction = connection.BeginTransaction())
|
||||
{
|
||||
// Query the database for the ids of duplicate extras
|
||||
var queryResult = connection.Query("SELECT t1.Path FROM TypedBaseItems AS t1, TypedBaseItems AS t2 WHERE t1.Path=t2.Path AND t1.Type!=t2.Type AND t1.Type='MediaBrowser.Controller.Entities.Video'");
|
||||
var bads = string.Join(", ", queryResult.Select(x => x.GetString(0)));
|
||||
|
||||
// Do nothing if no duplicate extras were detected
|
||||
if (bads.Length == 0)
|
||||
{
|
||||
_logger.LogInformation("No duplicate extras detected, skipping migration.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Back up the database before deleting any entries
|
||||
for (int i = 1; ; i++)
|
||||
{
|
||||
var bakPath = string.Format(CultureInfo.InvariantCulture, "{0}.bak{1}", dbPath, i);
|
||||
if (!File.Exists(bakPath))
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Copy(dbPath, bakPath);
|
||||
_logger.LogInformation("Library database backed up to {BackupPath}", bakPath);
|
||||
break;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Cannot make a backup of {Library} at path {BackupPath}", DbFilename, bakPath);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Delete all duplicate extras
|
||||
_logger.LogInformation("Removing found duplicated extras for the following items: {DuplicateExtras}", bads);
|
||||
connection.Execute("DELETE FROM TypedBaseItems WHERE rowid IN (SELECT t1.rowid FROM TypedBaseItems AS t1, TypedBaseItems AS t2 WHERE t1.Path=t2.Path AND t1.Type!=t2.Type AND t1.Type='MediaBrowser.Controller.Entities.Video')");
|
||||
transaction.Commit();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
// <copyright file="ReseedFolderFlag.cs" company="PlaceholderCompany">
|
||||
// Copyright (c) PlaceholderCompany. All rights reserved.
|
||||
// </copyright>
|
||||
|
||||
#pragma warning disable RS0030 // Do not use banned APIs
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Emby.Server.Implementations.Data;
|
||||
using Jellyfin.Database.Implementations;
|
||||
using Jellyfin.Server.ServerSetupApp;
|
||||
using MediaBrowser.Controller;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Jellyfin.Server.Migrations.Routines;
|
||||
|
||||
[JellyfinMigration("2025-07-30T21:50:00", nameof(ReseedFolderFlag), RequiresSqlite = true)]
|
||||
[JellyfinMigrationBackup(JellyfinDb = true)]
|
||||
internal class ReseedFolderFlag : IAsyncMigrationRoutine
|
||||
{
|
||||
private const string DbFilename = "library.db.old";
|
||||
|
||||
private readonly IStartupLogger _logger;
|
||||
private readonly IServerApplicationPaths _paths;
|
||||
private readonly IDbContextFactory<JellyfinDbContext> _provider;
|
||||
|
||||
public ReseedFolderFlag(
|
||||
IStartupLogger<MigrateLibraryDb> startupLogger,
|
||||
IDbContextFactory<JellyfinDbContext> provider,
|
||||
IServerApplicationPaths paths)
|
||||
{
|
||||
_logger = startupLogger;
|
||||
_provider = provider;
|
||||
_paths = paths;
|
||||
}
|
||||
|
||||
internal static bool RerunGuardFlag { get; set; } = false;
|
||||
|
||||
public async Task PerformAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (RerunGuardFlag)
|
||||
{
|
||||
_logger.LogInformation("Migration is skipped because it does not apply.");
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.LogInformation("Migrating the IsFolder flag from library.db.old may take a while, do not stop Jellyfin.");
|
||||
|
||||
var dataPath = _paths.DataPath;
|
||||
var libraryDbPath = Path.Combine(dataPath, DbFilename);
|
||||
if (!File.Exists(libraryDbPath))
|
||||
{
|
||||
_logger.LogError("Cannot migrate IsFolder flag from {LibraryDb} as it does not exist. This migration expects the MigrateLibraryDb to run first.", libraryDbPath);
|
||||
return;
|
||||
}
|
||||
|
||||
var dbContext = await _provider.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);
|
||||
await using (dbContext.ConfigureAwait(false))
|
||||
{
|
||||
using var connection = new SqliteConnection($"Filename={libraryDbPath};Mode=ReadOnly");
|
||||
var queryResult = connection.Query(
|
||||
"""
|
||||
SELECT guid FROM TypedBaseItems
|
||||
WHERE IsFolder = true
|
||||
""")
|
||||
.Select(entity => entity.GetGuid(0))
|
||||
.ToList();
|
||||
_logger.LogInformation("Migrating the IsFolder flag for {Count} items.", queryResult.Count);
|
||||
foreach (var id in queryResult)
|
||||
{
|
||||
await dbContext.BaseItems.Where(e => e.Id == id).ExecuteUpdateAsync(e => e.SetProperty(f => f.IsFolder, true), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -139,7 +139,7 @@ namespace Jellyfin.Server
|
||||
}
|
||||
}
|
||||
|
||||
StorageHelper.TestCommonPathsForStorageCapacity(appPaths, StartupLogger.Logger.With(_loggerFactory.CreateLogger<Startup>()).BeginGroup($"Storage Check"));
|
||||
StorageHelper.TestCommonPathsForStorageCapacity(appPaths, StartupLogger.Logger.Attach(_loggerFactory.CreateLogger<Startup>()).BeginGroup($"Storage Check"));
|
||||
|
||||
StartupHelpers.PerformStaticInitialization();
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ public interface IStartupLogger : ILogger
|
||||
/// </summary>
|
||||
/// <param name="logger">Other logger to rely messages to.</param>
|
||||
/// <returns>A combined logger.</returns>
|
||||
IStartupLogger With(ILogger logger);
|
||||
IStartupLogger Attach(ILogger logger);
|
||||
|
||||
/// <summary>
|
||||
/// Opens a new Group logger within the parent logger.
|
||||
@@ -37,7 +37,7 @@ public interface IStartupLogger : ILogger
|
||||
/// <param name="logger">Other logger to rely messages to.</param>
|
||||
/// <returns>A combined logger.</returns>
|
||||
/// <typeparam name="TCategory">The logger cateogry.</typeparam>
|
||||
IStartupLogger<TCategory> With<TCategory>(ILogger logger);
|
||||
IStartupLogger<TCategory> Attach<TCategory>(ILogger logger);
|
||||
|
||||
/// <summary>
|
||||
/// Opens a new Group logger within the parent logger.
|
||||
@@ -59,7 +59,7 @@ public interface IStartupLogger<TCategory> : IStartupLogger
|
||||
/// </summary>
|
||||
/// <param name="logger">Other logger to rely messages to.</param>
|
||||
/// <returns>A combined logger.</returns>
|
||||
new IStartupLogger<TCategory> With(ILogger logger);
|
||||
new IStartupLogger<TCategory> Attach(ILogger logger);
|
||||
|
||||
/// <summary>
|
||||
/// Opens a new Group logger within the parent logger.
|
||||
|
||||
@@ -12,8 +12,6 @@ using Microsoft.Extensions.Logging.Abstractions;
|
||||
/// <inheritdoc/>
|
||||
public class StartupLogger : IStartupLogger
|
||||
{
|
||||
private readonly StartupLogTopic? _topic;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="StartupLogger"/> class.
|
||||
/// </summary>
|
||||
@@ -21,6 +19,7 @@ public class StartupLogger : IStartupLogger
|
||||
public StartupLogger(ILogger logger)
|
||||
{
|
||||
BaseLogger = logger;
|
||||
Topic = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -30,13 +29,13 @@ public class StartupLogger : IStartupLogger
|
||||
/// <param name="topic">The group for this logger.</param>
|
||||
internal StartupLogger(ILogger logger, StartupLogTopic? topic) : this(logger)
|
||||
{
|
||||
_topic = topic;
|
||||
Topic = topic;
|
||||
}
|
||||
|
||||
internal static IStartupLogger Logger { get; set; } = new StartupLogger(NullLogger.Instance);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public StartupLogTopic? Topic => _topic;
|
||||
public StartupLogTopic? Topic { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or Sets the underlying base logger.
|
||||
@@ -50,13 +49,13 @@ public class StartupLogger : IStartupLogger
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IStartupLogger With(ILogger logger)
|
||||
public IStartupLogger Attach(ILogger logger)
|
||||
{
|
||||
return new StartupLogger(logger, Topic);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IStartupLogger<TCategory> With<TCategory>(ILogger logger)
|
||||
public IStartupLogger<TCategory> Attach<TCategory>(ILogger logger)
|
||||
{
|
||||
return new StartupLogger<TCategory>(logger, Topic);
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ public class StartupLogger<TCategory> : StartupLogger, IStartupLogger<TCategory>
|
||||
return new StartupLogger<TCategory>(BaseLogger, startupEntry);
|
||||
}
|
||||
|
||||
IStartupLogger<TCategory> IStartupLogger<TCategory>.With(ILogger logger)
|
||||
IStartupLogger<TCategory> IStartupLogger<TCategory>.Attach(ILogger logger)
|
||||
{
|
||||
return new StartupLogger<TCategory>(logger, Topic);
|
||||
}
|
||||
|
||||
+20
-13
@@ -85,18 +85,25 @@ namespace Jellyfin.Server
|
||||
var acceptJsonHeader = new MediaTypeWithQualityHeaderValue(MediaTypeNames.Application.Json, 1.0);
|
||||
var acceptXmlHeader = new MediaTypeWithQualityHeaderValue(MediaTypeNames.Application.Xml, 0.9);
|
||||
var acceptAnyHeader = new MediaTypeWithQualityHeaderValue("*/*", 0.8);
|
||||
Func<IServiceProvider, HttpMessageHandler> eyeballsHttpClientHandlerDelegate = (_) => new SocketsHttpHandler()
|
||||
{
|
||||
AutomaticDecompression = DecompressionMethods.All,
|
||||
RequestHeaderEncodingSelector = (_, _) => Encoding.UTF8,
|
||||
ConnectCallback = HttpClientExtension.OnConnect
|
||||
};
|
||||
|
||||
Func<IServiceProvider, HttpMessageHandler> defaultHttpClientHandlerDelegate = (_) => new SocketsHttpHandler()
|
||||
HttpMessageHandler CreateEyeballsHttpClientHandler(IServiceProvider serviceProvider)
|
||||
{
|
||||
AutomaticDecompression = DecompressionMethods.All,
|
||||
RequestHeaderEncodingSelector = (_, _) => Encoding.UTF8
|
||||
};
|
||||
return new SocketsHttpHandler()
|
||||
{
|
||||
AutomaticDecompression = DecompressionMethods.All,
|
||||
RequestHeaderEncodingSelector = (_, _) => Encoding.UTF8,
|
||||
ConnectCallback = HttpClientExtension.OnConnect
|
||||
};
|
||||
}
|
||||
|
||||
HttpMessageHandler CreateDefaultHttpClientHandler(IServiceProvider serviceProvider)
|
||||
{
|
||||
return new SocketsHttpHandler()
|
||||
{
|
||||
AutomaticDecompression = DecompressionMethods.All,
|
||||
RequestHeaderEncodingSelector = (_, _) => Encoding.UTF8
|
||||
};
|
||||
}
|
||||
|
||||
services.AddHttpClient(NamedClient.Default, c =>
|
||||
{
|
||||
@@ -105,7 +112,7 @@ namespace Jellyfin.Server
|
||||
c.DefaultRequestHeaders.Accept.Add(acceptXmlHeader);
|
||||
c.DefaultRequestHeaders.Accept.Add(acceptAnyHeader);
|
||||
})
|
||||
.ConfigurePrimaryHttpMessageHandler(eyeballsHttpClientHandlerDelegate);
|
||||
.ConfigurePrimaryHttpMessageHandler(CreateEyeballsHttpClientHandler);
|
||||
|
||||
services.AddHttpClient(NamedClient.MusicBrainz, c =>
|
||||
{
|
||||
@@ -114,7 +121,7 @@ namespace Jellyfin.Server
|
||||
c.DefaultRequestHeaders.Accept.Add(acceptXmlHeader);
|
||||
c.DefaultRequestHeaders.Accept.Add(acceptAnyHeader);
|
||||
})
|
||||
.ConfigurePrimaryHttpMessageHandler(eyeballsHttpClientHandlerDelegate);
|
||||
.ConfigurePrimaryHttpMessageHandler(CreateEyeballsHttpClientHandler);
|
||||
|
||||
services.AddHttpClient(NamedClient.DirectIp, c =>
|
||||
{
|
||||
@@ -123,7 +130,7 @@ namespace Jellyfin.Server
|
||||
c.DefaultRequestHeaders.Accept.Add(acceptXmlHeader);
|
||||
c.DefaultRequestHeaders.Accept.Add(acceptAnyHeader);
|
||||
})
|
||||
.ConfigurePrimaryHttpMessageHandler(defaultHttpClientHandlerDelegate);
|
||||
.ConfigurePrimaryHttpMessageHandler(CreateDefaultHttpClientHandler);
|
||||
|
||||
services.AddHealthChecks()
|
||||
.AddCheck<DbContextFactoryHealthCheck<JellyfinDbContext>>(nameof(JellyfinDbContext));
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+2
-16
@@ -1,4 +1,4 @@
|
||||
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 18
|
||||
VisualStudioVersion = 18.0.11222.15
|
||||
@@ -93,8 +93,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Jellyfin.Database", "Jellyf
|
||||
src\Jellyfin.Database\readme.md = src\Jellyfin.Database\readme.md
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Database.Providers.Sqlite", "src\Jellyfin.Database\Jellyfin.Database.Providers.Sqlite\Jellyfin.Database.Providers.Sqlite.csproj", "{A5590358-33CC-4B39-BDE7-DC62FEB03C76}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Database.Implementations", "src\Jellyfin.Database\Jellyfin.Database.Implementations\Jellyfin.Database.Implementations.csproj", "{8C9F9221-8415-496C-B1F5-E7756F03FA59}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.CodeAnalysis", "src\Jellyfin.CodeAnalysis\Jellyfin.CodeAnalysis.csproj", "{11643D0F-6761-4EF7-AB71-6F9F8DE00714}"
|
||||
@@ -555,18 +553,6 @@ Global
|
||||
{8C6B2B13-58A4-4506-9DAB-1F882A093FE0}.Release|x64.Build.0 = Release|Any CPU
|
||||
{8C6B2B13-58A4-4506-9DAB-1F882A093FE0}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{8C6B2B13-58A4-4506-9DAB-1F882A093FE0}.Release|x86.Build.0 = Release|Any CPU
|
||||
{A5590358-33CC-4B39-BDE7-DC62FEB03C76}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A5590358-33CC-4B39-BDE7-DC62FEB03C76}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A5590358-33CC-4B39-BDE7-DC62FEB03C76}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{A5590358-33CC-4B39-BDE7-DC62FEB03C76}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{A5590358-33CC-4B39-BDE7-DC62FEB03C76}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{A5590358-33CC-4B39-BDE7-DC62FEB03C76}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{A5590358-33CC-4B39-BDE7-DC62FEB03C76}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A5590358-33CC-4B39-BDE7-DC62FEB03C76}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{A5590358-33CC-4B39-BDE7-DC62FEB03C76}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{A5590358-33CC-4B39-BDE7-DC62FEB03C76}.Release|x64.Build.0 = Release|Any CPU
|
||||
{A5590358-33CC-4B39-BDE7-DC62FEB03C76}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{A5590358-33CC-4B39-BDE7-DC62FEB03C76}.Release|x86.Build.0 = Release|Any CPU
|
||||
{8C9F9221-8415-496C-B1F5-E7756F03FA59}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{8C9F9221-8415-496C-B1F5-E7756F03FA59}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{8C9F9221-8415-496C-B1F5-E7756F03FA59}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
@@ -632,7 +618,6 @@ Global
|
||||
{C4F71272-C6BE-4C30-BE0D-4E6ED651D6D3} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
|
||||
{8C6B2B13-58A4-4506-9DAB-1F882A093FE0} = {C9F0AB5D-F4D7-40C8-A353-3305C86D6D4C}
|
||||
{4C54CE05-69C8-48FA-8785-39F7F6DB1CAD} = {C9F0AB5D-F4D7-40C8-A353-3305C86D6D4C}
|
||||
{A5590358-33CC-4B39-BDE7-DC62FEB03C76} = {4C54CE05-69C8-48FA-8785-39F7F6DB1CAD}
|
||||
{8C9F9221-8415-496C-B1F5-E7756F03FA59} = {4C54CE05-69C8-48FA-8785-39F7F6DB1CAD}
|
||||
{11643D0F-6761-4EF7-AB71-6F9F8DE00714} = {C9F0AB5D-F4D7-40C8-A353-3305C86D6D4C}
|
||||
{E1A314DC-837D-4472-AC15-EC08947C55B7} = {4C54CE05-69C8-48FA-8785-39F7F6DB1CAD}
|
||||
@@ -659,3 +644,4 @@ Global
|
||||
$2.DirectoryNamespaceAssociation = PrefixedHierarchical
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
||||
|
||||
@@ -420,6 +420,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
|
||||
// Create a list for our validated children
|
||||
var newItems = new List<BaseItem>();
|
||||
var changedItems = new List<BaseItem>();
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
@@ -436,7 +437,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
|
||||
if (currentChild.UpdateFromResolvedItem(child) > ItemUpdateType.None)
|
||||
{
|
||||
await currentChild.UpdateToRepositoryAsync(ItemUpdateType.MetadataImport, cancellationToken).ConfigureAwait(false);
|
||||
changedItems.Add(currentChild);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -484,6 +485,11 @@ namespace MediaBrowser.Controller.Entities
|
||||
LibraryManager.CreateItems(newItems, this, cancellationToken);
|
||||
}
|
||||
|
||||
if (changedItems.Count > 0)
|
||||
{
|
||||
await LibraryManager.UpdateItemsAsync(changedItems, this, ItemUpdateType.MetadataImport, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
// After removing items, reattach any detached user data to remaining children
|
||||
// that share the same user data keys (eg. same episode replaced with a new file).
|
||||
if (actuallyRemoved.Count > 0)
|
||||
|
||||
@@ -68,11 +68,6 @@ namespace MediaBrowser.Controller.Extensions
|
||||
/// </summary>
|
||||
public const string UnixSocketPermissionsKey = "kestrel:socketPermissions";
|
||||
|
||||
/// <summary>
|
||||
/// The cache size of the SQL database, see cache_size.
|
||||
/// </summary>
|
||||
public const string SqliteCacheSizeKey = "sqlite:cacheSize";
|
||||
|
||||
/// <summary>
|
||||
/// The key for a setting that indicates whether the application should detect network status change.
|
||||
/// </summary>
|
||||
@@ -142,13 +137,5 @@ namespace MediaBrowser.Controller.Extensions
|
||||
/// <returns>The unix socket permissions.</returns>
|
||||
public static string? GetUnixSocketPermissions(this IConfiguration configuration)
|
||||
=> configuration[UnixSocketPermissionsKey];
|
||||
|
||||
/// <summary>
|
||||
/// Gets the cache_size from the <see cref="IConfiguration" />.
|
||||
/// </summary>
|
||||
/// <param name="configuration">The configuration to read the setting from.</param>
|
||||
/// <returns>The sqlite cache size.</returns>
|
||||
public static int? GetSqliteCacheSize(this IConfiguration configuration)
|
||||
=> configuration.GetValue<int?>(SqliteCacheSizeKey);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,7 +115,7 @@ public sealed class LimitedConcurrencyLibraryScheduler : ILimitedConcurrencyLibr
|
||||
if (fanoutConcurrency <= 0)
|
||||
{
|
||||
// in case the user did not set a limit manually, we can assume he has 3 or more cores as already checked by ShouldForceSequentialOperation.
|
||||
return Environment.ProcessorCount - 3;
|
||||
return Math.Max(2, Environment.ProcessorCount / 2);
|
||||
}
|
||||
|
||||
return fanoutConcurrency;
|
||||
|
||||
@@ -2,7 +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>
|
||||
<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;NU1510</NoWarn>
|
||||
<ProjectGuid>{442B5058-DCAF-4263-BB6A-F21E31120A1B}</ProjectGuid>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
<PackageReference Include="Jellyfin.Sdk" />
|
||||
<PackageReference Include="LrcParser" />
|
||||
<PackageReference Include="MetaBrainz.MusicBrainz" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" />
|
||||
<PackageReference Include="Newtonsoft.Json" />
|
||||
<PackageReference Include="PlaylistsNET" />
|
||||
|
||||
@@ -286,7 +286,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
|
||||
if (ep is not null)
|
||||
{
|
||||
seasonNumber = ep.SeasonNumber;
|
||||
episodeNumber = ep.EpisodeNumber;
|
||||
episodeNumber = (int)ep.EpisodeNumber;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
# PowerShell script to convert remaining SQL identifiers from quoted PascalCase to lowercase/snake_case
|
||||
# This script handles constraints, indexes, and foreign keys
|
||||
|
||||
param([string]$filepath = "Jellyfin.Server/sql/schema_init/create_database_schema.sql")
|
||||
|
||||
$content = Get-Content $filepath -Raw
|
||||
|
||||
# Table name mappings for constraint/index/FK replacement
|
||||
$tableMappings = @{
|
||||
'"ActivityLogs"' = 'activity_logs'
|
||||
'"ApiKeys"' = 'api_keys'
|
||||
'"DeviceOptions"' = 'device_options'
|
||||
'"Devices"' = 'devices'
|
||||
'"CustomItemDisplayPreferences"' = 'custom_item_display_preferences'
|
||||
'"DisplayPreferences"' = 'display_preferences'
|
||||
'"HomeSection"' = 'home_sections'
|
||||
'"ItemDisplayPreferences"' = 'item_display_preferences'
|
||||
'"AncestorIds"' = 'ancestor_ids'
|
||||
'"AttachmentStreamInfos"' = 'attachment_stream_infos'
|
||||
'"BaseItemImageInfos"' = 'base_item_image_infos'
|
||||
'"BaseItemMetadataFields"' = 'base_item_metadata_fields'
|
||||
'"BaseItemProviders"' = 'base_item_providers'
|
||||
'"BaseItemTrailerTypes"' = 'base_item_trailer_types'
|
||||
'"BaseItems"' = 'base_items'
|
||||
'"Chapters"' = 'chapters'
|
||||
'"ImageInfos"' = 'image_infos'
|
||||
'"ItemValues"' = 'item_values'
|
||||
'"ItemValuesMap"' = 'item_values_map'
|
||||
'"KeyframeData"' = 'keyframe_data'
|
||||
'"MediaSegments"' = 'media_segments'
|
||||
'"MediaStreamInfos"' = 'media_stream_infos'
|
||||
'"PeopleBaseItemMap"' = 'people_base_item_map'
|
||||
'"Peoples"' = 'peoples'
|
||||
'"TrickplayInfos"' = 'trickplay_infos'
|
||||
'"UserData"' = 'user_data'
|
||||
'"__EFMigrationsHistory"' = '__ef_migrations_history'
|
||||
'"AccessSchedules"' = 'access_schedules'
|
||||
'"Permissions"' = 'permissions'
|
||||
'"Preferences"' = 'preferences'
|
||||
'"Users"' = 'users'
|
||||
}
|
||||
|
||||
# Column name mappings (critical columns that appear in constraints)
|
||||
$columnMappings = @{
|
||||
'"Id"' = 'id'
|
||||
'"ParentItemId"' = 'parent_item_id'
|
||||
'"ItemId"' = 'item_id'
|
||||
'"Index"' = 'index'
|
||||
'"UserId"' = 'user_id'
|
||||
'"DisplayPreferencesId"' = 'display_preferences_id'
|
||||
'"ItemValueId"' = 'item_value_id'
|
||||
'"ChapterIndex"' = 'chapter_index'
|
||||
'"PeopleId"' = 'people_id'
|
||||
'"StreamIndex"' = 'stream_index'
|
||||
'"CustomDataKey"' = 'custom_data_key'
|
||||
'"MigrationId"' = 'migration_id'
|
||||
'"ProductVersion"' = 'product_version'
|
||||
'"DayOfWeek"' = 'day_of_week'
|
||||
'"StartHour"' = 'start_hour'
|
||||
'"EndHour"' = 'end_hour'
|
||||
'"Kind"' = 'kind'
|
||||
'"Value"' = 'value'
|
||||
'"RowVersion"' = 'row_version'
|
||||
'"Permission_Permissions_Guid"' = 'permission_permissions_guid'
|
||||
'"Preference_Preferences_Guid"' = 'preference_preferences_guid'
|
||||
}
|
||||
|
||||
# Apply table replacements
|
||||
foreach ($oldTable in $tableMappings.Keys) {
|
||||
$newTable = $tableMappings[$oldTable]
|
||||
$content = $content -replace [regex]::Escape($oldTable), $newTable
|
||||
}
|
||||
|
||||
# Apply column replacements
|
||||
foreach ($oldCol in $columnMappings.Keys) {
|
||||
$newCol = $columnMappings[$oldCol]
|
||||
$content = $content -replace [regex]::Escape($oldCol), $newCol
|
||||
}
|
||||
|
||||
# Constraint name replacements (remove quotes from constraint names)
|
||||
$content = $content -replace 'ADD CONSTRAINT "([^"]+)"', 'ADD CONSTRAINT $1'
|
||||
|
||||
# Index name replacements (remove quotes from index names)
|
||||
$content = $content -replace 'CREATE (\w+ )?INDEX "([^"]+)"', 'CREATE $1INDEX $2'
|
||||
|
||||
# Handle remaining quoted identifiers in special cases
|
||||
$content = $content -replace '"ProviderId"', 'provider_id'
|
||||
$content = $content -replace '"ProviderValue"', 'provider_value'
|
||||
$content = $content -replace '"SortOrder"', 'sort_order'
|
||||
$content = $content -replace '"ListOrder"', 'list_order'
|
||||
$content = $content -replace '"Role"', 'role'
|
||||
$content = $content -replace '"Name"', 'name'
|
||||
$content = $content -replace '"Path"', 'path'
|
||||
$content = $content -replace '"DateCreated"', 'date_created'
|
||||
$content = $content -replace '"DateLastActivity"', 'date_last_activity'
|
||||
$content = $content -replace '"DateModified"', 'date_modified'
|
||||
$content = $content -replace '"DateLastSaved"', 'date_last_saved'
|
||||
$content = $content -replace '"LastPlayedDate"', 'last_played_date'
|
||||
$content = $content -replace '"Played"', 'played'
|
||||
$content = $content -replace '"IsFavorite"', 'is_favorite'
|
||||
$content = $content -replace '"PlaybackPositionTicks"', 'playback_position_ticks'
|
||||
$content = $content -replace '"Type"', 'type'
|
||||
$content = $content -replace '"AccessToken"', 'access_token'
|
||||
$content = $content -replace '"DeviceId"', 'device_id'
|
||||
$content = $content -replace '"Client"', 'client'
|
||||
$content = $content -replace '"Username"', 'username'
|
||||
$content = $content -replace '"StartDate"', 'start_date'
|
||||
$content = $content -replace '"EndDate"', 'end_date'
|
||||
$content = $content -replace '"IsFolder"', 'is_folder'
|
||||
$content = $content -replace '"IsVirtualItem"', 'is_virtual_item'
|
||||
$content = $content -replace '"PresentationUniqueKey"', 'presentation_unique_key'
|
||||
$content = $content -replace '"SeriesPresentationUniqueKey"', 'series_presentation_unique_key'
|
||||
$content = $content -replace '"TopParentId"', 'top_parent_id'
|
||||
$content = $content -replace '"ProviderId"', 'provider_id'
|
||||
$content = $content -replace '"StreamType"', 'stream_type'
|
||||
$content = $content -replace '"Language"', 'language'
|
||||
$content = $content -replace '"Codec"', 'codec'
|
||||
$content = $content -replace '"CommunityRating"', 'community_rating'
|
||||
$content = $content -replace '"ProductionYear"', 'production_year'
|
||||
$content = $content -replace '"PremiereDate"', 'premiere_date'
|
||||
$content = $content -replace '"SortName"', 'sort_name'
|
||||
$content = $content -replace '"CleanValue"', 'clean_value'
|
||||
$content = $content -replace '"TotalDuration"', 'total_duration'
|
||||
$content = $content -replace '"KeyframeTicks"', 'keyframe_ticks'
|
||||
$content = $content -replace '"EndTicks"', 'end_ticks'
|
||||
$content = $content -replace '"StartTicks"', 'start_ticks'
|
||||
$content = $content -replace '"SegmentProviderId"', 'segment_provider_id'
|
||||
$content = $content -replace '"AudioStreamIndex"', 'audio_stream_index'
|
||||
$content = $content -replace '"SubtitleStreamIndex"', 'subtitle_stream_index'
|
||||
$content = $content -replace '"RetentionDate"', 'retention_date'
|
||||
|
||||
Set-Content -Path $filepath -Value $content
|
||||
Write-Host "SQL identifiers conversion complete!"
|
||||
@@ -0,0 +1,234 @@
|
||||
# Database Schema Mismatch - Detailed Entity Mapping
|
||||
|
||||
## Quick Reference: All Mismatches
|
||||
|
||||
### ACTIVITYLOG Schema (1 table)
|
||||
|
||||
| Entity | SQL Table Name | C# Expected Name | Columns Match? | Status |
|
||||
|--------|----------------|------------------|-----------------|--------|
|
||||
| ActivityLog | `"ActivityLogs"` | `activity_logs` | NO (11 cols) | ❌ CRITICAL |
|
||||
|
||||
**SQL Columns:**
|
||||
- "Id", "Name", "Overview", "ShortOverview", "Type", "UserId", "ItemId", "DateCreated", "LogSeverity", "RowVersion"
|
||||
|
||||
**C# Expected Columns (via SnakeCaseNamingConvention):**
|
||||
- id, name, overview, short_overview, type, user_id, item_id, date_created, log_severity, row_version
|
||||
|
||||
---
|
||||
|
||||
### AUTHENTICATION Schema (3 tables)
|
||||
|
||||
| Entity | SQL Table Name | C# Expected Name | Columns Match? | Status |
|
||||
|--------|----------------|------------------|-----------------|--------|
|
||||
| ApiKey | `"ApiKeys"` | `api_keys` | NO (5 cols) | ❌ CRITICAL |
|
||||
| Device | `"Devices"` | `devices` | NO (11 cols) | ❌ CRITICAL |
|
||||
| DeviceOptions | `"DeviceOptions"` | `device_options` | NO (3 cols) | ❌ CRITICAL |
|
||||
|
||||
#### ApiKey
|
||||
**SQL Columns:** "Id", "DateCreated", "DateLastActivity", "Name", "AccessToken"
|
||||
**C# Expected:** id, date_created, date_last_activity, name, access_token
|
||||
|
||||
#### Device
|
||||
**SQL Columns:** "Id", "UserId", "AccessToken", "AppName", "AppVersion", "DeviceName", "DeviceId", "IsActive", "DateCreated", "DateModified", "DateLastActivity"
|
||||
**C# Expected:** id, user_id, access_token, app_name, app_version, device_name, device_id, is_active, date_created, date_modified, date_last_activity
|
||||
|
||||
#### DeviceOptions
|
||||
**SQL Columns:** "Id", "DeviceId", "CustomName"
|
||||
**C# Expected:** id, device_id, custom_name
|
||||
|
||||
---
|
||||
|
||||
### DISPLAYPREFERENCES Schema (4 tables)
|
||||
|
||||
| Entity | SQL Table Name | C# Expected Name | Columns Match? | Status |
|
||||
|--------|----------------|------------------|-----------------|--------|
|
||||
| CustomItemDisplayPreferences | `"CustomItemDisplayPreferences"` | `custom_item_display_preferences` | NO (6 cols) | ❌ CRITICAL |
|
||||
| DisplayPreferences | `"DisplayPreferences"` | `display_preferences` | NO (14 cols) | ❌ CRITICAL |
|
||||
| HomeSection | `"HomeSection"` | `home_sections` | NO (4 cols) | ❌ CRITICAL |
|
||||
| ItemDisplayPreferences | `"ItemDisplayPreferences"` | `item_display_preferences` | NO (10 cols) | ❌ CRITICAL |
|
||||
|
||||
#### CustomItemDisplayPreferences
|
||||
**SQL Columns:** "Id", "UserId", "ItemId", "Client", "Key", "Value"
|
||||
**C# Expected:** id, user_id, item_id, client, key, value
|
||||
|
||||
#### DisplayPreferences
|
||||
**SQL Columns:** "Id", "UserId", "ItemId", "Client", "ShowSidebar", "ShowBackdrop", "ScrollDirection", "IndexBy", "SkipForwardLength", "SkipBackwardLength", "ChromecastVersion", "EnableNextVideoInfoOverlay", "DashboardTheme", "TvHome"
|
||||
**C# Expected:** id, user_id, item_id, client, show_sidebar, show_backdrop, scroll_direction, index_by, skip_forward_length, skip_backward_length, chromecast_version, enable_next_video_info_overlay, dashboard_theme, tv_home
|
||||
|
||||
#### HomeSection
|
||||
**SQL Columns:** "Id", "DisplayPreferencesId", "Order", "Type"
|
||||
**C# Expected:** id, display_preferences_id, order, type
|
||||
|
||||
#### ItemDisplayPreferences
|
||||
**SQL Columns:** "Id", "UserId", "ItemId", "Client", "ViewType", "RememberIndexing", "IndexBy", "RememberSorting", "SortBy", "SortOrder"
|
||||
**C# Expected:** id, user_id, item_id, client, view_type, remember_indexing, index_by, remember_sorting, sort_by, sort_order
|
||||
|
||||
---
|
||||
|
||||
### LIBRARY Schema (15 tables - MOST CRITICAL)
|
||||
|
||||
| Entity | SQL Table Name | C# Expected Name | Columns Match? | Status |
|
||||
|--------|----------------|------------------|-----------------|--------|
|
||||
| AncestorId | `"AncestorIds"` | `ancestor_ids` | NO (2 cols) | ❌ |
|
||||
| AttachmentStreamInfo | `"AttachmentStreamInfos"` | `attachment_stream_infos` | NO (7 cols) | ❌ |
|
||||
| BaseItemImageInfo | `"BaseItemImageInfos"` | `base_item_image_infos` | NO (8 cols) | ❌ |
|
||||
| BaseItemMetadataField | `"BaseItemMetadataFields"` | `base_item_metadata_fields` | NO (2 cols) | ❌ |
|
||||
| BaseItemProvider | `"BaseItemProviders"` | `base_item_providers` | NO (3 cols) | ❌ |
|
||||
| BaseItemTrailerType | `"BaseItemTrailerTypes"` | `base_item_trailer_types` | NO (2 cols) | ❌ |
|
||||
| BaseItemEntity | `"BaseItems"` | `base_items` | NO (73 cols!) | ❌ SEVERE |
|
||||
| Chapter | `"Chapters"` | `chapters` | NO (6 cols) | ❌ |
|
||||
| ImageInfo | `"ImageInfos"` | `image_infos` | NO (4 cols) | ❌ |
|
||||
| ItemValue | `"ItemValues"` | `item_values` | NO (4 cols) | ❌ |
|
||||
| ItemValueMap | `"ItemValuesMap"` | `item_values_map` | NO (2 cols) | ❌ |
|
||||
| KeyframeData | `"KeyframeData"` | `keyframe_data` | NO (3 cols) | ❌ |
|
||||
| MediaSegment | `"MediaSegments"` | `media_segments` | NO (6 cols) | ❌ |
|
||||
| MediaStreamInfo | `"MediaStreamInfos"` | `media_stream_infos` | NO (60+ cols) | ❌ SEVERE |
|
||||
| PeopleBaseItemMap | `"PeopleBaseItemMap"` | `people_base_item_map` | NO (5 cols) | ❌ |
|
||||
| People | `"Peoples"` | `peoples` | NO (3 cols) | ❌ |
|
||||
| TrickplayInfo | `"TrickplayInfos"` | `trickplay_infos` | NO (8 cols) | ❌ |
|
||||
| UserData | `"UserData"` | `user_data` | NO (13 cols) | ❌ |
|
||||
|
||||
#### BaseItemEntity (MOST CRITICAL - 73 columns)
|
||||
**SQL Columns:**
|
||||
"Id", "Type", "Data", "Path", "StartDate", "EndDate", "ChannelId", "IsMovie", "CommunityRating", "CustomRating", "IndexNumber", "IsLocked", "Name", "OfficialRating", "MediaType", "Overview", "ParentIndexNumber", "PremiereDate", "ProductionYear", "Genres", "SortName", "ForcedSortName", "RunTimeTicks", "DateCreated", "DateModified", "IsSeries", "EpisodeTitle", "IsRepeat", "PreferredMetadataLanguage", "PreferredMetadataCountryCode", "DateLastRefreshed", "DateLastSaved", "IsInMixedFolder", "Studios", "ExternalServiceId", "Tags", "IsFolder", "InheritedParentalRatingValue", "InheritedParentalRatingSubValue", "UnratedType", "CriticRating", "CleanName", "PresentationUniqueKey", "OriginalTitle", "PrimaryVersionId", "DateLastMediaAdded", "Album", "LUFS", "NormalizationGain", "IsVirtualItem", "SeriesName", "SeasonName", "ExternalSeriesId", "Tagline", "ProductionLocations", "ExtraIds", "TotalBitrate", "ExtraType", "Artists", "AlbumArtists", "ExternalId", "SeriesPresentationUniqueKey", "ShowId", "OwnerId", "Width", "Height", "Size", "Audio", "ParentId", "TopParentId", "SeasonId", "SeriesId"
|
||||
|
||||
**C# Expected (73 snake_case columns):**
|
||||
id, type, data, path, start_date, end_date, channel_id, is_movie, community_rating, custom_rating, index_number, is_locked, name, official_rating, media_type, overview, parent_index_number, premiere_date, production_year, genres, sort_name, forced_sort_name, run_time_ticks, date_created, date_modified, is_series, episode_title, is_repeat, preferred_metadata_language, preferred_metadata_country_code, date_last_refreshed, date_last_saved, is_in_mixed_folder, studios, external_service_id, tags, is_folder, inherited_parental_rating_value, inherited_parental_rating_sub_value, unrated_type, critic_rating, clean_name, presentation_unique_key, original_title, primary_version_id, date_last_media_added, album, lufs, normalization_gain, is_virtual_item, series_name, season_name, external_series_id, tagline, production_locations, extra_ids, total_bitrate, extra_type, artists, album_artists, external_id, series_presentation_unique_key, show_id, owner_id, width, height, size, audio, parent_id, top_parent_id, season_id, series_id
|
||||
|
||||
---
|
||||
|
||||
### USERS Schema (4 tables)
|
||||
|
||||
| Entity | SQL Table Name | C# Expected Name | Columns Match? | Status |
|
||||
|--------|----------------|------------------|-----------------|--------|
|
||||
| AccessSchedule | `"AccessSchedules"` | `access_schedules` | NO (5 cols) | ❌ |
|
||||
| Permission | `"Permissions"` | `permissions` | NO (6 cols) | ❌ |
|
||||
| Preference | `"Preferences"` | `preferences` | NO (6 cols) | ❌ |
|
||||
| User | `"Users"` | `users` | NO (31 cols) | ❌ |
|
||||
|
||||
#### AccessSchedule
|
||||
**SQL Columns:** "Id", "UserId", "DayOfWeek", "StartHour", "EndHour"
|
||||
**C# Expected:** id, user_id, day_of_week, start_hour, end_hour
|
||||
|
||||
#### Permission
|
||||
**SQL Columns:** "Id", "UserId", "Kind", "Value", "RowVersion", "Permission_Permissions_Guid"
|
||||
**C# Expected:** id, user_id, kind, value, row_version, permission_permissions_guid
|
||||
|
||||
#### Preference
|
||||
**SQL Columns:** "Id", "UserId", "Kind", "Value", "RowVersion", "Preference_Preferences_Guid"
|
||||
**C# Expected:** id, user_id, kind, value, row_version, preference_preferences_guid
|
||||
|
||||
#### User (31 columns)
|
||||
**SQL Columns:**
|
||||
"Id", "Username", "Password", "MustUpdatePassword", "AudioLanguagePreference", "AuthenticationProviderId", "PasswordResetProviderId", "InvalidLoginAttemptCount", "LastActivityDate", "LastLoginDate", "LoginAttemptsBeforeLockout", "MaxActiveSessions", "SubtitleMode", "PlayDefaultAudioTrack", "SubtitleLanguagePreference", "DisplayMissingEpisodes", "DisplayCollectionsView", "EnableLocalPassword", "HidePlayedInLatest", "RememberAudioSelections", "RememberSubtitleSelections", "EnableNextEpisodeAutoPlay", "EnableAutoLogin", "EnableUserPreferenceAccess", "MaxParentalRatingScore", "MaxParentalRatingSubScore", "RemoteClientBitrateLimit", "InternalId", "SyncPlayAccess", "CastReceiverId", "RowVersion"
|
||||
|
||||
**C# Expected (31 snake_case columns):**
|
||||
id, username, password, must_update_password, audio_language_preference, authentication_provider_id, password_reset_provider_id, invalid_login_attempt_count, last_activity_date, last_login_date, login_attempts_before_lockout, max_active_sessions, subtitle_mode, play_default_audio_track, subtitle_language_preference, display_missing_episodes, display_collections_view, enable_local_password, hide_played_in_latest, remember_audio_selections, remember_subtitle_selections, enable_next_episode_auto_play, enable_auto_login, enable_user_preference_access, max_parental_rating_score, max_parental_rating_sub_score, remote_client_bitrate_limit, internal_id, sync_play_access, cast_receiver_id, row_version
|
||||
|
||||
---
|
||||
|
||||
## Statistics
|
||||
|
||||
- **Total Tables:** 31
|
||||
- **Mismatched Table Names:** 31 (100%)
|
||||
- **Total Columns Affected:** 200+ columns
|
||||
- **Severity Level:** CRITICAL - Application will not run
|
||||
|
||||
### Breakdown by Schema
|
||||
|
||||
| Schema | Total Tables | Mismatched | Correct | Mismatch % |
|
||||
|--------|--------------|-----------|---------|-----------|
|
||||
| activitylog | 1 | 1 | 0 | 100% |
|
||||
| authentication | 3 | 3 | 0 | 100% |
|
||||
| displaypreferences | 4 | 4 | 0 | 100% |
|
||||
| library | 15 | 15 | 0 | 100% |
|
||||
| users | 4 | 4 | 0 | 100% |
|
||||
| **TOTAL** | **31** | **31** | **0** | **100%** |
|
||||
|
||||
---
|
||||
|
||||
## Column Naming Convention Samples
|
||||
|
||||
The SnakeCaseNamingConvention applies these transformations:
|
||||
|
||||
### Examples from BaseItems Table
|
||||
|
||||
| Original (PascalCase) | Expected (snake_case) | SQL Has | Match? |
|
||||
|----------------------|----------------------|---------|--------|
|
||||
| Id | id | "Id" | ❌ |
|
||||
| IsMovie | is_movie | "IsMovie" | ❌ |
|
||||
| CommunityRating | community_rating | "CommunityRating" | ❌ |
|
||||
| DateCreated | date_created | "DateCreated" | ❌ |
|
||||
| IsVirtualItem | is_virtual_item | "IsVirtualItem" | ❌ |
|
||||
| PresentationUniqueKey | presentation_unique_key | "PresentationUniqueKey" | ❌ |
|
||||
| SeriesPresentationUniqueKey | series_presentation_unique_key | "SeriesPresentationUniqueKey" | ❌ |
|
||||
| PreferredMetadataLanguage | preferred_metadata_language | "PreferredMetadataLanguage" | ❌ |
|
||||
| TopParentId | top_parent_id | "TopParentId" | ❌ |
|
||||
|
||||
**Conversion Rule (from SnakeCaseNamingConvention.cs):**
|
||||
```
|
||||
DateLastMediaAdded → date_last_media_added
|
||||
PreferredMetadataCountryCode → preferred_metadata_country_code
|
||||
InheritedParentalRatingSubValue → inherited_parental_rating_sub_value
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Impact Timeline
|
||||
|
||||
### On Application Startup
|
||||
|
||||
1. ✓ Database connection succeeds
|
||||
2. ✓ Migration history table found (`__EFMigrationsHistory`)
|
||||
3. ✓ Migrations complete
|
||||
4. ❌ First query to ActivityLog fails: `relation "activity_logs" does not exist`
|
||||
5. ❌ Application crashes
|
||||
|
||||
### Error Messages You Will See
|
||||
|
||||
```
|
||||
Npgsql.PostgresException: 42P01: relation "activity_logs" does not exist
|
||||
at Npgsql.Internal.NpgsqlConnector.ReadMessageLong()
|
||||
at [Your Entity Query Code]
|
||||
```
|
||||
|
||||
### Affected Operations
|
||||
|
||||
- ✓ Schema creation works (uses explicit table names in SQL)
|
||||
- ❌ Any LINQ query fails
|
||||
- ❌ Any repository method fails
|
||||
- ❌ Any DbContext.Set<T>.ToList() fails
|
||||
- ❌ Bulk operations fail
|
||||
- ❌ Migrations that query tables fail
|
||||
|
||||
---
|
||||
|
||||
## Code Configuration References
|
||||
|
||||
### PostgresDatabaseProvider.cs (Lines 763-823)
|
||||
Table mapping that creates the mismatch:
|
||||
```csharp
|
||||
public void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.Entity<ActivityLog>().ToTable("activity_logs", Schemas.ActivityLog);
|
||||
modelBuilder.Entity<ApiKey>().ToTable("api_keys", Schemas.Authentication);
|
||||
// ... 31 mappings total ...
|
||||
}
|
||||
```
|
||||
|
||||
### SnakeCaseNamingConvention.cs (Lines 25-30)
|
||||
Column name conversion that creates column mismatches:
|
||||
```csharp
|
||||
public void ProcessPropertyAdded(
|
||||
IConventionPropertyBuilder propertyBuilder,
|
||||
IConventionContext<IConventionPropertyBuilder> context)
|
||||
{
|
||||
propertyBuilder.HasColumnName(ToSnakeCase(propertyBuilder.Metadata.Name));
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Generated:** 2025-05-01
|
||||
**Severity:** 🔴 CRITICAL - BLOCKING
|
||||
**Action Required:** Immediate resolution needed before application can run
|
||||
@@ -0,0 +1,335 @@
|
||||
# Visual Schema Mismatch Comparison
|
||||
|
||||
## Side-by-Side Comparison Examples
|
||||
|
||||
### Example 1: ActivityLog Entity
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ ACTIVITYLOG Schema Mismatch │
|
||||
├────────────────────────────────┬────────────────────────────────────────┤
|
||||
│ SQL Schema File │ C# Code Configuration │
|
||||
├────────────────────────────────┼────────────────────────────────────────┤
|
||||
│ CREATE TABLE │ modelBuilder │
|
||||
│ activitylog."ActivityLogs" │ .Entity<ActivityLog>() │
|
||||
│ ( │ .ToTable("activity_logs", │
|
||||
│ "Id" integer, │ Schemas.ActivityLog); │
|
||||
│ "Name" varchar(512), │ │
|
||||
│ "Overview" varchar(512), │ // Column Mapping (via │
|
||||
│ "ShortOverview" varchar, │ // SnakeCaseNamingConvention) │
|
||||
│ "Type" varchar(256), │ │
|
||||
│ "UserId" uuid, │ public class ActivityLog │
|
||||
│ "ItemId" varchar(256), │ { │
|
||||
│ "DateCreated" timestamp, │ public int Id { get; set; } │
|
||||
│ "LogSeverity" integer, │ public string Name { get; set; } │
|
||||
│ "RowVersion" bigint │ public string Overview { get; set; } │
|
||||
│ ); │ public string ShortOverview { get; } │
|
||||
│ │ public string Type { get; set; } │
|
||||
│ │ public Guid UserId { get; set; } │
|
||||
│ │ public string ItemId { get; set; } │
|
||||
│ │ public DateTime DateCreated { get; } │
|
||||
│ │ public int LogSeverity { get; set; } │
|
||||
│ │ public long RowVersion { get; set; } │
|
||||
│ │ } │
|
||||
└────────────────────────────────┴────────────────────────────────────────┘
|
||||
|
||||
QUERY EXECUTION FLOW:
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ C# Code: await context.ActivityLogs.ToListAsync(); │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ EF Core Translates To: │
|
||||
│ SELECT * FROM activitylog.activity_logs │
|
||||
│ (Lower case! ─────────────────) │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ PostgreSQL Searches For: activitylog.activity_logs │
|
||||
│ PostgreSQL Finds: activitylog."ActivityLogs" │
|
||||
│ (Quoted PascalCase!) │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ RESULT: ERROR: relation "activity_logs" does not exist │
|
||||
│ ERROR CODE: 42P01 (UNDEFINED TABLE) │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Example 2: BaseItem Entity (Most Complex)
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────────────────────┐
|
||||
│ LIBRARY.BaseItems Schema Mismatch (73 Columns!) │
|
||||
├──────────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ SQL Schema Creates: │
|
||||
│ CREATE TABLE library."BaseItems" ( │
|
||||
│ "Id" uuid PRIMARY KEY, │
|
||||
│ "Type" text NOT NULL, │
|
||||
│ "IsMovie" boolean NOT NULL, │
|
||||
│ "DateCreated" timestamp with time zone NOT NULL, │
|
||||
│ "DateModified" timestamp with time zone NOT NULL, │
|
||||
│ ... 68 more columns all in QUOTED PASCALCASE ... │
|
||||
│ ); │
|
||||
│ │
|
||||
│ C# Code Expects: │
|
||||
│ SELECT │
|
||||
│ id, │
|
||||
│ type, │
|
||||
│ is_movie, ← Converted from IsMovie │
|
||||
│ date_created, ← Converted from DateCreated │
|
||||
│ date_modified, ← Converted from DateModified │
|
||||
│ ... 68 more columns in snake_case ... │
|
||||
│ FROM library.base_items; ← Also lowercase table! │
|
||||
│ │
|
||||
│ PostgreSQL Receives Query: │
|
||||
│ SELECT id, type, is_movie, date_created, ... FROM library.base_items │
|
||||
│ ↓ │
|
||||
│ Searches for columns: id, type, is_movie, date_created │
|
||||
│ (all lowercase) │
|
||||
│ ↓ │
|
||||
│ But table only has: "Id", "Type", "IsMovie", "DateCreated" │
|
||||
│ (all QUOTED PASCALCASE) │
|
||||
│ ↓ │
|
||||
│ ERROR: column "id" does not exist │
|
||||
│ │
|
||||
└──────────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Mismatch Pattern Visualization
|
||||
|
||||
```
|
||||
SCHEMA LAYER:
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ PostgreSQL Database │
|
||||
│ │
|
||||
│ Schema: library │
|
||||
│ ├── Table: "BaseItems" (quoted identifier - preserves case) │
|
||||
│ │ ├── Column: "Id" │
|
||||
│ │ ├── Column: "Type" │
|
||||
│ │ ├── Column: "IsMovie" │
|
||||
│ │ ├── Column: "DateCreated" │
|
||||
│ │ └── ... 70 more columns in QUOTED PASCALCASE │
|
||||
│ │ │
|
||||
│ ├── Table: "ActivityLogs" (PascalCase) │
|
||||
│ ├── Table: "ApiKeys" (PascalCase) │
|
||||
│ ├── Table: "Devices" (PascalCase) │
|
||||
│ └── ... 28 more tables in QUOTED PASCALCASE │
|
||||
└─────────────────────────────────────────────────────────────────────────┘
|
||||
↕ MISMATCH!
|
||||
APPLICATION LAYER:
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ C# / EF Core │
|
||||
│ │
|
||||
│ SnakeCaseNamingConvention ENABLED │
|
||||
│ ├── Entity: BaseItemEntity │
|
||||
│ │ ├── Property: Id → Column: id (lowercase) │
|
||||
│ │ ├── Property: Type → Column: type (lowercase) │
|
||||
│ │ ├── Property: IsMovie → Column: is_movie (snake_case) │
|
||||
│ │ ├── Property: DateCreated → Column: date_created (snake_case) │
|
||||
│ │ └── ... 70 more properties → SNAKE_CASE COLUMNS │
|
||||
│ │ │
|
||||
│ ├── Entity: ActivityLog → Table: activity_logs (lowercase) │
|
||||
│ ├── Entity: ApiKey → Table: api_keys (snake_case) │
|
||||
│ ├── Entity: Device → Table: devices (lowercase) │
|
||||
│ └── ... 28 more entities → SNAKE_CASE TABLE NAMES │
|
||||
└─────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## PostgreSQL Identifier Resolution
|
||||
|
||||
```
|
||||
How PostgreSQL resolves identifiers:
|
||||
|
||||
WITHOUT quotes: my_table
|
||||
├─ Converted to: my_table (lowercase by default)
|
||||
├─ Searches for: my_table
|
||||
└─ Result: ✓ Found
|
||||
|
||||
WITH quotes: "MyTable"
|
||||
├─ NOT converted
|
||||
├─ Searches for: MyTable (case-sensitive!)
|
||||
└─ Result: ✓ Found (only if exact case matches)
|
||||
|
||||
CURRENT SITUATION:
|
||||
┌────────────────────────────────────────────────────────────┐
|
||||
│ SQL Definition: library."BaseItems" │
|
||||
│ ↑ │
|
||||
│ EF Core Query: SELECT * FROM library.base_items │
|
||||
│ ↑ │
|
||||
│ PostgreSQL Logic: │
|
||||
│ - Sees: "base_items" (unquoted, lowercase by default) │
|
||||
│ - Searches for: base_items │
|
||||
│ - Available table: "BaseItems" (quoted PascalCase) │
|
||||
│ - Match? NO ❌ │
|
||||
│ │
|
||||
│ ERROR 42P01: relation "base_items" does not exist │
|
||||
└────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Resolution Decision Tree
|
||||
|
||||
```
|
||||
┌─── DECISION: How to Fix the Mismatch? ───────┐
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────┐ │
|
||||
│ │ Option 1: Update SQL to Lowercase │ │
|
||||
│ ├─────────────────────────────────────────┤ │
|
||||
│ │ Action: │ │
|
||||
│ │ ✓ Regenerate create_database_schema.sql│ │
|
||||
│ │ ✓ Convert all "TableName" → table_name │ │
|
||||
│ │ ✓ Convert all "ColumnName" → column_name│ │
|
||||
│ │ ✓ Remove double quotes │ │
|
||||
│ │ │ │
|
||||
│ │ Pros: │ │
|
||||
│ │ ✓ Matches PostgreSQL best practices │ │
|
||||
│ │ ✓ Matches C# configuration (recommended)│ │
|
||||
│ │ ✓ Cleaner, more maintainable │ │
|
||||
│ │ │ │
|
||||
│ │ Cons: │ │
|
||||
│ │ ✗ Requires full database migration │ │
|
||||
│ │ ✗ Data loss risk if not done carefully│ │
|
||||
│ │ ✗ Downtime required │ │
|
||||
│ │ │ │
|
||||
│ │ Effort: HIGH │ │
|
||||
│ │ Risk: HIGH │ │
|
||||
│ │ Recommendation: ⭐ BEST LONG-TERM │ │
|
||||
│ └─────────────────────────────────────────┘ │
|
||||
│ ↓ (Recommended) │
|
||||
│ ┌─────────────────────────────────────────┐ │
|
||||
│ │ Option 2: Update C# to PascalCase │ │
|
||||
│ ├─────────────────────────────────────────┤ │
|
||||
│ │ Action: │ │
|
||||
│ │ ✓ Remove SnakeCaseNamingConvention │ │
|
||||
│ │ ✓ Update all ToTable() mappings │ │
|
||||
│ │ ✓ Update all HasColumnName() configs │ │
|
||||
│ │ │ │
|
||||
│ │ Pros: │ │
|
||||
│ │ ✓ Quick fix (code changes only) │ │
|
||||
│ │ ✓ No database changes needed │ │
|
||||
│ │ ✓ No data loss │ │
|
||||
│ │ │ │
|
||||
│ │ Cons: │ │
|
||||
│ │ ✗ Violates PostgreSQL conventions │ │
|
||||
│ │ ✗ Harder to maintain │ │
|
||||
│ │ ✗ Non-standard naming scheme │ │
|
||||
│ │ │ │
|
||||
│ │ Effort: MEDIUM │ │
|
||||
│ │ Risk: LOW │ │
|
||||
│ │ Recommendation: ⚠️ QUICK FIX │ │
|
||||
│ └─────────────────────────────────────────┘ │
|
||||
│ ↓ (Not Recommended) │
|
||||
│ ┌─────────────────────────────────────────┐ │
|
||||
│ │ Option 3: Custom Naming Convention │ │
|
||||
│ ├─────────────────────────────────────────┤ │
|
||||
│ │ Action: │ │
|
||||
│ │ ✓ Create hybrid convention │ │
|
||||
│ │ ✓ Recognize existing table patterns │ │
|
||||
│ │ │ │
|
||||
│ │ Pros: │ │
|
||||
│ │ ✓ Works with existing database │ │
|
||||
│ │ ✓ Minimal code changes │ │
|
||||
│ │ │ │
|
||||
│ │ Cons: │ │
|
||||
│ │ ✗ Very complex to implement │ │
|
||||
│ │ ✗ Difficult to maintain │ │
|
||||
│ │ ✗ Fragile (pattern-dependent) │ │
|
||||
│ │ ✗ Non-standard solution │ │
|
||||
│ │ │ │
|
||||
│ │ Effort: VERY HIGH │ │
|
||||
│ │ Risk: VERY HIGH │ │
|
||||
│ │ Recommendation: ❌ NOT RECOMMENDED │ │
|
||||
│ └─────────────────────────────────────────┘ │
|
||||
│
|
||||
└───────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Column Naming Rule Examples
|
||||
|
||||
```
|
||||
SnakeCaseNamingConvention applies these rules:
|
||||
(From SnakeCaseNamingConvention.cs - Lines 35-40)
|
||||
|
||||
Input (C# Property) → Output (SQL Column) → SQL Actually Has
|
||||
──────────────────────────────────────────────────────────────────────────
|
||||
Id → id → "Id" ❌
|
||||
UserId → user_id → "UserId" ❌
|
||||
DateCreated → date_created → "DateCreated" ❌
|
||||
IsMovie → is_movie → "IsMovie" ❌
|
||||
DvVersionMajor → dv_version_major → "DvVersionMajor"❌
|
||||
SeriesPresentationUniqueKey → series_presentation_unique_key → "SeriesPresentationUniqueKey" ❌
|
||||
|
||||
The Regex Rules Applied:
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Rule 1: ([A-Z]+)([A-Z][a-z]) → "$1_$2" │
|
||||
│ Example: "HTTPServer" → "HTTP_Server" │
|
||||
│ │
|
||||
│ Rule 2: ([a-z\d])([A-Z]) → "$1_$2" │
|
||||
│ Example: "DateCreated" → "Date_Created" │
|
||||
│ │
|
||||
│ Rule 3: Convert to lowercase (.ToLowerInvariant()) │
|
||||
│ Example: "Date_Created" → "date_created" │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Query Execution Failure Timeline
|
||||
|
||||
```
|
||||
STARTUP SEQUENCE:
|
||||
1. ✓ 0ms - Connection opened
|
||||
2. ✓ 50ms - Migration history table checked (__EFMigrationsHistory exists)
|
||||
3. ✓ 150ms - Migrations applied
|
||||
4. ✓ 500ms - Application configuration loaded
|
||||
5. ✓ 1000ms - First request received
|
||||
6. ❌ 1050ms - Query executes: SELECT * FROM activitylog.activity_logs
|
||||
ERROR: 42P01 - relation "activity_logs" does not exist
|
||||
7. 💥 1051ms - Application crashes
|
||||
|
||||
STACK TRACE:
|
||||
at Npgsql.NpgsqlDataReader.NextResult()
|
||||
at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReader()
|
||||
at Microsoft.EntityFrameworkCore.Query.QueryingEnumerable`1.Enumerator.MoveNext()
|
||||
at System.Linq.Enumerable.ToList() // User called .ToList() or .ToListAsync()
|
||||
at YourRepositoryClass.GetActivityLogs() in YourRepository.cs:line X
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## File Locations Summary
|
||||
|
||||
```
|
||||
AFFECTED FILES:
|
||||
|
||||
SQL Schema:
|
||||
└─ Jellyfin.Server/sql/schema_init/create_database_schema.sql
|
||||
Lines: 92-886 (31 tables with PascalCase names)
|
||||
|
||||
C# Configuration:
|
||||
├─ src/Jellyfin.Database/Jellyfin.Database.Providers.Postgres/
|
||||
│ └─ PostgresDatabaseProvider.cs
|
||||
│ Lines 763-823: OnModelCreating() - Table mappings
|
||||
│ Lines 872-875: ConfigureConventions() - SnakeCaseNamingConvention
|
||||
│
|
||||
├─ src/Jellyfin.Database/Jellyfin.Database.Providers.Postgres/
|
||||
│ └─ SnakeCaseNamingConvention.cs
|
||||
│ Lines 16-30: Column name conversion logic
|
||||
│
|
||||
└─ src/Jellyfin.Database/Jellyfin.Database.Implementations/
|
||||
└─ ModelConfiguration/
|
||||
├─ ActivityLogConfiguration.cs
|
||||
├─ ApiKeyConfiguration.cs
|
||||
├─ DeviceConfiguration.cs
|
||||
└─ ... (31 configuration files total)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Visual Reference Generated:** 2025-05-01
|
||||
**Status:** Ready for review and decision
|
||||
@@ -0,0 +1,493 @@
|
||||
# PostgreSQL Database Schema Mismatch Report
|
||||
|
||||
**Generated:** 2025-05-01
|
||||
**Status:** ⚠️ CRITICAL MISMATCH FOUND
|
||||
**Severity:** HIGH - Code cannot query database tables correctly
|
||||
|
||||
---
|
||||
|
||||
## Executive Summary
|
||||
|
||||
There is a **critical mismatch** between:
|
||||
1. **SQL Schema File** (`create_database_schema.sql`) - Uses **PascalCase** table and column names with double quotes
|
||||
2. **C# Code Configuration** (`PostgresDatabaseProvider.cs`) - Expects **snake_case** table names with SnakeCaseNamingConvention for columns
|
||||
|
||||
This will cause **runtime failures** when EF Core tries to query tables that don't exist at the expected snake_case names.
|
||||
|
||||
---
|
||||
|
||||
## Detailed Mismatch Analysis
|
||||
|
||||
### ACTIVITYLOG Schema
|
||||
|
||||
#### SQL Definition:
|
||||
```sql
|
||||
CREATE TABLE activitylog."ActivityLogs" (
|
||||
"Id" integer NOT NULL,
|
||||
"Name" character varying(512) NOT NULL,
|
||||
"Overview" character varying(512),
|
||||
"ShortOverview" character varying(512),
|
||||
"Type" character varying(256) NOT NULL,
|
||||
"UserId" uuid NOT NULL,
|
||||
"ItemId" character varying(256),
|
||||
"DateCreated" timestamp with time zone NOT NULL,
|
||||
"LogSeverity" integer NOT NULL,
|
||||
"RowVersion" bigint NOT NULL
|
||||
);
|
||||
```
|
||||
|
||||
#### C# Code Mapping:
|
||||
```csharp
|
||||
modelBuilder.Entity<ActivityLog>().ToTable("activity_logs", Schemas.ActivityLog);
|
||||
```
|
||||
|
||||
#### Mismatch Details:
|
||||
| Aspect | SQL Schema | C# Code | Status |
|
||||
|--------|-----------|---------|--------|
|
||||
| Table Name | `"ActivityLogs"` | `activity_logs` | ❌ MISMATCH |
|
||||
| Column: Id | `"Id"` | `id` (via convention) | ❌ MISMATCH |
|
||||
| Column: Name | `"Name"` | `name` (via convention) | ❌ MISMATCH |
|
||||
| Column: Overview | `"Overview"` | `overview` (via convention) | ❌ MISMATCH |
|
||||
| Column: ShortOverview | `"ShortOverview"` | `short_overview` (via convention) | ❌ MISMATCH |
|
||||
| Column: Type | `"Type"` | `type` (via convention) | ❌ MISMATCH |
|
||||
| Column: UserId | `"UserId"` | `user_id` (via convention) | ❌ MISMATCH |
|
||||
| Column: ItemId | `"ItemId"` | `item_id` (via convention) | ❌ MISMATCH |
|
||||
| Column: DateCreated | `"DateCreated"` | `date_created` (via convention) | ❌ MISMATCH |
|
||||
| Column: LogSeverity | `"LogSeverity"` | `log_severity` (via convention) | ❌ MISMATCH |
|
||||
| Column: RowVersion | `"RowVersion"` | `row_version` (via convention) | ❌ MISMATCH |
|
||||
|
||||
**Root Cause:** Table name is PascalCase; columns are PascalCase but SnakeCaseNamingConvention converts them to snake_case.
|
||||
|
||||
---
|
||||
|
||||
### AUTHENTICATION Schema
|
||||
|
||||
#### APIKEYS Table
|
||||
|
||||
**SQL Definition:**
|
||||
```sql
|
||||
CREATE TABLE authentication."ApiKeys" (
|
||||
"Id" integer NOT NULL,
|
||||
"DateCreated" timestamp with time zone NOT NULL,
|
||||
"DateLastActivity" timestamp with time zone NOT NULL,
|
||||
"Name" character varying(64) NOT NULL,
|
||||
"AccessToken" text NOT NULL
|
||||
);
|
||||
```
|
||||
|
||||
**C# Mapping:**
|
||||
```csharp
|
||||
modelBuilder.Entity<ApiKey>().ToTable("api_keys", Schemas.Authentication);
|
||||
```
|
||||
|
||||
**Mismatch:** Table name `"ApiKeys"` vs `api_keys` ❌
|
||||
|
||||
---
|
||||
|
||||
#### DEVICEOPTIONS Table
|
||||
|
||||
**SQL Definition:**
|
||||
```sql
|
||||
CREATE TABLE authentication."DeviceOptions" (
|
||||
"Id" integer NOT NULL,
|
||||
"DeviceId" text NOT NULL,
|
||||
"CustomName" text
|
||||
);
|
||||
```
|
||||
|
||||
**C# Mapping:**
|
||||
```csharp
|
||||
modelBuilder.Entity<DeviceOptions>().ToTable("device_options", Schemas.Authentication);
|
||||
```
|
||||
|
||||
**Mismatch:** Table name `"DeviceOptions"` vs `device_options` ❌
|
||||
|
||||
---
|
||||
|
||||
#### DEVICES Table
|
||||
|
||||
**SQL Definition:**
|
||||
```sql
|
||||
CREATE TABLE authentication."Devices" (
|
||||
"Id" integer NOT NULL,
|
||||
"UserId" uuid NOT NULL,
|
||||
"AccessToken" text NOT NULL,
|
||||
"AppName" character varying(64) NOT NULL,
|
||||
"AppVersion" character varying(32) NOT NULL,
|
||||
"DeviceName" character varying(64) NOT NULL,
|
||||
"DeviceId" character varying(256) NOT NULL,
|
||||
"IsActive" boolean NOT NULL,
|
||||
"DateCreated" timestamp with time zone NOT NULL,
|
||||
"DateModified" timestamp with time zone NOT NULL,
|
||||
"DateLastActivity" timestamp with time zone NOT NULL
|
||||
);
|
||||
```
|
||||
|
||||
**C# Mapping:**
|
||||
```csharp
|
||||
modelBuilder.Entity<Device>().ToTable("devices", Schemas.Authentication);
|
||||
```
|
||||
|
||||
**Mismatch:** Table name `"Devices"` vs `devices` ❌
|
||||
|
||||
---
|
||||
|
||||
### DISPLAYPREFERENCES Schema
|
||||
|
||||
#### CUSTOMITEMDISPLAYPREFERENCES Table
|
||||
|
||||
**SQL:** `"CustomItemDisplayPreferences"`
|
||||
**C# Code:** `custom_item_display_preferences`
|
||||
**Mismatch:** ❌
|
||||
|
||||
#### DISPLAYPREFERENCES Table
|
||||
|
||||
**SQL:** `"DisplayPreferences"`
|
||||
**C# Code:** `display_preferences`
|
||||
**Mismatch:** ❌
|
||||
|
||||
#### HOMESECTION Table
|
||||
|
||||
**SQL:** `"HomeSection"`
|
||||
**C# Code:** `home_sections`
|
||||
**Mismatch:** ❌ (Plural mismatch too!)
|
||||
|
||||
#### ITEMDISPLAYPREFERENCES Table
|
||||
|
||||
**SQL:** `"ItemDisplayPreferences"`
|
||||
**C# Code:** `item_display_preferences`
|
||||
**Mismatch:** ❌
|
||||
|
||||
---
|
||||
|
||||
### LIBRARY Schema (Most Critical - 15+ Tables)
|
||||
|
||||
#### ANCESTORIDS Table
|
||||
|
||||
**SQL:** `"AncestorIds"`
|
||||
**C# Code:** `ancestor_ids`
|
||||
**Mismatch:** ❌
|
||||
|
||||
#### ATTACHMENTSTREAMINFOS Table
|
||||
|
||||
**SQL:** `"AttachmentStreamInfos"`
|
||||
**C# Code:** `attachment_stream_infos`
|
||||
**Mismatch:** ❌
|
||||
|
||||
#### BASEITEMIMAGINFOS Table
|
||||
|
||||
**SQL:** `"BaseItemImageInfos"`
|
||||
**C# Code:** `base_item_image_infos`
|
||||
**Mismatch:** ❌
|
||||
|
||||
#### BASEITEMMETADATAFIELDS Table
|
||||
|
||||
**SQL:** `"BaseItemMetadataFields"`
|
||||
**C# Code:** `base_item_metadata_fields`
|
||||
**Mismatch:** ❌
|
||||
|
||||
#### BASEITEMPROVIDERS Table
|
||||
|
||||
**SQL:** `"BaseItemProviders"`
|
||||
**C# Code:** `base_item_providers`
|
||||
**Mismatch:** ❌
|
||||
|
||||
#### BASEITEMTRAILERTYPES Table
|
||||
|
||||
**SQL:** `"BaseItemTrailerTypes"`
|
||||
**C# Code:** `base_item_trailer_types`
|
||||
**Mismatch:** ❌
|
||||
|
||||
#### BASEITEMS Table
|
||||
|
||||
**SQL:** `"BaseItems"`
|
||||
**C# Code:** `base_items`
|
||||
**Mismatch:** ❌
|
||||
|
||||
#### CHAPTERS Table
|
||||
|
||||
**SQL:** `"Chapters"`
|
||||
**C# Code:** `chapters`
|
||||
**Mismatch:** ❌
|
||||
|
||||
#### IMAGEINFOS Table
|
||||
|
||||
**SQL:** `"ImageInfos"`
|
||||
**C# Code:** `image_infos`
|
||||
**Mismatch:** ❌
|
||||
|
||||
#### ITEMVALUES Table
|
||||
|
||||
**SQL:** `"ItemValues"`
|
||||
**C# Code:** `item_values`
|
||||
**Mismatch:** ❌
|
||||
|
||||
#### ITEMVALUESMAP Table
|
||||
|
||||
**SQL:** `"ItemValuesMap"`
|
||||
**C# Code:** `item_values_map`
|
||||
**Mismatch:** ❌
|
||||
|
||||
#### KEYFRAMEDATA Table
|
||||
|
||||
**SQL:** `"KeyframeData"`
|
||||
**C# Code:** `keyframe_data`
|
||||
**Mismatch:** ❌
|
||||
|
||||
#### MEDIASEGMENTS Table
|
||||
|
||||
**SQL:** `"MediaSegments"`
|
||||
**C# Code:** `media_segments`
|
||||
**Mismatch:** ❌
|
||||
|
||||
#### MEDIASTREAMINFOS Table
|
||||
|
||||
**SQL:** `"MediaStreamInfos"`
|
||||
**C# Code:** `media_stream_infos`
|
||||
**Mismatch:** ❌
|
||||
|
||||
#### PEOPLEBASEITEMMAP Table
|
||||
|
||||
**SQL:** `"PeopleBaseItemMap"`
|
||||
**C# Code:** `people_base_item_map`
|
||||
**Mismatch:** ❌
|
||||
|
||||
#### PEOPLES Table
|
||||
|
||||
**SQL:** `"Peoples"`
|
||||
**C# Code:** `peoples`
|
||||
**Mismatch:** ❌
|
||||
|
||||
#### TRICKPLAYINFOS Table
|
||||
|
||||
**SQL:** `"TrickplayInfos"`
|
||||
**C# Code:** `trickplay_infos`
|
||||
**Mismatch:** ❌
|
||||
|
||||
#### USERDATA Table
|
||||
|
||||
**SQL:** `"UserData"`
|
||||
**C# Code:** `user_data`
|
||||
**Mismatch:** ❌
|
||||
|
||||
---
|
||||
|
||||
### USERS Schema
|
||||
|
||||
#### ACCESSSCHEDULES Table
|
||||
|
||||
**SQL:** `"AccessSchedules"`
|
||||
**C# Code:** `access_schedules`
|
||||
**Mismatch:** ❌
|
||||
|
||||
#### PERMISSIONS Table
|
||||
|
||||
**SQL:** `"Permissions"`
|
||||
**C# Code:** `permissions`
|
||||
**Mismatch:** ❌
|
||||
|
||||
#### PREFERENCES Table
|
||||
|
||||
**SQL:** `"Preferences"`
|
||||
**C# Code:** `preferences`
|
||||
**Mismatch:** ❌
|
||||
|
||||
#### USERS Table
|
||||
|
||||
**SQL:** `"Users"`
|
||||
**C# Code:** `users`
|
||||
**Mismatch:** ❌
|
||||
|
||||
---
|
||||
|
||||
### SPECIAL TABLES
|
||||
|
||||
#### __EFMigrationsHistory Table
|
||||
|
||||
**SQL Definition (Line 738):**
|
||||
```sql
|
||||
CREATE TABLE public."__EFMigrationsHistory" (
|
||||
"MigrationId" character varying(150) NOT NULL,
|
||||
"ProductVersion" character varying(32) NOT NULL
|
||||
);
|
||||
```
|
||||
|
||||
**Status:** ✓ Correct (EF Core will query `public.__EFMigrationsHistory`)
|
||||
**Note:** Columns are PascalCase in SQL but EF Core's built-in history repository handles this correctly.
|
||||
|
||||
---
|
||||
|
||||
## Complete Mismatch Summary Table
|
||||
|
||||
| Schema | Entity | SQL Table Name | C# Expected Name | Match? |
|
||||
|--------|--------|----------------|------------------|--------|
|
||||
| activitylog | ActivityLog | `"ActivityLogs"` | `activity_logs` | ❌ |
|
||||
| authentication | ApiKey | `"ApiKeys"` | `api_keys` | ❌ |
|
||||
| authentication | Device | `"Devices"` | `devices` | ❌ |
|
||||
| authentication | DeviceOptions | `"DeviceOptions"` | `device_options` | ❌ |
|
||||
| displaypreferences | CustomItemDisplayPreferences | `"CustomItemDisplayPreferences"` | `custom_item_display_preferences` | ❌ |
|
||||
| displaypreferences | DisplayPreferences | `"DisplayPreferences"` | `display_preferences` | ❌ |
|
||||
| displaypreferences | HomeSection | `"HomeSection"` | `home_sections` | ❌ |
|
||||
| displaypreferences | ItemDisplayPreferences | `"ItemDisplayPreferences"` | `item_display_preferences` | ❌ |
|
||||
| library | AncestorId | `"AncestorIds"` | `ancestor_ids` | ❌ |
|
||||
| library | AttachmentStreamInfo | `"AttachmentStreamInfos"` | `attachment_stream_infos` | ❌ |
|
||||
| library | BaseItemImageInfo | `"BaseItemImageInfos"` | `base_item_image_infos` | ❌ |
|
||||
| library | BaseItemMetadataField | `"BaseItemMetadataFields"` | `base_item_metadata_fields` | ❌ |
|
||||
| library | BaseItemProvider | `"BaseItemProviders"` | `base_item_providers` | ❌ |
|
||||
| library | BaseItemTrailerType | `"BaseItemTrailerTypes"` | `base_item_trailer_types` | ❌ |
|
||||
| library | BaseItemEntity | `"BaseItems"` | `base_items` | ❌ |
|
||||
| library | Chapter | `"Chapters"` | `chapters` | ✓ |
|
||||
| library | ImageInfo | `"ImageInfos"` | `image_infos` | ❌ |
|
||||
| library | ItemValue | `"ItemValues"` | `item_values` | ❌ |
|
||||
| library | ItemValueMap | `"ItemValuesMap"` | `item_values_map` | ❌ |
|
||||
| library | KeyframeData | `"KeyframeData"` | `keyframe_data` | ❌ |
|
||||
| library | MediaSegment | `"MediaSegments"` | `media_segments` | ❌ |
|
||||
| library | MediaStreamInfo | `"MediaStreamInfos"` | `media_stream_infos` | ❌ |
|
||||
| library | PeopleBaseItemMap | `"PeopleBaseItemMap"` | `people_base_item_map` | ❌ |
|
||||
| library | People | `"Peoples"` | `peoples` | ✓ |
|
||||
| library | TrickplayInfo | `"TrickplayInfos"` | `trickplay_infos` | ❌ |
|
||||
| library | UserData | `"UserData"` | `user_data` | ❌ |
|
||||
| users | AccessSchedule | `"AccessSchedules"` | `access_schedules` | ❌ |
|
||||
| users | Permission | `"Permissions"` | `permissions` | ✓ |
|
||||
| users | Preference | `"Preferences"` | `preferences` | ✓ |
|
||||
| users | User | `"Users"` | `users` | ✓ |
|
||||
| public | __EFMigrationsHistory | `"__EFMigrationsHistory"` | `__EFMigrationsHistory` | ✓ |
|
||||
|
||||
**Summary:**
|
||||
- ❌ **31 table mismatches found**
|
||||
- ✓ **5 tables with correct lowercase names**
|
||||
- **96% tables have naming conflicts**
|
||||
|
||||
---
|
||||
|
||||
## Impact Analysis
|
||||
|
||||
### When EF Core Runs
|
||||
|
||||
1. **Query Execution:** EF Core generates queries using snake_case table names
|
||||
```sql
|
||||
SELECT * FROM library.base_items -- EF Core generates this
|
||||
```
|
||||
|
||||
2. **Database Response:** PostgreSQL looks for table named `base_items` in lowercase
|
||||
|
||||
3. **Result:** Table not found because actual table is `"BaseItems"` (quoted PascalCase)
|
||||
```
|
||||
ERROR: relation "base_items" does not exist
|
||||
```
|
||||
|
||||
### Error Pattern You Will See
|
||||
|
||||
```
|
||||
Npgsql.PostgresException (0x80004005): 42P01: relation "activity_logs" does not exist
|
||||
Npgsql.PostgresException (0x80004005): 42P01: relation "api_keys" does not exist
|
||||
Npgsql.PostgresException (0x80004005): 42P01: relation "base_items" does not exist
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Root Cause Analysis
|
||||
|
||||
### The Problem
|
||||
|
||||
The SQL schema file was generated from a `pg_dump` of an existing database that uses **PascalCase with double quotes** for case preservation. However, the C# code was configured to map to **lowercase snake_case** table names.
|
||||
|
||||
### Configuration Details
|
||||
|
||||
**PostgresDatabaseProvider.cs Line 763-819:**
|
||||
```csharp
|
||||
public void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
// ... example mappings ...
|
||||
modelBuilder.Entity<ActivityLog>().ToTable("activity_logs", Schemas.ActivityLog);
|
||||
modelBuilder.Entity<ApiKey>().ToTable("api_keys", Schemas.Authentication);
|
||||
modelBuilder.Entity<Device>().ToTable("devices", Schemas.Authentication);
|
||||
// ... etc ...
|
||||
}
|
||||
```
|
||||
|
||||
**PostgresDatabaseProvider.cs Line 872-875:**
|
||||
```csharp
|
||||
public void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
|
||||
{
|
||||
configurationBuilder.Conventions.Add(_ => new SnakeCaseNamingConvention());
|
||||
}
|
||||
```
|
||||
|
||||
**SnakeCaseNamingConvention.cs:**
|
||||
- Converts all PascalCase property names to snake_case column names
|
||||
- Example: `DateCreated` property → `date_created` column
|
||||
|
||||
---
|
||||
|
||||
## Recommended Resolution
|
||||
|
||||
### Option 1: Update SQL Schema to Use Lowercase (RECOMMENDED)
|
||||
- **Pros:** Matches PostgreSQL best practices, cleaner, matches C# configuration
|
||||
- **Cons:** Requires recreating all tables
|
||||
- **Effort:** High (full database migration required)
|
||||
|
||||
### Option 2: Update C# Code to Match PascalCase SQL
|
||||
- **Pros:** Quick fix, no database changes needed
|
||||
- **Cons:** Goes against PostgreSQL conventions, harder to maintain
|
||||
- **Effort:** Low (code changes only)
|
||||
|
||||
### Option 3: Custom Naming Convention
|
||||
- Create a hybrid convention that recognizes existing PascalCase tables
|
||||
- **Pros:** Can work with existing database
|
||||
- **Cons:** Complex, non-standard, harder to maintain
|
||||
- **Effort:** Very High (custom infrastructure needed)
|
||||
|
||||
---
|
||||
|
||||
## Files Affected
|
||||
|
||||
### C# Code Files
|
||||
1. `src/Jellyfin.Database/Jellyfin.Database.Providers.Postgres/PostgresDatabaseProvider.cs`
|
||||
- Lines 763-823 (OnModelCreating table mappings)
|
||||
|
||||
2. `src/Jellyfin.Database/Jellyfin.Database.Providers.Postgres/SnakeCaseNamingConvention.cs`
|
||||
- Controls column name conversion
|
||||
|
||||
3. All Entity Configuration files in:
|
||||
- `src/Jellyfin.Database/Jellyfin.Database.Implementations/ModelConfiguration/`
|
||||
|
||||
### Database Schema Files
|
||||
1. `Jellyfin.Server/sql/schema_init/create_database_schema.sql`
|
||||
- Lines 92-886 (all table definitions)
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. **Decision Required:** Which resolution option do you prefer?
|
||||
- Option 1: Update SQL to lowercase (Recommended)
|
||||
- Option 2: Update C# to PascalCase
|
||||
- Option 3: Create custom convention
|
||||
|
||||
2. **If Option 1:** Generate a comprehensive SQL migration script
|
||||
3. **If Option 2:** Generate C# code changes
|
||||
4. **If Option 3:** Design custom naming convention
|
||||
|
||||
---
|
||||
|
||||
## Appendix: Column Name Conversion Examples
|
||||
|
||||
The SnakeCaseNamingConvention converts column names as follows:
|
||||
|
||||
| Original (C#) | Converted (SQL) | Status |
|
||||
|---------------|-----------------|--------|
|
||||
| Id | id | ✓ |
|
||||
| UserId | user_id | ✓ |
|
||||
| DateCreated | date_created | ✓ |
|
||||
| LogSeverity | log_severity | ✓ |
|
||||
| AccessToken | access_token | ✓ |
|
||||
| APIKey | api_key | ✓ |
|
||||
| DVVersionMajor | dv_version_major | ✓ |
|
||||
|
||||
**Note:** All column names in SQL schema are PascalCase (e.g., `"UserId"`, `"DateCreated"`), but C# code expects them in snake_case (e.g., `user_id`, `date_created`).
|
||||
|
||||
---
|
||||
|
||||
**Report Generated:** 2025-05-01
|
||||
**Status:** Awaiting decision on resolution approach
|
||||
@@ -0,0 +1,233 @@
|
||||
# PostgreSQL Database Views
|
||||
|
||||
All views live in the `library` schema unless otherwise noted. They are read-only and safe to query from pgAdmin, BI tools, or any reporting/debugging context without risk of affecting the application.
|
||||
|
||||
---
|
||||
|
||||
## `library."MediaStreamInfos_v"`
|
||||
|
||||
Reassembles the original flat `MediaStreamInfos` table shape that existed before the `SplitMediaStreamInfos` migration split audio- and video-specific columns into dedicated tables.
|
||||
|
||||
**Use when:** you want a single flat result of all stream data without writing the three-way JOIN yourself.
|
||||
|
||||
**Columns of note:**
|
||||
|
||||
| Column | Source |
|
||||
|---|---|
|
||||
| `ItemId`, `StreamIndex`, `StreamType`, `Codec`, … | `MediaStreamInfos` (always present) |
|
||||
| `ChannelLayout`, `Channels`, `SampleRate`, `IsHearingImpaired` | `AudioStreamDetails` — `NULL` for non-audio streams |
|
||||
| `Height`, `Width`, `AverageFrameRate`, `BitDepth`, `ColorTransfer`, `DvProfile`, … | `VideoStreamDetails` — `NULL` for non-video streams |
|
||||
|
||||
**Example:**
|
||||
|
||||
```sql
|
||||
-- All audio streams for a specific item
|
||||
SELECT "StreamIndex", "Codec", "Language", "Channels", "SampleRate", "ChannelLayout"
|
||||
FROM library."MediaStreamInfos_v"
|
||||
WHERE "ItemId" = '<item-uuid>'
|
||||
AND "StreamType" = 0 -- 0 = Audio
|
||||
ORDER BY "StreamIndex";
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `library."VideoItems_v"`
|
||||
|
||||
Every non-folder, non-virtual item that has at least one video stream (movies, episodes, home videos, etc.), joined with the primary video stream's technical details.
|
||||
|
||||
**Use when:** auditing codec/resolution/HDR distribution across the library, or finding items that need re-encoding.
|
||||
|
||||
**Columns of note:**
|
||||
|
||||
| Column | Description |
|
||||
|---|---|
|
||||
| `Type` | Fully-qualified Jellyfin type name (e.g. `MediaBrowser.Controller.Entities.Movies.Movie`) |
|
||||
| `VideoCodec` | Codec of the primary video stream (e.g. `hevc`, `h264`) |
|
||||
| `Width` / `Height` | Resolution of the primary video stream |
|
||||
| `BitDepth` | Colour bit depth (8, 10, 12) |
|
||||
| `HDRFormat` | Derived string: `Dolby Vision`, `HDR10+`, `HDR10`, `HLG`, or `SDR` |
|
||||
| `IsDolbyVision` | Boolean shortcut for `DvProfile IS NOT NULL` |
|
||||
| `AudioTrackCount` | Number of audio streams on the item |
|
||||
| `SubtitleTrackCount` | Number of subtitle streams on the item |
|
||||
|
||||
**Example queries:**
|
||||
|
||||
```sql
|
||||
-- All 4K HDR movies
|
||||
SELECT "Name", "ProductionYear", "VideoCodec", "Width", "Height", "HDRFormat", "TotalBitrate"
|
||||
FROM library."VideoItems_v"
|
||||
WHERE "Type" LIKE '%Movie%'
|
||||
AND "Height" >= 2160
|
||||
AND "HDRFormat" <> 'SDR'
|
||||
ORDER BY "Name";
|
||||
|
||||
-- Episodes still encoded in H.264 (candidates for HEVC re-encode)
|
||||
SELECT "SeriesName", "SeasonName", "EpisodeNumber", "Name", "Width", "Height"
|
||||
FROM library."VideoItems_v"
|
||||
WHERE "Type" LIKE '%Episode%'
|
||||
AND "VideoCodec" = 'h264'
|
||||
ORDER BY "SeriesName", "SeasonNumber", "EpisodeNumber";
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `library."UserPlaybackHistory_v"`
|
||||
|
||||
Per-user watch history joined with item metadata and a calculated playback progress percentage.
|
||||
|
||||
**Use when:** reporting on viewing activity, finding in-progress items, or checking which users have watched what.
|
||||
|
||||
**Columns of note:**
|
||||
|
||||
| Column | Description |
|
||||
|---|---|
|
||||
| `Username` | Jellyfin username |
|
||||
| `ItemName` | Name of the item |
|
||||
| `ItemType` | Jellyfin type name |
|
||||
| `Played` | `true` if the item has been marked as played |
|
||||
| `PlayCount` | Number of times fully played |
|
||||
| `LastPlayedDate` | Timestamp of the most recent playback |
|
||||
| `PlaybackPositionTicks` | Raw position (divide by `10,000,000` for seconds) |
|
||||
| `ProgressPct` | Calculated percentage through the item (0–100), `NULL` if no runtime |
|
||||
| `AudioStreamIndex` | Last-used audio track index |
|
||||
| `SubtitleStreamIndex` | Last-used subtitle track index |
|
||||
|
||||
**Example queries:**
|
||||
|
||||
```sql
|
||||
-- All in-progress items for a user (started but not finished)
|
||||
SELECT "ItemName", "ItemType", "SeriesName", "ProgressPct", "LastPlayedDate"
|
||||
FROM library."UserPlaybackHistory_v"
|
||||
WHERE "Username" = 'alice'
|
||||
AND "Played" = false
|
||||
AND "ProgressPct" > 0
|
||||
ORDER BY "LastPlayedDate" DESC;
|
||||
|
||||
-- Top 10 most-played items across all users
|
||||
SELECT "ItemName", "ItemType", SUM("PlayCount") AS "TotalPlays"
|
||||
FROM library."UserPlaybackHistory_v"
|
||||
GROUP BY "ItemName", "ItemType"
|
||||
ORDER BY "TotalPlays" DESC
|
||||
LIMIT 10;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `library."ItemProviders_v"`
|
||||
|
||||
Items with their external provider IDs (IMDb, TMDB, TVDB, etc.) pivoted into named columns. Providers not explicitly named appear in a JSONB `OtherProviders` column.
|
||||
|
||||
**Use when:** cross-referencing Jellyfin items against external databases, finding items with missing metadata IDs, or bulk-exporting IDs for external tools.
|
||||
|
||||
**Columns of note:**
|
||||
|
||||
| Column | Description |
|
||||
|---|---|
|
||||
| `ImdbId` | IMDb identifier (e.g. `tt0111161`) |
|
||||
| `TmdbId` | The Movie Database ID |
|
||||
| `TvdbId` | TheTVDB series/episode ID |
|
||||
| `TvRageId` | TVRage ID (legacy) |
|
||||
| `MusicBrainzAlbumId` | MusicBrainz album MBID |
|
||||
| `MusicBrainzArtistId` | MusicBrainz artist MBID |
|
||||
| `OtherProviders` | JSONB object of any remaining provider key/value pairs |
|
||||
|
||||
**Example queries:**
|
||||
|
||||
```sql
|
||||
-- Movies missing a TMDB ID
|
||||
SELECT "Name", "ProductionYear", "ImdbId"
|
||||
FROM library."ItemProviders_v"
|
||||
WHERE "Type" LIKE '%Movie%'
|
||||
AND "TmdbId" IS NULL
|
||||
ORDER BY "Name";
|
||||
|
||||
-- Look up an item by IMDb ID
|
||||
SELECT "ItemId", "Type", "Name", "ProductionYear"
|
||||
FROM library."ItemProviders_v"
|
||||
WHERE "ImdbId" = 'tt0111161';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `library."ItemPeople_v"`
|
||||
|
||||
All cast and crew entries linked to the items they appear in.
|
||||
|
||||
**Use when:** browsing which items feature a specific actor or director, building cast-focused reports, or auditing people metadata.
|
||||
|
||||
**Columns of note:**
|
||||
|
||||
| Column | Description |
|
||||
|---|---|
|
||||
| `PersonName` | Name of the person |
|
||||
| `PersonType` | Role type: `Actor`, `Director`, `Writer`, `Producer`, etc. |
|
||||
| `Role` | Specific character name or role description (may be empty) |
|
||||
| `SortOrder` / `ListOrder` | Display ordering within the item's people list |
|
||||
|
||||
**Example queries:**
|
||||
|
||||
```sql
|
||||
-- All items featuring a specific actor
|
||||
SELECT "ItemType", "ItemName", "SeriesName", "ProductionYear", "Role"
|
||||
FROM library."ItemPeople_v"
|
||||
WHERE "PersonName" = 'Bryan Cranston'
|
||||
AND "PersonType" = 'Actor'
|
||||
ORDER BY "ProductionYear";
|
||||
|
||||
-- Directors with the most items in the library
|
||||
SELECT "PersonName", COUNT(*) AS "DirectedItems"
|
||||
FROM library."ItemPeople_v"
|
||||
WHERE "PersonType" = 'Director'
|
||||
GROUP BY "PersonName"
|
||||
ORDER BY "DirectedItems" DESC
|
||||
LIMIT 20;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `library."LibrarySummary_v"`
|
||||
|
||||
Aggregate statistics broken down by item type — one row per type. Provides a quick dashboard overview of the entire library.
|
||||
|
||||
**Use when:** getting a high-level snapshot of library size, total runtime, storage usage, and video quality distribution.
|
||||
|
||||
**Columns of note:**
|
||||
|
||||
| Column | Description |
|
||||
|---|---|
|
||||
| `Type` | Jellyfin item type |
|
||||
| `ItemCount` | Total number of items of this type |
|
||||
| `TotalRuntimeHours` | Sum of all runtimes in hours |
|
||||
| `TotalSizeGB` | Sum of all file sizes in gigabytes |
|
||||
| `AvgCommunityRating` | Average community rating across items with a rating |
|
||||
| `Count4K` | Items with primary video height ≥ 2160 |
|
||||
| `Count1080p` | Items with height 1080–2159 |
|
||||
| `Count720p` | Items with height 720–1079 |
|
||||
| `CountSD` | Items with height < 720 |
|
||||
| `CountDolbyVision` | Items with a Dolby Vision video stream |
|
||||
| `CountHDR10Plus` | Items with HDR10+ flag set |
|
||||
| `CountHDR10` | Items with HDR10 (`smpte2084` transfer, no DV or HDR10+) |
|
||||
|
||||
**Example:**
|
||||
|
||||
```sql
|
||||
-- Full summary
|
||||
SELECT "Type", "ItemCount", "TotalRuntimeHours", "TotalSizeGB",
|
||||
"Count4K", "Count1080p", "Count720p", "CountSD",
|
||||
"CountDolbyVision", "CountHDR10Plus", "CountHDR10"
|
||||
FROM library."LibrarySummary_v";
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## StreamType Reference
|
||||
|
||||
The `StreamType` integer used in `MediaStreamInfos` and `MediaStreamInfos_v` maps to:
|
||||
|
||||
| Value | Type |
|
||||
|---|---|
|
||||
| 0 | Audio |
|
||||
| 1 | Video |
|
||||
| 2 | Subtitle |
|
||||
| 3 | EmbeddedImage |
|
||||
| 4 | Data / Attachment |
|
||||
@@ -0,0 +1,244 @@
|
||||
# EF Core Removal Analysis — pgsql-jellyfin Fork
|
||||
|
||||
**Date**: 2025
|
||||
**Scope**: Full codebase analysis of whether EF Core can and should be removed from this PostgreSQL-only fork.
|
||||
**Verdict**: **Do not remove EF Core.** Address specific friction points instead.
|
||||
|
||||
---
|
||||
|
||||
## 1. Usage Inventory
|
||||
|
||||
EF Core (`IDbContextFactory<JellyfinDbContext>`) is injected into **82 call sites across ~35 source files**, permeating every layer:
|
||||
|
||||
| Layer | Files |
|
||||
|---|---|
|
||||
| API | `DatabaseViewsController` |
|
||||
| Repository (Item) | `BaseItemRepository`, `PeopleRepository`, `MediaStreamRepository`, `MediaAttachmentRepository`, `KeyframeRepository`, `ChapterRepository` |
|
||||
| Repository (Other) | `ActivityManager`, `DeviceManager`, `DisplayPreferencesManager`, `UserManager`, `TrickplayManager`, `MediaSegmentManager`, `AuthenticationManager`, `AuthorizationContext`, `LibraryOptionsRepository`, `BackupService` |
|
||||
| Scheduled Tasks | `PeopleValidationTask`, `CleanupUserDataTask`, `CleanDatabaseScheduledTask`, `UserDataManager` |
|
||||
| Migration Routines | 13 routines: `MigrateActivityLogDb`, `MigrateAuthenticationDb`, `MigrateDisplayPreferencesDb`, `MigrateKeyframeData`, `MigrateLibraryDb`, `MigrateLibraryUserData`, `MigrateRatingLevels`, `MoveExtractedFiles`, `RefreshCleanNames`, `RefreshInternalDateModified`, `FixDates`, `CleanMusicArtist`, (+ more) |
|
||||
| Infrastructure | `JellyfinMigrationService`, `PostgresDatabaseProvider`, `DbContextFactoryHealthCheck`, `Program.cs`, `ServiceCollectionExtensions` |
|
||||
|
||||
The `JellyfinDbContext` itself defines **~35 DbSets** spanning 5 schemas (`activitylog`, `authentication`, `displaypreferences`, `library`, `users`).
|
||||
|
||||
---
|
||||
|
||||
## 2. Query Complexity — What EF Core Actually Does
|
||||
|
||||
### 2.1 BaseItemRepository.cs — The Core Case
|
||||
|
||||
`BaseItemRepository.cs` is **3,583 lines / 152 KB**, the largest file in the repository. It is the performance-critical code path for all library browsing, search, and playback-related queries. It is built entirely on EF Core LINQ.
|
||||
|
||||
**Key EF-dependent sub-systems inside it:**
|
||||
|
||||
| Method | EF Feature Used | Purpose |
|
||||
|---|---|---|
|
||||
| `TranslateQuery()` (lines 2548+, ~500 lines) | LINQ predicate composition over `IQueryable<BaseItemEntity>` | Translates `InternalItemsQuery` (60+ filter parameters) to SQL WHERE clauses |
|
||||
| `ApplyOrder()` (~80 lines) | `OrderBy` / `ThenBy` over EF expression trees, `OrderMapper.MapOrderByField()` | Translates `ItemSortBy` enum to typed SQL ORDER BY |
|
||||
| `ApplyNavigations()` | Conditional `Include()` / `ThenInclude()` | Eagerly loads TvExtras, AudioExtras, LiveTvExtras, Provider, Images, UserData per DTO field set |
|
||||
| `ApplyGroupingFilter()` | `DistinctBy()` → PostgreSQL `DISTINCT ON` | Deduplication for presentation unique keys |
|
||||
| `GetNextUpSeriesKeys()` | `Join()` + `GroupBy()` + `Max()` | Complex TV "next up" episode query |
|
||||
| `GetItemValues()` | Nested `Join()` + `SelectMany()` + correlated subquery pattern | Artist/Genre/Studio item-value lookups |
|
||||
| `UpdateOrInsertItemsAsync()` | `CreateExecutionStrategy().ExecuteAsync()`, `SaveChangesAsync()`, `Attach().State = EntityState.Modified`, `AddRange()` | Transactional batch upsert |
|
||||
| Entity state management | `context.Entry(placeholder).State = EntityState.Detached` | Upsert conflict resolution |
|
||||
| `EF.Constant()` | PostgreSQL-specific plan hint | Prevents parameterization of the placeholder UUID to allow index scans |
|
||||
|
||||
**LINQ operator counts in BaseItemRepository.cs** (approximate from code review):
|
||||
- `Where()`, `Select()`, `Join()`, `GroupBy()`, `Include()`, `ThenInclude()`, `OrderBy()`: **200+ call sites**
|
||||
- `ExecuteDelete()` / `ExecuteUpdate()` bulk ops: **~25 call sites**
|
||||
- `FromSqlRaw` / `FromSqlInterpolated`: **0** — reads are pure LINQ
|
||||
- `ExecuteSqlAsync()` raw SQL upserts: **~8** (ON CONFLICT upserts for extension tables)
|
||||
|
||||
The LINQ query composition is not superficial mapping sugar — `TranslateQuery` conditionally applies 30+ predicate branches (user data filters, rating filters, type filters, ancestor traversal, search term scoring, tag inheritance, etc.) based on `InternalItemsQuery` flags. This is the equivalent of a 500-line dynamic SQL builder, expressed in type-safe C#.
|
||||
|
||||
### 2.2 Other Repositories
|
||||
|
||||
The other repositories (`ActivityManager`, `UserManager`, `DeviceManager`, etc.) use EF more simply — primarily `Where()` + `Select()` + `SaveChanges()` patterns — but collectively represent hundreds more LINQ call sites.
|
||||
|
||||
### 2.3 Hybrid Usage Already Present
|
||||
|
||||
`BaseItemRepository.UpdateOrInsertItemsAsync()` already uses a hybrid approach: EF Core for reads and change-tracking, **raw `ExecuteSqlAsync()` for ON CONFLICT upserts**. This tells us the team is already reaching EF's limits on writes.
|
||||
|
||||
---
|
||||
|
||||
## 3. Migration Infrastructure
|
||||
|
||||
### 3.1 EF Schema Migrations Are DISABLED
|
||||
|
||||
Per `JellyfinMigrationService.cs` (line ~275):
|
||||
```csharp
|
||||
// DISABLED for .NET 11 preview: EF Core database migrations don't work with preview SDK
|
||||
// Database schema is initialized from SQL scripts in PostgresDatabaseProvider instead
|
||||
/*
|
||||
if (stage is JellyfinMigrationStageTypes.CoreInitialisation)
|
||||
{
|
||||
pendingDatabaseMigrations = migrationsAssembly.Migrations...
|
||||
}
|
||||
*/
|
||||
```
|
||||
|
||||
**EF's primary justification — schema migration management — is already bypassed.** Schema is initialized via `sql/schema_init/create_database_schema.sql` + `psql`.
|
||||
|
||||
### 3.2 EF Is Still Used for Code Migration Tracking
|
||||
|
||||
`JellyfinMigrationService` still uses EF to track *which code routines have run* via `__EFMigrationsHistory`:
|
||||
|
||||
```csharp
|
||||
var historyRepository = dbContext.GetService<IHistoryRepository>();
|
||||
await historyRepository.CreateIfNotExistsAsync();
|
||||
var appliedMigrations = await dbContext.Database.GetAppliedMigrationsAsync();
|
||||
await dbContext.Database.ExecuteSqlRawAsync(
|
||||
historyRepository.GetInsertScript(new HistoryRow(migrationId, version)));
|
||||
```
|
||||
|
||||
This usage of `IHistoryRepository` is what required the `SnakeCaseHistoryRepository` workaround (custom class overriding `MigrationIdColumnName` and `ProductVersionColumnName`, using `#pragma warning disable EF1001` because the base class is internal EF infrastructure).
|
||||
|
||||
---
|
||||
|
||||
## 4. Known Friction Points
|
||||
|
||||
These are the specific problems that prompted this analysis:
|
||||
|
||||
### 4.1 Snake_case Naming Impedance (FIXED, but fragile)
|
||||
|
||||
EF Core defaults to PascalCase. This fork uses snake_case PostgreSQL conventions. Three workarounds are required:
|
||||
|
||||
1. **`SnakeCaseNamingConvention`** — `IPropertyAddedConvention` that converts all property names to snake_case at model build time
|
||||
2. **`SnakeCaseHistoryRepository`** — custom `NpgsqlHistoryRepository` subclass overriding column name properties; requires `#pragma warning disable EF1001` (breaking internal EF API)
|
||||
3. **Manual table names in model configurations** — 31 `IEntityTypeConfiguration` files; two (`LibraryOptionsEntityConfiguration`, `HomeSectionConfiguration`) had PascalCase table names that overrode the convention and caused `relation "library.LibraryOptions" does not exist` errors at runtime
|
||||
|
||||
**Risk**: Every future upstream EF Core upgrade may break `SnakeCaseHistoryRepository` since it depends on internal EF infrastructure.
|
||||
|
||||
### 4.2 Execution Strategy Transaction Friction (FIXED, but imposed on all writers)
|
||||
|
||||
`EnableRetryOnFailure` in the PostgreSQL retry strategy makes EF Core incompatible with user-initiated `BeginTransaction()`. All transactional code must wrap in `context.Database.CreateExecutionStrategy().Execute(...)`. This has already been fixed in `MigrateRatingLevels` and `MigrateKeyframeData` but is an ongoing constraint.
|
||||
|
||||
### 4.3 EF-PostgreSQL Translation Gaps
|
||||
|
||||
Multiple code comments document EF Core translation failures:
|
||||
|
||||
- `DistinctBy()` on complex types sometimes cannot be translated → fallback to `ApplyGroupingInMemory()` using **reflection on anonymous types**
|
||||
- `GetItemValues()` in some paths falls back to `.AsEnumerable()` for in-memory filtering
|
||||
- `GetLatestItemList()` had to be split into two queries to avoid untranslatable nested `Min()` subqueries
|
||||
- A documented 165s query regression was caused by EF generating correlated subqueries — fixed by switching to `DistinctBy()` → `DISTINCT ON`
|
||||
- `AsSingleQuery()` required explicitly to prevent N+1 for multi-table includes
|
||||
|
||||
### 4.4 Inconsistent Table Naming in Raw SQL
|
||||
|
||||
`UpdateOrInsertItemsAsync()` uses raw `ExecuteSqlAsync()` with PascalCase quoted names:
|
||||
|
||||
```csharp
|
||||
$@"INSERT INTO library.""BaseItemProviders"" (""ItemId"", ""ProviderId"", ""ProviderValue"")..."
|
||||
$@"INSERT INTO library.""BaseItemTvExtras"" (""ItemId"", ..."
|
||||
```
|
||||
|
||||
These PascalCase quoted names (`"BaseItemProviders"`, `"BaseItemTvExtras"`) are inconsistent with the snake_case goal of the fork. The actual table names in PostgreSQL must match these quoted strings exactly — implying the schema init SQL creates them with PascalCase quoted names, not the snake_case convention applied to EF properties.
|
||||
|
||||
### 4.5 Known Performance Anti-Pattern in `GetItemValues`
|
||||
|
||||
```csharp
|
||||
// TODO: This is bad refactor!
|
||||
itemCount = new ItemCounts() {
|
||||
SeriesCount = itemCountQuery!.Count(f => f.Type == seriesTypeName),
|
||||
EpisodeCount = itemCountQuery!.Count(f => f.Type == episodeTypeName),
|
||||
// ... 5 more COUNT queries
|
||||
}
|
||||
```
|
||||
|
||||
This executes 7 separate `COUNT` queries per item in a result set — a classic N+1 problem embedded in an EF projection. This is an EF translation limitation (aggregate counts in a nested `SELECT` project).
|
||||
|
||||
---
|
||||
|
||||
## 5. Can EF Core Be Removed?
|
||||
|
||||
**Technically: Yes. Practically: The cost is prohibitive.**
|
||||
|
||||
A full removal would require:
|
||||
|
||||
1. **Rewriting `BaseItemRepository.cs`** (3,583 lines) as raw SQL — either Dapper or direct `NpgsqlCommand`. The `TranslateQuery` method alone is a ~500-line dynamic query builder that would need to be rewritten as a SQL string builder. All `Include()` chains would become explicit JOINs. All `ExecuteUpdate`/`ExecuteDelete` calls would become parameterized SQL.
|
||||
|
||||
2. **Replacing 31 ModelConfiguration files** with SQL schema definitions (partially done via `sql/schema_init/`).
|
||||
|
||||
3. **Replacing the migration tracking system** (`IHistoryRepository`) with a simple SQL table + custom tracker.
|
||||
|
||||
4. **Rewriting all other repositories** (~15 files) as Dapper or raw Npgsql.
|
||||
|
||||
5. **Replacing all `IDbContextFactory` injection points** (~35 files, 82 sites) with new connection/command factories.
|
||||
|
||||
**Estimated effort: 3–6 person-months of careful rewrite, plus extensive testing coverage.**
|
||||
|
||||
Alternatives assessed:
|
||||
|
||||
| Alternative | Pros | Cons |
|
||||
|---|---|---|
|
||||
| **Full removal (Dapper + Npgsql)** | Full control of SQL; no EF friction; better PostgreSQL-native features | 3-6 month rewrite; no dynamic LINQ query composition; lose `ExecuteUpdate`/`ExecuteDelete`; high regression risk |
|
||||
| **Hybrid (EF reads, Dapper writes)** | Eliminates write friction; keeps LINQ query composition | Two persistence paradigms; still need EF for reads; does not eliminate snake_case friction |
|
||||
| **Keep EF, address friction points** | Minimal disruption; preserves LINQ query composition | Ongoing EF+PostgreSQL compatibility work; preview SDK risk |
|
||||
| **Replace only migration tracking** | Eliminates `SnakeCaseHistoryRepository` hack | Small gain; does not address execution strategy or translation friction |
|
||||
|
||||
---
|
||||
|
||||
## 6. Should EF Core Be Removed?
|
||||
|
||||
**No — but specific friction points should be addressed deliberately.**
|
||||
|
||||
### 6.1 Why EF Core Should Stay
|
||||
|
||||
**The LINQ query composition in `TranslateQuery` / `ApplyOrder` is EF Core's core contribution to this codebase.** It provides:
|
||||
|
||||
- **Type safety** across 60+ filter parameters in `InternalItemsQuery`
|
||||
- **Composability** — predicates are built incrementally, each branch independently testable
|
||||
- **Maintainability** — 500 lines of C# expression trees is significantly more maintainable than equivalent raw SQL string building
|
||||
- **`ExecuteUpdate`/`ExecuteDelete`** — these EF 7+ bulk operations map directly to `UPDATE ... WHERE` and `DELETE ... WHERE` without loading entities; replacing these with raw SQL would produce functionally identical code with more boilerplate
|
||||
- **Navigation property loading** (`Include`) — the conditional navigation loading in `ApplyNavigations` maps cleanly to the DTO field selection pattern used by the API layer
|
||||
|
||||
EF Core's value in this codebase is almost entirely in the **read path** (LINQ-to-SQL translation) and **bulk operations** (`ExecuteUpdate`/`ExecuteDelete`). Its weaknesses are in the write path and infrastructure (migration history, naming conventions, execution strategy).
|
||||
|
||||
### 6.2 Recommended Targeted Actions
|
||||
|
||||
In priority order:
|
||||
|
||||
#### Priority 1 — Replace Migration History Tracking (Low effort, high safety gain)
|
||||
|
||||
The `SnakeCaseHistoryRepository` workaround depends on EF internal APIs (`#pragma warning disable EF1001`). Replace the code migration tracking with a simple dedicated table and Dapper/Npgsql:
|
||||
|
||||
```sql
|
||||
CREATE TABLE IF NOT EXISTS public.jellyfin_applied_migrations (
|
||||
migration_id VARCHAR(150) PRIMARY KEY,
|
||||
applied_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
product_version VARCHAR(32) NOT NULL
|
||||
);
|
||||
```
|
||||
|
||||
Remove `JellyfinMigrationService`'s dependency on `IHistoryRepository` and `GetAppliedMigrationsAsync()`. This eliminates the most fragile EF workaround.
|
||||
|
||||
#### Priority 2 — Fix Raw SQL Table Names to Match snake_case Schema (Low effort, correctness)
|
||||
|
||||
`UpdateOrInsertItemsAsync` uses `library."BaseItemProviders"`, `library."BaseItemTvExtras"`, etc. If the fork's goal is snake_case naming, verify whether these tables exist as quoted PascalCase or as snake_case in PostgreSQL, and make the raw SQL consistent. Quoted PascalCase in PostgreSQL is case-sensitive and is a footgun.
|
||||
|
||||
#### Priority 3 — Replace Write Path with Raw SQL/Dapper (Medium effort, eliminates execution strategy friction)
|
||||
|
||||
The `UpdateOrInsertItemsAsync` method is already a hybrid (raw SQL for ON CONFLICT upserts + EF for the surrounding logic). Converting the remaining EF write operations (`SaveChangesAsync`, `Attach().State = EntityState.Modified`, `AddRange()`) in this method to direct Npgsql/Dapper calls would:
|
||||
|
||||
- Eliminate the `CreateExecutionStrategy().Execute()` wrappers
|
||||
- Enable direct PostgreSQL `ON CONFLICT DO UPDATE` throughout
|
||||
- Remove the impedance between EF change tracking and PostgreSQL's native upsert semantics
|
||||
|
||||
**Do not extend this to reads** — the LINQ query composition is worth keeping.
|
||||
|
||||
#### Priority 4 — Fix the N+1 in `GetItemValues` (Medium effort, performance)
|
||||
|
||||
The 7 separate `COUNT` queries per item should be replaced with a single `GROUP BY type, COUNT(*)` query executed once and pivoted in memory. This is a correctness/performance issue independent of whether EF is removed.
|
||||
|
||||
---
|
||||
|
||||
## 7. Summary
|
||||
|
||||
| Question | Answer |
|
||||
|---|---|
|
||||
| **Can EF Core be removed?** | Yes, technically. Estimated 3–6 person-months of work. |
|
||||
| **Should EF Core be removed?** | No. The LINQ query composition in `BaseItemRepository` is EF's core value and cost-prohibitive to replace. |
|
||||
| **Is EF Core being used optimally?** | No. The write path, migration tracking, and naming convention workarounds are sources of ongoing friction. |
|
||||
| **What should be done instead?** | Replace migration tracking with a native SQL table; convert the write path in `BaseItemRepository` to raw SQL; keep EF for the LINQ read path. |
|
||||
| **Biggest ongoing risk?** | `SnakeCaseHistoryRepository` uses internal EF APIs (`EF1001`) that may break on EF Core version bumps. Replacing migration tracking eliminates this. |
|
||||
| **Second biggest risk?** | EF Core + Npgsql preview packages (`11.0.0-preview.1`) on `net11.0`. Schema migrations are already disabled because of this. Pin to stable as soon as net11.0 releases. |
|
||||
@@ -0,0 +1,478 @@
|
||||
# SQL Column Conversion Reference - All Tables
|
||||
|
||||
## Activity Log Schema
|
||||
|
||||
### activity_logs (activitylog."ActivityLogs")
|
||||
| Current (SQL) | Target (snake_case) |
|
||||
|--------------|-------------------|
|
||||
| `"Id"` | `id` |
|
||||
| `"Name"` | `name` |
|
||||
| `"Overview"` | `overview` |
|
||||
| `"ShortOverview"` | `short_overview` |
|
||||
| `"Type"` | `type` |
|
||||
| `"UserId"` | `user_id` |
|
||||
| `"ItemId"` | `item_id` |
|
||||
| `"DateCreated"` | `date_created` |
|
||||
| `"LogSeverity"` | `log_severity` |
|
||||
| `"RowVersion"` | `row_version` |
|
||||
|
||||
---
|
||||
|
||||
## Authentication Schema
|
||||
|
||||
### api_keys (authentication."ApiKeys")
|
||||
| Current (SQL) | Target (snake_case) |
|
||||
|--------------|-------------------|
|
||||
| `"Id"` | `id` |
|
||||
| `"DateCreated"` | `date_created` |
|
||||
| `"DateLastActivity"` | `date_last_activity` |
|
||||
| `"Name"` | `name` |
|
||||
| `"AccessToken"` | `access_token` |
|
||||
|
||||
### devices (authentication."Devices")
|
||||
| Current (SQL) | Target (snake_case) |
|
||||
|--------------|-------------------|
|
||||
| `"Id"` | `id` |
|
||||
| `"UserId"` | `user_id` |
|
||||
| `"AccessToken"` | `access_token` |
|
||||
| `"AppName"` | `app_name` |
|
||||
| `"AppVersion"` | `app_version` |
|
||||
| `"DeviceName"` | `device_name` |
|
||||
| `"DeviceId"` | `device_id` |
|
||||
| `"IsActive"` | `is_active` |
|
||||
| `"DateCreated"` | `date_created` |
|
||||
| `"DateModified"` | `date_modified` |
|
||||
| `"DateLastActivity"` | `date_last_activity` |
|
||||
|
||||
### device_options (authentication."DeviceOptions")
|
||||
| Current (SQL) | Target (snake_case) |
|
||||
|--------------|-------------------|
|
||||
| `"Id"` | `id` |
|
||||
| `"DeviceId"` | `device_id` |
|
||||
| `"CustomName"` | `custom_name` |
|
||||
|
||||
---
|
||||
|
||||
## Display Preferences Schema
|
||||
|
||||
### display_preferences (displaypreferences."DisplayPreferences")
|
||||
| Current (SQL) | Target (snake_case) |
|
||||
|--------------|-------------------|
|
||||
| `"Id"` | `id` |
|
||||
| `"UserId"` | `user_id` |
|
||||
| `"ItemId"` | `item_id` |
|
||||
| `"Client"` | `client` |
|
||||
| `"ShowSidebar"` | `show_sidebar` |
|
||||
| `"ShowBackdrop"` | `show_backdrop` |
|
||||
| `"ScrollDirection"` | `scroll_direction` |
|
||||
| `"IndexBy"` | `index_by` |
|
||||
| `"SkipForwardLength"` | `skip_forward_length` |
|
||||
| `"SkipBackwardLength"` | `skip_backward_length` |
|
||||
| `"ChromecastVersion"` | `chromecast_version` |
|
||||
| `"EnableNextVideoInfoOverlay"` | `enable_next_video_info_overlay` |
|
||||
| `"DashboardTheme"` | `dashboard_theme` |
|
||||
| `"TvHome"` | `tv_home` |
|
||||
|
||||
### item_display_preferences (displaypreferences."ItemDisplayPreferences")
|
||||
| Current (SQL) | Target (snake_case) |
|
||||
|--------------|-------------------|
|
||||
| `"Id"` | `id` |
|
||||
| `"UserId"` | `user_id` |
|
||||
| `"ItemId"` | `item_id` |
|
||||
| `"Client"` | `client` |
|
||||
| `"ViewType"` | `view_type` |
|
||||
| `"RememberIndexing"` | `remember_indexing` |
|
||||
| `"IndexBy"` | `index_by` |
|
||||
| `"RememberSorting"` | `remember_sorting` |
|
||||
| `"SortBy"` | `sort_by` |
|
||||
| `"SortOrder"` | `sort_order` |
|
||||
|
||||
### custom_item_display_preferences (displaypreferences."CustomItemDisplayPreferences")
|
||||
| Current (SQL) | Target (snake_case) |
|
||||
|--------------|-------------------|
|
||||
| `"Id"` | `id` |
|
||||
| `"UserId"` | `user_id` |
|
||||
| `"ItemId"` | `item_id` |
|
||||
| `"Client"` | `client` |
|
||||
| `"Key"` | `key` |
|
||||
| `"Value"` | `value` |
|
||||
|
||||
### home_sections (displaypreferences."HomeSection")
|
||||
| Current (SQL) | Target (snake_case) |
|
||||
|--------------|-------------------|
|
||||
| `"Id"` | `id` |
|
||||
| `"DisplayPreferencesId"` | `display_preferences_id` |
|
||||
| `"Order"` | `order` |
|
||||
| `"Type"` | `type` |
|
||||
|
||||
---
|
||||
|
||||
## Library Schema
|
||||
|
||||
### base_items (library."BaseItems") - 73 columns
|
||||
| Current (SQL) | Target (snake_case) |
|
||||
|--------------|-------------------|
|
||||
| `"Id"` | `id` |
|
||||
| `"Type"` | `type` |
|
||||
| `"Data"` | `data` |
|
||||
| `"Path"` | `path` |
|
||||
| `"StartDate"` | `start_date` |
|
||||
| `"EndDate"` | `end_date` |
|
||||
| `"ChannelId"` | `channel_id` |
|
||||
| `"IsMovie"` | `is_movie` |
|
||||
| `"CommunityRating"` | `community_rating` |
|
||||
| `"CustomRating"` | `custom_rating` |
|
||||
| `"IndexNumber"` | `index_number` |
|
||||
| `"IsLocked"` | `is_locked` |
|
||||
| `"Name"` | `name` |
|
||||
| `"OfficialRating"` | `official_rating` |
|
||||
| `"MediaType"` | `media_type` |
|
||||
| `"Overview"` | `overview` |
|
||||
| `"ParentIndexNumber"` | `parent_index_number` |
|
||||
| `"PremiereDate"` | `premiere_date` |
|
||||
| `"ProductionYear"` | `production_year` |
|
||||
| `"Genres"` | `genres` |
|
||||
| `"SortName"` | `sort_name` |
|
||||
| `"ForcedSortName"` | `forced_sort_name` |
|
||||
| `"RunTimeTicks"` | `run_time_ticks` |
|
||||
| `"DateCreated"` | `date_created` |
|
||||
| `"DateModified"` | `date_modified` |
|
||||
| `"IsSeries"` | `is_series` |
|
||||
| `"EpisodeTitle"` | `episode_title` |
|
||||
| `"IsRepeat"` | `is_repeat` |
|
||||
| `"PreferredMetadataLanguage"` | `preferred_metadata_language` |
|
||||
| `"PreferredMetadataCountryCode"` | `preferred_metadata_country_code` |
|
||||
| `"DateLastRefreshed"` | `date_last_refreshed` |
|
||||
| `"DateLastSaved"` | `date_last_saved` |
|
||||
| `"IsInMixedFolder"` | `is_in_mixed_folder` |
|
||||
| `"Studios"` | `studios` |
|
||||
| `"ExternalServiceId"` | `external_service_id` |
|
||||
| `"Tags"` | `tags` |
|
||||
| `"IsFolder"` | `is_folder` |
|
||||
| `"InheritedParentalRatingValue"` | `inherited_parental_rating_value` |
|
||||
| `"InheritedParentalRatingSubValue"` | `inherited_parental_rating_sub_value` |
|
||||
| `"UnratedType"` | `unrated_type` |
|
||||
| `"CriticRating"` | `critic_rating` |
|
||||
| `"CleanName"` | `clean_name` |
|
||||
| `"PresentationUniqueKey"` | `presentation_unique_key` |
|
||||
| `"OriginalTitle"` | `original_title` |
|
||||
| `"PrimaryVersionId"` | `primary_version_id` |
|
||||
| `"DateLastMediaAdded"` | `date_last_media_added` |
|
||||
| `"Album"` | `album` |
|
||||
| `"LUFS"` | `lufs` |
|
||||
| `"NormalizationGain"` | `normalization_gain` |
|
||||
| `"IsVirtualItem"` | `is_virtual_item` |
|
||||
| `"SeriesName"` | `series_name` |
|
||||
| `"SeasonName"` | `season_name` |
|
||||
| `"ExternalSeriesId"` | `external_series_id` |
|
||||
| `"Tagline"` | `tagline` |
|
||||
| `"ProductionLocations"` | `production_locations` |
|
||||
| `"ExtraIds"` | `extra_ids` |
|
||||
| `"TotalBitrate"` | `total_bitrate` |
|
||||
| `"ExtraType"` | `extra_type` |
|
||||
| `"Artists"` | `artists` |
|
||||
| `"AlbumArtists"` | `album_artists` |
|
||||
| `"ExternalId"` | `external_id` |
|
||||
| `"SeriesPresentationUniqueKey"` | `series_presentation_unique_key` |
|
||||
| `"ShowId"` | `show_id` |
|
||||
| `"OwnerId"` | `owner_id` |
|
||||
| `"Width"` | `width` |
|
||||
| `"Height"` | `height` |
|
||||
| `"Size"` | `size` |
|
||||
| `"Audio"` | `audio` |
|
||||
| `"ParentId"` | `parent_id` |
|
||||
| `"TopParentId"` | `top_parent_id` |
|
||||
| `"SeasonId"` | `season_id` |
|
||||
| `"SeriesId"` | `series_id` |
|
||||
|
||||
### chapters (library."Chapters")
|
||||
| Current (SQL) | Target (snake_case) |
|
||||
|--------------|-------------------|
|
||||
| `"ItemId"` | `item_id` |
|
||||
| `"ChapterIndex"` | `chapter_index` |
|
||||
| `"StartPositionTicks"` | `start_position_ticks` |
|
||||
| `"Name"` | `name` |
|
||||
| `"ImagePath"` | `image_path` |
|
||||
| `"ImageDateModified"` | `image_date_modified` |
|
||||
|
||||
### media_stream_infos (library."MediaStreamInfos") - 51 columns
|
||||
| Current (SQL) | Target (snake_case) |
|
||||
|--------------|-------------------|
|
||||
| `"ItemId"` | `item_id` |
|
||||
| `"StreamIndex"` | `stream_index` |
|
||||
| `"StreamType"` | `stream_type` |
|
||||
| `"Codec"` | `codec` |
|
||||
| `"Language"` | `language` |
|
||||
| `"ChannelLayout"` | `channel_layout` |
|
||||
| `"Profile"` | `profile` |
|
||||
| `"AspectRatio"` | `aspect_ratio` |
|
||||
| `"Path"` | `path` |
|
||||
| `"IsInterlaced"` | `is_interlaced` |
|
||||
| `"BitRate"` | `bit_rate` |
|
||||
| `"Channels"` | `channels` |
|
||||
| `"SampleRate"` | `sample_rate` |
|
||||
| `"IsDefault"` | `is_default` |
|
||||
| `"IsForced"` | `is_forced` |
|
||||
| `"IsExternal"` | `is_external` |
|
||||
| `"Height"` | `height` |
|
||||
| `"Width"` | `width` |
|
||||
| `"AverageFrameRate"` | `average_frame_rate` |
|
||||
| `"RealFrameRate"` | `real_frame_rate` |
|
||||
| `"Level"` | `level` |
|
||||
| `"PixelFormat"` | `pixel_format` |
|
||||
| `"BitDepth"` | `bit_depth` |
|
||||
| `"IsAnamorphic"` | `is_anamorphic` |
|
||||
| `"RefFrames"` | `ref_frames` |
|
||||
| `"CodecTag"` | `codec_tag` |
|
||||
| `"Comment"` | `comment` |
|
||||
| `"NalLengthSize"` | `nal_length_size` |
|
||||
| `"IsAvc"` | `is_avc` |
|
||||
| `"Title"` | `title` |
|
||||
| `"TimeBase"` | `time_base` |
|
||||
| `"CodecTimeBase"` | `codec_time_base` |
|
||||
| `"ColorPrimaries"` | `color_primaries` |
|
||||
| `"ColorSpace"` | `color_space` |
|
||||
| `"ColorTransfer"` | `color_transfer` |
|
||||
| `"DvVersionMajor"` | `dv_version_major` |
|
||||
| `"DvVersionMinor"` | `dv_version_minor` |
|
||||
| `"DvProfile"` | `dv_profile` |
|
||||
| `"DvLevel"` | `dv_level` |
|
||||
| `"RpuPresentFlag"` | `rpu_present_flag` |
|
||||
| `"ElPresentFlag"` | `el_present_flag` |
|
||||
| `"BlPresentFlag"` | `bl_present_flag` |
|
||||
| `"DvBlSignalCompatibilityId"` | `dv_bl_signal_compatibility_id` |
|
||||
| `"IsHearingImpaired"` | `is_hearing_impaired` |
|
||||
| `"Rotation"` | `rotation` |
|
||||
| `"KeyFrames"` | `key_frames` |
|
||||
| `"Hdr10PlusPresentFlag"` | `hdr10_plus_present_flag` |
|
||||
|
||||
### attachment_stream_infos (library."AttachmentStreamInfos")
|
||||
| Current (SQL) | Target (snake_case) |
|
||||
|--------------|-------------------|
|
||||
| `"ItemId"` | `item_id` |
|
||||
| `"Index"` | `index` |
|
||||
| `"Codec"` | `codec` |
|
||||
| `"CodecTag"` | `codec_tag` |
|
||||
| `"Comment"` | `comment` |
|
||||
| `"Filename"` | `filename` |
|
||||
| `"MimeType"` | `mime_type` |
|
||||
|
||||
### image_infos (library."ImageInfos")
|
||||
| Current (SQL) | Target (snake_case) |
|
||||
|--------------|-------------------|
|
||||
| `"Id"` | `id` |
|
||||
| `"UserId"` | `user_id` |
|
||||
| `"Path"` | `path` |
|
||||
| `"LastModified"` | `last_modified` |
|
||||
|
||||
### base_item_image_infos (library."BaseItemImageInfos")
|
||||
| Current (SQL) | Target (snake_case) |
|
||||
|--------------|-------------------|
|
||||
| `"Id"` | `id` |
|
||||
| `"Path"` | `path` |
|
||||
| `"DateModified"` | `date_modified` |
|
||||
| `"ImageType"` | `image_type` |
|
||||
| `"Width"` | `width` |
|
||||
| `"Height"` | `height` |
|
||||
| `"Blurhash"` | `blurhash` |
|
||||
| `"ItemId"` | `item_id` |
|
||||
|
||||
### base_item_metadata_fields (library."BaseItemMetadataFields")
|
||||
| Current (SQL) | Target (snake_case) |
|
||||
|--------------|-------------------|
|
||||
| `"Id"` | `id` |
|
||||
| `"ItemId"` | `item_id` |
|
||||
|
||||
### base_item_providers (library."BaseItemProviders")
|
||||
| Current (SQL) | Target (snake_case) |
|
||||
|--------------|-------------------|
|
||||
| `"ItemId"` | `item_id` |
|
||||
| `"ProviderId"` | `provider_id` |
|
||||
| `"ProviderValue"` | `provider_value` |
|
||||
|
||||
### base_item_trailer_types (library."BaseItemTrailerTypes")
|
||||
| Current (SQL) | Target (snake_case) |
|
||||
|--------------|-------------------|
|
||||
| `"Id"` | `id` |
|
||||
| `"ItemId"` | `item_id` |
|
||||
|
||||
### item_values (library."ItemValues")
|
||||
| Current (SQL) | Target (snake_case) |
|
||||
|--------------|-------------------|
|
||||
| `"ItemValueId"` | `item_value_id` |
|
||||
| `"Type"` | `type` |
|
||||
| `"Value"` | `value` |
|
||||
| `"CleanValue"` | `clean_value` |
|
||||
|
||||
### item_values_map (library."ItemValuesMap")
|
||||
| Current (SQL) | Target (snake_case) |
|
||||
|--------------|-------------------|
|
||||
| `"ItemId"` | `item_id` |
|
||||
| `"ItemValueId"` | `item_value_id` |
|
||||
|
||||
### peoples (library."Peoples")
|
||||
| Current (SQL) | Target (snake_case) |
|
||||
|--------------|-------------------|
|
||||
| `"Id"` | `id` |
|
||||
| `"Name"` | `name` |
|
||||
| `"PersonType"` | `person_type` |
|
||||
|
||||
### people_base_item_map (library."PeopleBaseItemMap")
|
||||
| Current (SQL) | Target (snake_case) |
|
||||
|--------------|-------------------|
|
||||
| `"Role"` | `role` |
|
||||
| `"ItemId"` | `item_id` |
|
||||
| `"PeopleId"` | `people_id` |
|
||||
| `"SortOrder"` | `sort_order` |
|
||||
| `"ListOrder"` | `list_order` |
|
||||
|
||||
### user_data (library."UserData")
|
||||
| Current (SQL) | Target (snake_case) |
|
||||
|--------------|-------------------|
|
||||
| `"CustomDataKey"` | `custom_data_key` |
|
||||
| `"ItemId"` | `item_id` |
|
||||
| `"UserId"` | `user_id` |
|
||||
| `"Rating"` | `rating` |
|
||||
| `"PlaybackPositionTicks"` | `playback_position_ticks` |
|
||||
| `"PlayCount"` | `play_count` |
|
||||
| `"IsFavorite"` | `is_favorite` |
|
||||
| `"LastPlayedDate"` | `last_played_date` |
|
||||
| `"Played"` | `played` |
|
||||
| `"AudioStreamIndex"` | `audio_stream_index` |
|
||||
| `"SubtitleStreamIndex"` | `subtitle_stream_index` |
|
||||
| `"Likes"` | `likes` |
|
||||
| `"RetentionDate"` | `retention_date` |
|
||||
|
||||
### ancestor_ids (library."AncestorIds")
|
||||
| Current (SQL) | Target (snake_case) |
|
||||
|--------------|-------------------|
|
||||
| `"ParentItemId"` | `parent_item_id` |
|
||||
| `"ItemId"` | `item_id` |
|
||||
|
||||
### trickplay_infos (library."TrickplayInfos")
|
||||
| Current (SQL) | Target (snake_case) |
|
||||
|--------------|-------------------|
|
||||
| `"ItemId"` | `item_id` |
|
||||
| `"Width"` | `width` |
|
||||
| `"Height"` | `height` |
|
||||
| `"TileWidth"` | `tile_width` |
|
||||
| `"TileHeight"` | `tile_height` |
|
||||
| `"ThumbnailCount"` | `thumbnail_count` |
|
||||
| `"Interval"` | `interval` |
|
||||
| `"Bandwidth"` | `bandwidth` |
|
||||
|
||||
### media_segments (library."MediaSegments")
|
||||
| Current (SQL) | Target (snake_case) |
|
||||
|--------------|-------------------|
|
||||
| `"Id"` | `id` |
|
||||
| `"ItemId"` | `item_id` |
|
||||
| `"Type"` | `type` |
|
||||
| `"EndTicks"` | `end_ticks` |
|
||||
| `"StartTicks"` | `start_ticks` |
|
||||
| `"SegmentProviderId"` | `segment_provider_id` |
|
||||
|
||||
### keyframe_data (library."KeyframeData")
|
||||
| Current (SQL) | Target (snake_case) |
|
||||
|--------------|-------------------|
|
||||
| `"ItemId"` | `item_id` |
|
||||
| `"TotalDuration"` | `total_duration` |
|
||||
| `"KeyframeTicks"` | `keyframe_ticks` |
|
||||
|
||||
### library_options (library."LibraryOptions") - Note: Not yet found in schema, may need verification
|
||||
Expected columns (from LibraryOptionsEntity EF mapping):
|
||||
- LibraryPath (primary key, text/string)
|
||||
- DateModified (timestamp with time zone)
|
||||
|
||||
---
|
||||
|
||||
## Users Schema
|
||||
|
||||
### users (users."Users") - 36+ columns
|
||||
| Current (SQL) | Target (snake_case) |
|
||||
|--------------|-------------------|
|
||||
| `"Id"` | `id` |
|
||||
| `"Username"` | `username` |
|
||||
| `"Password"` | `password` |
|
||||
| `"MustUpdatePassword"` | `must_update_password` |
|
||||
| `"AudioLanguagePreference"` | `audio_language_preference` |
|
||||
| `"AuthenticationProviderId"` | `authentication_provider_id` |
|
||||
| `"PasswordResetProviderId"` | `password_reset_provider_id` |
|
||||
| `"InvalidLoginAttemptCount"` | `invalid_login_attempt_count` |
|
||||
| `"LastActivityDate"` | `last_activity_date` |
|
||||
| `"LastLoginDate"` | `last_login_date` |
|
||||
| `"LoginAttemptsBeforeLockout"` | `login_attempts_before_lockout` |
|
||||
| `"MaxActiveSessions"` | `max_active_sessions` |
|
||||
| `"SubtitleMode"` | `subtitle_mode` |
|
||||
| `"PlayDefaultAudioTrack"` | `play_default_audio_track` |
|
||||
| `"SubtitleLanguagePreference"` | `subtitle_language_preference` |
|
||||
| `"DisplayMissingEpisodes"` | `display_missing_episodes` |
|
||||
| `"DisplayCollectionsView"` | `display_collections_view` |
|
||||
| `"EnableLocalPassword"` | `enable_local_password` |
|
||||
| `"HidePlayedInLatest"` | `hide_played_in_latest` |
|
||||
| `"RememberAudioSelections"` | `remember_audio_selections` |
|
||||
| `"RememberSubtitleSelections"` | `remember_subtitle_selections` |
|
||||
| `"EnableNextEpisodeAutoPlay"` | `enable_next_episode_auto_play` |
|
||||
| `"EnableAutoLogin"` | `enable_auto_login` |
|
||||
| `"EnableUserPreferenceAccess"` | `enable_user_preference_access` |
|
||||
| `"MaxParentalRatingScore"` | `max_parental_rating_score` |
|
||||
| `"MaxParentalRatingSubScore"` | `max_parental_rating_sub_score` |
|
||||
| `"RemoteClientBitrateLimit"` | `remote_client_bitrate_limit` |
|
||||
| `"InternalId"` | `internal_id` |
|
||||
| `"SyncPlayAccess"` | `sync_play_access` |
|
||||
| `"CastReceiverId"` | `cast_receiver_id` |
|
||||
| `"RowVersion"` | `row_version` |
|
||||
|
||||
### permissions (users."Permissions")
|
||||
| Current (SQL) | Target (snake_case) |
|
||||
|--------------|-------------------|
|
||||
| `"Id"` | `id` |
|
||||
| `"UserId"` | `user_id` |
|
||||
| `"Kind"` | `kind` |
|
||||
| `"Value"` | `value` |
|
||||
| `"RowVersion"` | `row_version` |
|
||||
| `"Permission_Permissions_Guid"` | `permission_permissions_guid` |
|
||||
|
||||
### preferences (users."Preferences")
|
||||
| Current (SQL) | Target (snake_case) |
|
||||
|--------------|-------------------|
|
||||
| `"Id"` | `id` |
|
||||
| `"UserId"` | `user_id` |
|
||||
| `"Kind"` | `kind` |
|
||||
| `"Value"` | `value` |
|
||||
| `"RowVersion"` | `row_version` |
|
||||
| `"Preference_Preferences_Guid"` | `preference_preferences_guid` |
|
||||
|
||||
### access_schedules (users."AccessSchedules")
|
||||
| Current (SQL) | Target (snake_case) |
|
||||
|--------------|-------------------|
|
||||
| `"Id"` | `id` |
|
||||
| `"UserId"` | `user_id` |
|
||||
| `"DayOfWeek"` | `day_of_week` |
|
||||
| `"StartHour"` | `start_hour` |
|
||||
| `"EndHour"` | `end_hour` |
|
||||
|
||||
---
|
||||
|
||||
## Special System Tables
|
||||
|
||||
### __ef_migrations_history (public."__EFMigrationsHistory")
|
||||
| Current (SQL) | Target (snake_case) |
|
||||
|--------------|-------------------|
|
||||
| `"MigrationId"` | `migration_id` |
|
||||
| `"ProductVersion"` | `product_version` |
|
||||
|
||||
---
|
||||
|
||||
## Summary Statistics
|
||||
|
||||
- **Total Application Tables**: 19
|
||||
- **Total Library Schema Tables**: 19 (with BaseItems being the largest at 73 columns)
|
||||
- **Total Media Stream Columns**: 51 (MediaStreamInfos is the second-largest table)
|
||||
- **Total Columns Across All Tables**: ~250+
|
||||
- **Sequences to Rename**: 17 (one per auto-increment table)
|
||||
- **Special System Table**: 1 (`__EFMigrationsHistory`)
|
||||
|
||||
---
|
||||
|
||||
Generated: 2025-05-01
|
||||
For use in create_database_schema.sql conversion from quoted PascalCase to lowercase/snake_case identifiers
|
||||
@@ -0,0 +1,182 @@
|
||||
# SQL Schema Conversion Guide - PascalCase to snake_case
|
||||
|
||||
## Authoritative Table Mappings (from PostgresDatabaseProvider.OnModelCreating)
|
||||
|
||||
### Activity Log Schema
|
||||
| Current (SQL) | Target (C#/PostgreSQL) | Entity Class |
|
||||
|--------------|------------------------|--------------|
|
||||
| `"ActivityLogs"` | `activity_logs` | `ActivityLog` |
|
||||
|
||||
### Authentication Schema
|
||||
| Current (SQL) | Target (C#/PostgreSQL) | Entity Class |
|
||||
|--------------|------------------------|--------------|
|
||||
| `"ApiKeys"` | `api_keys` | `ApiKey` |
|
||||
| `"Devices"` | `devices` | `Device` |
|
||||
| `"DeviceOptions"` | `device_options` | `DeviceOptions` |
|
||||
|
||||
### Display Preferences Schema
|
||||
| Current (SQL) | Target (C#/PostgreSQL) | Entity Class |
|
||||
|--------------|------------------------|--------------|
|
||||
| `"DisplayPreferences"` | `display_preferences` | `DisplayPreferences` |
|
||||
| `"ItemDisplayPreferences"` | `item_display_preferences` | `ItemDisplayPreferences` |
|
||||
| `"CustomItemDisplayPreferences"` | `custom_item_display_preferences` | `CustomItemDisplayPreferences` |
|
||||
| `"HomeSections"` | `home_sections` | `HomeSection` |
|
||||
|
||||
### Users Schema
|
||||
| Current (SQL) | Target (C#/PostgreSQL) | Entity Class |
|
||||
|--------------|------------------------|--------------|
|
||||
| `"Users"` | `users` | `User` |
|
||||
| `"Permissions"` | `permissions` | `Permission` |
|
||||
| `"Preferences"` | `preferences` | `Preference` |
|
||||
| `"AccessSchedules"` | `access_schedules` | `AccessSchedule` |
|
||||
|
||||
### Library Schema
|
||||
| Current (SQL) | Target (C#/PostgreSQL) | Entity Class |
|
||||
|--------------|------------------------|--------------|
|
||||
| `"BaseItems"` | `base_items` | `BaseItemEntity` |
|
||||
| `"Chapters"` | `chapters` | `Chapter` |
|
||||
| `"MediaStreamInfos"` | `media_stream_infos` | `MediaStreamInfo` |
|
||||
| `"AttachmentStreamInfos"` | `attachment_stream_infos` | `AttachmentStreamInfo` |
|
||||
| `"ImageInfos"` | `image_infos` | `ImageInfo` |
|
||||
| `"BaseItemImageInfos"` | `base_item_image_infos` | `BaseItemImageInfo` |
|
||||
| `"BaseItemProviders"` | `base_item_providers` | `BaseItemProvider` |
|
||||
| `"BaseItemMetadataFields"` | `base_item_metadata_fields` | `BaseItemMetadataField` |
|
||||
| `"BaseItemTrailerTypes"` | `base_item_trailer_types` | `BaseItemTrailerType` |
|
||||
| `"ItemValues"` | `item_values` | `ItemValue` |
|
||||
| `"ItemValuesMaps"` | `item_values_map` | `ItemValueMap` |
|
||||
| `"Peoples"` | `peoples` | `People` |
|
||||
| `"PeopleBaseItemMaps"` | `people_base_item_map` | `PeopleBaseItemMap` |
|
||||
| `"UserData"` | `user_data` | `UserData` |
|
||||
| `"AncestorIds"` | `ancestor_ids` | `AncestorId` |
|
||||
| `"TrickplayInfos"` | `trickplay_infos` | `TrickplayInfo` |
|
||||
| `"MediaSegments"` | `media_segments` | `MediaSegment` |
|
||||
| `"KeyframeData"` | `keyframe_data` | `KeyframeData` |
|
||||
| `"LibraryOptions"` | `library_options` | `LibraryOptionsEntity` |
|
||||
|
||||
### Special Tables
|
||||
| Current (SQL) | Target (C#/PostgreSQL) | Notes |
|
||||
|--------------|------------------------|-------|
|
||||
| `"__EFMigrationsHistory"` | `__ef_migrations_history` | System table, special handling |
|
||||
|
||||
### Views (Library Schema)
|
||||
| Current (SQL) | Target (C#/PostgreSQL) | Entity Class |
|
||||
|--------------|------------------------|--------------|
|
||||
| `"media_stream_infos_v"` | `media_stream_infos_v` | `MediaStreamInfoView` (already lowercase) |
|
||||
| `"video_items_v"` | `video_items_v` | `VideoItemView` (already lowercase) |
|
||||
| `"user_playback_history_v"` | `user_playback_history_v` | `UserPlaybackHistoryView` (already lowercase) |
|
||||
| `"item_providers_v"` | `item_providers_v` | `ItemProviderView` (already lowercase) |
|
||||
| `"item_people_v"` | `item_people_v` | `ItemPersonView` (already lowercase) |
|
||||
| `"library_summary_v"` | `library_summary_v` | `LibrarySummaryView` (already lowercase) |
|
||||
|
||||
---
|
||||
|
||||
## Column Naming Rules
|
||||
|
||||
### SnakeCaseNamingConvention Conversion Rules
|
||||
|
||||
```csharp
|
||||
// From SnakeCaseNamingConvention.cs - regex patterns applied:
|
||||
// Rule 1: ([A-Z]+)([A-Z][a-z]) → "$1_$2" // Consecutive capitals: HTTPServer → HTTP_Server
|
||||
// Rule 2: ([a-z\d])([A-Z]) → "$1_$2" // Lowercase/digit to capital: dateCreated → date_Created
|
||||
// Rule 3: .ToLowerInvariant() // Lowercase: date_Created → date_created
|
||||
```
|
||||
|
||||
### Common Column Conversions
|
||||
|
||||
Single-Word Columns:
|
||||
- `"Id"` → `id`
|
||||
- `"Name"` → `name`
|
||||
- `"Type"` → `type`
|
||||
- `"UserId"` → `user_id`
|
||||
- `"ItemId"` → `item_id`
|
||||
|
||||
Multi-Word Columns:
|
||||
- `"DateCreated"` → `date_created`
|
||||
- `"DateModified"` → `date_modified`
|
||||
- `"DateLastActivity"` → `date_last_activity`
|
||||
- `"AccessToken"` → `access_token`
|
||||
- `"IsMovie"` → `is_movie`
|
||||
- `"IsSeries"` → `is_series`
|
||||
- `"ShortOverview"` → `short_overview`
|
||||
- `"LogSeverity"` → `log_severity`
|
||||
- `"RowVersion"` → `row_version`
|
||||
- `"DisplayOrder"` → `display_order`
|
||||
- `"BaseItemId"` → `base_item_id`
|
||||
- `"DeviceId"` → `device_id`
|
||||
- `"ProviderId"` → `provider_id`
|
||||
- `"SortName"` → `sort_name`
|
||||
- `"ProviderIds"` → `provider_ids`
|
||||
- `"DvVersionMajor"` → `dv_version_major`
|
||||
- `"DvVersionMinor"` → `dv_version_minor`
|
||||
- `"DvProfile"` → `dv_profile`
|
||||
- `"DvLevel"` → `dv_level`
|
||||
- `"RpuVersion"` → `rpu_version`
|
||||
- `"CodecTag"` → `codec_tag`
|
||||
- `"IsHDR"` → `is_hdr`
|
||||
- `"IsHDR10"` → `is_hdr10`
|
||||
- `"SeriesPresentationUniqueKey"` → `series_presentation_unique_key`
|
||||
|
||||
---
|
||||
|
||||
## Conversion Strategy in create_database_schema.sql
|
||||
|
||||
### Phase 1: Table Definition Lines
|
||||
Find and replace quoted PascalCase table names immediately after `CREATE TABLE`:
|
||||
- `CREATE TABLE activitylog."ActivityLogs"` → `CREATE TABLE activitylog.activity_logs`
|
||||
- `CREATE TABLE authentication."ApiKeys"` → `CREATE TABLE authentication.api_keys`
|
||||
- etc. (all 19 tables in schema sections)
|
||||
|
||||
### Phase 2: Column Definitions
|
||||
Within each `CREATE TABLE` block, replace all quoted column names:
|
||||
- `"Id"` → `id`
|
||||
- `"Name"` → `name`
|
||||
- `"DateCreated"` → `date_created`
|
||||
- etc. (200+ column replacements across all tables)
|
||||
|
||||
### Phase 3: Sequence Names
|
||||
Update sequence definitions to match renamed tables:
|
||||
- `SEQUENCE NAME activitylog."ActivityLogs_Id_seq"` → `SEQUENCE NAME activitylog.activity_logs_id_seq`
|
||||
- etc.
|
||||
|
||||
### Phase 4: Constraint References
|
||||
Update all foreign key and constraint definitions:
|
||||
- `CONSTRAINT "fk_base_items_users_user_id" FOREIGN KEY ("BaseItemId") REFERENCES users."Users" ("Id")`
|
||||
→ `CONSTRAINT fk_base_items_users_user_id FOREIGN KEY (base_item_id) REFERENCES users.users (id)`
|
||||
|
||||
### Phase 5: Index References
|
||||
Update index definitions:
|
||||
- `CREATE INDEX "idx_activity_logs_user_id" ON activitylog."ActivityLogs" ("UserId")`
|
||||
→ `CREATE INDEX idx_activity_logs_user_id ON activitylog.activity_logs (user_id)`
|
||||
|
||||
### Phase 6: Special Table (__EFMigrationsHistory)
|
||||
Update system table for consistency:
|
||||
- `"__EFMigrationsHistory"` → `__ef_migrations_history`
|
||||
- All its columns follow the same snake_case rule
|
||||
|
||||
---
|
||||
|
||||
## File Location
|
||||
- **Source**: `Jellyfin.Server/sql/schema_init/create_database_schema.sql` (1879 lines)
|
||||
- **Output**: Same file (in-place conversion)
|
||||
|
||||
---
|
||||
|
||||
## Validation Checklist
|
||||
- [ ] All 19 application tables converted from PascalCase to snake_case
|
||||
- [ ] All 200+ columns converted to lowercase/snake_case
|
||||
- [ ] All sequence names updated
|
||||
- [ ] All foreign key references updated
|
||||
- [ ] All indexes updated
|
||||
- [ ] Special table `__ef_migrations_history` created with lowercase columns
|
||||
- [ ] Views remain unchanged (already lowercase)
|
||||
- [ ] SQL syntax is valid
|
||||
- [ ] No orphaned quotes remain in identifier positions
|
||||
- [ ] Schema still respects owner = jellyfin
|
||||
|
||||
---
|
||||
|
||||
Generated: 2025-05-01
|
||||
Reference Documents:
|
||||
- `PostgresDatabaseProvider.cs` lines 763-875
|
||||
- `SnakeCaseNamingConvention.cs` lines 16-40
|
||||
- `create_database_schema.sql` full schema dump
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0"?>
|
||||
<doc>
|
||||
<assembly>
|
||||
<name>Emby.Photos</name>
|
||||
</assembly>
|
||||
<members>
|
||||
<member name="T:Emby.Photos.PhotoProvider">
|
||||
<summary>
|
||||
Metadata provider for photos.
|
||||
</summary>
|
||||
<param name="logger">The logger.</param>
|
||||
<param name="imageProcessor">The image processor.</param>
|
||||
</member>
|
||||
<member name="M:Emby.Photos.PhotoProvider.#ctor(Microsoft.Extensions.Logging.ILogger{Emby.Photos.PhotoProvider},MediaBrowser.Controller.Drawing.IImageProcessor)">
|
||||
<summary>
|
||||
Metadata provider for photos.
|
||||
</summary>
|
||||
<param name="logger">The logger.</param>
|
||||
<param name="imageProcessor">The image processor.</param>
|
||||
</member>
|
||||
<member name="P:Emby.Photos.PhotoProvider.Name">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:Emby.Photos.PhotoProvider.HasChanged(MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Controller.Providers.IDirectoryService)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:Emby.Photos.PhotoProvider.FetchAsync(MediaBrowser.Controller.Entities.Photo,MediaBrowser.Controller.Providers.MetadataRefreshOptions,System.Threading.CancellationToken)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
</members>
|
||||
</doc>
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,484 @@
|
||||
<?xml version="1.0"?>
|
||||
<doc>
|
||||
<assembly>
|
||||
<name>Jellyfin.Database.Providers.Postgres</name>
|
||||
</assembly>
|
||||
<members>
|
||||
<member name="T:Jellyfin.Database.Providers.Postgres.BulkOperationExtensions">
|
||||
<summary>
|
||||
Extension methods for optimized bulk operations on PostgreSQL.
|
||||
These methods help reduce dead tuple generation during media library scans by batching
|
||||
operations and using more efficient transaction patterns.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Database.Providers.Postgres.BulkOperationExtensions.GetRecommendedBatchSize(System.Int32)">
|
||||
<summary>
|
||||
Gets the recommended batch size for bulk operations based on the data size.
|
||||
PostgreSQL works best with smaller batches to reduce dead tuples during scans.
|
||||
</summary>
|
||||
<param name="entityCount">The total number of entities to process.</param>
|
||||
<returns>The recommended batch size.</returns>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Database.Providers.Postgres.BulkOperationExtensions.SaveChangesInBatchesAsync(Microsoft.EntityFrameworkCore.DbContext,System.Int32,System.Threading.CancellationToken)">
|
||||
<summary>
|
||||
Saves changes in batches to reduce dead tuple generation during bulk operations.
|
||||
This is especially useful during media library scans where many items are created/updated/deleted.
|
||||
</summary>
|
||||
<param name="context">The database context.</param>
|
||||
<param name="batchSize">The batch size for SaveChanges calls. If 0 or negative, uses automatic sizing.</param>
|
||||
<param name="cancellationToken">Cancellation token.</param>
|
||||
<returns>Total number of changes saved.</returns>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Database.Providers.Postgres.BulkOperationExtensions.BulkDeleteAsync(Microsoft.EntityFrameworkCore.DbContext,System.String,System.String,System.String,System.Threading.CancellationToken)">
|
||||
<summary>
|
||||
Deletes entities in batches using raw SQL for better performance.
|
||||
This reduces dead tuple generation compared to loading and deleting entities individually.
|
||||
</summary>
|
||||
<param name="context">The database context.</param>
|
||||
<param name="schema">The PostgreSQL schema name.</param>
|
||||
<param name="tableName">The table name.</param>
|
||||
<param name="whereClause">Optional WHERE clause (without WHERE keyword). If null, all rows are deleted.</param>
|
||||
<param name="cancellationToken">Cancellation token.</param>
|
||||
<returns>The number of rows deleted.</returns>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Database.Providers.Postgres.BulkOperationExtensions.BulkTruncateAsync(Microsoft.EntityFrameworkCore.DbContext,System.String,System.String,System.Boolean,System.Threading.CancellationToken)">
|
||||
<summary>
|
||||
Truncates a table completely, which is much faster than DELETE for large tables.
|
||||
Note: This cannot be used if there are foreign key references.
|
||||
</summary>
|
||||
<param name="context">The database context.</param>
|
||||
<param name="schema">The PostgreSQL schema name.</param>
|
||||
<param name="tableName">The table name.</param>
|
||||
<param name="cascadeDelete">If true, will CASCADE delete related rows. Use with caution.</param>
|
||||
<param name="cancellationToken">Cancellation token.</param>
|
||||
<returns>A task representing the asynchronous operation.</returns>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Database.Providers.Postgres.BulkOperationExtensions.BulkAddAsync``1(Microsoft.EntityFrameworkCore.DbContext,System.Collections.Generic.IEnumerable{``0},System.Int32,System.Boolean,System.Threading.CancellationToken)">
|
||||
<summary>
|
||||
Adds a large collection of entities to the context in batches to avoid memory issues.
|
||||
</summary>
|
||||
<typeparam name="T">The entity type.</typeparam>
|
||||
<param name="context">The database context.</param>
|
||||
<param name="entities">The entities to add.</param>
|
||||
<param name="batchSize">The batch size for adding entities.</param>
|
||||
<param name="saveAfterEachBatch">Whether to save changes after each batch.</param>
|
||||
<param name="cancellationToken">Cancellation token.</param>
|
||||
<returns>Total number of entities saved.</returns>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Database.Providers.Postgres.BulkOperationExtensions.BulkUpdateAsync``1(Microsoft.EntityFrameworkCore.DbContext,System.Collections.Generic.IEnumerable{``0},System.Int32,System.Threading.CancellationToken)">
|
||||
<summary>
|
||||
Updates entities in batches to reduce dead tuple generation.
|
||||
</summary>
|
||||
<typeparam name="T">The entity type.</typeparam>
|
||||
<param name="context">The database context.</param>
|
||||
<param name="entities">The entities to update.</param>
|
||||
<param name="batchSize">The batch size for updates.</param>
|
||||
<param name="cancellationToken">Cancellation token.</param>
|
||||
<returns>Total number of entities updated.</returns>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Database.Providers.Postgres.BulkOperationExtensions.BulkRemoveAsync``1(Microsoft.EntityFrameworkCore.DbContext,System.Collections.Generic.IEnumerable{``0},System.Int32,System.Threading.CancellationToken)">
|
||||
<summary>
|
||||
Removes entities in batches to reduce dead tuple generation.
|
||||
</summary>
|
||||
<typeparam name="T">The entity type.</typeparam>
|
||||
<param name="context">The database context.</param>
|
||||
<param name="entities">The entities to remove.</param>
|
||||
<param name="batchSize">The batch size for removals.</param>
|
||||
<param name="cancellationToken">Cancellation token.</param>
|
||||
<returns>Total number of entities removed.</returns>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Database.Providers.Postgres.BulkOperationExtensions.VacuumTableAsync(Microsoft.EntityFrameworkCore.DbContext,System.String,System.String,System.Threading.CancellationToken)">
|
||||
<summary>
|
||||
Executes VACUUM ANALYZE on a specific table to reclaim dead tuples and update statistics.
|
||||
This is useful to call after bulk deletion operations.
|
||||
</summary>
|
||||
<param name="context">The database context.</param>
|
||||
<param name="schema">The PostgreSQL schema name.</param>
|
||||
<param name="tableName">The table name.</param>
|
||||
<param name="cancellationToken">Cancellation token.</param>
|
||||
<returns>A task representing the asynchronous operation.</returns>
|
||||
</member>
|
||||
<member name="T:Jellyfin.Database.Providers.Postgres.BulkOperationTransaction">
|
||||
<summary>
|
||||
Helper class for managing transactions during bulk operations to reduce dead tuple generation.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Database.Providers.Postgres.BulkOperationTransaction.#ctor(Jellyfin.Database.Implementations.JellyfinDbContext)">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:Jellyfin.Database.Providers.Postgres.BulkOperationTransaction"/> class.
|
||||
</summary>
|
||||
<param name="context">The database context.</param>
|
||||
</member>
|
||||
<member name="P:Jellyfin.Database.Providers.Postgres.BulkOperationTransaction.IsCommitted">
|
||||
<summary>
|
||||
Gets a value indicating whether the transaction has been committed.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Database.Providers.Postgres.BulkOperationTransaction.BeginAsync(System.Threading.CancellationToken)">
|
||||
<summary>
|
||||
Begins a new transaction with isolation level optimized for bulk operations.
|
||||
</summary>
|
||||
<param name="cancellationToken">Cancellation token.</param>
|
||||
<returns>A task representing the asynchronous operation.</returns>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Database.Providers.Postgres.BulkOperationTransaction.CommitAsync(System.Threading.CancellationToken)">
|
||||
<summary>
|
||||
Commits the transaction.
|
||||
</summary>
|
||||
<param name="cancellationToken">Cancellation token.</param>
|
||||
<returns>A task representing the asynchronous operation.</returns>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Database.Providers.Postgres.BulkOperationTransaction.RollbackAsync(System.Threading.CancellationToken)">
|
||||
<summary>
|
||||
Rolls back the transaction.
|
||||
</summary>
|
||||
<param name="cancellationToken">Cancellation token.</param>
|
||||
<returns>A task representing the asynchronous operation.</returns>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Database.Providers.Postgres.BulkOperationTransaction.Dispose">
|
||||
<summary>
|
||||
Disposes the transaction if not already committed.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Database.Providers.Postgres.BulkOperationTransaction.DisposeAsync">
|
||||
<summary>
|
||||
Disposes the transaction asynchronously if not already committed.
|
||||
</summary>
|
||||
<returns>A task representing the asynchronous operation.</returns>
|
||||
</member>
|
||||
<member name="T:Jellyfin.Database.Providers.Postgres.BulkTransactionExtensions">
|
||||
<summary>
|
||||
Extension methods for managing bulk operation transactions.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Database.Providers.Postgres.BulkTransactionExtensions.ExecuteBulkOperationAsync(Jellyfin.Database.Implementations.JellyfinDbContext,System.Func{System.Threading.Tasks.Task},System.Threading.CancellationToken)">
|
||||
<summary>
|
||||
Executes a bulk operation within a transaction.
|
||||
</summary>
|
||||
<param name="context">The database context.</param>
|
||||
<param name="operation">The bulk operation to execute.</param>
|
||||
<param name="cancellationToken">Cancellation token.</param>
|
||||
<returns>A task representing the asynchronous operation.</returns>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Database.Providers.Postgres.BulkTransactionExtensions.ExecuteBulkOperationAsync``1(Jellyfin.Database.Implementations.JellyfinDbContext,System.Func{System.Threading.Tasks.Task{``0}},System.Threading.CancellationToken)">
|
||||
<summary>
|
||||
Executes a bulk operation within a transaction and returns a result.
|
||||
</summary>
|
||||
<typeparam name="T">The type of result returned by the operation.</typeparam>
|
||||
<param name="context">The database context.</param>
|
||||
<param name="operation">The bulk operation to execute.</param>
|
||||
<param name="cancellationToken">Cancellation token.</param>
|
||||
<returns>The result of the operation.</returns>
|
||||
</member>
|
||||
<member name="T:Jellyfin.Database.Providers.Postgres.Exceptions.PostgresVersionMismatchException">
|
||||
<summary>
|
||||
Exception thrown when there is a version mismatch between PostgreSQL server and client tools (pg_dump/pg_restore).
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Database.Providers.Postgres.Exceptions.PostgresVersionMismatchException.#ctor(System.String,System.String)">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:Jellyfin.Database.Providers.Postgres.Exceptions.PostgresVersionMismatchException"/> class.
|
||||
</summary>
|
||||
<param name="serverVersion">The PostgreSQL server version.</param>
|
||||
<param name="clientVersion">The pg_dump/pg_restore client version.</param>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Database.Providers.Postgres.Exceptions.PostgresVersionMismatchException.#ctor(System.String,System.String,System.Exception)">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:Jellyfin.Database.Providers.Postgres.Exceptions.PostgresVersionMismatchException"/> class.
|
||||
</summary>
|
||||
<param name="serverVersion">The PostgreSQL server version.</param>
|
||||
<param name="clientVersion">The pg_dump/pg_restore client version.</param>
|
||||
<param name="innerException">The inner exception.</param>
|
||||
</member>
|
||||
<member name="P:Jellyfin.Database.Providers.Postgres.Exceptions.PostgresVersionMismatchException.ServerVersion">
|
||||
<summary>
|
||||
Gets the PostgreSQL server version.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:Jellyfin.Database.Providers.Postgres.Exceptions.PostgresVersionMismatchException.ClientVersion">
|
||||
<summary>
|
||||
Gets the pg_dump/pg_restore client version.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:Jellyfin.Database.Providers.Postgres.Migrations.InitialCreate">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:Jellyfin.Database.Providers.Postgres.Migrations.InitialCreate.Up(Microsoft.EntityFrameworkCore.Migrations.MigrationBuilder)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:Jellyfin.Database.Providers.Postgres.Migrations.InitialCreate.Down(Microsoft.EntityFrameworkCore.Migrations.MigrationBuilder)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:Jellyfin.Database.Providers.Postgres.Migrations.InitialCreate.BuildTargetModel(Microsoft.EntityFrameworkCore.ModelBuilder)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="T:Jellyfin.Database.Providers.Postgres.Migrations.AddBasePerformanceIndexes">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:Jellyfin.Database.Providers.Postgres.Migrations.AddBasePerformanceIndexes.Up(Microsoft.EntityFrameworkCore.Migrations.MigrationBuilder)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:Jellyfin.Database.Providers.Postgres.Migrations.AddBasePerformanceIndexes.Down(Microsoft.EntityFrameworkCore.Migrations.MigrationBuilder)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="T:Jellyfin.Database.Providers.Postgres.Migrations.AddSupplementaryIndexes">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:Jellyfin.Database.Providers.Postgres.Migrations.AddSupplementaryIndexes.Up(Microsoft.EntityFrameworkCore.Migrations.MigrationBuilder)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:Jellyfin.Database.Providers.Postgres.Migrations.AddSupplementaryIndexes.Down(Microsoft.EntityFrameworkCore.Migrations.MigrationBuilder)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="T:Jellyfin.Database.Providers.Postgres.Migrations.AddLibraryOptionsTable">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:Jellyfin.Database.Providers.Postgres.Migrations.AddLibraryOptionsTable.Up(Microsoft.EntityFrameworkCore.Migrations.MigrationBuilder)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:Jellyfin.Database.Providers.Postgres.Migrations.AddLibraryOptionsTable.Down(Microsoft.EntityFrameworkCore.Migrations.MigrationBuilder)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="T:Jellyfin.Database.Providers.Postgres.Migrations.SplitMediaStreamInfos">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:Jellyfin.Database.Providers.Postgres.Migrations.SplitMediaStreamInfos.Up(Microsoft.EntityFrameworkCore.Migrations.MigrationBuilder)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:Jellyfin.Database.Providers.Postgres.Migrations.SplitMediaStreamInfos.Down(Microsoft.EntityFrameworkCore.Migrations.MigrationBuilder)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="T:Jellyfin.Database.Providers.Postgres.Migrations.DropBaseItemFlatExtensionColumns">
|
||||
<summary>
|
||||
Removes the 17 flat extension columns from library.BaseItems now that all data has been
|
||||
migrated to the BaseItemTvExtras, BaseItemLiveTvExtras, and BaseItemAudioExtras tables.
|
||||
Must run AFTER SplitBaseItemExtras (20260503000000).
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Database.Providers.Postgres.Migrations.DropBaseItemFlatExtensionColumns.Up(Microsoft.EntityFrameworkCore.Migrations.MigrationBuilder)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:Jellyfin.Database.Providers.Postgres.Migrations.DropBaseItemFlatExtensionColumns.Down(Microsoft.EntityFrameworkCore.Migrations.MigrationBuilder)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="T:Jellyfin.Database.Providers.Postgres.Migrations.PostgresDesignTimeJellyfinDbFactory">
|
||||
<summary>
|
||||
The design time factory for <see cref="T:Jellyfin.Database.Implementations.JellyfinDbContext"/>.
|
||||
This is only used for the creation of migrations and not during runtime.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Database.Providers.Postgres.Migrations.PostgresDesignTimeJellyfinDbFactory.CreateDbContext(System.String[])">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="T:Jellyfin.Database.Providers.Postgres.Migrations.SplitBaseItemExtras">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:Jellyfin.Database.Providers.Postgres.Migrations.SplitBaseItemExtras.Up(Microsoft.EntityFrameworkCore.Migrations.MigrationBuilder)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:Jellyfin.Database.Providers.Postgres.Migrations.SplitBaseItemExtras.Down(Microsoft.EntityFrameworkCore.Migrations.MigrationBuilder)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="T:Jellyfin.Database.Providers.Postgres.ModelBuilderExtensions">
|
||||
<summary>
|
||||
Model builder extensions for PostgreSQL.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Database.Providers.Postgres.ModelBuilderExtensions.UseValueConverterForType``1(Microsoft.EntityFrameworkCore.ModelBuilder,Microsoft.EntityFrameworkCore.Storage.ValueConversion.ValueConverter)">
|
||||
<summary>
|
||||
Specify value converter for the object type.
|
||||
</summary>
|
||||
<param name="modelBuilder">The model builder.</param>
|
||||
<param name="converter">The <see cref="T:Microsoft.EntityFrameworkCore.Storage.ValueConversion.ValueConverter`2"/>.</param>
|
||||
<typeparam name="T">The type to convert.</typeparam>
|
||||
<returns>The modified <see cref="T:Microsoft.EntityFrameworkCore.ModelBuilder"/>.</returns>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Database.Providers.Postgres.ModelBuilderExtensions.SetDefaultDateTimeKind(Microsoft.EntityFrameworkCore.ModelBuilder,System.DateTimeKind)">
|
||||
<summary>
|
||||
Specify the default <see cref="T:System.DateTimeKind"/>.
|
||||
</summary>
|
||||
<param name="modelBuilder">The model builder to extend.</param>
|
||||
<param name="kind">The <see cref="T:System.DateTimeKind"/> to specify.</param>
|
||||
</member>
|
||||
<member name="T:Jellyfin.Database.Providers.Postgres.PostgresBackupService">
|
||||
<summary>
|
||||
Service for creating and restoring PostgreSQL database backups using pg_dump/pg_restore.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Database.Providers.Postgres.PostgresBackupService.#ctor(Microsoft.Extensions.Logging.ILogger{Jellyfin.Database.Providers.Postgres.PostgresBackupService},MediaBrowser.Common.Configuration.IConfigurationManager)">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:Jellyfin.Database.Providers.Postgres.PostgresBackupService"/> class.
|
||||
</summary>
|
||||
<param name="logger">The logger.</param>
|
||||
<param name="configurationManager">The configuration manager.</param>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Database.Providers.Postgres.PostgresBackupService.CreateBackupAsync(System.String,System.Threading.CancellationToken)">
|
||||
<summary>
|
||||
Creates a backup of the PostgreSQL database.
|
||||
</summary>
|
||||
<param name="backupDirectory">The directory to save the backup file.</param>
|
||||
<param name="cancellationToken">Cancellation token.</param>
|
||||
<returns>The path to the created backup file.</returns>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Database.Providers.Postgres.PostgresBackupService.RestoreBackupAsync(System.String,System.Threading.CancellationToken)">
|
||||
<summary>
|
||||
Restores a PostgreSQL database from a backup file.
|
||||
</summary>
|
||||
<param name="backupFilePath">The path to the backup file.</param>
|
||||
<param name="cancellationToken">Cancellation token.</param>
|
||||
<returns>A task representing the restore operation.</returns>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Database.Providers.Postgres.PostgresBackupService.ParseVersionMismatchError(System.String)">
|
||||
<summary>
|
||||
Parses PostgreSQL version mismatch error message to extract server and client versions.
|
||||
</summary>
|
||||
<param name="errorMessage">The error message from pg_dump.</param>
|
||||
<returns>A tuple containing server version and client version.</returns>
|
||||
</member>
|
||||
<member name="T:Jellyfin.Database.Providers.Postgres.PostgresDatabaseProvider">
|
||||
<summary>
|
||||
Configures Jellyfin to use a PostgreSQL database.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Database.Providers.Postgres.PostgresDatabaseProvider.#ctor(MediaBrowser.Common.Configuration.IApplicationPaths,Microsoft.Extensions.Logging.ILogger{Jellyfin.Database.Providers.Postgres.PostgresDatabaseProvider},MediaBrowser.Common.Configuration.IConfigurationManager)">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:Jellyfin.Database.Providers.Postgres.PostgresDatabaseProvider"/> class.
|
||||
</summary>
|
||||
<param name="applicationPaths">Service to construct the fallback when the old data path configuration is used.</param>
|
||||
<param name="logger">A logger.</param>
|
||||
<param name="configurationManager">The configuration manager (optional, for backup support).</param>
|
||||
</member>
|
||||
<member name="T:Jellyfin.Database.Providers.Postgres.PostgresDatabaseProvider.Schemas">
|
||||
<summary>
|
||||
Schema names mapping to legacy database files.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:Jellyfin.Database.Providers.Postgres.PostgresDatabaseProvider.Schemas.ActivityLog">
|
||||
<summary>
|
||||
Schema for activity log tables (legacy: activitylog.db).
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:Jellyfin.Database.Providers.Postgres.PostgresDatabaseProvider.Schemas.Authentication">
|
||||
<summary>
|
||||
Schema for authentication tables (legacy: authentication.db).
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:Jellyfin.Database.Providers.Postgres.PostgresDatabaseProvider.Schemas.DisplayPreferences">
|
||||
<summary>
|
||||
Schema for display preferences tables (legacy: displaypreferences.db).
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:Jellyfin.Database.Providers.Postgres.PostgresDatabaseProvider.Schemas.Library">
|
||||
<summary>
|
||||
Schema for library tables (legacy: library.db).
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:Jellyfin.Database.Providers.Postgres.PostgresDatabaseProvider.Schemas.Users">
|
||||
<summary>
|
||||
Schema for user tables (legacy: users.db).
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:Jellyfin.Database.Providers.Postgres.PostgresDatabaseProvider.Schemas.All">
|
||||
<summary>
|
||||
Gets all schema names.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:Jellyfin.Database.Providers.Postgres.PostgresDatabaseProvider.DbContextFactory">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Database.Providers.Postgres.PostgresDatabaseProvider.Initialise(Microsoft.EntityFrameworkCore.DbContextOptionsBuilder,Jellyfin.Database.Implementations.DbConfiguration.DatabaseConfigurationOptions)">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Database.Providers.Postgres.PostgresDatabaseProvider.EnsureDatabaseExistsAsync(Jellyfin.Database.Implementations.DbConfiguration.DatabaseConfigurationOptions,System.Threading.CancellationToken)">
|
||||
<summary>
|
||||
Ensures the PostgreSQL database exists, creating it if necessary.
|
||||
</summary>
|
||||
<param name="databaseConfiguration">The database configuration.</param>
|
||||
<param name="cancellationToken">Cancellation token.</param>
|
||||
<returns>A task representing the asynchronous operation.</returns>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Database.Providers.Postgres.PostgresDatabaseProvider.EnsureSchemasExistAsync(System.String,System.Int32,System.String,System.String,System.String,System.Int32,System.Threading.CancellationToken)">
|
||||
<summary>
|
||||
Ensures all required schemas exist in the PostgreSQL database.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Database.Providers.Postgres.PostgresDatabaseProvider.EnsureTablesExistAsync(System.Threading.CancellationToken)">
|
||||
<summary>
|
||||
Ensures all required database tables exist by applying pending migrations.
|
||||
</summary>
|
||||
<param name="cancellationToken">Cancellation token.</param>
|
||||
<returns>A task representing the asynchronous operation.</returns>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Database.Providers.Postgres.PostgresDatabaseProvider.RunScheduledOptimisation(System.Threading.CancellationToken)">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Database.Providers.Postgres.PostgresDatabaseProvider.OnModelCreating(Microsoft.EntityFrameworkCore.ModelBuilder)">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Database.Providers.Postgres.PostgresDatabaseProvider.AssignEntitiesToSchemas(Microsoft.EntityFrameworkCore.ModelBuilder)">
|
||||
<summary>
|
||||
Assigns entities to PostgreSQL schemas based on their legacy database origin.
|
||||
</summary>
|
||||
<param name="modelBuilder">The model builder.</param>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Database.Providers.Postgres.PostgresDatabaseProvider.RegisterViewMappings(Microsoft.EntityFrameworkCore.ModelBuilder)">
|
||||
<summary>
|
||||
Registers read-only keyless view projections for PostgreSQL.
|
||||
Each view lives in the <c>library</c> schema and is mapped with
|
||||
<c>HasNoKey().ToView()</c> so EF Core never tries to create a table or migration for it.
|
||||
</summary>
|
||||
<param name="modelBuilder">The model builder.</param>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Database.Providers.Postgres.PostgresDatabaseProvider.RunShutdownTask(System.Threading.CancellationToken)">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Database.Providers.Postgres.PostgresDatabaseProvider.ConfigureConventions(Microsoft.EntityFrameworkCore.ModelConfigurationBuilder)">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Database.Providers.Postgres.PostgresDatabaseProvider.MigrationBackupFast(System.Threading.CancellationToken)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:Jellyfin.Database.Providers.Postgres.PostgresDatabaseProvider.RestoreBackupFast(System.String,System.Threading.CancellationToken)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:Jellyfin.Database.Providers.Postgres.PostgresDatabaseProvider.DeleteBackup(System.String)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:Jellyfin.Database.Providers.Postgres.PostgresDatabaseProvider.IsLocalHost(System.String)">
|
||||
<summary>
|
||||
Determines if the given host is localhost.
|
||||
</summary>
|
||||
<param name="host">The host to check.</param>
|
||||
<returns>True if the host is localhost or 127.0.0.1, false otherwise.</returns>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Database.Providers.Postgres.PostgresDatabaseProvider.PurgeDatabase(Jellyfin.Database.Implementations.JellyfinDbContext,System.Collections.Generic.IEnumerable{System.String})">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="T:Jellyfin.Database.Providers.Postgres.SnakeCaseNamingConvention">
|
||||
<summary>
|
||||
EF Core convention that maps all entity property names to snake_case column names.
|
||||
Applied only by the PostgreSQL provider so SQLite is unaffected.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Database.Providers.Postgres.SnakeCaseNamingConvention.ProcessPropertyAdded(Microsoft.EntityFrameworkCore.Metadata.Builders.IConventionPropertyBuilder,Microsoft.EntityFrameworkCore.Metadata.Conventions.IConventionContext{Microsoft.EntityFrameworkCore.Metadata.Builders.IConventionPropertyBuilder})">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:Jellyfin.Database.Providers.Postgres.SnakeCaseNamingConvention.ToSnakeCase(System.String)">
|
||||
<summary>Converts a PascalCase identifier to snake_case.</summary>
|
||||
<param name="name">The identifier to convert.</param>
|
||||
<returns>The snake_case representation of the identifier.</returns>
|
||||
</member>
|
||||
<member name="T:Jellyfin.Database.Providers.Postgres.ValueConverters.DateTimeKindValueConverter">
|
||||
<summary>
|
||||
ValueConverter to specify DateTime kind for PostgreSQL.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Database.Providers.Postgres.ValueConverters.DateTimeKindValueConverter.#ctor(System.DateTimeKind,Microsoft.EntityFrameworkCore.Storage.ValueConversion.ConverterMappingHints)">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:Jellyfin.Database.Providers.Postgres.ValueConverters.DateTimeKindValueConverter"/> class.
|
||||
</summary>
|
||||
<param name="kind">The kind to specify.</param>
|
||||
<param name="mappingHints">The mapping hints.</param>
|
||||
</member>
|
||||
</members>
|
||||
</doc>
|
||||
@@ -0,0 +1,352 @@
|
||||
<?xml version="1.0"?>
|
||||
<doc>
|
||||
<assembly>
|
||||
<name>Jellyfin.Drawing.Skia</name>
|
||||
</assembly>
|
||||
<members>
|
||||
<member name="T:Jellyfin.Drawing.Skia.PercentPlayedDrawer">
|
||||
<summary>
|
||||
Static helper class used to draw percentage-played indicators on images.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Drawing.Skia.PercentPlayedDrawer.Process(SkiaSharp.SKCanvas,MediaBrowser.Model.Drawing.ImageDimensions,System.Double)">
|
||||
<summary>
|
||||
Draw a percentage played indicator on a canvas.
|
||||
</summary>
|
||||
<param name="canvas">The canvas to draw the indicator on.</param>
|
||||
<param name="imageSize">The size of the image being drawn on.</param>
|
||||
<param name="percent">The percentage played to display with the indicator.</param>
|
||||
</member>
|
||||
<member name="T:Jellyfin.Drawing.Skia.SkiaEncoder">
|
||||
<summary>
|
||||
Image encoder that uses <see cref="N:SkiaSharp"/> to manipulate images.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:Jellyfin.Drawing.Skia.SkiaEncoder.UpscaleSamplingOptions">
|
||||
<summary>
|
||||
The default sampling options, equivalent to old high quality filter settings when upscaling.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:Jellyfin.Drawing.Skia.SkiaEncoder.DefaultSamplingOptions">
|
||||
<summary>
|
||||
The sampling options, used for downscaling images, equivalent to old high quality filter settings when not upscaling.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Drawing.Skia.SkiaEncoder.#ctor(Microsoft.Extensions.Logging.ILogger{Jellyfin.Drawing.Skia.SkiaEncoder},MediaBrowser.Common.Configuration.IApplicationPaths)">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:Jellyfin.Drawing.Skia.SkiaEncoder"/> class.
|
||||
</summary>
|
||||
<param name="logger">The application logger.</param>
|
||||
<param name="appPaths">The application paths.</param>
|
||||
</member>
|
||||
<member name="P:Jellyfin.Drawing.Skia.SkiaEncoder.Name">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="P:Jellyfin.Drawing.Skia.SkiaEncoder.SupportsImageCollageCreation">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="P:Jellyfin.Drawing.Skia.SkiaEncoder.SupportsImageEncoding">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="P:Jellyfin.Drawing.Skia.SkiaEncoder.SupportedInputFormats">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="P:Jellyfin.Drawing.Skia.SkiaEncoder.SupportedOutputFormats">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="P:Jellyfin.Drawing.Skia.SkiaEncoder.DefaultTypeFace">
|
||||
<summary>
|
||||
Gets the default typeface to use.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Drawing.Skia.SkiaEncoder.IsNativeLibAvailable">
|
||||
<summary>
|
||||
Check if the native lib is available.
|
||||
</summary>
|
||||
<returns>True if the native lib is available, otherwise false.</returns>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Drawing.Skia.SkiaEncoder.GetImageFormat(MediaBrowser.Model.Drawing.ImageFormat)">
|
||||
<summary>
|
||||
Convert a <see cref="T:MediaBrowser.Model.Drawing.ImageFormat"/> to a <see cref="T:SkiaSharp.SKEncodedImageFormat"/>.
|
||||
</summary>
|
||||
<param name="selectedFormat">The format to convert.</param>
|
||||
<returns>The converted format.</returns>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Drawing.Skia.SkiaEncoder.GetImageSize(System.String)">
|
||||
<inheritdoc />
|
||||
<exception cref="T:System.IO.FileNotFoundException">The path is not valid.</exception>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Drawing.Skia.SkiaEncoder.GetImageBlurHash(System.Int32,System.Int32,System.String)">
|
||||
<inheritdoc />
|
||||
<exception cref="T:System.ArgumentNullException">The path is null.</exception>
|
||||
<exception cref="T:System.IO.FileNotFoundException">The path is not valid.</exception>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Drawing.Skia.SkiaEncoder.Decode(System.String,System.Boolean,System.Nullable{MediaBrowser.Model.Drawing.ImageOrientation},SkiaSharp.SKEncodedOrigin@)">
|
||||
<summary>
|
||||
Decode an image.
|
||||
</summary>
|
||||
<param name="path">The filepath of the image to decode.</param>
|
||||
<param name="forceCleanBitmap">Whether to force clean the bitmap.</param>
|
||||
<param name="orientation">The orientation of the image.</param>
|
||||
<param name="origin">The detected origin of the image.</param>
|
||||
<returns>The resulting bitmap of the image.</returns>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Drawing.Skia.SkiaEncoder.ResizeImage(SkiaSharp.SKBitmap,SkiaSharp.SKImageInfo,System.Boolean,System.Boolean)">
|
||||
<summary>
|
||||
Resizes an image on the CPU, by utilizing a surface and canvas.
|
||||
|
||||
The convolutional matrix kernel used in this resize function gives a (light) sharpening effect.
|
||||
This technique is similar to effect that can be created using for example the [Convolution matrix filter in GIMP](https://docs.gimp.org/2.10/en/gimp-filter-convolution-matrix.html).
|
||||
</summary>
|
||||
<param name="source">The source bitmap.</param>
|
||||
<param name="targetInfo">This specifies the target size and other information required to create the surface.</param>
|
||||
<param name="isAntialias">This enables anti-aliasing on the SKPaint instance.</param>
|
||||
<param name="isDither">This enables dithering on the SKPaint instance.</param>
|
||||
<returns>The resized image.</returns>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Drawing.Skia.SkiaEncoder.EncodeImage(System.String,System.DateTime,System.String,System.Boolean,System.Nullable{MediaBrowser.Model.Drawing.ImageOrientation},System.Int32,MediaBrowser.Controller.Drawing.ImageProcessingOptions,MediaBrowser.Model.Drawing.ImageFormat)">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Drawing.Skia.SkiaEncoder.CreateImageCollage(MediaBrowser.Controller.Drawing.ImageCollageOptions,System.String)">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Drawing.Skia.SkiaEncoder.CreateSplashscreen(System.Collections.Generic.IReadOnlyList{System.String},System.Collections.Generic.IReadOnlyList{System.String})">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:Jellyfin.Drawing.Skia.SkiaEncoder.CreateTrickplayTile(MediaBrowser.Controller.Drawing.ImageCollageOptions,System.Int32,System.Int32,System.Nullable{System.Int32})">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:Jellyfin.Drawing.Skia.SkiaEncoder.GetFontForCharacter(System.String)">
|
||||
<summary>
|
||||
Return the typeface that contains the glyph for the given character.
|
||||
</summary>
|
||||
<param name="c">The text character.</param>
|
||||
<returns>The typeface contains the character.</returns>
|
||||
</member>
|
||||
<member name="T:Jellyfin.Drawing.Skia.SkiaExtensions">
|
||||
<summary>
|
||||
The SkiaSharp extensions.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Drawing.Skia.SkiaExtensions.DrawBitmap(SkiaSharp.SKCanvas,SkiaSharp.SKBitmap,SkiaSharp.SKRect,SkiaSharp.SKSamplingOptions,SkiaSharp.SKPaint)">
|
||||
<summary>
|
||||
Draws an SKBitmap on the canvas with specified SkSamplingOptions.
|
||||
</summary>
|
||||
<param name="canvas">The SKCanvas to draw on.</param>
|
||||
<param name="bitmap">The SKBitmap to draw.</param>
|
||||
<param name="dest">The destination SKRect.</param>
|
||||
<param name="options">The SKSamplingOptions to use for rendering.</param>
|
||||
<param name="paint">Optional SKPaint to apply additional effects or styles.</param>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Drawing.Skia.SkiaExtensions.DrawBitmap(SkiaSharp.SKCanvas,SkiaSharp.SKBitmap,System.Single,System.Single,SkiaSharp.SKSamplingOptions,SkiaSharp.SKPaint)">
|
||||
<summary>
|
||||
Draws an SKBitmap on the canvas at the specified coordinates with the given SkSamplingOptions.
|
||||
</summary>
|
||||
<param name="canvas">The SKCanvas to draw on.</param>
|
||||
<param name="bitmap">The SKBitmap to draw.</param>
|
||||
<param name="x">The x-coordinate where the bitmap will be drawn.</param>
|
||||
<param name="y">The y-coordinate where the bitmap will be drawn.</param>
|
||||
<param name="options">The SKSamplingOptions to use for rendering.</param>
|
||||
<param name="paint">Optional SKPaint to apply additional effects or styles.</param>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Drawing.Skia.SkiaExtensions.DrawBitmap(SkiaSharp.SKCanvas,SkiaSharp.SKBitmap,SkiaSharp.SKRect,SkiaSharp.SKRect,SkiaSharp.SKSamplingOptions,SkiaSharp.SKPaint)">
|
||||
<summary>
|
||||
Draws an SKBitmap on the canvas using a specified source rectangle, destination rectangle,
|
||||
and optional paint, with the given SkSamplingOptions.
|
||||
</summary>
|
||||
<param name="canvas">The SKCanvas to draw on.</param>
|
||||
<param name="bitmap">The SKBitmap to draw.</param>
|
||||
<param name="source">
|
||||
The source SKRect defining the portion of the bitmap to draw.
|
||||
</param>
|
||||
<param name="dest">
|
||||
The destination SKRect defining the area on the canvas where the bitmap will be drawn.
|
||||
</param>
|
||||
<param name="options">The SKSamplingOptions to use for rendering.</param>
|
||||
<param name="paint">Optional SKPaint to apply additional effects or styles.</param>
|
||||
</member>
|
||||
<member name="T:Jellyfin.Drawing.Skia.SkiaHelper">
|
||||
<summary>
|
||||
Class containing helper methods for working with SkiaSharp.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Drawing.Skia.SkiaHelper.GetNextValidImage(Jellyfin.Drawing.Skia.SkiaEncoder,System.Collections.Generic.IReadOnlyList{System.String},System.Int32,System.Int32@)">
|
||||
<summary>
|
||||
Gets the next valid image as a bitmap.
|
||||
</summary>
|
||||
<param name="skiaEncoder">The current skia encoder.</param>
|
||||
<param name="paths">The list of image paths.</param>
|
||||
<param name="currentIndex">The current checked index.</param>
|
||||
<param name="newIndex">The new index.</param>
|
||||
<returns>A valid bitmap, or null if no bitmap exists after <c>currentIndex</c>.</returns>
|
||||
</member>
|
||||
<member name="T:Jellyfin.Drawing.Skia.SplashscreenBuilder">
|
||||
<summary>
|
||||
Used to build the splashscreen.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Drawing.Skia.SplashscreenBuilder.#ctor(Jellyfin.Drawing.Skia.SkiaEncoder,Microsoft.Extensions.Logging.ILogger)">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:Jellyfin.Drawing.Skia.SplashscreenBuilder"/> class.
|
||||
</summary>
|
||||
<param name="skiaEncoder">The SkiaEncoder.</param>
|
||||
<param name="logger">The logger.</param>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Drawing.Skia.SplashscreenBuilder.GenerateSplash(System.Collections.Generic.IReadOnlyList{System.String},System.Collections.Generic.IReadOnlyList{System.String},System.String)">
|
||||
<summary>
|
||||
Generate a splashscreen.
|
||||
</summary>
|
||||
<param name="posters">The poster paths.</param>
|
||||
<param name="backdrops">The landscape paths.</param>
|
||||
<param name="outputPath">The output path.</param>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Drawing.Skia.SplashscreenBuilder.GenerateCollage(System.Collections.Generic.IReadOnlyList{System.String},System.Collections.Generic.IReadOnlyList{System.String})">
|
||||
<summary>
|
||||
Generates a collage of posters and landscape pictures.
|
||||
</summary>
|
||||
<param name="posters">The poster paths.</param>
|
||||
<param name="backdrops">The landscape paths.</param>
|
||||
<returns>The created collage as a bitmap.</returns>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Drawing.Skia.SplashscreenBuilder.Transform3D(SkiaSharp.SKBitmap)">
|
||||
<summary>
|
||||
Transform the collage in 3D space.
|
||||
</summary>
|
||||
<param name="input">The bitmap to transform.</param>
|
||||
<returns>The transformed image.</returns>
|
||||
</member>
|
||||
<member name="T:Jellyfin.Drawing.Skia.StripCollageBuilder">
|
||||
<summary>
|
||||
Used to build collages of multiple images arranged in vertical strips.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Drawing.Skia.StripCollageBuilder.#ctor(Jellyfin.Drawing.Skia.SkiaEncoder)">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:Jellyfin.Drawing.Skia.StripCollageBuilder"/> class.
|
||||
</summary>
|
||||
<param name="skiaEncoder">The encoder to use for building collages.</param>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Drawing.Skia.StripCollageBuilder.IsRtlTextRegex">
|
||||
<remarks>
|
||||
Pattern:<br/>
|
||||
<code>\p{IsArabic}|\p{IsArmenian}|\p{IsHebrew}|\p{IsSyriac}|\p{IsThaana}</code><br/>
|
||||
Explanation:<br/>
|
||||
<code>
|
||||
○ Match a character in the set [\u0530-\u074F\u0780-\u07BF].<br/>
|
||||
</code>
|
||||
</remarks>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Drawing.Skia.StripCollageBuilder.GetEncodedFormat(System.String)">
|
||||
<summary>
|
||||
Check which format an image has been encoded with using its filename extension.
|
||||
</summary>
|
||||
<param name="outputPath">The path to the image to get the format for.</param>
|
||||
<returns>The image format.</returns>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Drawing.Skia.StripCollageBuilder.BuildSquareCollage(System.Collections.Generic.IReadOnlyList{System.String},System.String,System.Int32,System.Int32)">
|
||||
<summary>
|
||||
Create a square collage.
|
||||
</summary>
|
||||
<param name="paths">The paths of the images to use in the collage.</param>
|
||||
<param name="outputPath">The path at which to place the resulting collage image.</param>
|
||||
<param name="width">The desired width of the collage.</param>
|
||||
<param name="height">The desired height of the collage.</param>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Drawing.Skia.StripCollageBuilder.BuildThumbCollage(System.Collections.Generic.IReadOnlyList{System.String},System.String,System.Int32,System.Int32,System.String)">
|
||||
<summary>
|
||||
Create a thumb collage.
|
||||
</summary>
|
||||
<param name="paths">The paths of the images to use in the collage.</param>
|
||||
<param name="outputPath">The path at which to place the resulting image.</param>
|
||||
<param name="width">The desired width of the collage.</param>
|
||||
<param name="height">The desired height of the collage.</param>
|
||||
<param name="libraryName">The name of the library to draw on the collage.</param>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Drawing.Skia.StripCollageBuilder.MeasureAndDrawText(SkiaSharp.SKCanvas,System.Single,System.Single,System.String,SkiaSharp.SKPaint,SkiaSharp.SKFont,SkiaSharp.SKTextAlign)">
|
||||
<summary>
|
||||
Draw shaped text with given SKPaint.
|
||||
</summary>
|
||||
<param name="canvas">If not null, draw text to this canvas, otherwise only measure the text width.</param>
|
||||
<param name="x">x position of the canvas to draw text.</param>
|
||||
<param name="y">y position of the canvas to draw text.</param>
|
||||
<param name="text">The text to draw.</param>
|
||||
<param name="textPaint">The SKPaint to style the text.</param>
|
||||
<param name="textFont">The SKFont to style the text.</param>
|
||||
<param name="alignment">The alignment of the text. Default aligns to left.</param>
|
||||
<returns>The width of the text.</returns>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Drawing.Skia.StripCollageBuilder.DrawText(SkiaSharp.SKCanvas,System.Single,System.Single,System.String,SkiaSharp.SKPaint,SkiaSharp.SKFont,System.Boolean)">
|
||||
<summary>
|
||||
Draw shaped text with given SKPaint, search defined type faces to render as many texts as possible.
|
||||
</summary>
|
||||
<param name="canvas">If not null, draw text to this canvas, otherwise only measure the text width.</param>
|
||||
<param name="x">x position of the canvas to draw text.</param>
|
||||
<param name="y">y position of the canvas to draw text.</param>
|
||||
<param name="text">The text to draw.</param>
|
||||
<param name="textPaint">The SKPaint to style the text.</param>
|
||||
<param name="textFont">The SKFont to style the text.</param>
|
||||
<param name="isRtl">If true, render from right to left.</param>
|
||||
<returns>The width of the text.</returns>
|
||||
</member>
|
||||
<member name="T:Jellyfin.Drawing.Skia.UnplayedCountIndicator">
|
||||
<summary>
|
||||
Static helper class for drawing unplayed count indicators.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:Jellyfin.Drawing.Skia.UnplayedCountIndicator.OffsetFromTopRightCorner">
|
||||
<summary>
|
||||
The x-offset used when drawing an unplayed count indicator.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Drawing.Skia.UnplayedCountIndicator.DrawUnplayedCountIndicator(SkiaSharp.SKCanvas,MediaBrowser.Model.Drawing.ImageDimensions,System.Int32)">
|
||||
<summary>
|
||||
Draw an unplayed count indicator in the top right corner of a canvas.
|
||||
</summary>
|
||||
<param name="canvas">The canvas to draw the indicator on.</param>
|
||||
<param name="imageSize">
|
||||
The dimensions of the image to draw the indicator on. The width is used to determine the x-position of the
|
||||
indicator.
|
||||
</param>
|
||||
<param name="count">The number to draw in the indicator.</param>
|
||||
</member>
|
||||
<member name="T:System.Text.RegularExpressions.Generated.IsRtlTextRegex_0">
|
||||
<summary>Custom <see cref="T:System.Text.RegularExpressions.Regex"/>-derived type for the IsRtlTextRegex method.</summary>
|
||||
</member>
|
||||
<member name="F:System.Text.RegularExpressions.Generated.IsRtlTextRegex_0.Instance">
|
||||
<summary>Cached, thread-safe singleton instance.</summary>
|
||||
</member>
|
||||
<member name="M:System.Text.RegularExpressions.Generated.IsRtlTextRegex_0.#ctor">
|
||||
<summary>Initializes the instance.</summary>
|
||||
</member>
|
||||
<member name="T:System.Text.RegularExpressions.Generated.IsRtlTextRegex_0.RunnerFactory">
|
||||
<summary>Provides a factory for creating <see cref="T:System.Text.RegularExpressions.RegexRunner"/> instances to be used by methods on <see cref="T:System.Text.RegularExpressions.Regex"/>.</summary>
|
||||
</member>
|
||||
<member name="M:System.Text.RegularExpressions.Generated.IsRtlTextRegex_0.RunnerFactory.CreateInstance">
|
||||
<summary>Creates an instance of a <see cref="T:System.Text.RegularExpressions.RegexRunner"/> used by methods on <see cref="T:System.Text.RegularExpressions.Regex"/>.</summary>
|
||||
</member>
|
||||
<member name="T:System.Text.RegularExpressions.Generated.IsRtlTextRegex_0.RunnerFactory.Runner">
|
||||
<summary>Provides the runner that contains the custom logic implementing the specified regular expression.</summary>
|
||||
</member>
|
||||
<member name="M:System.Text.RegularExpressions.Generated.IsRtlTextRegex_0.RunnerFactory.Runner.Scan(System.ReadOnlySpan{System.Char})">
|
||||
<summary>Scan the <paramref name="inputSpan"/> starting from base.runtextstart for the next match.</summary>
|
||||
<param name="inputSpan">The text being scanned by the regular expression.</param>
|
||||
</member>
|
||||
<member name="M:System.Text.RegularExpressions.Generated.IsRtlTextRegex_0.RunnerFactory.Runner.TryFindNextPossibleStartingPosition(System.ReadOnlySpan{System.Char})">
|
||||
<summary>Search <paramref name="inputSpan"/> starting from base.runtextpos for the next location a match could possibly start.</summary>
|
||||
<param name="inputSpan">The text being scanned by the regular expression.</param>
|
||||
<returns>true if a possible match was found; false if no more matches are possible.</returns>
|
||||
</member>
|
||||
<member name="T:System.Text.RegularExpressions.Generated.Utilities">
|
||||
<summary>Helper methods used by generated <see cref="T:System.Text.RegularExpressions.Regex"/>-derived implementations.</summary>
|
||||
</member>
|
||||
<member name="F:System.Text.RegularExpressions.Generated.Utilities.s_defaultTimeout">
|
||||
<summary>Default timeout value set in <see cref="T:System.AppContext"/>, or <see cref="F:System.Text.RegularExpressions.Regex.InfiniteMatchTimeout"/> if none was set.</summary>
|
||||
</member>
|
||||
<member name="F:System.Text.RegularExpressions.Generated.Utilities.s_hasTimeout">
|
||||
<summary>Whether <see cref="F:System.Text.RegularExpressions.Generated.Utilities.s_defaultTimeout"/> is non-infinite.</summary>
|
||||
</member>
|
||||
<member name="M:System.Text.RegularExpressions.Generated.Utilities.IndexOfNonAsciiOrAny_1861681229F79B552251C84FF351AFD5B7682E7A5D071E5C5F2BB68943FFB41B(System.ReadOnlySpan{System.Char})">
|
||||
<summary>Finds the next index of any character that matches a character in the set [\u0530-\u074F\u0780-\u07BF].</summary>
|
||||
</member>
|
||||
</members>
|
||||
</doc>
|
||||
@@ -0,0 +1,143 @@
|
||||
<?xml version="1.0"?>
|
||||
<doc>
|
||||
<assembly>
|
||||
<name>Jellyfin.Drawing</name>
|
||||
</assembly>
|
||||
<members>
|
||||
<member name="T:Jellyfin.Drawing.ImageProcessor">
|
||||
<summary>
|
||||
Class ImageProcessor.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Drawing.ImageProcessor.#ctor(Microsoft.Extensions.Logging.ILogger{Jellyfin.Drawing.ImageProcessor},MediaBrowser.Controller.IServerApplicationPaths,MediaBrowser.Model.IO.IFileSystem,MediaBrowser.Controller.Drawing.IImageEncoder,MediaBrowser.Controller.Configuration.IServerConfigurationManager)">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:Jellyfin.Drawing.ImageProcessor"/> class.
|
||||
</summary>
|
||||
<param name="logger">The logger.</param>
|
||||
<param name="appPaths">The server application paths.</param>
|
||||
<param name="fileSystem">The filesystem.</param>
|
||||
<param name="imageEncoder">The image encoder.</param>
|
||||
<param name="config">The configuration.</param>
|
||||
</member>
|
||||
<member name="P:Jellyfin.Drawing.ImageProcessor.SupportedInputFormats">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="P:Jellyfin.Drawing.ImageProcessor.SupportsImageCollageCreation">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:Jellyfin.Drawing.ImageProcessor.GetSupportedImageOutputFormats">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:Jellyfin.Drawing.ImageProcessor.ProcessImage(MediaBrowser.Controller.Drawing.ImageProcessingOptions)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:Jellyfin.Drawing.ImageProcessor.GetCacheFilePath(System.String,System.Nullable{System.Int32},System.Nullable{System.Int32},System.Nullable{System.Int32},System.Nullable{System.Int32},System.Nullable{System.Int32},System.Nullable{System.Int32},System.Int32,System.DateTime,MediaBrowser.Model.Drawing.ImageFormat,System.Double,System.Nullable{System.Int32},System.Nullable{System.Int32},System.String,System.String)">
|
||||
<summary>
|
||||
Gets the cache file path based on a set of parameters.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Drawing.ImageProcessor.GetImageDimensions(MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Controller.Entities.ItemImageInfo)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:Jellyfin.Drawing.ImageProcessor.GetImageDimensions(System.String)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:Jellyfin.Drawing.ImageProcessor.GetImageBlurHash(System.String)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:Jellyfin.Drawing.ImageProcessor.GetImageBlurHash(System.String,MediaBrowser.Model.Drawing.ImageDimensions)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:Jellyfin.Drawing.ImageProcessor.GetImageCacheTag(System.String,System.DateTime)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:Jellyfin.Drawing.ImageProcessor.GetImageCacheTag(MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Controller.Entities.ItemImageInfo)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:Jellyfin.Drawing.ImageProcessor.GetImageCacheTag(MediaBrowser.Model.Dto.BaseItemDto,MediaBrowser.Controller.Entities.ItemImageInfo)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:Jellyfin.Drawing.ImageProcessor.GetImageCacheTag(MediaBrowser.Model.Dto.BaseItemDto,MediaBrowser.Model.Entities.ChapterInfo)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:Jellyfin.Drawing.ImageProcessor.GetImageCacheTag(MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Model.Entities.ChapterInfo)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:Jellyfin.Drawing.ImageProcessor.GetImageCacheTag(Jellyfin.Database.Implementations.Entities.User)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:Jellyfin.Drawing.ImageProcessor.GetCachePath(System.String,System.String,System.String)">
|
||||
<summary>
|
||||
Gets the cache path.
|
||||
</summary>
|
||||
<param name="path">The path.</param>
|
||||
<param name="uniqueName">Name of the unique.</param>
|
||||
<param name="fileExtension">The file extension.</param>
|
||||
<returns>System.String.</returns>
|
||||
<exception cref="T:System.ArgumentNullException">
|
||||
path
|
||||
or
|
||||
uniqueName
|
||||
or
|
||||
fileExtension.
|
||||
</exception>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Drawing.ImageProcessor.GetCachePath(System.ReadOnlySpan{System.Char},System.ReadOnlySpan{System.Char})">
|
||||
<summary>
|
||||
Gets the cache path.
|
||||
</summary>
|
||||
<param name="path">The path.</param>
|
||||
<param name="filename">The filename.</param>
|
||||
<returns>System.String.</returns>
|
||||
<exception cref="T:System.ArgumentNullException">
|
||||
path
|
||||
or
|
||||
filename.
|
||||
</exception>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Drawing.ImageProcessor.CreateImageCollage(MediaBrowser.Controller.Drawing.ImageCollageOptions,System.String)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:Jellyfin.Drawing.ImageProcessor.Dispose">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="T:Jellyfin.Drawing.NullImageEncoder">
|
||||
<summary>
|
||||
A fallback implementation of <see cref="T:MediaBrowser.Controller.Drawing.IImageEncoder" />.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:Jellyfin.Drawing.NullImageEncoder.SupportedInputFormats">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="P:Jellyfin.Drawing.NullImageEncoder.SupportedOutputFormats">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="P:Jellyfin.Drawing.NullImageEncoder.Name">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="P:Jellyfin.Drawing.NullImageEncoder.SupportsImageCollageCreation">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="P:Jellyfin.Drawing.NullImageEncoder.SupportsImageEncoding">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:Jellyfin.Drawing.NullImageEncoder.GetImageSize(System.String)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:Jellyfin.Drawing.NullImageEncoder.EncodeImage(System.String,System.DateTime,System.String,System.Boolean,System.Nullable{MediaBrowser.Model.Drawing.ImageOrientation},System.Int32,MediaBrowser.Controller.Drawing.ImageProcessingOptions,MediaBrowser.Model.Drawing.ImageFormat)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:Jellyfin.Drawing.NullImageEncoder.CreateImageCollage(MediaBrowser.Controller.Drawing.ImageCollageOptions,System.String)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:Jellyfin.Drawing.NullImageEncoder.CreateSplashscreen(System.Collections.Generic.IReadOnlyList{System.String},System.Collections.Generic.IReadOnlyList{System.String})">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:Jellyfin.Drawing.NullImageEncoder.CreateTrickplayTile(MediaBrowser.Controller.Drawing.ImageCollageOptions,System.Int32,System.Int32,System.Nullable{System.Int32})">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:Jellyfin.Drawing.NullImageEncoder.GetImageBlurHash(System.Int32,System.Int32,System.String)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
</members>
|
||||
</doc>
|
||||
@@ -0,0 +1,716 @@
|
||||
<?xml version="1.0"?>
|
||||
<doc>
|
||||
<assembly>
|
||||
<name>Jellyfin.Extensions</name>
|
||||
</assembly>
|
||||
<members>
|
||||
<member name="T:Jellyfin.Extensions.CopyToExtensions">
|
||||
<summary>
|
||||
Provides <c>CopyTo</c> extensions methods for <see cref="T:System.Collections.Generic.IReadOnlyList`1" />.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Extensions.CopyToExtensions.CopyTo``1(System.Collections.Generic.IReadOnlyList{``0},System.Collections.Generic.IList{``0},System.Int32)">
|
||||
<summary>
|
||||
Copies all the elements of the current collection to the specified list
|
||||
starting at the specified destination array index. The index is specified as a 32-bit integer.
|
||||
</summary>
|
||||
<param name="source">The current collection that is the source of the elements.</param>
|
||||
<param name="destination">The list that is the destination of the elements copied from the current collection.</param>
|
||||
<param name="index">A 32-bit integer that represents the index in <c>destination</c> at which copying begins.</param>
|
||||
<typeparam name="T">The type of the array.</typeparam>
|
||||
</member>
|
||||
<member name="T:Jellyfin.Extensions.DictionaryExtensions">
|
||||
<summary>
|
||||
Static extensions for the <see cref="T:System.Collections.Generic.IReadOnlyDictionary`2"/> interface.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Extensions.DictionaryExtensions.GetFirstNotNullNorWhiteSpaceValue(System.Collections.Generic.IReadOnlyDictionary{System.String,System.String},System.String,System.String,System.String,System.String)">
|
||||
<summary>
|
||||
Gets a string from a string dictionary, checking all keys sequentially,
|
||||
stopping at the first key that returns a result that's neither null nor blank.
|
||||
</summary>
|
||||
<param name="dictionary">The dictionary.</param>
|
||||
<param name="key1">The first checked key.</param>
|
||||
<param name="key2">The second checked key.</param>
|
||||
<param name="key3">The third checked key.</param>
|
||||
<param name="key4">The fourth checked key.</param>
|
||||
<returns>System.String.</returns>
|
||||
</member>
|
||||
<member name="T:Jellyfin.Extensions.EnumerableExtensions">
|
||||
<summary>
|
||||
Static extensions for the <see cref="T:System.Collections.Generic.IEnumerable`1"/> interface.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Extensions.EnumerableExtensions.Contains(System.Collections.Generic.IEnumerable{System.String},System.ReadOnlySpan{System.Char},System.StringComparison)">
|
||||
<summary>
|
||||
Determines whether the value is contained in the source collection.
|
||||
</summary>
|
||||
<param name="source">An instance of the <see cref="T:System.Collections.Generic.IEnumerable`1"/> interface.</param>
|
||||
<param name="value">The value to look for in the collection.</param>
|
||||
<param name="stringComparison">The string comparison.</param>
|
||||
<returns>A value indicating whether the value is contained in the collection.</returns>
|
||||
<exception cref="T:System.ArgumentNullException">The source is null.</exception>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Extensions.EnumerableExtensions.SingleItemAsEnumerable``1(``0)">
|
||||
<summary>
|
||||
Gets an IEnumerable from a single item.
|
||||
</summary>
|
||||
<param name="item">The item to return.</param>
|
||||
<typeparam name="T">The type of item.</typeparam>
|
||||
<returns>The IEnumerable{T}.</returns>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Extensions.EnumerableExtensions.GetUniqueFlags``1(``0)">
|
||||
<summary>
|
||||
Gets an IEnumerable consisting of all flags of an enum.
|
||||
</summary>
|
||||
<param name="flags">The flags enum.</param>
|
||||
<typeparam name="T">The type of item.</typeparam>
|
||||
<returns>The IEnumerable{Enum}.</returns>
|
||||
</member>
|
||||
<member name="T:Jellyfin.Extensions.FileHelper">
|
||||
<summary>
|
||||
Provides helper functions for <see cref="T:System.IO.File" />.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Extensions.FileHelper.CreateEmpty(System.String)">
|
||||
<summary>
|
||||
Creates, or truncates a file in the specified path.
|
||||
</summary>
|
||||
<param name="path">The path and name of the file to create.</param>
|
||||
</member>
|
||||
<member name="T:Jellyfin.Extensions.FormattingStreamWriter">
|
||||
<summary>
|
||||
A custom StreamWriter which supports setting a IFormatProvider.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Extensions.FormattingStreamWriter.#ctor(System.IO.Stream,System.IFormatProvider)">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:Jellyfin.Extensions.FormattingStreamWriter"/> class.
|
||||
</summary>
|
||||
<param name="stream">The stream to write to.</param>
|
||||
<param name="formatProvider">The format provider to use.</param>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Extensions.FormattingStreamWriter.#ctor(System.String,System.IFormatProvider)">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:Jellyfin.Extensions.FormattingStreamWriter"/> class.
|
||||
</summary>
|
||||
<param name="path">The complete file path to write to.</param>
|
||||
<param name="formatProvider">The format provider to use.</param>
|
||||
</member>
|
||||
<member name="P:Jellyfin.Extensions.FormattingStreamWriter.FormatProvider">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="T:Jellyfin.Extensions.GuidExtensions">
|
||||
<summary>
|
||||
Guid specific extensions.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Extensions.GuidExtensions.IsEmpty(System.Guid)">
|
||||
<summary>
|
||||
Determine whether the guid is default.
|
||||
</summary>
|
||||
<param name="guid">The guid.</param>
|
||||
<returns>Whether the guid is the default value.</returns>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Extensions.GuidExtensions.IsNullOrEmpty(System.Nullable{System.Guid})">
|
||||
<summary>
|
||||
Determine whether the guid is null or default.
|
||||
</summary>
|
||||
<param name="guid">The guid.</param>
|
||||
<returns>Whether the guid is null or the default valueF.</returns>
|
||||
</member>
|
||||
<member name="T:Jellyfin.Extensions.Json.Converters.JsonBoolNumberConverter">
|
||||
<summary>
|
||||
Converts a number to a boolean.
|
||||
This is needed for HDHomerun.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Extensions.Json.Converters.JsonBoolNumberConverter.Read(System.Text.Json.Utf8JsonReader@,System.Type,System.Text.Json.JsonSerializerOptions)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:Jellyfin.Extensions.Json.Converters.JsonBoolNumberConverter.Write(System.Text.Json.Utf8JsonWriter,System.Boolean,System.Text.Json.JsonSerializerOptions)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="T:Jellyfin.Extensions.Json.Converters.JsonBoolStringConverter">
|
||||
<summary>
|
||||
Converts a string to a boolean.
|
||||
This is needed for FFprobe.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Extensions.Json.Converters.JsonBoolStringConverter.Read(System.Text.Json.Utf8JsonReader@,System.Type,System.Text.Json.JsonSerializerOptions)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:Jellyfin.Extensions.Json.Converters.JsonBoolStringConverter.Write(System.Text.Json.Utf8JsonWriter,System.Boolean,System.Text.Json.JsonSerializerOptions)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="T:Jellyfin.Extensions.Json.Converters.JsonCommaDelimitedCollectionConverter`1">
|
||||
<summary>
|
||||
Convert comma delimited string to collection of type.
|
||||
</summary>
|
||||
<typeparam name="T">Type to convert to.</typeparam>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Extensions.Json.Converters.JsonCommaDelimitedCollectionConverter`1.#ctor">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:Jellyfin.Extensions.Json.Converters.JsonCommaDelimitedCollectionConverter`1"/> class.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:Jellyfin.Extensions.Json.Converters.JsonCommaDelimitedCollectionConverter`1.Delimiter">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="T:Jellyfin.Extensions.Json.Converters.JsonCommaDelimitedCollectionConverterFactory">
|
||||
<summary>
|
||||
Json comma delimited collection converter factory.
|
||||
</summary>
|
||||
<remarks>
|
||||
This must be applied as an attribute, adding to the JsonConverter list causes stack overflow.
|
||||
</remarks>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Extensions.Json.Converters.JsonCommaDelimitedCollectionConverterFactory.CanConvert(System.Type)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:Jellyfin.Extensions.Json.Converters.JsonCommaDelimitedCollectionConverterFactory.CreateConverter(System.Type,System.Text.Json.JsonSerializerOptions)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="T:Jellyfin.Extensions.Json.Converters.JsonDateTimeConverter">
|
||||
<summary>
|
||||
Legacy DateTime converter.
|
||||
Milliseconds aren't output if zero by default.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Extensions.Json.Converters.JsonDateTimeConverter.Read(System.Text.Json.Utf8JsonReader@,System.Type,System.Text.Json.JsonSerializerOptions)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:Jellyfin.Extensions.Json.Converters.JsonDateTimeConverter.Write(System.Text.Json.Utf8JsonWriter,System.DateTime,System.Text.Json.JsonSerializerOptions)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="T:Jellyfin.Extensions.Json.Converters.JsonDefaultStringEnumConverter`1">
|
||||
<summary>
|
||||
Json unknown enum converter.
|
||||
</summary>
|
||||
<typeparam name="T">The type of enum.</typeparam>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Extensions.Json.Converters.JsonDefaultStringEnumConverter`1.#ctor(System.Text.Json.Serialization.JsonConverter{`0})">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:Jellyfin.Extensions.Json.Converters.JsonDefaultStringEnumConverter`1"/> class.
|
||||
</summary>
|
||||
<param name="baseConverter">The base json converter.</param>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Extensions.Json.Converters.JsonDefaultStringEnumConverter`1.Read(System.Text.Json.Utf8JsonReader@,System.Type,System.Text.Json.JsonSerializerOptions)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:Jellyfin.Extensions.Json.Converters.JsonDefaultStringEnumConverter`1.Write(System.Text.Json.Utf8JsonWriter,`0,System.Text.Json.JsonSerializerOptions)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="T:Jellyfin.Extensions.Json.Converters.JsonDefaultStringEnumConverterFactory">
|
||||
<summary>
|
||||
Utilizes the JsonStringEnumConverter and sets a default value if not provided.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Extensions.Json.Converters.JsonDefaultStringEnumConverterFactory.CanConvert(System.Type)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:Jellyfin.Extensions.Json.Converters.JsonDefaultStringEnumConverterFactory.CreateConverter(System.Type,System.Text.Json.JsonSerializerOptions)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="T:Jellyfin.Extensions.Json.Converters.JsonDelimitedCollectionConverter`1">
|
||||
<summary>
|
||||
Convert delimited string to array of type.
|
||||
</summary>
|
||||
<typeparam name="T">Type to convert to.</typeparam>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Extensions.Json.Converters.JsonDelimitedCollectionConverter`1.#ctor">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:Jellyfin.Extensions.Json.Converters.JsonDelimitedCollectionConverter`1"/> class.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:Jellyfin.Extensions.Json.Converters.JsonDelimitedCollectionConverter`1.Delimiter">
|
||||
<summary>
|
||||
Gets the array delimiter.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Extensions.Json.Converters.JsonDelimitedCollectionConverter`1.Read(System.Text.Json.Utf8JsonReader@,System.Type,System.Text.Json.JsonSerializerOptions)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:Jellyfin.Extensions.Json.Converters.JsonDelimitedCollectionConverter`1.Write(System.Text.Json.Utf8JsonWriter,System.Collections.Generic.IReadOnlyCollection{`0},System.Text.Json.JsonSerializerOptions)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="T:Jellyfin.Extensions.Json.Converters.JsonFlagEnumConverter`1">
|
||||
<summary>
|
||||
Enum flag to json array converter.
|
||||
</summary>
|
||||
<typeparam name="T">The type of enum.</typeparam>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Extensions.Json.Converters.JsonFlagEnumConverter`1.Read(System.Text.Json.Utf8JsonReader@,System.Type,System.Text.Json.JsonSerializerOptions)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:Jellyfin.Extensions.Json.Converters.JsonFlagEnumConverter`1.Write(System.Text.Json.Utf8JsonWriter,`0,System.Text.Json.JsonSerializerOptions)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="T:Jellyfin.Extensions.Json.Converters.JsonFlagEnumConverterFactory">
|
||||
<summary>
|
||||
Json flag enum converter factory.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Extensions.Json.Converters.JsonFlagEnumConverterFactory.CanConvert(System.Type)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:Jellyfin.Extensions.Json.Converters.JsonFlagEnumConverterFactory.CreateConverter(System.Type,System.Text.Json.JsonSerializerOptions)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="T:Jellyfin.Extensions.Json.Converters.JsonGuidConverter">
|
||||
<summary>
|
||||
Converts a GUID object or value to/from JSON.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Extensions.Json.Converters.JsonGuidConverter.Read(System.Text.Json.Utf8JsonReader@,System.Type,System.Text.Json.JsonSerializerOptions)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:Jellyfin.Extensions.Json.Converters.JsonGuidConverter.Write(System.Text.Json.Utf8JsonWriter,System.Guid,System.Text.Json.JsonSerializerOptions)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="T:Jellyfin.Extensions.Json.Converters.JsonNullableGuidConverter">
|
||||
<summary>
|
||||
Converts a GUID object or value to/from JSON.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Extensions.Json.Converters.JsonNullableGuidConverter.Read(System.Text.Json.Utf8JsonReader@,System.Type,System.Text.Json.JsonSerializerOptions)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:Jellyfin.Extensions.Json.Converters.JsonNullableGuidConverter.Write(System.Text.Json.Utf8JsonWriter,System.Nullable{System.Guid},System.Text.Json.JsonSerializerOptions)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="T:Jellyfin.Extensions.Json.Converters.JsonNullableStructConverter`1">
|
||||
<summary>
|
||||
Converts a nullable struct or value to/from JSON.
|
||||
Required - some clients send an empty string.
|
||||
</summary>
|
||||
<typeparam name="TStruct">The struct type.</typeparam>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Extensions.Json.Converters.JsonNullableStructConverter`1.Read(System.Text.Json.Utf8JsonReader@,System.Type,System.Text.Json.JsonSerializerOptions)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:Jellyfin.Extensions.Json.Converters.JsonNullableStructConverter`1.Write(System.Text.Json.Utf8JsonWriter,System.Nullable{`0},System.Text.Json.JsonSerializerOptions)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="T:Jellyfin.Extensions.Json.Converters.JsonNullableStructConverterFactory">
|
||||
<summary>
|
||||
Json nullable struct converter factory.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Extensions.Json.Converters.JsonNullableStructConverterFactory.CanConvert(System.Type)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:Jellyfin.Extensions.Json.Converters.JsonNullableStructConverterFactory.CreateConverter(System.Type,System.Text.Json.JsonSerializerOptions)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="T:Jellyfin.Extensions.Json.Converters.JsonPipeDelimitedCollectionConverter`1">
|
||||
<summary>
|
||||
Convert Pipe delimited string to array of type.
|
||||
</summary>
|
||||
<typeparam name="T">Type to convert to.</typeparam>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Extensions.Json.Converters.JsonPipeDelimitedCollectionConverter`1.#ctor">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:Jellyfin.Extensions.Json.Converters.JsonPipeDelimitedCollectionConverter`1"/> class.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:Jellyfin.Extensions.Json.Converters.JsonPipeDelimitedCollectionConverter`1.Delimiter">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="T:Jellyfin.Extensions.Json.Converters.JsonPipeDelimitedCollectionConverterFactory">
|
||||
<summary>
|
||||
Json Pipe delimited collection converter factory.
|
||||
</summary>
|
||||
<remarks>
|
||||
This must be applied as an attribute, adding to the JsonConverter list causes stack overflow.
|
||||
</remarks>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Extensions.Json.Converters.JsonPipeDelimitedCollectionConverterFactory.CanConvert(System.Type)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:Jellyfin.Extensions.Json.Converters.JsonPipeDelimitedCollectionConverterFactory.CreateConverter(System.Type,System.Text.Json.JsonSerializerOptions)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="T:Jellyfin.Extensions.Json.Converters.JsonStringConverter">
|
||||
<summary>
|
||||
Converter to allow the serializer to read strings.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Extensions.Json.Converters.JsonStringConverter.Read(System.Text.Json.Utf8JsonReader@,System.Type,System.Text.Json.JsonSerializerOptions)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:Jellyfin.Extensions.Json.Converters.JsonStringConverter.Write(System.Text.Json.Utf8JsonWriter,System.String,System.Text.Json.JsonSerializerOptions)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="T:Jellyfin.Extensions.Json.Converters.JsonVersionConverter">
|
||||
<summary>
|
||||
Converts a Version object or value to/from JSON.
|
||||
</summary>
|
||||
<remarks>
|
||||
Required to send <see cref="T:System.Version"/> as a string instead of an object.
|
||||
</remarks>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Extensions.Json.Converters.JsonVersionConverter.Read(System.Text.Json.Utf8JsonReader@,System.Type,System.Text.Json.JsonSerializerOptions)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:Jellyfin.Extensions.Json.Converters.JsonVersionConverter.Write(System.Text.Json.Utf8JsonWriter,System.Version,System.Text.Json.JsonSerializerOptions)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="T:Jellyfin.Extensions.Json.JsonDefaults">
|
||||
<summary>
|
||||
Helper class for having compatible JSON throughout the codebase.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:Jellyfin.Extensions.Json.JsonDefaults.PascalCaseMediaType">
|
||||
<summary>
|
||||
Pascal case json profile media type.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:Jellyfin.Extensions.Json.JsonDefaults.CamelCaseMediaType">
|
||||
<summary>
|
||||
Camel case json profile media type.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:Jellyfin.Extensions.Json.JsonDefaults._jsonSerializerOptions">
|
||||
<summary>
|
||||
When changing these options, update
|
||||
Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
|
||||
-> AddJellyfinApi
|
||||
-> AddJsonOptions.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:Jellyfin.Extensions.Json.JsonDefaults.Options">
|
||||
<summary>
|
||||
Gets the default <see cref="T:System.Text.Json.JsonSerializerOptions" /> options.
|
||||
</summary>
|
||||
<remarks>
|
||||
The return value must not be modified.
|
||||
If the defaults must be modified the author must use the copy constructor.
|
||||
</remarks>
|
||||
<returns>The default <see cref="T:System.Text.Json.JsonSerializerOptions" /> options.</returns>
|
||||
</member>
|
||||
<member name="P:Jellyfin.Extensions.Json.JsonDefaults.CamelCaseOptions">
|
||||
<summary>
|
||||
Gets camelCase json options.
|
||||
</summary>
|
||||
<remarks>
|
||||
The return value must not be modified.
|
||||
If the defaults must be modified the author must use the copy constructor.
|
||||
</remarks>
|
||||
<returns>The camelCase <see cref="T:System.Text.Json.JsonSerializerOptions" /> options.</returns>
|
||||
</member>
|
||||
<member name="P:Jellyfin.Extensions.Json.JsonDefaults.PascalCaseOptions">
|
||||
<summary>
|
||||
Gets PascalCase json options.
|
||||
</summary>
|
||||
<remarks>
|
||||
The return value must not be modified.
|
||||
If the defaults must be modified the author must use the copy constructor.
|
||||
</remarks>
|
||||
<returns>The PascalCase <see cref="T:System.Text.Json.JsonSerializerOptions" /> options.</returns>
|
||||
</member>
|
||||
<member name="T:Jellyfin.Extensions.Json.Utf8JsonExtensions">
|
||||
<summary>
|
||||
Extensions for Utf8JsonReader and Utf8JsonWriter.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Extensions.Json.Utf8JsonExtensions.IsEmptyString(System.Text.Json.Utf8JsonReader)">
|
||||
<summary>
|
||||
Determines if the reader contains an empty string.
|
||||
</summary>
|
||||
<param name="reader">The reader.</param>
|
||||
<returns>Whether the reader contains an empty string.</returns>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Extensions.Json.Utf8JsonExtensions.IsNull(System.Text.Json.Utf8JsonReader)">
|
||||
<summary>
|
||||
Determines if the reader contains a null value.
|
||||
</summary>
|
||||
<param name="reader">The reader.</param>
|
||||
<returns>Whether the reader contains null.</returns>
|
||||
</member>
|
||||
<member name="T:Jellyfin.Extensions.ReadOnlyListExtension">
|
||||
<summary>
|
||||
Static extensions for the <see cref="T:System.Collections.Generic.IReadOnlyList`1"/> interface.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Extensions.ReadOnlyListExtension.IndexOf``1(System.Collections.Generic.IReadOnlyList{``0},``0)">
|
||||
<summary>
|
||||
Finds the index of the desired item.
|
||||
</summary>
|
||||
<param name="source">The source list.</param>
|
||||
<param name="value">The value to fine.</param>
|
||||
<typeparam name="T">The type of item to find.</typeparam>
|
||||
<returns>Index if found, else -1.</returns>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Extensions.ReadOnlyListExtension.FindIndex``1(System.Collections.Generic.IReadOnlyList{``0},System.Predicate{``0})">
|
||||
<summary>
|
||||
Finds the index of the predicate.
|
||||
</summary>
|
||||
<param name="source">The source list.</param>
|
||||
<param name="match">The value to find.</param>
|
||||
<typeparam name="T">The type of item to find.</typeparam>
|
||||
<returns>Index if found, else -1.</returns>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Extensions.ReadOnlyListExtension.FirstOrDefault``1(System.Collections.Generic.IReadOnlyList{``0})">
|
||||
<summary>
|
||||
Get the first or default item from a list.
|
||||
</summary>
|
||||
<param name="source">The source list.</param>
|
||||
<typeparam name="T">The type of item.</typeparam>
|
||||
<returns>The first item or default if list is empty.</returns>
|
||||
</member>
|
||||
<member name="T:Jellyfin.Extensions.ShuffleExtensions">
|
||||
<summary>
|
||||
Provides <c>Shuffle</c> extensions methods for <see cref="T:System.Collections.Generic.IList`1" />.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Extensions.ShuffleExtensions.Shuffle``1(System.Collections.Generic.IList{``0})">
|
||||
<summary>
|
||||
Shuffles the items in a list.
|
||||
</summary>
|
||||
<param name="list">The list that should get shuffled.</param>
|
||||
<typeparam name="T">The type.</typeparam>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Extensions.ShuffleExtensions.Shuffle``1(System.Collections.Generic.IList{``0},System.Random)">
|
||||
<summary>
|
||||
Shuffles the items in a list.
|
||||
</summary>
|
||||
<param name="list">The list that should get shuffled.</param>
|
||||
<param name="rng">The random number generator to use.</param>
|
||||
<typeparam name="T">The type.</typeparam>
|
||||
</member>
|
||||
<member name="T:Jellyfin.Extensions.SplitStringExtensions">
|
||||
<summary>
|
||||
Extension class for splitting lines without unnecessary allocations.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Extensions.SplitStringExtensions.SpanSplit(System.String,System.Char)">
|
||||
<summary>
|
||||
Creates a new string split enumerator.
|
||||
</summary>
|
||||
<param name="str">The string to split.</param>
|
||||
<param name="separator">The separator to split on.</param>
|
||||
<returns>The enumerator struct.</returns>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Extensions.SplitStringExtensions.Split(System.ReadOnlySpan{System.Char},System.Char)">
|
||||
<summary>
|
||||
Creates a new span split enumerator.
|
||||
</summary>
|
||||
<param name="str">The span to split.</param>
|
||||
<param name="separator">The separator to split on.</param>
|
||||
<returns>The enumerator struct.</returns>
|
||||
</member>
|
||||
<member name="T:Jellyfin.Extensions.SplitStringExtensions.Enumerator">
|
||||
<summary>
|
||||
Provides an enumerator for the substrings separated by the separator.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Extensions.SplitStringExtensions.Enumerator.#ctor(System.ReadOnlySpan{System.Char},System.Char)">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:Jellyfin.Extensions.SplitStringExtensions.Enumerator"/> struct.
|
||||
</summary>
|
||||
<param name="str">The span to split.</param>
|
||||
<param name="separator">The separator to split on.</param>
|
||||
</member>
|
||||
<member name="P:Jellyfin.Extensions.SplitStringExtensions.Enumerator.Current">
|
||||
<summary>
|
||||
Gets a reference to the item at the current position of the enumerator.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Extensions.SplitStringExtensions.Enumerator.GetEnumerator">
|
||||
<summary>
|
||||
Returns <c>this</c>.
|
||||
</summary>
|
||||
<returns><c>this</c>.</returns>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Extensions.SplitStringExtensions.Enumerator.MoveNext">
|
||||
<summary>
|
||||
Advances the enumerator to the next item.
|
||||
</summary>
|
||||
<returns><c>true</c> if there is a next element; otherwise <c>false</c>.</returns>
|
||||
</member>
|
||||
<member name="T:Jellyfin.Extensions.StreamExtensions">
|
||||
<summary>
|
||||
Class BaseExtensions.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Extensions.StreamExtensions.ReadAllLines(System.IO.Stream)">
|
||||
<summary>
|
||||
Reads all lines in the <see cref="T:System.IO.Stream" />.
|
||||
</summary>
|
||||
<param name="stream">The <see cref="T:System.IO.Stream" /> to read from.</param>
|
||||
<returns>All lines in the stream.</returns>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Extensions.StreamExtensions.ReadAllLines(System.IO.Stream,System.Text.Encoding)">
|
||||
<summary>
|
||||
Reads all lines in the <see cref="T:System.IO.Stream" />.
|
||||
</summary>
|
||||
<param name="stream">The <see cref="T:System.IO.Stream" /> to read from.</param>
|
||||
<param name="encoding">The character encoding to use.</param>
|
||||
<returns>All lines in the stream.</returns>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Extensions.StreamExtensions.ReadAllLines(System.IO.TextReader)">
|
||||
<summary>
|
||||
Reads all lines in the <see cref="T:System.IO.TextReader" />.
|
||||
</summary>
|
||||
<param name="reader">The <see cref="T:System.IO.TextReader" /> to read from.</param>
|
||||
<returns>All lines in the stream.</returns>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Extensions.StreamExtensions.ReadAllLinesAsync(System.IO.TextReader,System.Threading.CancellationToken)">
|
||||
<summary>
|
||||
Reads all lines in the <see cref="T:System.IO.TextReader" />.
|
||||
</summary>
|
||||
<param name="reader">The <see cref="T:System.IO.TextReader" /> to read from.</param>
|
||||
<param name="cancellationToken">The token to monitor for cancellation requests.</param>
|
||||
<returns>All lines in the stream.</returns>
|
||||
</member>
|
||||
<member name="T:Jellyfin.Extensions.StringBuilderExtensions">
|
||||
<summary>
|
||||
Extension methods for the <see cref="T:System.Text.StringBuilder"/> class.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Extensions.StringBuilderExtensions.AppendJoinInSingleQuotes(System.Text.StringBuilder,System.Char,System.Collections.Generic.IReadOnlyList{System.String})">
|
||||
<summary>
|
||||
Concatenates and appends the members of a collection in single quotes using the specified delimiter.
|
||||
</summary>
|
||||
<param name="builder">The string builder.</param>
|
||||
<param name="delimiter">The character delimiter.</param>
|
||||
<param name="values">The collection of strings to concatenate.</param>
|
||||
<returns>The updated string builder.</returns>
|
||||
</member>
|
||||
<member name="T:Jellyfin.Extensions.StringExtensions">
|
||||
<summary>
|
||||
Provides extensions methods for <see cref="T:System.String" />.
|
||||
</summary>
|
||||
<summary>
|
||||
Checks whether or not the specified string has diacritics in it.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Extensions.StringExtensions.RemoveDiacritics(System.String)">
|
||||
<summary>
|
||||
Removes the diacritics character from the strings.
|
||||
</summary>
|
||||
<param name="text">The string to act on.</param>
|
||||
<returns>The string without diacritics character.</returns>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Extensions.StringExtensions.HasDiacritics(System.String)">
|
||||
<summary>
|
||||
Checks whether or not the specified string has diacritics in it.
|
||||
</summary>
|
||||
<param name="text">The string to check.</param>
|
||||
<returns>True if the string has diacritics, false otherwise.</returns>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Extensions.StringExtensions.Count(System.ReadOnlySpan{System.Char},System.Char)">
|
||||
<summary>
|
||||
Counts the number of occurrences of [needle] in the string.
|
||||
</summary>
|
||||
<param name="value">The haystack to search in.</param>
|
||||
<param name="needle">The character to search for.</param>
|
||||
<returns>The number of occurrences of the [needle] character.</returns>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Extensions.StringExtensions.LeftPart(System.ReadOnlySpan{System.Char},System.Char)">
|
||||
<summary>
|
||||
Returns the part on the left of the <c>needle</c>.
|
||||
</summary>
|
||||
<param name="haystack">The string to seek.</param>
|
||||
<param name="needle">The needle to find.</param>
|
||||
<returns>The part left of the <paramref name="needle" />.</returns>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Extensions.StringExtensions.RightPart(System.ReadOnlySpan{System.Char},System.Char)">
|
||||
<summary>
|
||||
Returns the part on the right of the <c>needle</c>.
|
||||
</summary>
|
||||
<param name="haystack">The string to seek.</param>
|
||||
<param name="needle">The needle to find.</param>
|
||||
<returns>The part right of the <paramref name="needle" />.</returns>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Extensions.StringExtensions.Transliterated(System.String)">
|
||||
<summary>
|
||||
Returns a transliterated string which only contain ascii characters.
|
||||
</summary>
|
||||
<param name="text">The string to act on.</param>
|
||||
<returns>The transliterated string.</returns>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Extensions.StringExtensions.Trimmed(System.Collections.Generic.IEnumerable{System.String})">
|
||||
<summary>
|
||||
Ensures all strings are non-null and trimmed of leading an trailing blanks.
|
||||
</summary>
|
||||
<param name="values">The enumerable of strings to trim.</param>
|
||||
<returns>The enumeration of trimmed strings.</returns>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Extensions.StringExtensions.TruncateAtNull(System.String)">
|
||||
<summary>
|
||||
Truncates a string at the first null character ('\0').
|
||||
</summary>
|
||||
<param name="text">The input string.</param>
|
||||
<returns>
|
||||
The substring up to (but not including) the first null character,
|
||||
or the original string if no null character is present.
|
||||
</returns>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Extensions.StringExtensions.NonConformingUnicodeRegex">
|
||||
<remarks>
|
||||
Pattern:<br/>
|
||||
<code>([\uD800-\uDBFF](?![\uDC00-\uDFFF]))|((?<![\uD800-\uDBFF])[\uDC00-\uDFFF])|(�)</code><br/>
|
||||
Explanation:<br/>
|
||||
<code>
|
||||
○ Match with 3 alternative expressions, atomically.<br/>
|
||||
○ 1st capture group.<br/>
|
||||
○ Match a character in the set [\uD800-\uDBFF].<br/>
|
||||
○ Zero-width negative lookahead.<br/>
|
||||
○ Match a character in the set [\uDC00-\uDFFF].<br/>
|
||||
○ 2nd capture group.<br/>
|
||||
○ Zero-width negative lookbehind.<br/>
|
||||
○ Match a character in the set [\uD800-\uDBFF] right-to-left.<br/>
|
||||
○ Match a character in the set [\uDC00-\uDFFF].<br/>
|
||||
○ 3rd capture group.<br/>
|
||||
○ Match '�'.<br/>
|
||||
</code>
|
||||
</remarks>
|
||||
</member>
|
||||
<member name="T:System.Text.RegularExpressions.Generated.NonConformingUnicodeRegex_0">
|
||||
<summary>Custom <see cref="T:System.Text.RegularExpressions.Regex"/>-derived type for the NonConformingUnicodeRegex method.</summary>
|
||||
</member>
|
||||
<member name="F:System.Text.RegularExpressions.Generated.NonConformingUnicodeRegex_0.Instance">
|
||||
<summary>Cached, thread-safe singleton instance.</summary>
|
||||
</member>
|
||||
<member name="M:System.Text.RegularExpressions.Generated.NonConformingUnicodeRegex_0.#ctor">
|
||||
<summary>Initializes the instance.</summary>
|
||||
</member>
|
||||
<member name="T:System.Text.RegularExpressions.Generated.NonConformingUnicodeRegex_0.RunnerFactory">
|
||||
<summary>Provides a factory for creating <see cref="T:System.Text.RegularExpressions.RegexRunner"/> instances to be used by methods on <see cref="T:System.Text.RegularExpressions.Regex"/>.</summary>
|
||||
</member>
|
||||
<member name="M:System.Text.RegularExpressions.Generated.NonConformingUnicodeRegex_0.RunnerFactory.CreateInstance">
|
||||
<summary>Creates an instance of a <see cref="T:System.Text.RegularExpressions.RegexRunner"/> used by methods on <see cref="T:System.Text.RegularExpressions.Regex"/>.</summary>
|
||||
</member>
|
||||
<member name="T:System.Text.RegularExpressions.Generated.NonConformingUnicodeRegex_0.RunnerFactory.Runner">
|
||||
<summary>Provides the runner that contains the custom logic implementing the specified regular expression.</summary>
|
||||
</member>
|
||||
<member name="M:System.Text.RegularExpressions.Generated.NonConformingUnicodeRegex_0.RunnerFactory.Runner.Scan(System.ReadOnlySpan{System.Char})">
|
||||
<summary>Scan the <paramref name="inputSpan"/> starting from base.runtextstart for the next match.</summary>
|
||||
<param name="inputSpan">The text being scanned by the regular expression.</param>
|
||||
</member>
|
||||
<member name="M:System.Text.RegularExpressions.Generated.NonConformingUnicodeRegex_0.RunnerFactory.Runner.TryFindNextPossibleStartingPosition(System.ReadOnlySpan{System.Char})">
|
||||
<summary>Search <paramref name="inputSpan"/> starting from base.runtextpos for the next location a match could possibly start.</summary>
|
||||
<param name="inputSpan">The text being scanned by the regular expression.</param>
|
||||
<returns>true if a possible match was found; false if no more matches are possible.</returns>
|
||||
</member>
|
||||
<member name="M:System.Text.RegularExpressions.Generated.NonConformingUnicodeRegex_0.RunnerFactory.Runner.TryMatchAtCurrentPosition(System.ReadOnlySpan{System.Char})">
|
||||
<summary>Determine whether <paramref name="inputSpan"/> at base.runtextpos is a match for the regular expression.</summary>
|
||||
<param name="inputSpan">The text being scanned by the regular expression.</param>
|
||||
<returns>true if the regular expression matches at the current position; otherwise, false.</returns>
|
||||
</member>
|
||||
<member name="T:System.Text.RegularExpressions.Generated.Utilities">
|
||||
<summary>Helper methods used by generated <see cref="T:System.Text.RegularExpressions.Regex"/>-derived implementations.</summary>
|
||||
</member>
|
||||
<member name="F:System.Text.RegularExpressions.Generated.Utilities.s_defaultTimeout">
|
||||
<summary>Default timeout value set in <see cref="T:System.AppContext"/>, or <see cref="F:System.Text.RegularExpressions.Regex.InfiniteMatchTimeout"/> if none was set.</summary>
|
||||
</member>
|
||||
<member name="F:System.Text.RegularExpressions.Generated.Utilities.s_hasTimeout">
|
||||
<summary>Whether <see cref="F:System.Text.RegularExpressions.Generated.Utilities.s_defaultTimeout"/> is non-infinite.</summary>
|
||||
</member>
|
||||
<member name="M:System.Text.RegularExpressions.Generated.Utilities.IndexOfNonAsciiOrAny_AEA8CFA2B5BC71ABC904D402F4CC4D3E0211E0681824955E81054C16A76CB030(System.ReadOnlySpan{System.Char})">
|
||||
<summary>Finds the next index of any character that matches a character in the set [\uD800-\uDFFF\uFFFD].</summary>
|
||||
</member>
|
||||
</members>
|
||||
</doc>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,224 @@
|
||||
<?xml version="1.0"?>
|
||||
<doc>
|
||||
<assembly>
|
||||
<name>Jellyfin.MediaEncoding.Hls</name>
|
||||
</assembly>
|
||||
<members>
|
||||
<member name="T:Jellyfin.MediaEncoding.Hls.Cache.CacheDecorator">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:Jellyfin.MediaEncoding.Hls.Cache.CacheDecorator.#ctor(MediaBrowser.Controller.Persistence.IKeyframeRepository,Jellyfin.MediaEncoding.Hls.Extractors.IKeyframeExtractor,Microsoft.Extensions.Logging.ILogger{Jellyfin.MediaEncoding.Hls.Cache.CacheDecorator})">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:Jellyfin.MediaEncoding.Hls.Cache.CacheDecorator"/> class.
|
||||
</summary>
|
||||
<param name="keyframeRepository">An instance of the <see cref="T:MediaBrowser.Controller.Persistence.IKeyframeRepository"/> interface.</param>
|
||||
<param name="keyframeExtractor">An instance of the <see cref="T:Jellyfin.MediaEncoding.Hls.Extractors.IKeyframeExtractor"/> interface.</param>
|
||||
<param name="logger">An instance of the <see cref="T:Microsoft.Extensions.Logging.ILogger`1"/> interface.</param>
|
||||
</member>
|
||||
<member name="P:Jellyfin.MediaEncoding.Hls.Cache.CacheDecorator.IsMetadataBased">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:Jellyfin.MediaEncoding.Hls.Cache.CacheDecorator.TryExtractKeyframes(System.Guid,System.String,Jellyfin.MediaEncoding.Keyframes.KeyframeData@)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:Jellyfin.MediaEncoding.Hls.Cache.CacheDecorator.LogKeyframeExtractionFailed(Microsoft.Extensions.Logging.ILogger,System.String)">
|
||||
<summary>
|
||||
<para><b>Message:</b> Failed to extract keyframes using {ExtractorName}</para>
|
||||
<para><b>Level:</b> Debug</para>
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Jellyfin.MediaEncoding.Hls.Cache.CacheDecorator.LogKeyframeExtractionSucceeded(Microsoft.Extensions.Logging.ILogger,System.String)">
|
||||
<summary>
|
||||
<para><b>Message:</b> Successfully extracted keyframes using {ExtractorName}</para>
|
||||
<para><b>Level:</b> Debug</para>
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:Jellyfin.MediaEncoding.Hls.Extensions.MediaEncodingHlsServiceCollectionExtensions">
|
||||
<summary>
|
||||
Extensions for the <see cref="T:Microsoft.Extensions.DependencyInjection.IServiceCollection"/> interface.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Jellyfin.MediaEncoding.Hls.Extensions.MediaEncodingHlsServiceCollectionExtensions.AddHlsPlaylistGenerator(Microsoft.Extensions.DependencyInjection.IServiceCollection)">
|
||||
<summary>
|
||||
Adds the hls playlist generators to the <see cref="T:Microsoft.Extensions.DependencyInjection.IServiceCollection"/>.
|
||||
</summary>
|
||||
<param name="serviceCollection">An instance of the <see cref="T:Microsoft.Extensions.DependencyInjection.IServiceCollection"/> interface.</param>
|
||||
<returns>The updated service collection.</returns>
|
||||
</member>
|
||||
<member name="T:Jellyfin.MediaEncoding.Hls.Extractors.FfProbeKeyframeExtractor">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:Jellyfin.MediaEncoding.Hls.Extractors.FfProbeKeyframeExtractor.#ctor(MediaBrowser.Controller.MediaEncoding.IMediaEncoder,Emby.Naming.Common.NamingOptions,Microsoft.Extensions.Logging.ILogger{Jellyfin.MediaEncoding.Hls.Extractors.FfProbeKeyframeExtractor})">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:Jellyfin.MediaEncoding.Hls.Extractors.FfProbeKeyframeExtractor"/> class.
|
||||
</summary>
|
||||
<param name="mediaEncoder">An instance of the <see cref="T:MediaBrowser.Controller.MediaEncoding.IMediaEncoder"/> interface.</param>
|
||||
<param name="namingOptions">An instance of <see cref="T:Emby.Naming.Common.NamingOptions"/>.</param>
|
||||
<param name="logger">An instance of the <see cref="T:Microsoft.Extensions.Logging.ILogger`1"/> interface.</param>
|
||||
</member>
|
||||
<member name="P:Jellyfin.MediaEncoding.Hls.Extractors.FfProbeKeyframeExtractor.IsMetadataBased">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:Jellyfin.MediaEncoding.Hls.Extractors.FfProbeKeyframeExtractor.TryExtractKeyframes(System.Guid,System.String,Jellyfin.MediaEncoding.Keyframes.KeyframeData@)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:Jellyfin.MediaEncoding.Hls.Extractors.FfProbeKeyframeExtractor.LogKeyframeExtractionError(Microsoft.Extensions.Logging.ILogger,System.String,System.Exception)">
|
||||
<summary>
|
||||
<para><b>Message:</b> Extracting keyframes from {FilePath} using ffprobe failed</para>
|
||||
<para><b>Level:</b> Error</para>
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:Jellyfin.MediaEncoding.Hls.Extractors.IKeyframeExtractor">
|
||||
<summary>
|
||||
Keyframe extractor.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:Jellyfin.MediaEncoding.Hls.Extractors.IKeyframeExtractor.IsMetadataBased">
|
||||
<summary>
|
||||
Gets a value indicating whether the extractor is based on container metadata.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Jellyfin.MediaEncoding.Hls.Extractors.IKeyframeExtractor.TryExtractKeyframes(System.Guid,System.String,Jellyfin.MediaEncoding.Keyframes.KeyframeData@)">
|
||||
<summary>
|
||||
Attempt to extract keyframes.
|
||||
</summary>
|
||||
<param name="itemId">The item id.</param>
|
||||
<param name="filePath">The path to the file.</param>
|
||||
<param name="keyframeData">The keyframes.</param>
|
||||
<returns>A value indicating whether the keyframe extraction was successful.</returns>
|
||||
</member>
|
||||
<member name="T:Jellyfin.MediaEncoding.Hls.Extractors.MatroskaKeyframeExtractor">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:Jellyfin.MediaEncoding.Hls.Extractors.MatroskaKeyframeExtractor.#ctor(Microsoft.Extensions.Logging.ILogger{Jellyfin.MediaEncoding.Hls.Extractors.MatroskaKeyframeExtractor})">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:Jellyfin.MediaEncoding.Hls.Extractors.MatroskaKeyframeExtractor"/> class.
|
||||
</summary>
|
||||
<param name="logger">An instance of the <see cref="T:Microsoft.Extensions.Logging.ILogger`1"/> interface.</param>
|
||||
</member>
|
||||
<member name="P:Jellyfin.MediaEncoding.Hls.Extractors.MatroskaKeyframeExtractor.IsMetadataBased">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:Jellyfin.MediaEncoding.Hls.Extractors.MatroskaKeyframeExtractor.TryExtractKeyframes(System.Guid,System.String,Jellyfin.MediaEncoding.Keyframes.KeyframeData@)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:Jellyfin.MediaEncoding.Hls.Extractors.MatroskaKeyframeExtractor.LogKeyframeExtractionError(Microsoft.Extensions.Logging.ILogger,System.String,System.Exception)">
|
||||
<summary>
|
||||
<para><b>Message:</b> Extracting keyframes from {FilePath} using matroska metadata failed</para>
|
||||
<para><b>Level:</b> Error</para>
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:Jellyfin.MediaEncoding.Hls.Playlist.CreateMainPlaylistRequest">
|
||||
<summary>
|
||||
Request class for the <see cref="M:Jellyfin.MediaEncoding.Hls.Playlist.IDynamicHlsPlaylistGenerator.CreateMainPlaylist(Jellyfin.MediaEncoding.Hls.Playlist.CreateMainPlaylistRequest)"/> method.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Jellyfin.MediaEncoding.Hls.Playlist.CreateMainPlaylistRequest.#ctor(System.Nullable{System.Guid},System.String,System.Int32,System.Int64,System.String,System.String,System.String,System.Boolean)">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:Jellyfin.MediaEncoding.Hls.Playlist.CreateMainPlaylistRequest"/> class.
|
||||
</summary>
|
||||
<param name="mediaSourceId">The media source id.</param>
|
||||
<param name="filePath">The absolute file path to the file.</param>
|
||||
<param name="desiredSegmentLengthMs">The desired segment length in milliseconds.</param>
|
||||
<param name="totalRuntimeTicks">The total duration of the file in ticks.</param>
|
||||
<param name="segmentContainer">The desired segment container eg. "ts".</param>
|
||||
<param name="endpointPrefix">The URI prefix for the relative URL in the playlist.</param>
|
||||
<param name="queryString">The desired query string to append (must start with ?).</param>
|
||||
<param name="isRemuxingVideo">Whether the video is being remuxed.</param>
|
||||
</member>
|
||||
<member name="P:Jellyfin.MediaEncoding.Hls.Playlist.CreateMainPlaylistRequest.MediaSourceId">
|
||||
<summary>
|
||||
Gets the media source id.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:Jellyfin.MediaEncoding.Hls.Playlist.CreateMainPlaylistRequest.FilePath">
|
||||
<summary>
|
||||
Gets the file path.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:Jellyfin.MediaEncoding.Hls.Playlist.CreateMainPlaylistRequest.DesiredSegmentLengthMs">
|
||||
<summary>
|
||||
Gets the desired segment length in milliseconds.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:Jellyfin.MediaEncoding.Hls.Playlist.CreateMainPlaylistRequest.TotalRuntimeTicks">
|
||||
<summary>
|
||||
Gets the total runtime in ticks.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:Jellyfin.MediaEncoding.Hls.Playlist.CreateMainPlaylistRequest.SegmentContainer">
|
||||
<summary>
|
||||
Gets the segment container.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:Jellyfin.MediaEncoding.Hls.Playlist.CreateMainPlaylistRequest.EndpointPrefix">
|
||||
<summary>
|
||||
Gets the endpoint prefix for the URL.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:Jellyfin.MediaEncoding.Hls.Playlist.CreateMainPlaylistRequest.QueryString">
|
||||
<summary>
|
||||
Gets the query string.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:Jellyfin.MediaEncoding.Hls.Playlist.CreateMainPlaylistRequest.IsRemuxingVideo">
|
||||
<summary>
|
||||
Gets a value indicating whether the video is being remuxed.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:Jellyfin.MediaEncoding.Hls.Playlist.DynamicHlsPlaylistGenerator">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:Jellyfin.MediaEncoding.Hls.Playlist.DynamicHlsPlaylistGenerator.#ctor(MediaBrowser.Controller.Configuration.IServerConfigurationManager,System.Collections.Generic.IEnumerable{Jellyfin.MediaEncoding.Hls.Extractors.IKeyframeExtractor})">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:Jellyfin.MediaEncoding.Hls.Playlist.DynamicHlsPlaylistGenerator"/> class.
|
||||
</summary>
|
||||
<param name="serverConfigurationManager">An instance of the see <see cref="T:MediaBrowser.Controller.Configuration.IServerConfigurationManager"/> interface.</param>
|
||||
<param name="extractors">An instance of <see cref="T:System.Collections.Generic.IEnumerable`1"/>.</param>
|
||||
</member>
|
||||
<member name="M:Jellyfin.MediaEncoding.Hls.Playlist.DynamicHlsPlaylistGenerator.CreateMainPlaylist(Jellyfin.MediaEncoding.Hls.Playlist.CreateMainPlaylistRequest)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="T:Jellyfin.MediaEncoding.Hls.Playlist.IDynamicHlsPlaylistGenerator">
|
||||
<summary>
|
||||
Generator for dynamic HLS playlists where the segment lengths aren't known in advance.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Jellyfin.MediaEncoding.Hls.Playlist.IDynamicHlsPlaylistGenerator.CreateMainPlaylist(Jellyfin.MediaEncoding.Hls.Playlist.CreateMainPlaylistRequest)">
|
||||
<summary>
|
||||
Creates the main playlist containing the main video or audio stream.
|
||||
</summary>
|
||||
<param name="request">An instance of the <see cref="T:Jellyfin.MediaEncoding.Hls.Playlist.CreateMainPlaylistRequest"/> class.</param>
|
||||
<returns>The playlist as a formatted string.</returns>
|
||||
</member>
|
||||
<member name="T:Jellyfin.MediaEncoding.Hls.ScheduledTasks.KeyframeExtractionScheduledTask">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:Jellyfin.MediaEncoding.Hls.ScheduledTasks.KeyframeExtractionScheduledTask.#ctor(MediaBrowser.Model.Globalization.ILocalizationManager,MediaBrowser.Controller.Library.ILibraryManager,System.Collections.Generic.IEnumerable{Jellyfin.MediaEncoding.Hls.Extractors.IKeyframeExtractor})">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:Jellyfin.MediaEncoding.Hls.ScheduledTasks.KeyframeExtractionScheduledTask"/> class.
|
||||
</summary>
|
||||
<param name="localizationManager">An instance of the <see cref="T:MediaBrowser.Model.Globalization.ILocalizationManager"/> interface.</param>
|
||||
<param name="libraryManager">An instance of the <see cref="T:MediaBrowser.Controller.Library.ILibraryManager"/> interface.</param>
|
||||
<param name="keyframeExtractors">The keyframe extractors.</param>
|
||||
</member>
|
||||
<member name="P:Jellyfin.MediaEncoding.Hls.ScheduledTasks.KeyframeExtractionScheduledTask.Name">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="P:Jellyfin.MediaEncoding.Hls.ScheduledTasks.KeyframeExtractionScheduledTask.Key">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="P:Jellyfin.MediaEncoding.Hls.ScheduledTasks.KeyframeExtractionScheduledTask.Description">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="P:Jellyfin.MediaEncoding.Hls.ScheduledTasks.KeyframeExtractionScheduledTask.Category">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:Jellyfin.MediaEncoding.Hls.ScheduledTasks.KeyframeExtractionScheduledTask.ExecuteAsync(System.IProgress{System.Double},System.Threading.CancellationToken)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:Jellyfin.MediaEncoding.Hls.ScheduledTasks.KeyframeExtractionScheduledTask.GetDefaultTriggers">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
</members>
|
||||
</doc>
|
||||
@@ -0,0 +1,260 @@
|
||||
<?xml version="1.0"?>
|
||||
<doc>
|
||||
<assembly>
|
||||
<name>Jellyfin.MediaEncoding.Keyframes</name>
|
||||
</assembly>
|
||||
<members>
|
||||
<member name="T:Jellyfin.MediaEncoding.Keyframes.FfProbe.FfProbeKeyframeExtractor">
|
||||
<summary>
|
||||
FfProbe based keyframe extractor.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Jellyfin.MediaEncoding.Keyframes.FfProbe.FfProbeKeyframeExtractor.GetKeyframeData(System.String,System.String)">
|
||||
<summary>
|
||||
Extracts the keyframes using the ffprobe executable at the specified path.
|
||||
</summary>
|
||||
<param name="ffProbePath">The path to the ffprobe executable.</param>
|
||||
<param name="filePath">The file path.</param>
|
||||
<returns>An instance of <see cref="T:Jellyfin.MediaEncoding.Keyframes.KeyframeData"/>.</returns>
|
||||
</member>
|
||||
<member name="M:Jellyfin.MediaEncoding.Keyframes.FfProbe.FfProbeKeyframeExtractor.ParseStream(System.IO.StreamReader)">
|
||||
<summary>
|
||||
Parses the keyframe data from the FFProbe stream.
|
||||
</summary>
|
||||
<param name="reader">The stream reader containing FFProbe output.</param>
|
||||
<returns>The parsed keyframe data.</returns>
|
||||
</member>
|
||||
<member name="T:Jellyfin.MediaEncoding.Keyframes.FfTool.FfToolKeyframeExtractor">
|
||||
<summary>
|
||||
FfTool based keyframe extractor.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Jellyfin.MediaEncoding.Keyframes.FfTool.FfToolKeyframeExtractor.GetKeyframeData(System.String,System.String)">
|
||||
<summary>
|
||||
Extracts the keyframes using the fftool executable at the specified path.
|
||||
</summary>
|
||||
<param name="ffToolPath">The path to the fftool executable.</param>
|
||||
<param name="filePath">The file path.</param>
|
||||
<returns>An instance of <see cref="T:Jellyfin.MediaEncoding.Keyframes.KeyframeData"/>.</returns>
|
||||
</member>
|
||||
<member name="T:Jellyfin.MediaEncoding.Keyframes.KeyframeData">
|
||||
<summary>
|
||||
Keyframe information for a specific file.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Jellyfin.MediaEncoding.Keyframes.KeyframeData.#ctor(System.Int64,System.Collections.Generic.IReadOnlyList{System.Int64})">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:Jellyfin.MediaEncoding.Keyframes.KeyframeData"/> class.
|
||||
</summary>
|
||||
<param name="totalDuration">The total duration of the video stream in ticks.</param>
|
||||
<param name="keyframeTicks">The video keyframes in ticks.</param>
|
||||
</member>
|
||||
<member name="P:Jellyfin.MediaEncoding.Keyframes.KeyframeData.TotalDuration">
|
||||
<summary>
|
||||
Gets the total duration of the stream in ticks.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:Jellyfin.MediaEncoding.Keyframes.KeyframeData.KeyframeTicks">
|
||||
<summary>
|
||||
Gets the keyframes in ticks.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:Jellyfin.MediaEncoding.Keyframes.Matroska.Extensions.EbmlReaderExtensions">
|
||||
<summary>
|
||||
Extension methods for the <see cref="T:NEbml.Core.EbmlReader"/> class.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Jellyfin.MediaEncoding.Keyframes.Matroska.Extensions.EbmlReaderExtensions.FindElement(NEbml.Core.EbmlReader,System.UInt64)">
|
||||
<summary>
|
||||
Traverses the current container to find the element with <paramref name="identifier"/> identifier.
|
||||
</summary>
|
||||
<param name="reader">An instance of <see cref="T:NEbml.Core.EbmlReader"/>.</param>
|
||||
<param name="identifier">The element identifier.</param>
|
||||
<returns>A value indicating whether the element was found.</returns>
|
||||
</member>
|
||||
<member name="M:Jellyfin.MediaEncoding.Keyframes.Matroska.Extensions.EbmlReaderExtensions.ReadUIntFromBinary(NEbml.Core.EbmlReader)">
|
||||
<summary>
|
||||
Reads the current position in the file as an unsigned integer converted from binary.
|
||||
</summary>
|
||||
<param name="reader">An instance of <see cref="T:NEbml.Core.EbmlReader"/>.</param>
|
||||
<returns>The unsigned integer.</returns>
|
||||
</member>
|
||||
<member name="M:Jellyfin.MediaEncoding.Keyframes.Matroska.Extensions.EbmlReaderExtensions.ReadSeekHead(NEbml.Core.EbmlReader)">
|
||||
<summary>
|
||||
Reads from the start of the file to retrieve the SeekHead segment.
|
||||
</summary>
|
||||
<param name="reader">An instance of <see cref="T:NEbml.Core.EbmlReader"/>.</param>
|
||||
<returns>Instance of <see cref="T:Jellyfin.MediaEncoding.Keyframes.Matroska.Models.SeekHead"/>.</returns>
|
||||
</member>
|
||||
<member name="M:Jellyfin.MediaEncoding.Keyframes.Matroska.Extensions.EbmlReaderExtensions.ReadInfo(NEbml.Core.EbmlReader,System.Int64)">
|
||||
<summary>
|
||||
Reads from SegmentContainer to retrieve the Info segment.
|
||||
</summary>
|
||||
<param name="reader">An instance of <see cref="T:NEbml.Core.EbmlReader"/>.</param>
|
||||
<param name="position">The position of the info segment relative to the Segment container.</param>
|
||||
<returns>Instance of <see cref="T:Jellyfin.MediaEncoding.Keyframes.Matroska.Models.Info"/>.</returns>
|
||||
</member>
|
||||
<member name="M:Jellyfin.MediaEncoding.Keyframes.Matroska.Extensions.EbmlReaderExtensions.FindFirstTrackNumberByType(NEbml.Core.EbmlReader,System.Int64,System.UInt64)">
|
||||
<summary>
|
||||
Enters the Tracks segment and reads all tracks to find the specified type.
|
||||
</summary>
|
||||
<param name="reader">Instance of <see cref="T:NEbml.Core.EbmlReader"/>.</param>
|
||||
<param name="tracksPosition">The relative position of the tracks segment.</param>
|
||||
<param name="type">The track type identifier.</param>
|
||||
<returns>The first track number with the specified type.</returns>
|
||||
<exception cref="T:System.InvalidOperationException">Stream type is not found.</exception>
|
||||
</member>
|
||||
<member name="T:Jellyfin.MediaEncoding.Keyframes.Matroska.MatroskaConstants">
|
||||
<summary>
|
||||
Constants for the Matroska identifiers.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:Jellyfin.MediaEncoding.Keyframes.Matroska.MatroskaConstants.SegmentContainer">
|
||||
<summary>
|
||||
The Matroska segment container identifier.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:Jellyfin.MediaEncoding.Keyframes.Matroska.MatroskaConstants.SeekHead">
|
||||
<summary>
|
||||
The Matroska seek head identifier.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:Jellyfin.MediaEncoding.Keyframes.Matroska.MatroskaConstants.Seek">
|
||||
<summary>
|
||||
The Matroska seek identifier.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:Jellyfin.MediaEncoding.Keyframes.Matroska.MatroskaConstants.Info">
|
||||
<summary>
|
||||
The Matroska info identifier.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:Jellyfin.MediaEncoding.Keyframes.Matroska.MatroskaConstants.TimestampScale">
|
||||
<summary>
|
||||
The Matroska timestamp scale identifier.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:Jellyfin.MediaEncoding.Keyframes.Matroska.MatroskaConstants.Duration">
|
||||
<summary>
|
||||
The Matroska duration identifier.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:Jellyfin.MediaEncoding.Keyframes.Matroska.MatroskaConstants.Tracks">
|
||||
<summary>
|
||||
The Matroska tracks identifier.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:Jellyfin.MediaEncoding.Keyframes.Matroska.MatroskaConstants.TrackEntry">
|
||||
<summary>
|
||||
The Matroska track entry identifier.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:Jellyfin.MediaEncoding.Keyframes.Matroska.MatroskaConstants.TrackNumber">
|
||||
<summary>
|
||||
The Matroska track number identifier.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:Jellyfin.MediaEncoding.Keyframes.Matroska.MatroskaConstants.TrackType">
|
||||
<summary>
|
||||
The Matroska track type identifier.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:Jellyfin.MediaEncoding.Keyframes.Matroska.MatroskaConstants.TrackTypeVideo">
|
||||
<summary>
|
||||
The Matroska video track type value.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:Jellyfin.MediaEncoding.Keyframes.Matroska.MatroskaConstants.TrackTypeSubtitle">
|
||||
<summary>
|
||||
The Matroska subtitle track type value.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:Jellyfin.MediaEncoding.Keyframes.Matroska.MatroskaConstants.Cues">
|
||||
<summary>
|
||||
The Matroska cues identifier.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:Jellyfin.MediaEncoding.Keyframes.Matroska.MatroskaConstants.CueTime">
|
||||
<summary>
|
||||
The Matroska cue time identifier.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:Jellyfin.MediaEncoding.Keyframes.Matroska.MatroskaConstants.CuePoint">
|
||||
<summary>
|
||||
The Matroska cue point identifier.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:Jellyfin.MediaEncoding.Keyframes.Matroska.MatroskaConstants.CueTrackPositions">
|
||||
<summary>
|
||||
The Matroska cue track positions identifier.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:Jellyfin.MediaEncoding.Keyframes.Matroska.MatroskaConstants.CuePointTrackNumber">
|
||||
<summary>
|
||||
The Matroska cue point track number identifier.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:Jellyfin.MediaEncoding.Keyframes.Matroska.MatroskaKeyframeExtractor">
|
||||
<summary>
|
||||
The keyframe extractor for the matroska container.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Jellyfin.MediaEncoding.Keyframes.Matroska.MatroskaKeyframeExtractor.GetKeyframeData(System.String)">
|
||||
<summary>
|
||||
Extracts the keyframes in ticks (scaled using the container timestamp scale) from the matroska container.
|
||||
</summary>
|
||||
<param name="filePath">The file path.</param>
|
||||
<returns>An instance of <see cref="T:Jellyfin.MediaEncoding.Keyframes.KeyframeData"/>.</returns>
|
||||
</member>
|
||||
<member name="T:Jellyfin.MediaEncoding.Keyframes.Matroska.Models.Info">
|
||||
<summary>
|
||||
The matroska Info segment.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Jellyfin.MediaEncoding.Keyframes.Matroska.Models.Info.#ctor(System.Int64,System.Nullable{System.Double})">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:Jellyfin.MediaEncoding.Keyframes.Matroska.Models.Info"/> class.
|
||||
</summary>
|
||||
<param name="timestampScale">The timestamp scale in nanoseconds.</param>
|
||||
<param name="duration">The duration of the entire file.</param>
|
||||
</member>
|
||||
<member name="P:Jellyfin.MediaEncoding.Keyframes.Matroska.Models.Info.TimestampScale">
|
||||
<summary>
|
||||
Gets the timestamp scale in nanoseconds.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:Jellyfin.MediaEncoding.Keyframes.Matroska.Models.Info.Duration">
|
||||
<summary>
|
||||
Gets the total duration of the file.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:Jellyfin.MediaEncoding.Keyframes.Matroska.Models.SeekHead">
|
||||
<summary>
|
||||
The matroska SeekHead segment. All positions are relative to the Segment container.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Jellyfin.MediaEncoding.Keyframes.Matroska.Models.SeekHead.#ctor(System.Int64,System.Int64,System.Int64)">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:Jellyfin.MediaEncoding.Keyframes.Matroska.Models.SeekHead"/> class.
|
||||
</summary>
|
||||
<param name="infoPosition">The relative file position of the info segment.</param>
|
||||
<param name="tracksPosition">The relative file position of the tracks segment.</param>
|
||||
<param name="cuesPosition">The relative file position of the cues segment.</param>
|
||||
</member>
|
||||
<member name="P:Jellyfin.MediaEncoding.Keyframes.Matroska.Models.SeekHead.InfoPosition">
|
||||
<summary>
|
||||
Gets relative file position of the info segment.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:Jellyfin.MediaEncoding.Keyframes.Matroska.Models.SeekHead.TracksPosition">
|
||||
<summary>
|
||||
Gets the relative file position of the tracks segment.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:Jellyfin.MediaEncoding.Keyframes.Matroska.Models.SeekHead.CuesPosition">
|
||||
<summary>
|
||||
Gets the relative file position of the cues segment.
|
||||
</summary>
|
||||
</member>
|
||||
</members>
|
||||
</doc>
|
||||
@@ -0,0 +1,305 @@
|
||||
<?xml version="1.0"?>
|
||||
<doc>
|
||||
<assembly>
|
||||
<name>Jellyfin.Networking</name>
|
||||
</assembly>
|
||||
<members>
|
||||
<member name="T:Jellyfin.Networking.AutoDiscoveryHost">
|
||||
<summary>
|
||||
<see cref="T:Microsoft.Extensions.Hosting.BackgroundService"/> responsible for responding to auto-discovery messages.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:Jellyfin.Networking.AutoDiscoveryHost.PortNumber">
|
||||
<summary>
|
||||
The port to listen on for auto-discovery messages.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Networking.AutoDiscoveryHost.#ctor(Microsoft.Extensions.Logging.ILogger{Jellyfin.Networking.AutoDiscoveryHost},MediaBrowser.Controller.IServerApplicationHost,MediaBrowser.Common.Configuration.IConfigurationManager,MediaBrowser.Common.Net.INetworkManager)">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:Jellyfin.Networking.AutoDiscoveryHost" /> class.
|
||||
</summary>
|
||||
<param name="logger">The <see cref="T:Microsoft.Extensions.Logging.ILogger`1"/>.</param>
|
||||
<param name="appHost">The <see cref="T:MediaBrowser.Controller.IServerApplicationHost"/>.</param>
|
||||
<param name="configurationManager">The <see cref="T:MediaBrowser.Common.Configuration.IConfigurationManager"/>.</param>
|
||||
<param name="networkManager">The <see cref="T:MediaBrowser.Common.Net.INetworkManager"/>.</param>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Networking.AutoDiscoveryHost.ExecuteAsync(System.Threading.CancellationToken)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="T:Jellyfin.Networking.HappyEyeballs.HttpClientExtension">
|
||||
<summary>
|
||||
Defines the <see cref="T:Jellyfin.Networking.HappyEyeballs.HttpClientExtension"/> class.
|
||||
|
||||
Implementation taken from https://github.com/ppy/osu-framework/pull/4191 .
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:Jellyfin.Networking.HappyEyeballs.HttpClientExtension.UseIPv6">
|
||||
<summary>
|
||||
Gets or sets a value indicating whether the client should use IPv6.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Networking.HappyEyeballs.HttpClientExtension.OnConnect(System.Net.Http.SocketsHttpConnectionContext,System.Threading.CancellationToken)">
|
||||
<summary>
|
||||
Implements the httpclient callback method.
|
||||
</summary>
|
||||
<param name="context">The <see cref="T:System.Net.Http.SocketsHttpConnectionContext"/> instance.</param>
|
||||
<param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken"/> instance.</param>
|
||||
<returns>The http steam.</returns>
|
||||
</member>
|
||||
<member name="T:Jellyfin.Networking.Manager.NetworkManager">
|
||||
<summary>
|
||||
Class to take care of network interface management.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:Jellyfin.Networking.Manager.NetworkManager._initLock">
|
||||
<summary>
|
||||
Threading lock for network properties.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:Jellyfin.Networking.Manager.NetworkManager._publishedServerUrls">
|
||||
<summary>
|
||||
Holds the published server URLs and the IPs to use them on.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:Jellyfin.Networking.Manager.NetworkManager._eventfire">
|
||||
<summary>
|
||||
Used to stop "event-racing conditions".
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:Jellyfin.Networking.Manager.NetworkManager._interfaces">
|
||||
<summary>
|
||||
Dictionary containing interface addresses and their subnets.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:Jellyfin.Networking.Manager.NetworkManager._lanSubnets">
|
||||
<summary>
|
||||
Unfiltered user defined LAN subnets (<see cref="P:MediaBrowser.Common.Net.NetworkConfiguration.LocalNetworkSubnets"/>)
|
||||
or internal interface network subnets if undefined by user.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:Jellyfin.Networking.Manager.NetworkManager._excludedSubnets">
|
||||
<summary>
|
||||
User defined list of subnets to excluded from the LAN.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:Jellyfin.Networking.Manager.NetworkManager._disposed">
|
||||
<summary>
|
||||
True if this object is disposed.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Networking.Manager.NetworkManager.#ctor(MediaBrowser.Common.Configuration.IConfigurationManager,Microsoft.Extensions.Configuration.IConfiguration,Microsoft.Extensions.Logging.ILogger{Jellyfin.Networking.Manager.NetworkManager})">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:Jellyfin.Networking.Manager.NetworkManager"/> class.
|
||||
</summary>
|
||||
<param name="configurationManager">The <see cref="T:MediaBrowser.Common.Configuration.IConfigurationManager"/> instance.</param>
|
||||
<param name="startupConfig">The <see cref="T:Microsoft.Extensions.Configuration.IConfiguration"/> instance holding startup parameters.</param>
|
||||
<param name="logger">Logger to use for messages.</param>
|
||||
</member>
|
||||
<member name="E:Jellyfin.Networking.Manager.NetworkManager.NetworkChanged">
|
||||
<summary>
|
||||
Event triggered on network changes.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:Jellyfin.Networking.Manager.NetworkManager.MockNetworkSettings">
|
||||
<summary>
|
||||
Gets or sets a value indicating whether testing is taking place.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:Jellyfin.Networking.Manager.NetworkManager.IsIPv4Enabled">
|
||||
<summary>
|
||||
Gets a value indicating whether IPv4 is enabled.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:Jellyfin.Networking.Manager.NetworkManager.IsIPv6Enabled">
|
||||
<summary>
|
||||
Gets a value indicating whether IP6 is enabled.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:Jellyfin.Networking.Manager.NetworkManager.TrustAllIPv6Interfaces">
|
||||
<summary>
|
||||
Gets a value indicating whether is all IPv6 interfaces are trusted as internal.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:Jellyfin.Networking.Manager.NetworkManager.PublishedServerUrls">
|
||||
<summary>
|
||||
Gets the Published server override list.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Networking.Manager.NetworkManager.Dispose">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Networking.Manager.NetworkManager.OnNetworkAvailabilityChanged(System.Object,System.Net.NetworkInformation.NetworkAvailabilityEventArgs)">
|
||||
<summary>
|
||||
Handler for network change events.
|
||||
</summary>
|
||||
<param name="sender">Sender.</param>
|
||||
<param name="e">A <see cref="T:System.Net.NetworkInformation.NetworkAvailabilityEventArgs"/> containing network availability information.</param>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Networking.Manager.NetworkManager.OnNetworkAddressChanged(System.Object,System.EventArgs)">
|
||||
<summary>
|
||||
Handler for network change events.
|
||||
</summary>
|
||||
<param name="sender">Sender.</param>
|
||||
<param name="e">An <see cref="T:System.EventArgs"/>.</param>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Networking.Manager.NetworkManager.HandleNetworkChange">
|
||||
<summary>
|
||||
Triggers our event, and re-loads interface information.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Networking.Manager.NetworkManager.OnNetworkChange">
|
||||
<summary>
|
||||
Waits for 2 seconds before re-initialising the settings, as typically these events fire multiple times in succession.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Networking.Manager.NetworkManager.InitializeInterfaces">
|
||||
<summary>
|
||||
Generate a list of all the interface ip addresses and submasks where that are in the active/unknown state.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Networking.Manager.NetworkManager.GetInterfacesCore(Microsoft.Extensions.Logging.ILogger,System.Boolean,System.Boolean)">
|
||||
<summary>
|
||||
Generate a list of all the interface ip addresses and submasks where that are in the active/unknown state.
|
||||
</summary>
|
||||
<param name="logger">The logger.</param>
|
||||
<param name="isIPv4Enabled">If true evaluates IPV4 type ip addresses.</param>
|
||||
<param name="isIPv6Enabled">If true evaluates IPV6 type ip addresses.</param>
|
||||
<returns>A list of all locally known up addresses and submasks that are to be considered usable.</returns>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Networking.Manager.NetworkManager.InitializeLan(MediaBrowser.Common.Net.NetworkConfiguration)">
|
||||
<summary>
|
||||
Initializes internal LAN cache.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Networking.Manager.NetworkManager.EnforceBindSettings(MediaBrowser.Common.Net.NetworkConfiguration)">
|
||||
<summary>
|
||||
Enforce bind addresses and exclusions on available interfaces.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Networking.Manager.NetworkManager.FilterBindSettings(MediaBrowser.Common.Net.NetworkConfiguration,System.Collections.Generic.IList{MediaBrowser.Model.Net.IPData},System.Boolean,System.Boolean)">
|
||||
<summary>
|
||||
Filters a list of bind addresses and exclusions on available interfaces.
|
||||
</summary>
|
||||
<param name="config">The network config to be filtered by.</param>
|
||||
<param name="interfaces">A list of possible interfaces to be filtered.</param>
|
||||
<param name="isIPv4Enabled">If true evaluates IPV4 type ip addresses.</param>
|
||||
<param name="isIPv6Enabled">If true evaluates IPV6 type ip addresses.</param>
|
||||
<returns>A list of all locally known up addresses and submasks that are to be considered usable.</returns>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Networking.Manager.NetworkManager.InitializeRemote(MediaBrowser.Common.Net.NetworkConfiguration)">
|
||||
<summary>
|
||||
Initializes the remote address values.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Networking.Manager.NetworkManager.InitializeOverrides(MediaBrowser.Common.Net.NetworkConfiguration)">
|
||||
<summary>
|
||||
Parses the user defined overrides into the dictionary object.
|
||||
Overrides are the equivalent of localised publishedServerUrl, enabling
|
||||
different addresses to be advertised over different subnets.
|
||||
format is subnet=ipaddress|host|uri
|
||||
when subnet = 0.0.0.0, any external address matches.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Networking.Manager.NetworkManager.UpdateSettings(System.Object)">
|
||||
<summary>
|
||||
Reloads all settings and re-Initializes the instance.
|
||||
</summary>
|
||||
<param name="configuration">The <see cref="T:MediaBrowser.Common.Net.NetworkConfiguration"/> to use.</param>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Networking.Manager.NetworkManager.Dispose(System.Boolean)">
|
||||
<summary>
|
||||
Protected implementation of Dispose pattern.
|
||||
</summary>
|
||||
<param name="disposing"><c>True</c> to dispose the managed state.</param>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Networking.Manager.NetworkManager.TryParseInterface(System.String,System.Collections.Generic.IReadOnlyList{MediaBrowser.Model.Net.IPData}@)">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Networking.Manager.NetworkManager.ShouldAllowServerAccess(System.Net.IPAddress)">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Networking.Manager.NetworkManager.GetLoopbacks">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Networking.Manager.NetworkManager.GetAllBindInterfaces(System.Boolean)">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Networking.Manager.NetworkManager.GetAllBindInterfaces(System.Boolean,MediaBrowser.Common.Configuration.IConfigurationManager,System.Collections.Generic.IReadOnlyList{MediaBrowser.Model.Net.IPData},System.Boolean,System.Boolean)">
|
||||
<summary>
|
||||
Reads the jellyfin configuration of the configuration manager and produces a list of interfaces that should be bound.
|
||||
</summary>
|
||||
<param name="individualInterfaces">Defines that only known interfaces should be used.</param>
|
||||
<param name="configurationManager">The ConfigurationManager.</param>
|
||||
<param name="knownInterfaces">The known interfaces that gets returned if possible or instructed.</param>
|
||||
<param name="readIpv4">Include IPV4 type interfaces.</param>
|
||||
<param name="readIpv6">Include IPV6 type interfaces.</param>
|
||||
<returns>A list of ip address of which jellyfin should bind to.</returns>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Networking.Manager.NetworkManager.GetBindAddress(System.String,System.Nullable{System.Int32}@)">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Networking.Manager.NetworkManager.GetBindAddress(Microsoft.AspNetCore.Http.HttpRequest,System.Nullable{System.Int32}@)">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Networking.Manager.NetworkManager.GetBindAddress(System.Net.IPAddress,System.Nullable{System.Int32}@,System.Boolean)">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Networking.Manager.NetworkManager.GetInternalBindAddresses">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Networking.Manager.NetworkManager.IsInLocalNetwork(System.String)">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Networking.Manager.NetworkManager.IsLinkLocalAddress(System.Net.IPAddress)">
|
||||
<summary>
|
||||
Get if the IPAddress is Link-local.
|
||||
</summary>
|
||||
<param name="address">The IP Address.</param>
|
||||
<returns>Bool indicates if the address is link-local.</returns>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Networking.Manager.NetworkManager.IsInLocalNetwork(System.Net.IPAddress)">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Networking.Manager.NetworkManager.CheckIfLanAndNotExcluded(System.Net.IPAddress)">
|
||||
<summary>
|
||||
Check if the address is in the LAN and not excluded.
|
||||
</summary>
|
||||
<param name="address">The IP address to check. The caller should make sure this is not an IPv4MappedToIPv6 address.</param>
|
||||
<returns>Boolean indicates whether the address is in LAN.</returns>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Networking.Manager.NetworkManager.MatchesPublishedServerUrl(System.Net.IPAddress,System.Boolean,System.String@)">
|
||||
<summary>
|
||||
Attempts to match the source against the published server URL overrides.
|
||||
</summary>
|
||||
<param name="source">IP source address to use.</param>
|
||||
<param name="isInExternalSubnet">True if the source is in an external subnet.</param>
|
||||
<param name="bindPreference">The published server URL that matches the source address.</param>
|
||||
<returns><c>true</c> if a match is found, <c>false</c> otherwise.</returns>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Networking.Manager.NetworkManager.MatchesBindInterface(System.Net.IPAddress,System.Boolean,System.String@)">
|
||||
<summary>
|
||||
Attempts to match the source against the user defined bind interfaces.
|
||||
</summary>
|
||||
<param name="source">IP source address to use.</param>
|
||||
<param name="isInExternalSubnet">True if the source is in the external subnet.</param>
|
||||
<param name="result">The result, if a match is found.</param>
|
||||
<returns><c>true</c> if a match is found, <c>false</c> otherwise.</returns>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Networking.Manager.NetworkManager.MatchesExternalInterface(System.Net.IPAddress,System.String@)">
|
||||
<summary>
|
||||
Attempts to match the source against external interfaces.
|
||||
</summary>
|
||||
<param name="source">IP source address to use.</param>
|
||||
<param name="result">The result, if a match is found.</param>
|
||||
<returns><c>true</c> if a match is found, <c>false</c> otherwise.</returns>
|
||||
</member>
|
||||
<member name="T:Jellyfin.Networking.Udp.SocketFactory">
|
||||
<summary>
|
||||
Factory class to create different kinds of sockets.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Jellyfin.Networking.Udp.SocketFactory.CreateUdpBroadcastSocket(System.Int32)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
</members>
|
||||
</doc>
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,500 @@
|
||||
<?xml version="1.0"?>
|
||||
<doc>
|
||||
<assembly>
|
||||
<name>MediaBrowser.LocalMetadata</name>
|
||||
</assembly>
|
||||
<members>
|
||||
<member name="T:MediaBrowser.LocalMetadata.BaseXmlProvider`1">
|
||||
<summary>
|
||||
The BaseXmlProvider.
|
||||
</summary>
|
||||
<typeparam name="T">Type of provider.</typeparam>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.LocalMetadata.BaseXmlProvider`1.#ctor(MediaBrowser.Model.IO.IFileSystem)">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:MediaBrowser.LocalMetadata.BaseXmlProvider`1"/> class.
|
||||
</summary>
|
||||
<param name="fileSystem">Instance of the <see cref="T:MediaBrowser.Model.IO.IFileSystem"/> interface.</param>
|
||||
</member>
|
||||
<member name="P:MediaBrowser.LocalMetadata.BaseXmlProvider`1.Name">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="P:MediaBrowser.LocalMetadata.BaseXmlProvider`1.Order">
|
||||
After Nfo
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="P:MediaBrowser.LocalMetadata.BaseXmlProvider`1.FileSystem">
|
||||
<summary>
|
||||
Gets the IFileSystem.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.LocalMetadata.BaseXmlProvider`1.GetMetadata(MediaBrowser.Controller.Providers.ItemInfo,MediaBrowser.Controller.Providers.IDirectoryService,System.Threading.CancellationToken)">
|
||||
<summary>
|
||||
Gets metadata for item.
|
||||
</summary>
|
||||
<param name="info">The item info.</param>
|
||||
<param name="directoryService">Instance of the <see cref="T:MediaBrowser.Controller.Providers.IDirectoryService"/> interface.</param>
|
||||
<param name="cancellationToken">The cancellation token.</param>
|
||||
<returns>The metadata for item.</returns>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.LocalMetadata.BaseXmlProvider`1.Fetch(MediaBrowser.Controller.Providers.MetadataResult{`0},System.String,System.Threading.CancellationToken)">
|
||||
<summary>
|
||||
Get metadata from path.
|
||||
</summary>
|
||||
<param name="result">Resulting metadata.</param>
|
||||
<param name="path">The path.</param>
|
||||
<param name="cancellationToken">The cancellation token.</param>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.LocalMetadata.BaseXmlProvider`1.GetXmlFile(MediaBrowser.Controller.Providers.ItemInfo,MediaBrowser.Controller.Providers.IDirectoryService)">
|
||||
<summary>
|
||||
Get metadata from xml file.
|
||||
</summary>
|
||||
<param name="info">Item inf.</param>
|
||||
<param name="directoryService">Instance of the <see cref="T:MediaBrowser.Controller.Providers.IDirectoryService"/> interface.</param>
|
||||
<returns>The file system metadata.</returns>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.LocalMetadata.BaseXmlProvider`1.HasChanged(MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Controller.Providers.IDirectoryService)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="T:MediaBrowser.LocalMetadata.Images.CollectionFolderLocalImageProvider">
|
||||
<summary>
|
||||
Collection folder local image provider.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.LocalMetadata.Images.CollectionFolderLocalImageProvider.#ctor(MediaBrowser.Model.IO.IFileSystem)">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:MediaBrowser.LocalMetadata.Images.CollectionFolderLocalImageProvider"/> class.
|
||||
</summary>
|
||||
<param name="fileSystem">Instance of the <see cref="T:MediaBrowser.Model.IO.IFileSystem"/> interface.</param>
|
||||
</member>
|
||||
<member name="P:MediaBrowser.LocalMetadata.Images.CollectionFolderLocalImageProvider.Name">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="P:MediaBrowser.LocalMetadata.Images.CollectionFolderLocalImageProvider.Order">
|
||||
Run after LocalImageProvider
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:MediaBrowser.LocalMetadata.Images.CollectionFolderLocalImageProvider.Supports(MediaBrowser.Controller.Entities.BaseItem)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:MediaBrowser.LocalMetadata.Images.CollectionFolderLocalImageProvider.GetImages(MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Controller.Providers.IDirectoryService)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="T:MediaBrowser.LocalMetadata.Images.EpisodeLocalImageProvider">
|
||||
<summary>
|
||||
Episode local image provider.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:MediaBrowser.LocalMetadata.Images.EpisodeLocalImageProvider.Name">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="P:MediaBrowser.LocalMetadata.Images.EpisodeLocalImageProvider.Order">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:MediaBrowser.LocalMetadata.Images.EpisodeLocalImageProvider.Supports(MediaBrowser.Controller.Entities.BaseItem)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:MediaBrowser.LocalMetadata.Images.EpisodeLocalImageProvider.GetImages(MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Controller.Providers.IDirectoryService)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="T:MediaBrowser.LocalMetadata.Images.InternalMetadataFolderImageProvider">
|
||||
<summary>
|
||||
Internal metadata folder image provider.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.LocalMetadata.Images.InternalMetadataFolderImageProvider.#ctor(MediaBrowser.Model.IO.IFileSystem,Microsoft.Extensions.Logging.ILogger{MediaBrowser.LocalMetadata.Images.InternalMetadataFolderImageProvider})">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:MediaBrowser.LocalMetadata.Images.InternalMetadataFolderImageProvider"/> class.
|
||||
</summary>
|
||||
<param name="fileSystem">Instance of the <see cref="T:MediaBrowser.Model.IO.IFileSystem"/> interface.</param>
|
||||
<param name="logger">Instance of the <see cref="T:Microsoft.Extensions.Logging.ILogger`1"/> interface.</param>
|
||||
</member>
|
||||
<member name="P:MediaBrowser.LocalMetadata.Images.InternalMetadataFolderImageProvider.Order">
|
||||
Make sure this is last so that all other locations are scanned first
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="P:MediaBrowser.LocalMetadata.Images.InternalMetadataFolderImageProvider.Name">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:MediaBrowser.LocalMetadata.Images.InternalMetadataFolderImageProvider.Supports(MediaBrowser.Controller.Entities.BaseItem)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:MediaBrowser.LocalMetadata.Images.InternalMetadataFolderImageProvider.GetImages(MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Controller.Providers.IDirectoryService)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="T:MediaBrowser.LocalMetadata.Images.LocalImageProvider">
|
||||
<summary>
|
||||
Local image provider.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.LocalMetadata.Images.LocalImageProvider.#ctor(MediaBrowser.Model.IO.IFileSystem)">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:MediaBrowser.LocalMetadata.Images.LocalImageProvider"/> class.
|
||||
</summary>
|
||||
<param name="fileSystem">Instance of the <see cref="T:MediaBrowser.Model.IO.IFileSystem"/> interface.</param>
|
||||
</member>
|
||||
<member name="P:MediaBrowser.LocalMetadata.Images.LocalImageProvider.Name">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="P:MediaBrowser.LocalMetadata.Images.LocalImageProvider.Order">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:MediaBrowser.LocalMetadata.Images.LocalImageProvider.Supports(MediaBrowser.Controller.Entities.BaseItem)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:MediaBrowser.LocalMetadata.Images.LocalImageProvider.GetImages(MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Controller.Providers.IDirectoryService)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:MediaBrowser.LocalMetadata.Images.LocalImageProvider.GetImages(MediaBrowser.Controller.Entities.BaseItem,System.String,MediaBrowser.Controller.Providers.IDirectoryService)">
|
||||
<summary>
|
||||
Get images for item.
|
||||
</summary>
|
||||
<param name="item">The item.</param>
|
||||
<param name="path">The images path.</param>
|
||||
<param name="directoryService">Instance of the <see cref="T:MediaBrowser.Controller.Providers.IDirectoryService"/> interface.</param>
|
||||
<returns>The local image info.</returns>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.LocalMetadata.Images.LocalImageProvider.GetImages(MediaBrowser.Controller.Entities.BaseItem,System.Collections.Generic.IEnumerable{System.String},MediaBrowser.Controller.Providers.IDirectoryService)">
|
||||
<summary>
|
||||
Get images for item from multiple paths.
|
||||
</summary>
|
||||
<param name="item">The item.</param>
|
||||
<param name="paths">The image paths.</param>
|
||||
<param name="directoryService">Instance of the <see cref="T:MediaBrowser.Controller.Providers.IDirectoryService"/> interface.</param>
|
||||
<returns>The local image info.</returns>
|
||||
</member>
|
||||
<member name="T:MediaBrowser.LocalMetadata.Parsers.BaseItemXmlParser`1">
|
||||
<summary>
|
||||
Provides a base class for parsing metadata xml.
|
||||
</summary>
|
||||
<typeparam name="T">Type of item xml parser.</typeparam>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.LocalMetadata.Parsers.BaseItemXmlParser`1.#ctor(Microsoft.Extensions.Logging.ILogger{MediaBrowser.LocalMetadata.Parsers.BaseItemXmlParser{`0}},MediaBrowser.Controller.Providers.IProviderManager)">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:MediaBrowser.LocalMetadata.Parsers.BaseItemXmlParser`1" /> class.
|
||||
</summary>
|
||||
<param name="logger">Instance of the <see cref="T:Microsoft.Extensions.Logging.ILogger`1"/> interface.</param>
|
||||
<param name="providerManager">Instance of the <see cref="T:MediaBrowser.Controller.Providers.IProviderManager"/> interface.</param>
|
||||
</member>
|
||||
<member name="P:MediaBrowser.LocalMetadata.Parsers.BaseItemXmlParser`1.Logger">
|
||||
<summary>
|
||||
Gets the logger.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:MediaBrowser.LocalMetadata.Parsers.BaseItemXmlParser`1.ProviderManager">
|
||||
<summary>
|
||||
Gets the provider manager.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.LocalMetadata.Parsers.BaseItemXmlParser`1.Fetch(MediaBrowser.Controller.Providers.MetadataResult{`0},System.String,System.Threading.CancellationToken)">
|
||||
<summary>
|
||||
Fetches metadata for an item from one xml file.
|
||||
</summary>
|
||||
<param name="item">The item.</param>
|
||||
<param name="metadataFile">The metadata file.</param>
|
||||
<param name="cancellationToken">The cancellation token.</param>
|
||||
<exception cref="T:System.ArgumentNullException">Item is null.</exception>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.LocalMetadata.Parsers.BaseItemXmlParser`1.Fetch(MediaBrowser.Controller.Providers.MetadataResult{`0},System.String,System.Xml.XmlReaderSettings,System.Text.Encoding,System.Threading.CancellationToken)">
|
||||
<summary>
|
||||
Fetches the specified item.
|
||||
</summary>
|
||||
<param name="item">The item.</param>
|
||||
<param name="metadataFile">The metadata file.</param>
|
||||
<param name="settings">The settings.</param>
|
||||
<param name="encoding">The encoding.</param>
|
||||
<param name="cancellationToken">The cancellation token.</param>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.LocalMetadata.Parsers.BaseItemXmlParser`1.FetchDataFromXmlNode(System.Xml.XmlReader,MediaBrowser.Controller.Providers.MetadataResult{`0})">
|
||||
<summary>
|
||||
Fetches metadata from one Xml Element.
|
||||
</summary>
|
||||
<param name="reader">The reader.</param>
|
||||
<param name="itemResult">The item result.</param>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.LocalMetadata.Parsers.BaseItemXmlParser`1.FetchFromTaglinesNode(System.Xml.XmlReader,`0)">
|
||||
<summary>
|
||||
Fetches from taglines node.
|
||||
</summary>
|
||||
<param name="reader">The reader.</param>
|
||||
<param name="item">The item.</param>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.LocalMetadata.Parsers.BaseItemXmlParser`1.FetchFromGenresNode(System.Xml.XmlReader,`0)">
|
||||
<summary>
|
||||
Fetches from genres node.
|
||||
</summary>
|
||||
<param name="reader">The reader.</param>
|
||||
<param name="item">The item.</param>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.LocalMetadata.Parsers.BaseItemXmlParser`1.FetchDataFromPersonsNode(System.Xml.XmlReader,MediaBrowser.Controller.Providers.MetadataResult{`0})">
|
||||
<summary>
|
||||
Fetches the data from persons node.
|
||||
</summary>
|
||||
<param name="reader">The reader.</param>
|
||||
<param name="item">The item.</param>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.LocalMetadata.Parsers.BaseItemXmlParser`1.FetchFromStudiosNode(System.Xml.XmlReader,`0)">
|
||||
<summary>
|
||||
Fetches from studios node.
|
||||
</summary>
|
||||
<param name="reader">The reader.</param>
|
||||
<param name="item">The item.</param>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.LocalMetadata.Parsers.BaseItemXmlParser`1.GetLinkedChild(System.Xml.XmlReader)">
|
||||
<summary>
|
||||
Get linked child.
|
||||
</summary>
|
||||
<param name="reader">The xml reader.</param>
|
||||
<returns>The linked child.</returns>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.LocalMetadata.Parsers.BaseItemXmlParser`1.GetShare(System.Xml.XmlReader)">
|
||||
<summary>
|
||||
Get share.
|
||||
</summary>
|
||||
<param name="reader">The xml reader.</param>
|
||||
<returns>The share.</returns>
|
||||
</member>
|
||||
<member name="T:MediaBrowser.LocalMetadata.Parsers.BoxSetXmlParser">
|
||||
<summary>
|
||||
The box set xml parser.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.LocalMetadata.Parsers.BoxSetXmlParser.#ctor(Microsoft.Extensions.Logging.ILogger{MediaBrowser.LocalMetadata.Parsers.BoxSetXmlParser},MediaBrowser.Controller.Providers.IProviderManager)">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:MediaBrowser.LocalMetadata.Parsers.BoxSetXmlParser"/> class.
|
||||
</summary>
|
||||
<param name="logger">Instance of the <see cref="T:Microsoft.Extensions.Logging.ILogger`1"/> interface.</param>
|
||||
<param name="providerManager">Instance of the <see cref="T:MediaBrowser.Controller.Providers.IProviderManager"/> interface.</param>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.LocalMetadata.Parsers.BoxSetXmlParser.FetchDataFromXmlNode(System.Xml.XmlReader,MediaBrowser.Controller.Providers.MetadataResult{MediaBrowser.Controller.Entities.Movies.BoxSet})">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="T:MediaBrowser.LocalMetadata.Parsers.PlaylistXmlParser">
|
||||
<summary>
|
||||
Playlist xml parser.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.LocalMetadata.Parsers.PlaylistXmlParser.#ctor(Microsoft.Extensions.Logging.ILogger{MediaBrowser.LocalMetadata.Parsers.PlaylistXmlParser},MediaBrowser.Controller.Providers.IProviderManager)">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:MediaBrowser.LocalMetadata.Parsers.PlaylistXmlParser"/> class.
|
||||
</summary>
|
||||
<param name="logger">Instance of the <see cref="T:Microsoft.Extensions.Logging.ILogger`1"/> interface.</param>
|
||||
<param name="providerManager">Instance of the <see cref="T:MediaBrowser.Controller.Providers.IProviderManager"/> interface.</param>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.LocalMetadata.Parsers.PlaylistXmlParser.FetchDataFromXmlNode(System.Xml.XmlReader,MediaBrowser.Controller.Providers.MetadataResult{MediaBrowser.Controller.Playlists.Playlist})">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="T:MediaBrowser.LocalMetadata.Providers.BoxSetXmlProvider">
|
||||
<summary>
|
||||
Class BoxSetXmlProvider.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.LocalMetadata.Providers.BoxSetXmlProvider.#ctor(MediaBrowser.Model.IO.IFileSystem,Microsoft.Extensions.Logging.ILogger{MediaBrowser.LocalMetadata.Parsers.BoxSetXmlParser},MediaBrowser.Controller.Providers.IProviderManager)">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:MediaBrowser.LocalMetadata.Providers.BoxSetXmlProvider"/> class.
|
||||
</summary>
|
||||
<param name="fileSystem">Instance of the <see cref="T:MediaBrowser.Model.IO.IFileSystem"/> interface.</param>
|
||||
<param name="logger">Instance of the <see cref="T:Microsoft.Extensions.Logging.ILogger`1"/> interface.</param>
|
||||
<param name="providerManager">Instance of the <see cref="T:MediaBrowser.Controller.Providers.IProviderManager"/> interface.</param>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.LocalMetadata.Providers.BoxSetXmlProvider.Fetch(MediaBrowser.Controller.Providers.MetadataResult{MediaBrowser.Controller.Entities.Movies.BoxSet},System.String,System.Threading.CancellationToken)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:MediaBrowser.LocalMetadata.Providers.BoxSetXmlProvider.GetXmlFile(MediaBrowser.Controller.Providers.ItemInfo,MediaBrowser.Controller.Providers.IDirectoryService)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="T:MediaBrowser.LocalMetadata.Providers.PlaylistXmlProvider">
|
||||
<summary>
|
||||
Playlist xml provider.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.LocalMetadata.Providers.PlaylistXmlProvider.#ctor(MediaBrowser.Model.IO.IFileSystem,Microsoft.Extensions.Logging.ILogger{MediaBrowser.LocalMetadata.Parsers.PlaylistXmlParser},MediaBrowser.Controller.Providers.IProviderManager)">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:MediaBrowser.LocalMetadata.Providers.PlaylistXmlProvider"/> class.
|
||||
</summary>
|
||||
<param name="fileSystem">Instance of the <see cref="T:MediaBrowser.Model.IO.IFileSystem"/> interface.</param>
|
||||
<param name="logger">Instance of the <see cref="T:Microsoft.Extensions.Logging.ILogger`1"/> interface.</param>
|
||||
<param name="providerManager">Instance of the <see cref="T:MediaBrowser.Controller.Providers.IProviderManager"/> interface.</param>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.LocalMetadata.Providers.PlaylistXmlProvider.Fetch(MediaBrowser.Controller.Providers.MetadataResult{MediaBrowser.Controller.Playlists.Playlist},System.String,System.Threading.CancellationToken)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:MediaBrowser.LocalMetadata.Providers.PlaylistXmlProvider.GetXmlFile(MediaBrowser.Controller.Providers.ItemInfo,MediaBrowser.Controller.Providers.IDirectoryService)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="T:MediaBrowser.LocalMetadata.Savers.BaseXmlSaver">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:MediaBrowser.LocalMetadata.Savers.BaseXmlSaver.#ctor(MediaBrowser.Model.IO.IFileSystem,MediaBrowser.Controller.Configuration.IServerConfigurationManager,MediaBrowser.Controller.Library.ILibraryManager,Microsoft.Extensions.Logging.ILogger{MediaBrowser.LocalMetadata.Savers.BaseXmlSaver})">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:MediaBrowser.LocalMetadata.Savers.BaseXmlSaver"/> class.
|
||||
</summary>
|
||||
<param name="fileSystem">Instance of the <see cref="T:MediaBrowser.Model.IO.IFileSystem"/> interface.</param>
|
||||
<param name="configurationManager">Instance of the <see cref="T:MediaBrowser.Controller.Configuration.IServerConfigurationManager"/> interface.</param>
|
||||
<param name="libraryManager">Instance of the <see cref="T:MediaBrowser.Controller.Library.ILibraryManager"/> interface.</param>
|
||||
<param name="logger">Instance of the <see cref="T:Microsoft.Extensions.Logging.ILogger`1"/> interface.</param>
|
||||
</member>
|
||||
<member name="P:MediaBrowser.LocalMetadata.Savers.BaseXmlSaver.FileSystem">
|
||||
<summary>
|
||||
Gets the file system.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:MediaBrowser.LocalMetadata.Savers.BaseXmlSaver.ConfigurationManager">
|
||||
<summary>
|
||||
Gets the configuration manager.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:MediaBrowser.LocalMetadata.Savers.BaseXmlSaver.LibraryManager">
|
||||
<summary>
|
||||
Gets the library manager.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:MediaBrowser.LocalMetadata.Savers.BaseXmlSaver.Logger">
|
||||
<summary>
|
||||
Gets the logger.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:MediaBrowser.LocalMetadata.Savers.BaseXmlSaver.Name">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:MediaBrowser.LocalMetadata.Savers.BaseXmlSaver.GetSavePath(MediaBrowser.Controller.Entities.BaseItem)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:MediaBrowser.LocalMetadata.Savers.BaseXmlSaver.GetLocalSavePath(MediaBrowser.Controller.Entities.BaseItem)">
|
||||
<summary>
|
||||
Gets the save path.
|
||||
</summary>
|
||||
<param name="item">The item.</param>
|
||||
<returns>System.String.</returns>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.LocalMetadata.Savers.BaseXmlSaver.GetRootElementName(MediaBrowser.Controller.Entities.BaseItem)">
|
||||
<summary>
|
||||
Gets the name of the root element.
|
||||
</summary>
|
||||
<param name="item">The item.</param>
|
||||
<returns>System.String.</returns>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.LocalMetadata.Savers.BaseXmlSaver.IsEnabledFor(MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Controller.Library.ItemUpdateType)">
|
||||
<summary>
|
||||
Determines whether [is enabled for] [the specified item].
|
||||
</summary>
|
||||
<param name="item">The item.</param>
|
||||
<param name="updateType">Type of the update.</param>
|
||||
<returns><c>true</c> if [is enabled for] [the specified item]; otherwise, <c>false</c>.</returns>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.LocalMetadata.Savers.BaseXmlSaver.SaveAsync(MediaBrowser.Controller.Entities.BaseItem,System.Threading.CancellationToken)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:MediaBrowser.LocalMetadata.Savers.BaseXmlSaver.WriteCustomElementsAsync(MediaBrowser.Controller.Entities.BaseItem,System.Xml.XmlWriter)">
|
||||
<summary>
|
||||
Write custom elements.
|
||||
</summary>
|
||||
<param name="item">The item.</param>
|
||||
<param name="writer">The xml writer.</param>
|
||||
<returns>The task object representing the asynchronous operation.</returns>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.LocalMetadata.Savers.BaseXmlSaver.AddCommonNodesAsync(MediaBrowser.Controller.Entities.BaseItem,System.Xml.XmlWriter)">
|
||||
<summary>
|
||||
Adds the common nodes.
|
||||
</summary>
|
||||
<param name="item">The item.</param>
|
||||
<param name="writer">The xml writer.</param>
|
||||
<returns>The task object representing the asynchronous operation.</returns>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.LocalMetadata.Savers.BaseXmlSaver.AddSharesAsync(MediaBrowser.Model.Entities.IHasShares,System.Xml.XmlWriter)">
|
||||
<summary>
|
||||
Add shares.
|
||||
</summary>
|
||||
<param name="item">The item.</param>
|
||||
<param name="writer">The xml writer.</param>
|
||||
<returns>The task object representing the asynchronous operation.</returns>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.LocalMetadata.Savers.BaseXmlSaver.AddMediaInfo``1(``0,System.Xml.XmlWriter)">
|
||||
<summary>
|
||||
Appends the media info.
|
||||
</summary>
|
||||
<param name="item">The item.</param>
|
||||
<param name="writer">The xml writer.</param>
|
||||
<typeparam name="T">Type of item.</typeparam>
|
||||
<returns>The task object representing the asynchronous operation.</returns>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.LocalMetadata.Savers.BaseXmlSaver.AddLinkedChildren(MediaBrowser.Controller.Entities.Folder,System.Xml.XmlWriter,System.String,System.String)">
|
||||
<summary>
|
||||
ADd linked children.
|
||||
</summary>
|
||||
<param name="item">The item.</param>
|
||||
<param name="writer">The xml writer.</param>
|
||||
<param name="pluralNodeName">The plural node name.</param>
|
||||
<param name="singularNodeName">The singular node name.</param>
|
||||
<returns>The task object representing the asynchronous operation.</returns>
|
||||
</member>
|
||||
<member name="T:MediaBrowser.LocalMetadata.Savers.BoxSetXmlSaver">
|
||||
<summary>
|
||||
Box set xml saver.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.LocalMetadata.Savers.BoxSetXmlSaver.#ctor(MediaBrowser.Model.IO.IFileSystem,MediaBrowser.Controller.Configuration.IServerConfigurationManager,MediaBrowser.Controller.Library.ILibraryManager,Microsoft.Extensions.Logging.ILogger{MediaBrowser.LocalMetadata.Savers.BoxSetXmlSaver})">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:MediaBrowser.LocalMetadata.Savers.BoxSetXmlSaver"/> class.
|
||||
</summary>
|
||||
<param name="fileSystem">Instance of the <see cref="T:MediaBrowser.Model.IO.IFileSystem"/> interface.</param>
|
||||
<param name="configurationManager">Instance of the <see cref="T:MediaBrowser.Controller.Configuration.IServerConfigurationManager"/> interface.</param>
|
||||
<param name="libraryManager">Instance of the <see cref="T:MediaBrowser.Controller.Library.ILibraryManager"/> interface.</param>
|
||||
<param name="logger">Instance of the <see cref="T:Microsoft.Extensions.Logging.ILogger`1"/> interface.</param>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.LocalMetadata.Savers.BoxSetXmlSaver.IsEnabledFor(MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Controller.Library.ItemUpdateType)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:MediaBrowser.LocalMetadata.Savers.BoxSetXmlSaver.WriteCustomElementsAsync(MediaBrowser.Controller.Entities.BaseItem,System.Xml.XmlWriter)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:MediaBrowser.LocalMetadata.Savers.BoxSetXmlSaver.GetLocalSavePath(MediaBrowser.Controller.Entities.BaseItem)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="T:MediaBrowser.LocalMetadata.Savers.PlaylistXmlSaver">
|
||||
<summary>
|
||||
Playlist xml saver.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:MediaBrowser.LocalMetadata.Savers.PlaylistXmlSaver.DefaultPlaylistFilename">
|
||||
<summary>
|
||||
The default file name to use when creating a new playlist.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.LocalMetadata.Savers.PlaylistXmlSaver.#ctor(MediaBrowser.Model.IO.IFileSystem,MediaBrowser.Controller.Configuration.IServerConfigurationManager,MediaBrowser.Controller.Library.ILibraryManager,Microsoft.Extensions.Logging.ILogger{MediaBrowser.LocalMetadata.Savers.PlaylistXmlSaver})">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:MediaBrowser.LocalMetadata.Savers.PlaylistXmlSaver"/> class.
|
||||
</summary>
|
||||
<param name="fileSystem">Instance of the <see cref="T:MediaBrowser.Model.IO.IFileSystem"/> interface.</param>
|
||||
<param name="configurationManager">Instance of the <see cref="T:MediaBrowser.Controller.Configuration.IServerConfigurationManager"/> interface.</param>
|
||||
<param name="libraryManager">Instance of the <see cref="T:MediaBrowser.Controller.Library.ILibraryManager"/> interface.</param>
|
||||
<param name="logger">Instance of the <see cref="T:Microsoft.Extensions.Logging.ILogger`1"/> interface.</param>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.LocalMetadata.Savers.PlaylistXmlSaver.IsEnabledFor(MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Controller.Library.ItemUpdateType)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:MediaBrowser.LocalMetadata.Savers.PlaylistXmlSaver.WriteCustomElementsAsync(MediaBrowser.Controller.Entities.BaseItem,System.Xml.XmlWriter)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:MediaBrowser.LocalMetadata.Savers.PlaylistXmlSaver.GetLocalSavePath(MediaBrowser.Controller.Entities.BaseItem)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:MediaBrowser.LocalMetadata.Savers.PlaylistXmlSaver.GetSavePath(System.String)">
|
||||
<summary>
|
||||
Get the save path.
|
||||
</summary>
|
||||
<param name="itemPath">The item path.</param>
|
||||
<returns>The save path.</returns>
|
||||
</member>
|
||||
<member name="T:MediaBrowser.LocalMetadata.XmlProviderUtils">
|
||||
<summary>
|
||||
The xml provider utils.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:MediaBrowser.LocalMetadata.XmlProviderUtils.Name">
|
||||
<summary>
|
||||
Gets the name.
|
||||
</summary>
|
||||
</member>
|
||||
</members>
|
||||
</doc>
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,710 @@
|
||||
<?xml version="1.0"?>
|
||||
<doc>
|
||||
<assembly>
|
||||
<name>MediaBrowser.XbmcMetadata</name>
|
||||
</assembly>
|
||||
<members>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Configuration.NfoConfigurationFactory.GetConfigurations">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="T:MediaBrowser.XbmcMetadata.NfoUserDataSaver">
|
||||
<summary>
|
||||
<see cref="T:Microsoft.Extensions.Hosting.IHostedService"/> responsible for updating NFO files' user data.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.NfoUserDataSaver.#ctor(Microsoft.Extensions.Logging.ILogger{MediaBrowser.XbmcMetadata.NfoUserDataSaver},MediaBrowser.Common.Configuration.IConfigurationManager,MediaBrowser.Controller.Library.IUserDataManager,MediaBrowser.Controller.Providers.IProviderManager)">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:MediaBrowser.XbmcMetadata.NfoUserDataSaver"/> class.
|
||||
</summary>
|
||||
<param name="logger">The <see cref="T:Microsoft.Extensions.Logging.ILogger"/>.</param>
|
||||
<param name="config">The <see cref="T:MediaBrowser.Common.Configuration.IConfigurationManager"/>.</param>
|
||||
<param name="userDataManager">The <see cref="T:MediaBrowser.Controller.Library.IUserDataManager"/>.</param>
|
||||
<param name="providerManager">The <see cref="T:MediaBrowser.Controller.Providers.IProviderManager"/>.</param>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.NfoUserDataSaver.StartAsync(System.Threading.CancellationToken)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.NfoUserDataSaver.StopAsync(System.Threading.CancellationToken)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="T:MediaBrowser.XbmcMetadata.Parsers.BaseNfoParser`1">
|
||||
<summary>
|
||||
The BaseNfoParser class.
|
||||
</summary>
|
||||
<typeparam name="T">The type.</typeparam>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Parsers.BaseNfoParser`1.#ctor(Microsoft.Extensions.Logging.ILogger,MediaBrowser.Common.Configuration.IConfigurationManager,MediaBrowser.Controller.Providers.IProviderManager,MediaBrowser.Controller.Library.IUserManager,MediaBrowser.Controller.Library.IUserDataManager,MediaBrowser.Controller.Providers.IDirectoryService)">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:MediaBrowser.XbmcMetadata.Parsers.BaseNfoParser`1" /> class.
|
||||
</summary>
|
||||
<param name="logger">Instance of the <see cref="T:Microsoft.Extensions.Logging.ILogger"/> interface.</param>
|
||||
<param name="config">Instance of the <see cref="T:MediaBrowser.Common.Configuration.IConfigurationManager"/> interface.</param>
|
||||
<param name="providerManager">Instance of the <see cref="T:MediaBrowser.Controller.Providers.IProviderManager"/> interface.</param>
|
||||
<param name="userManager">Instance of the <see cref="T:MediaBrowser.Controller.Library.IUserManager"/> interface.</param>
|
||||
<param name="userDataManager">Instance of the <see cref="T:MediaBrowser.Controller.Library.IUserDataManager"/> interface.</param>
|
||||
<param name="directoryService">Instance of the <see cref="T:MediaBrowser.Controller.Providers.IDirectoryService"/> interface.</param>
|
||||
</member>
|
||||
<member name="P:MediaBrowser.XbmcMetadata.Parsers.BaseNfoParser`1.Logger">
|
||||
<summary>
|
||||
Gets the logger.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:MediaBrowser.XbmcMetadata.Parsers.BaseNfoParser`1.ProviderManager">
|
||||
<summary>
|
||||
Gets the provider manager.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:MediaBrowser.XbmcMetadata.Parsers.BaseNfoParser`1.SupportsUrlAfterClosingXmlTag">
|
||||
<summary>
|
||||
Gets a value indicating whether URLs after a closing XML tag are supported.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Parsers.BaseNfoParser`1.Fetch(MediaBrowser.Controller.Providers.MetadataResult{`0},System.String,System.Threading.CancellationToken)">
|
||||
<summary>
|
||||
Fetches metadata for an item from one xml file.
|
||||
</summary>
|
||||
<param name="item">The <see cref="T:MediaBrowser.Controller.Providers.MetadataResult`1"/>.</param>
|
||||
<param name="metadataFile">The metadata file.</param>
|
||||
<param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken"/>.</param>
|
||||
<exception cref="T:System.ArgumentNullException"><c>item</c> is <c>null</c>.</exception>
|
||||
<exception cref="T:System.ArgumentException"><c>metadataFile</c> is <c>null</c> or empty.</exception>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Parsers.BaseNfoParser`1.Fetch(MediaBrowser.Controller.Providers.MetadataResult{`0},System.String,System.Xml.XmlReaderSettings,System.Threading.CancellationToken)">
|
||||
<summary>
|
||||
Fetches the specified item.
|
||||
</summary>
|
||||
<param name="item">The <see cref="T:MediaBrowser.Controller.Providers.MetadataResult`1"/>.</param>
|
||||
<param name="metadataFile">The metadata file.</param>
|
||||
<param name="settings">The <see cref="T:System.Xml.XmlReaderSettings"/>.</param>
|
||||
<param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken"/>.</param>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Parsers.BaseNfoParser`1.ParseProviderLinks(`0,System.ReadOnlySpan{System.Char})">
|
||||
<summary>
|
||||
Parses a XML tag to a provider id.
|
||||
</summary>
|
||||
<param name="item">The item.</param>
|
||||
<param name="xml">The xml tag.</param>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Parsers.BaseNfoParser`1.FetchDataFromXmlNode(System.Xml.XmlReader,MediaBrowser.Controller.Providers.MetadataResult{`0})">
|
||||
<summary>
|
||||
Fetches metadata from an XML node.
|
||||
</summary>
|
||||
<param name="reader">The <see cref="T:System.Xml.XmlReader"/>.</param>
|
||||
<param name="itemResult">The <see cref="T:MediaBrowser.Controller.Providers.MetadataResult`1"/>.</param>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Parsers.BaseNfoParser`1.GetImageType(System.String)">
|
||||
<summary>
|
||||
Parses the <see cref="T:MediaBrowser.Model.Entities.ImageType"/> from the NFO aspect property.
|
||||
</summary>
|
||||
<param name="aspect">The NFO aspect property.</param>
|
||||
<returns>The <see cref="T:MediaBrowser.Model.Entities.ImageType"/>.</returns>
|
||||
</member>
|
||||
<member name="T:MediaBrowser.XbmcMetadata.Parsers.EpisodeNfoParser">
|
||||
<summary>
|
||||
Nfo parser for episodes.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Parsers.EpisodeNfoParser.#ctor(Microsoft.Extensions.Logging.ILogger,MediaBrowser.Common.Configuration.IConfigurationManager,MediaBrowser.Controller.Providers.IProviderManager,MediaBrowser.Controller.Library.IUserManager,MediaBrowser.Controller.Library.IUserDataManager,MediaBrowser.Controller.Providers.IDirectoryService)">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:MediaBrowser.XbmcMetadata.Parsers.EpisodeNfoParser"/> class.
|
||||
</summary>
|
||||
<param name="logger">Instance of the <see cref="T:Microsoft.Extensions.Logging.ILogger`1"/> interface.</param>
|
||||
<param name="config">Instance of the <see cref="T:MediaBrowser.Common.Configuration.IConfigurationManager"/> interface.</param>
|
||||
<param name="providerManager">Instance of the <see cref="T:MediaBrowser.Controller.Providers.IProviderManager"/> interface.</param>
|
||||
<param name="userManager">Instance of the <see cref="T:MediaBrowser.Controller.Library.IUserManager"/> interface.</param>
|
||||
<param name="userDataManager">Instance of the <see cref="T:MediaBrowser.Controller.Library.IUserDataManager"/> interface.</param>
|
||||
<param name="directoryService">Instance of the <see cref="T:MediaBrowser.Controller.Providers.IDirectoryService"/> interface.</param>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Parsers.EpisodeNfoParser.Fetch(MediaBrowser.Controller.Providers.MetadataResult{MediaBrowser.Controller.Entities.TV.Episode},System.String,System.Xml.XmlReaderSettings,System.Threading.CancellationToken)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Parsers.EpisodeNfoParser.FetchDataFromXmlNode(System.Xml.XmlReader,MediaBrowser.Controller.Providers.MetadataResult{MediaBrowser.Controller.Entities.TV.Episode})">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Parsers.EpisodeNfoParser.ReadEpisodeDetailsFromXml(MediaBrowser.Controller.Providers.MetadataResult{MediaBrowser.Controller.Entities.TV.Episode},System.String,System.Xml.XmlReaderSettings,System.Threading.CancellationToken)">
|
||||
<summary>
|
||||
Reads the episode details from the given xml and saves the result in the provided result item.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:MediaBrowser.XbmcMetadata.Parsers.MovieNfoParser">
|
||||
<summary>
|
||||
Nfo parser for movies.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Parsers.MovieNfoParser.#ctor(Microsoft.Extensions.Logging.ILogger,MediaBrowser.Common.Configuration.IConfigurationManager,MediaBrowser.Controller.Providers.IProviderManager,MediaBrowser.Controller.Library.IUserManager,MediaBrowser.Controller.Library.IUserDataManager,MediaBrowser.Controller.Providers.IDirectoryService)">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:MediaBrowser.XbmcMetadata.Parsers.MovieNfoParser"/> class.
|
||||
</summary>
|
||||
<param name="logger">Instance of the <see cref="T:Microsoft.Extensions.Logging.ILogger"/> interface.</param>
|
||||
<param name="config">Instance of the <see cref="T:MediaBrowser.Common.Configuration.IConfigurationManager"/> interface.</param>
|
||||
<param name="providerManager">Instance of the <see cref="T:MediaBrowser.Controller.Providers.IProviderManager"/> interface.</param>
|
||||
<param name="userManager">Instance of the <see cref="T:MediaBrowser.Controller.Library.IUserManager"/> interface.</param>
|
||||
<param name="userDataManager">Instance of the <see cref="T:MediaBrowser.Controller.Library.IUserDataManager"/> interface.</param>
|
||||
<param name="directoryService">Instance of the <see cref="T:MediaBrowser.Controller.Providers.DirectoryService"/> interface.</param>
|
||||
</member>
|
||||
<member name="P:MediaBrowser.XbmcMetadata.Parsers.MovieNfoParser.SupportsUrlAfterClosingXmlTag">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Parsers.MovieNfoParser.FetchDataFromXmlNode(System.Xml.XmlReader,MediaBrowser.Controller.Providers.MetadataResult{MediaBrowser.Controller.Entities.Video})">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="T:MediaBrowser.XbmcMetadata.Parsers.SeasonNfoParser">
|
||||
<summary>
|
||||
Nfo parser for seasons.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Parsers.SeasonNfoParser.#ctor(Microsoft.Extensions.Logging.ILogger,MediaBrowser.Common.Configuration.IConfigurationManager,MediaBrowser.Controller.Providers.IProviderManager,MediaBrowser.Controller.Library.IUserManager,MediaBrowser.Controller.Library.IUserDataManager,MediaBrowser.Controller.Providers.IDirectoryService)">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:MediaBrowser.XbmcMetadata.Parsers.SeasonNfoParser"/> class.
|
||||
</summary>
|
||||
<param name="logger">Instance of the <see cref="T:Microsoft.Extensions.Logging.ILogger"/> interface.</param>
|
||||
<param name="config">Instance of the <see cref="T:MediaBrowser.Common.Configuration.IConfigurationManager"/> interface.</param>
|
||||
<param name="providerManager">Instance of the <see cref="T:MediaBrowser.Controller.Providers.IProviderManager"/> interface.</param>
|
||||
<param name="userManager">Instance of the <see cref="T:MediaBrowser.Controller.Library.IUserManager"/> interface.</param>
|
||||
<param name="userDataManager">Instance of the <see cref="T:MediaBrowser.Controller.Library.IUserDataManager"/> interface.</param>
|
||||
<param name="directoryService">Instance of the <see cref="T:MediaBrowser.Controller.Providers.DirectoryService"/> interface.</param>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Parsers.SeasonNfoParser.FetchDataFromXmlNode(System.Xml.XmlReader,MediaBrowser.Controller.Providers.MetadataResult{MediaBrowser.Controller.Entities.TV.Season})">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="T:MediaBrowser.XbmcMetadata.Parsers.SeriesNfoParser">
|
||||
<summary>
|
||||
Nfo parser for series.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Parsers.SeriesNfoParser.#ctor(Microsoft.Extensions.Logging.ILogger,MediaBrowser.Common.Configuration.IConfigurationManager,MediaBrowser.Controller.Providers.IProviderManager,MediaBrowser.Controller.Library.IUserManager,MediaBrowser.Controller.Library.IUserDataManager,MediaBrowser.Controller.Providers.IDirectoryService)">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:MediaBrowser.XbmcMetadata.Parsers.SeriesNfoParser"/> class.
|
||||
</summary>
|
||||
<param name="logger">Instance of the <see cref="T:Microsoft.Extensions.Logging.ILogger"/> interface.</param>
|
||||
<param name="config">Instance of the <see cref="T:MediaBrowser.Common.Configuration.IConfigurationManager"/> interface.</param>
|
||||
<param name="providerManager">Instance of the <see cref="T:MediaBrowser.Controller.Providers.IProviderManager"/> interface.</param>
|
||||
<param name="userManager">Instance of the <see cref="T:MediaBrowser.Controller.Library.IUserManager"/> interface.</param>
|
||||
<param name="userDataManager">Instance of the <see cref="T:MediaBrowser.Controller.Library.IUserDataManager"/> interface.</param>
|
||||
<param name="directoryService">Instance of the <see cref="T:MediaBrowser.Controller.Providers.IDirectoryService"/> interface.</param>
|
||||
</member>
|
||||
<member name="P:MediaBrowser.XbmcMetadata.Parsers.SeriesNfoParser.SupportsUrlAfterClosingXmlTag">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Parsers.SeriesNfoParser.FetchDataFromXmlNode(System.Xml.XmlReader,MediaBrowser.Controller.Providers.MetadataResult{MediaBrowser.Controller.Entities.TV.Series})">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="T:MediaBrowser.XbmcMetadata.Parsers.SeriesNfoSeasonParser">
|
||||
<summary>
|
||||
NFO parser for seasons based on series NFO.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Parsers.SeriesNfoSeasonParser.#ctor(Microsoft.Extensions.Logging.ILogger,MediaBrowser.Common.Configuration.IConfigurationManager,MediaBrowser.Controller.Providers.IProviderManager,MediaBrowser.Controller.Library.IUserManager,MediaBrowser.Controller.Library.IUserDataManager,MediaBrowser.Controller.Providers.IDirectoryService)">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:MediaBrowser.XbmcMetadata.Parsers.SeriesNfoSeasonParser"/> class.
|
||||
</summary>
|
||||
<param name="logger">Instance of the <see cref="T:Microsoft.Extensions.Logging.ILogger"/> interface.</param>
|
||||
<param name="config">Instance of the <see cref="T:MediaBrowser.Common.Configuration.IConfigurationManager"/> interface.</param>
|
||||
<param name="providerManager">Instance of the <see cref="T:MediaBrowser.Controller.Providers.IProviderManager"/> interface.</param>
|
||||
<param name="userManager">Instance of the <see cref="T:MediaBrowser.Controller.Library.IUserManager"/> interface.</param>
|
||||
<param name="userDataManager">Instance of the <see cref="T:MediaBrowser.Controller.Library.IUserDataManager"/> interface.</param>
|
||||
<param name="directoryService">Instance of the <see cref="T:MediaBrowser.Controller.Providers.IDirectoryService"/> interface.</param>
|
||||
</member>
|
||||
<member name="P:MediaBrowser.XbmcMetadata.Parsers.SeriesNfoSeasonParser.SupportsUrlAfterClosingXmlTag">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Parsers.SeriesNfoSeasonParser.FetchDataFromXmlNode(System.Xml.XmlReader,MediaBrowser.Controller.Providers.MetadataResult{MediaBrowser.Controller.Entities.TV.Season})">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="T:MediaBrowser.XbmcMetadata.Providers.AlbumNfoProvider">
|
||||
<summary>
|
||||
Nfo provider for albums.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Providers.AlbumNfoProvider.#ctor(Microsoft.Extensions.Logging.ILogger{MediaBrowser.XbmcMetadata.Providers.AlbumNfoProvider},MediaBrowser.Model.IO.IFileSystem,MediaBrowser.Common.Configuration.IConfigurationManager,MediaBrowser.Controller.Providers.IProviderManager,MediaBrowser.Controller.Library.IUserManager,MediaBrowser.Controller.Library.IUserDataManager,MediaBrowser.Controller.Providers.IDirectoryService)">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:MediaBrowser.XbmcMetadata.Providers.AlbumNfoProvider"/> class.
|
||||
</summary>
|
||||
<param name="logger">Instance of the <see cref="T:Microsoft.Extensions.Logging.ILogger`1"/> interface.</param>
|
||||
<param name="fileSystem">Instance of the <see cref="T:MediaBrowser.Model.IO.IFileSystem"/> interface.</param>
|
||||
<param name="config">Instance of the <see cref="T:MediaBrowser.Common.Configuration.IConfigurationManager"/> interface.</param>
|
||||
<param name="providerManager">Instance of the <see cref="T:MediaBrowser.Controller.Providers.IProviderManager"/> interface.</param>
|
||||
<param name="userManager">Instance of the <see cref="T:MediaBrowser.Controller.Library.IUserManager"/> interface.</param>
|
||||
<param name="userDataManager">Instance of the <see cref="T:MediaBrowser.Controller.Library.IUserDataManager"/> interface.</param>
|
||||
<param name="directoryService">Instance of the <see cref="T:MediaBrowser.Controller.Providers.IDirectoryService"/> interface.</param>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Providers.AlbumNfoProvider.Fetch(MediaBrowser.Controller.Providers.MetadataResult{MediaBrowser.Controller.Entities.Audio.MusicAlbum},System.String,System.Threading.CancellationToken)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Providers.AlbumNfoProvider.GetXmlFile(MediaBrowser.Controller.Providers.ItemInfo,MediaBrowser.Controller.Providers.IDirectoryService)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="T:MediaBrowser.XbmcMetadata.Providers.ArtistNfoProvider">
|
||||
<summary>
|
||||
Nfo provider for artists.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Providers.ArtistNfoProvider.#ctor(MediaBrowser.Model.IO.IFileSystem,Microsoft.Extensions.Logging.ILogger{MediaBrowser.XbmcMetadata.Providers.ArtistNfoProvider},MediaBrowser.Common.Configuration.IConfigurationManager,MediaBrowser.Controller.Providers.IProviderManager,MediaBrowser.Controller.Library.IUserManager,MediaBrowser.Controller.Library.IUserDataManager,MediaBrowser.Controller.Providers.IDirectoryService)">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:MediaBrowser.XbmcMetadata.Providers.ArtistNfoProvider"/> class.
|
||||
</summary>
|
||||
<param name="logger">Instance of the <see cref="T:Microsoft.Extensions.Logging.ILogger`1"/> interface.</param>
|
||||
<param name="fileSystem">Instance of the <see cref="T:MediaBrowser.Model.IO.IFileSystem"/> interface.</param>
|
||||
<param name="config">Instance of the <see cref="T:MediaBrowser.Common.Configuration.IConfigurationManager"/> interface.</param>
|
||||
<param name="providerManager">Instance of the <see cref="T:MediaBrowser.Controller.Providers.IProviderManager"/> interface.</param>
|
||||
<param name="userManager">Instance of the <see cref="T:MediaBrowser.Controller.Library.IUserManager"/> interface.</param>
|
||||
<param name="userDataManager">Instance of the <see cref="T:MediaBrowser.Controller.Library.IUserDataManager"/> interface.</param>
|
||||
<param name="directoryService">Instance of the <see cref="T:MediaBrowser.Controller.Providers.IDirectoryService"/> interface.</param>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Providers.ArtistNfoProvider.Fetch(MediaBrowser.Controller.Providers.MetadataResult{MediaBrowser.Controller.Entities.Audio.MusicArtist},System.String,System.Threading.CancellationToken)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Providers.ArtistNfoProvider.GetXmlFile(MediaBrowser.Controller.Providers.ItemInfo,MediaBrowser.Controller.Providers.IDirectoryService)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="P:MediaBrowser.XbmcMetadata.Providers.BaseNfoProvider`1.Name">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Providers.BaseNfoProvider`1.GetMetadata(MediaBrowser.Controller.Providers.ItemInfo,MediaBrowser.Controller.Providers.IDirectoryService,System.Threading.CancellationToken)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Providers.BaseNfoProvider`1.HasChanged(MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Controller.Providers.IDirectoryService)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Providers.BaseVideoNfoProvider`1.Fetch(MediaBrowser.Controller.Providers.MetadataResult{`0},System.String,System.Threading.CancellationToken)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Providers.BaseVideoNfoProvider`1.GetXmlFile(MediaBrowser.Controller.Providers.ItemInfo,MediaBrowser.Controller.Providers.IDirectoryService)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="T:MediaBrowser.XbmcMetadata.Providers.EpisodeNfoProvider">
|
||||
<summary>
|
||||
Nfo provider for episodes.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Providers.EpisodeNfoProvider.#ctor(Microsoft.Extensions.Logging.ILogger{MediaBrowser.XbmcMetadata.Providers.EpisodeNfoProvider},MediaBrowser.Model.IO.IFileSystem,MediaBrowser.Common.Configuration.IConfigurationManager,MediaBrowser.Controller.Providers.IProviderManager,MediaBrowser.Controller.Library.IUserManager,MediaBrowser.Controller.Library.IUserDataManager,MediaBrowser.Controller.Providers.IDirectoryService)">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:MediaBrowser.XbmcMetadata.Providers.EpisodeNfoProvider"/> class.
|
||||
</summary>
|
||||
<param name="logger">Instance of the <see cref="T:Microsoft.Extensions.Logging.ILogger`1"/> interface.</param>
|
||||
<param name="fileSystem">Instance of the <see cref="T:MediaBrowser.Model.IO.IFileSystem"/> interface.</param>
|
||||
<param name="config">Instance of the <see cref="T:MediaBrowser.Common.Configuration.IConfigurationManager"/> interface.</param>
|
||||
<param name="providerManager">Instance of the <see cref="T:MediaBrowser.Controller.Providers.IProviderManager"/> interface.</param>
|
||||
<param name="userManager">Instance of the <see cref="T:MediaBrowser.Controller.Library.IUserManager"/> interface.</param>
|
||||
<param name="userDataManager">Instance of the <see cref="T:MediaBrowser.Controller.Library.IUserDataManager"/> interface.</param>
|
||||
<param name="directoryService">Instance of the <see cref="T:MediaBrowser.Controller.Providers.IDirectoryService"/> interface.</param>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Providers.EpisodeNfoProvider.Fetch(MediaBrowser.Controller.Providers.MetadataResult{MediaBrowser.Controller.Entities.TV.Episode},System.String,System.Threading.CancellationToken)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Providers.EpisodeNfoProvider.GetXmlFile(MediaBrowser.Controller.Providers.ItemInfo,MediaBrowser.Controller.Providers.IDirectoryService)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="T:MediaBrowser.XbmcMetadata.Providers.MovieNfoProvider">
|
||||
<summary>
|
||||
Nfo provider for movies.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Providers.MovieNfoProvider.#ctor(Microsoft.Extensions.Logging.ILogger{MediaBrowser.XbmcMetadata.Providers.MovieNfoProvider},MediaBrowser.Model.IO.IFileSystem,MediaBrowser.Common.Configuration.IConfigurationManager,MediaBrowser.Controller.Providers.IProviderManager,MediaBrowser.Controller.Library.IUserManager,MediaBrowser.Controller.Library.IUserDataManager,MediaBrowser.Controller.Providers.IDirectoryService)">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:MediaBrowser.XbmcMetadata.Providers.MovieNfoProvider"/> class.
|
||||
</summary>
|
||||
<param name="logger">Instance of the <see cref="T:Microsoft.Extensions.Logging.ILogger`1"/> interface.</param>
|
||||
<param name="fileSystem">Instance of the <see cref="T:MediaBrowser.Model.IO.IFileSystem"/> interface.</param>
|
||||
<param name="config">Instance of the <see cref="T:MediaBrowser.Common.Configuration.IConfigurationManager"/> interface.</param>
|
||||
<param name="providerManager">Instance of the <see cref="T:MediaBrowser.Controller.Providers.IProviderManager"/> interface.</param>
|
||||
<param name="userManager">Instance of the <see cref="T:MediaBrowser.Controller.Library.IUserManager"/> interface.</param>
|
||||
<param name="userDataManager">Instance of the <see cref="T:MediaBrowser.Controller.Library.IUserDataManager"/> interface.</param>
|
||||
<param name="directoryService">Instance of the <see cref="T:MediaBrowser.Controller.Providers.IDirectoryService"/> interface.</param>
|
||||
</member>
|
||||
<member name="T:MediaBrowser.XbmcMetadata.Providers.MusicVideoNfoProvider">
|
||||
<summary>
|
||||
Nfo provider for music videos.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Providers.MusicVideoNfoProvider.#ctor(Microsoft.Extensions.Logging.ILogger{MediaBrowser.XbmcMetadata.Providers.MusicVideoNfoProvider},MediaBrowser.Model.IO.IFileSystem,MediaBrowser.Common.Configuration.IConfigurationManager,MediaBrowser.Controller.Providers.IProviderManager,MediaBrowser.Controller.Library.IUserManager,MediaBrowser.Controller.Library.IUserDataManager,MediaBrowser.Controller.Providers.IDirectoryService)">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:MediaBrowser.XbmcMetadata.Providers.MusicVideoNfoProvider"/> class.
|
||||
</summary>
|
||||
<param name="logger">Instance of the <see cref="T:Microsoft.Extensions.Logging.ILogger`1"/> interface.</param>
|
||||
<param name="fileSystem">Instance of the <see cref="T:MediaBrowser.Model.IO.IFileSystem"/> interface.</param>
|
||||
<param name="config">Instance of the <see cref="T:MediaBrowser.Common.Configuration.IConfigurationManager"/> interface.</param>
|
||||
<param name="providerManager">Instance of the <see cref="T:MediaBrowser.Controller.Providers.IProviderManager"/> interface.</param>
|
||||
<param name="userManager">Instance of the <see cref="T:MediaBrowser.Controller.Library.IUserManager"/> interface.</param>
|
||||
<param name="userDataManager">Instance of the <see cref="T:MediaBrowser.Controller.Library.IUserDataManager"/> interface.</param>
|
||||
<param name="directoryService">Instance of the <see cref="T:MediaBrowser.Controller.Providers.IDirectoryService"/> interface.</param>
|
||||
</member>
|
||||
<member name="T:MediaBrowser.XbmcMetadata.Providers.SeasonNfoProvider">
|
||||
<summary>
|
||||
Nfo provider for seasons.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Providers.SeasonNfoProvider.#ctor(Microsoft.Extensions.Logging.ILogger{MediaBrowser.XbmcMetadata.Providers.SeasonNfoProvider},MediaBrowser.Model.IO.IFileSystem,MediaBrowser.Common.Configuration.IConfigurationManager,MediaBrowser.Controller.Providers.IProviderManager,MediaBrowser.Controller.Library.IUserManager,MediaBrowser.Controller.Library.IUserDataManager,MediaBrowser.Controller.Providers.IDirectoryService)">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:MediaBrowser.XbmcMetadata.Providers.SeasonNfoProvider"/> class.
|
||||
</summary>
|
||||
<param name="logger">Instance of the <see cref="T:Microsoft.Extensions.Logging.ILogger`1"/> interface.</param>
|
||||
<param name="fileSystem">Instance of the <see cref="T:MediaBrowser.Model.IO.IFileSystem"/> interface.</param>
|
||||
<param name="config">Instance of the <see cref="T:MediaBrowser.Common.Configuration.IConfigurationManager"/> interface.</param>
|
||||
<param name="providerManager">Instance of the <see cref="T:MediaBrowser.Controller.Providers.IProviderManager"/> interface.</param>
|
||||
<param name="userManager">Instance of the <see cref="T:MediaBrowser.Controller.Library.IUserManager"/> interface.</param>
|
||||
<param name="userDataManager">Instance of the <see cref="T:MediaBrowser.Controller.Library.IUserDataManager"/> interface.</param>
|
||||
<param name="directoryService">Instance of the <see cref="T:MediaBrowser.Controller.Providers.IDirectoryService"/> interface.</param>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Providers.SeasonNfoProvider.Fetch(MediaBrowser.Controller.Providers.MetadataResult{MediaBrowser.Controller.Entities.TV.Season},System.String,System.Threading.CancellationToken)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Providers.SeasonNfoProvider.GetXmlFile(MediaBrowser.Controller.Providers.ItemInfo,MediaBrowser.Controller.Providers.IDirectoryService)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="T:MediaBrowser.XbmcMetadata.Providers.SeriesNfoProvider">
|
||||
<summary>
|
||||
Nfo provider for series.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Providers.SeriesNfoProvider.#ctor(Microsoft.Extensions.Logging.ILogger{MediaBrowser.XbmcMetadata.Providers.SeriesNfoProvider},MediaBrowser.Model.IO.IFileSystem,MediaBrowser.Common.Configuration.IConfigurationManager,MediaBrowser.Controller.Providers.IProviderManager,MediaBrowser.Controller.Library.IUserManager,MediaBrowser.Controller.Library.IUserDataManager,MediaBrowser.Controller.Providers.IDirectoryService)">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:MediaBrowser.XbmcMetadata.Providers.SeriesNfoProvider"/> class.
|
||||
</summary>
|
||||
<param name="logger">Instance of the <see cref="T:Microsoft.Extensions.Logging.ILogger`1"/> interface.</param>
|
||||
<param name="fileSystem">Instance of the <see cref="T:MediaBrowser.Model.IO.IFileSystem"/> interface.</param>
|
||||
<param name="config">Instance of the <see cref="T:MediaBrowser.Common.Configuration.IConfigurationManager"/> interface.</param>
|
||||
<param name="providerManager">Instance of the <see cref="T:MediaBrowser.Controller.Providers.IProviderManager"/> interface.</param>
|
||||
<param name="userManager">Instance of the <see cref="T:MediaBrowser.Controller.Library.IUserManager"/> interface.</param>
|
||||
<param name="userDataManager">Instance of the <see cref="T:MediaBrowser.Controller.Library.IUserDataManager"/> interface.</param>
|
||||
<param name="directoryService">Instance of the <see cref="T:MediaBrowser.Controller.Providers.IDirectoryService"/> interface.</param>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Providers.SeriesNfoProvider.Fetch(MediaBrowser.Controller.Providers.MetadataResult{MediaBrowser.Controller.Entities.TV.Series},System.String,System.Threading.CancellationToken)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Providers.SeriesNfoProvider.GetXmlFile(MediaBrowser.Controller.Providers.ItemInfo,MediaBrowser.Controller.Providers.IDirectoryService)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="T:MediaBrowser.XbmcMetadata.Providers.SeriesNfoSeasonProvider">
|
||||
<summary>
|
||||
NFO provider for seasons based on series NFO.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Providers.SeriesNfoSeasonProvider.#ctor(Microsoft.Extensions.Logging.ILogger{MediaBrowser.XbmcMetadata.Providers.SeriesNfoSeasonProvider},MediaBrowser.Model.IO.IFileSystem,MediaBrowser.Common.Configuration.IConfigurationManager,MediaBrowser.Controller.Providers.IProviderManager,MediaBrowser.Controller.Library.IUserManager,MediaBrowser.Controller.Library.IUserDataManager,MediaBrowser.Controller.Providers.IDirectoryService,MediaBrowser.Controller.Library.ILibraryManager)">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:MediaBrowser.XbmcMetadata.Providers.SeriesNfoSeasonProvider"/> class.
|
||||
</summary>
|
||||
<param name="logger">Instance of the <see cref="T:Microsoft.Extensions.Logging.ILogger`1"/> interface.</param>
|
||||
<param name="fileSystem">Instance of the <see cref="T:MediaBrowser.Model.IO.IFileSystem"/> interface.</param>
|
||||
<param name="config">Instance of the <see cref="T:MediaBrowser.Common.Configuration.IConfigurationManager"/> interface.</param>
|
||||
<param name="providerManager">Instance of the <see cref="T:MediaBrowser.Controller.Providers.IProviderManager"/> interface.</param>
|
||||
<param name="userManager">Instance of the <see cref="T:MediaBrowser.Controller.Library.IUserManager"/> interface.</param>
|
||||
<param name="userDataManager">Instance of the <see cref="T:MediaBrowser.Controller.Library.IUserDataManager"/> interface.</param>
|
||||
<param name="directoryService">Instance of the <see cref="T:MediaBrowser.Controller.Providers.IDirectoryService"/> interface.</param>
|
||||
<param name="libraryManager">Instance of the <see cref="T:MediaBrowser.Controller.Library.ILibraryManager"/> interface.</param>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Providers.SeriesNfoSeasonProvider.Fetch(MediaBrowser.Controller.Providers.MetadataResult{MediaBrowser.Controller.Entities.TV.Season},System.String,System.Threading.CancellationToken)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Providers.SeriesNfoSeasonProvider.GetXmlFile(MediaBrowser.Controller.Providers.ItemInfo,MediaBrowser.Controller.Providers.IDirectoryService)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="T:MediaBrowser.XbmcMetadata.Providers.VideoNfoProvider">
|
||||
<summary>
|
||||
Nfo provider for videos.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Providers.VideoNfoProvider.#ctor(Microsoft.Extensions.Logging.ILogger{MediaBrowser.XbmcMetadata.Providers.VideoNfoProvider},MediaBrowser.Model.IO.IFileSystem,MediaBrowser.Common.Configuration.IConfigurationManager,MediaBrowser.Controller.Providers.IProviderManager,MediaBrowser.Controller.Library.IUserManager,MediaBrowser.Controller.Library.IUserDataManager,MediaBrowser.Controller.Providers.IDirectoryService)">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:MediaBrowser.XbmcMetadata.Providers.VideoNfoProvider"/> class.
|
||||
</summary>
|
||||
<param name="logger">Instance of the <see cref="T:Microsoft.Extensions.Logging.ILogger`1"/> interface.</param>
|
||||
<param name="fileSystem">Instance of the <see cref="T:MediaBrowser.Model.IO.IFileSystem"/> interface.</param>
|
||||
<param name="config">Instance of the <see cref="T:MediaBrowser.Common.Configuration.IConfigurationManager"/> interface.</param>
|
||||
<param name="providerManager">Instance of the <see cref="T:MediaBrowser.Controller.Providers.IProviderManager"/> interface.</param>
|
||||
<param name="userManager">Instance of the <see cref="T:MediaBrowser.Controller.Library.IUserManager"/> interface.</param>
|
||||
<param name="userDataManager">Instance of the <see cref="T:MediaBrowser.Controller.Library.IUserDataManager"/> interface.</param>
|
||||
<param name="directoryService">Instance of the <see cref="T:MediaBrowser.Controller.Providers.IDirectoryService"/> interface.</param>
|
||||
</member>
|
||||
<member name="T:MediaBrowser.XbmcMetadata.Savers.AlbumNfoSaver">
|
||||
<summary>
|
||||
Nfo saver for albums.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Savers.AlbumNfoSaver.#ctor(MediaBrowser.Model.IO.IFileSystem,MediaBrowser.Controller.Configuration.IServerConfigurationManager,MediaBrowser.Controller.Library.ILibraryManager,MediaBrowser.Controller.Library.IUserManager,MediaBrowser.Controller.Library.IUserDataManager,Microsoft.Extensions.Logging.ILogger{MediaBrowser.XbmcMetadata.Savers.AlbumNfoSaver})">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:MediaBrowser.XbmcMetadata.Savers.AlbumNfoSaver"/> class.
|
||||
</summary>
|
||||
<param name="fileSystem">The file system.</param>
|
||||
<param name="configurationManager">the server configuration manager.</param>
|
||||
<param name="libraryManager">The library manager.</param>
|
||||
<param name="userManager">The user manager.</param>
|
||||
<param name="userDataManager">The user data manager.</param>
|
||||
<param name="logger">The logger.</param>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Savers.AlbumNfoSaver.GetLocalSavePath(MediaBrowser.Controller.Entities.BaseItem)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Savers.AlbumNfoSaver.GetRootElementName(MediaBrowser.Controller.Entities.BaseItem)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Savers.AlbumNfoSaver.IsEnabledFor(MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Controller.Library.ItemUpdateType)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Savers.AlbumNfoSaver.WriteCustomElements(MediaBrowser.Controller.Entities.BaseItem,System.Xml.XmlWriter)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Savers.AlbumNfoSaver.GetTagsUsed(MediaBrowser.Controller.Entities.BaseItem)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="T:MediaBrowser.XbmcMetadata.Savers.ArtistNfoSaver">
|
||||
<summary>
|
||||
Nfo saver for artist.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Savers.ArtistNfoSaver.#ctor(MediaBrowser.Model.IO.IFileSystem,MediaBrowser.Controller.Configuration.IServerConfigurationManager,MediaBrowser.Controller.Library.ILibraryManager,MediaBrowser.Controller.Library.IUserManager,MediaBrowser.Controller.Library.IUserDataManager,Microsoft.Extensions.Logging.ILogger{MediaBrowser.XbmcMetadata.Savers.ArtistNfoSaver})">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:MediaBrowser.XbmcMetadata.Savers.ArtistNfoSaver"/> class.
|
||||
</summary>
|
||||
<param name="fileSystem">The file system.</param>
|
||||
<param name="configurationManager">the server configuration manager.</param>
|
||||
<param name="libraryManager">The library manager.</param>
|
||||
<param name="userManager">The user manager.</param>
|
||||
<param name="userDataManager">The user data manager.</param>
|
||||
<param name="logger">The logger.</param>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Savers.ArtistNfoSaver.GetLocalSavePath(MediaBrowser.Controller.Entities.BaseItem)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Savers.ArtistNfoSaver.GetRootElementName(MediaBrowser.Controller.Entities.BaseItem)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Savers.ArtistNfoSaver.IsEnabledFor(MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Controller.Library.ItemUpdateType)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Savers.ArtistNfoSaver.WriteCustomElements(MediaBrowser.Controller.Entities.BaseItem,System.Xml.XmlWriter)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Savers.ArtistNfoSaver.GetTagsUsed(MediaBrowser.Controller.Entities.BaseItem)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="P:MediaBrowser.XbmcMetadata.Savers.BaseNfoSaver.Name">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Savers.BaseNfoSaver.InvalidXMLCharsRegexRegex">
|
||||
<remarks>
|
||||
Pattern:<br/>
|
||||
<code>(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x9F\uFEFF\uFFFE\uFFFF]</code><br/>
|
||||
Explanation:<br/>
|
||||
<code>
|
||||
○ Match with 3 alternative expressions, atomically.<br/>
|
||||
○ Match a sequence of expressions.<br/>
|
||||
○ Zero-width negative lookbehind.<br/>
|
||||
○ Match a character in the set [\uD800-\uDBFF] right-to-left.<br/>
|
||||
○ Match a character in the set [\uDC00-\uDFFF].<br/>
|
||||
○ Match a sequence of expressions.<br/>
|
||||
○ Match a character in the set [\uD800-\uDBFF].<br/>
|
||||
○ Zero-width negative lookahead.<br/>
|
||||
○ Match a character in the set [\uDC00-\uDFFF].<br/>
|
||||
○ Match a character in the set [^\t\n\r -~\u00A0-\uFEFE\uFF00-\uFFFD].<br/>
|
||||
</code>
|
||||
</remarks>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Savers.BaseNfoSaver.GetSavePath(MediaBrowser.Controller.Entities.BaseItem)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Savers.BaseNfoSaver.GetLocalSavePath(MediaBrowser.Controller.Entities.BaseItem)">
|
||||
<summary>
|
||||
Gets the save path.
|
||||
</summary>
|
||||
<param name="item">The item.</param>
|
||||
<returns><see cref="T:System.String" />.</returns>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Savers.BaseNfoSaver.GetRootElementName(MediaBrowser.Controller.Entities.BaseItem)">
|
||||
<summary>
|
||||
Gets the name of the root element.
|
||||
</summary>
|
||||
<param name="item">The item.</param>
|
||||
<returns><see cref="T:System.String" />.</returns>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Savers.BaseNfoSaver.IsEnabledFor(MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Controller.Library.ItemUpdateType)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Savers.BaseNfoSaver.SaveAsync(MediaBrowser.Controller.Entities.BaseItem,System.Threading.CancellationToken)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Savers.BaseNfoSaver.AddCommonNodes(MediaBrowser.Controller.Entities.BaseItem,System.Xml.XmlWriter,MediaBrowser.Controller.Library.ILibraryManager,MediaBrowser.Controller.Library.IUserManager,MediaBrowser.Controller.Library.IUserDataManager,MediaBrowser.Controller.Configuration.IServerConfigurationManager)">
|
||||
<summary>
|
||||
Adds the common nodes.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Savers.BaseNfoSaver.GetOutputTrailerUrl(System.String)">
|
||||
<summary>
|
||||
Gets the output trailer URL.
|
||||
</summary>
|
||||
<param name="url">The URL.</param>
|
||||
<returns>System.String.</returns>
|
||||
</member>
|
||||
<member name="T:MediaBrowser.XbmcMetadata.Savers.EpisodeNfoSaver">
|
||||
<summary>
|
||||
Nfo saver for episodes.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Savers.EpisodeNfoSaver.#ctor(MediaBrowser.Model.IO.IFileSystem,MediaBrowser.Controller.Configuration.IServerConfigurationManager,MediaBrowser.Controller.Library.ILibraryManager,MediaBrowser.Controller.Library.IUserManager,MediaBrowser.Controller.Library.IUserDataManager,Microsoft.Extensions.Logging.ILogger{MediaBrowser.XbmcMetadata.Savers.EpisodeNfoSaver})">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:MediaBrowser.XbmcMetadata.Savers.EpisodeNfoSaver"/> class.
|
||||
</summary>
|
||||
<param name="fileSystem">The file system.</param>
|
||||
<param name="configurationManager">the server configuration manager.</param>
|
||||
<param name="libraryManager">The library manager.</param>
|
||||
<param name="userManager">The user manager.</param>
|
||||
<param name="userDataManager">The user data manager.</param>
|
||||
<param name="logger">The logger.</param>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Savers.EpisodeNfoSaver.GetLocalSavePath(MediaBrowser.Controller.Entities.BaseItem)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Savers.EpisodeNfoSaver.GetRootElementName(MediaBrowser.Controller.Entities.BaseItem)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Savers.EpisodeNfoSaver.IsEnabledFor(MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Controller.Library.ItemUpdateType)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Savers.EpisodeNfoSaver.WriteCustomElements(MediaBrowser.Controller.Entities.BaseItem,System.Xml.XmlWriter)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Savers.EpisodeNfoSaver.GetTagsUsed(MediaBrowser.Controller.Entities.BaseItem)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="T:MediaBrowser.XbmcMetadata.Savers.MovieNfoSaver">
|
||||
<summary>
|
||||
Nfo saver for movies.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Savers.MovieNfoSaver.#ctor(MediaBrowser.Model.IO.IFileSystem,MediaBrowser.Controller.Configuration.IServerConfigurationManager,MediaBrowser.Controller.Library.ILibraryManager,MediaBrowser.Controller.Library.IUserManager,MediaBrowser.Controller.Library.IUserDataManager,Microsoft.Extensions.Logging.ILogger{MediaBrowser.XbmcMetadata.Savers.MovieNfoSaver})">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:MediaBrowser.XbmcMetadata.Savers.MovieNfoSaver"/> class.
|
||||
</summary>
|
||||
<param name="fileSystem">The file system.</param>
|
||||
<param name="configurationManager">the server configuration manager.</param>
|
||||
<param name="libraryManager">The library manager.</param>
|
||||
<param name="userManager">The user manager.</param>
|
||||
<param name="userDataManager">The user data manager.</param>
|
||||
<param name="logger">The logger.</param>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Savers.MovieNfoSaver.GetLocalSavePath(MediaBrowser.Controller.Entities.BaseItem)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Savers.MovieNfoSaver.GetRootElementName(MediaBrowser.Controller.Entities.BaseItem)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Savers.MovieNfoSaver.IsEnabledFor(MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Controller.Library.ItemUpdateType)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Savers.MovieNfoSaver.WriteCustomElements(MediaBrowser.Controller.Entities.BaseItem,System.Xml.XmlWriter)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Savers.MovieNfoSaver.GetTagsUsed(MediaBrowser.Controller.Entities.BaseItem)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="T:MediaBrowser.XbmcMetadata.Savers.SeasonNfoSaver">
|
||||
<summary>
|
||||
Nfo saver for seasons.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Savers.SeasonNfoSaver.#ctor(MediaBrowser.Model.IO.IFileSystem,MediaBrowser.Controller.Configuration.IServerConfigurationManager,MediaBrowser.Controller.Library.ILibraryManager,MediaBrowser.Controller.Library.IUserManager,MediaBrowser.Controller.Library.IUserDataManager,Microsoft.Extensions.Logging.ILogger{MediaBrowser.XbmcMetadata.Savers.SeasonNfoSaver})">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:MediaBrowser.XbmcMetadata.Savers.SeasonNfoSaver"/> class.
|
||||
</summary>
|
||||
<param name="fileSystem">The file system.</param>
|
||||
<param name="configurationManager">the server configuration manager.</param>
|
||||
<param name="libraryManager">The library manager.</param>
|
||||
<param name="userManager">The user manager.</param>
|
||||
<param name="userDataManager">The user data manager.</param>
|
||||
<param name="logger">The logger.</param>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Savers.SeasonNfoSaver.GetLocalSavePath(MediaBrowser.Controller.Entities.BaseItem)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Savers.SeasonNfoSaver.GetRootElementName(MediaBrowser.Controller.Entities.BaseItem)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Savers.SeasonNfoSaver.IsEnabledFor(MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Controller.Library.ItemUpdateType)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Savers.SeasonNfoSaver.WriteCustomElements(MediaBrowser.Controller.Entities.BaseItem,System.Xml.XmlWriter)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Savers.SeasonNfoSaver.GetTagsUsed(MediaBrowser.Controller.Entities.BaseItem)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="T:MediaBrowser.XbmcMetadata.Savers.SeriesNfoSaver">
|
||||
<summary>
|
||||
Nfo saver for series.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Savers.SeriesNfoSaver.#ctor(MediaBrowser.Model.IO.IFileSystem,MediaBrowser.Controller.Configuration.IServerConfigurationManager,MediaBrowser.Controller.Library.ILibraryManager,MediaBrowser.Controller.Library.IUserManager,MediaBrowser.Controller.Library.IUserDataManager,Microsoft.Extensions.Logging.ILogger{MediaBrowser.XbmcMetadata.Savers.SeriesNfoSaver})">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:MediaBrowser.XbmcMetadata.Savers.SeriesNfoSaver"/> class.
|
||||
</summary>
|
||||
<param name="fileSystem">The file system.</param>
|
||||
<param name="configurationManager">the server configuration manager.</param>
|
||||
<param name="libraryManager">The library manager.</param>
|
||||
<param name="userManager">The user manager.</param>
|
||||
<param name="userDataManager">The user data manager.</param>
|
||||
<param name="logger">The logger.</param>
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Savers.SeriesNfoSaver.GetLocalSavePath(MediaBrowser.Controller.Entities.BaseItem)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Savers.SeriesNfoSaver.GetRootElementName(MediaBrowser.Controller.Entities.BaseItem)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Savers.SeriesNfoSaver.IsEnabledFor(MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Controller.Library.ItemUpdateType)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Savers.SeriesNfoSaver.WriteCustomElements(MediaBrowser.Controller.Entities.BaseItem,System.Xml.XmlWriter)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:MediaBrowser.XbmcMetadata.Savers.SeriesNfoSaver.GetTagsUsed(MediaBrowser.Controller.Entities.BaseItem)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="T:System.Text.RegularExpressions.Generated.InvalidXMLCharsRegexRegex_0">
|
||||
<summary>Custom <see cref="T:System.Text.RegularExpressions.Regex"/>-derived type for the InvalidXMLCharsRegexRegex method.</summary>
|
||||
</member>
|
||||
<member name="F:System.Text.RegularExpressions.Generated.InvalidXMLCharsRegexRegex_0.Instance">
|
||||
<summary>Cached, thread-safe singleton instance.</summary>
|
||||
</member>
|
||||
<member name="M:System.Text.RegularExpressions.Generated.InvalidXMLCharsRegexRegex_0.#ctor">
|
||||
<summary>Initializes the instance.</summary>
|
||||
</member>
|
||||
<member name="T:System.Text.RegularExpressions.Generated.InvalidXMLCharsRegexRegex_0.RunnerFactory">
|
||||
<summary>Provides a factory for creating <see cref="T:System.Text.RegularExpressions.RegexRunner"/> instances to be used by methods on <see cref="T:System.Text.RegularExpressions.Regex"/>.</summary>
|
||||
</member>
|
||||
<member name="M:System.Text.RegularExpressions.Generated.InvalidXMLCharsRegexRegex_0.RunnerFactory.CreateInstance">
|
||||
<summary>Creates an instance of a <see cref="T:System.Text.RegularExpressions.RegexRunner"/> used by methods on <see cref="T:System.Text.RegularExpressions.Regex"/>.</summary>
|
||||
</member>
|
||||
<member name="T:System.Text.RegularExpressions.Generated.InvalidXMLCharsRegexRegex_0.RunnerFactory.Runner">
|
||||
<summary>Provides the runner that contains the custom logic implementing the specified regular expression.</summary>
|
||||
</member>
|
||||
<member name="M:System.Text.RegularExpressions.Generated.InvalidXMLCharsRegexRegex_0.RunnerFactory.Runner.Scan(System.ReadOnlySpan{System.Char})">
|
||||
<summary>Scan the <paramref name="inputSpan"/> starting from base.runtextstart for the next match.</summary>
|
||||
<param name="inputSpan">The text being scanned by the regular expression.</param>
|
||||
</member>
|
||||
<member name="M:System.Text.RegularExpressions.Generated.InvalidXMLCharsRegexRegex_0.RunnerFactory.Runner.TryFindNextPossibleStartingPosition(System.ReadOnlySpan{System.Char})">
|
||||
<summary>Search <paramref name="inputSpan"/> starting from base.runtextpos for the next location a match could possibly start.</summary>
|
||||
<param name="inputSpan">The text being scanned by the regular expression.</param>
|
||||
<returns>true if a possible match was found; false if no more matches are possible.</returns>
|
||||
</member>
|
||||
<member name="M:System.Text.RegularExpressions.Generated.InvalidXMLCharsRegexRegex_0.RunnerFactory.Runner.TryMatchAtCurrentPosition(System.ReadOnlySpan{System.Char})">
|
||||
<summary>Determine whether <paramref name="inputSpan"/> at base.runtextpos is a match for the regular expression.</summary>
|
||||
<param name="inputSpan">The text being scanned by the regular expression.</param>
|
||||
<returns>true if the regular expression matches at the current position; otherwise, false.</returns>
|
||||
</member>
|
||||
<member name="T:System.Text.RegularExpressions.Generated.Utilities">
|
||||
<summary>Helper methods used by generated <see cref="T:System.Text.RegularExpressions.Regex"/>-derived implementations.</summary>
|
||||
</member>
|
||||
<member name="F:System.Text.RegularExpressions.Generated.Utilities.s_defaultTimeout">
|
||||
<summary>Default timeout value set in <see cref="T:System.AppContext"/>, or <see cref="F:System.Text.RegularExpressions.Regex.InfiniteMatchTimeout"/> if none was set.</summary>
|
||||
</member>
|
||||
<member name="F:System.Text.RegularExpressions.Generated.Utilities.s_hasTimeout">
|
||||
<summary>Whether <see cref="F:System.Text.RegularExpressions.Generated.Utilities.s_defaultTimeout"/> is non-infinite.</summary>
|
||||
</member>
|
||||
<member name="M:System.Text.RegularExpressions.Generated.Utilities.IndexOfNonAsciiOrAny_054D88A1CA74F303E737A5D6CB3A2FC78EF8B4B74FED9DBE7BF41353919E123B(System.ReadOnlySpan{System.Char})">
|
||||
<summary>Finds the next index of any character that matches a character in the set [^\t\n\r -~\u00A0-\uD7FF\uE000-\uFEFE\uFF00-\uFFFD].</summary>
|
||||
</member>
|
||||
<member name="F:System.Text.RegularExpressions.Generated.Utilities.s_ascii_260000FFFFFFFFFFFFFFFFFFFFFF7F">
|
||||
<summary>Supports searching for characters in or not in "\t\n\r !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~".</summary>
|
||||
</member>
|
||||
</members>
|
||||
</doc>
|
||||
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"Serilog": {
|
||||
"MinimumLevel": {
|
||||
"Default": "Information",
|
||||
"Override": {
|
||||
"Microsoft": "Warning",
|
||||
"System": "Warning",
|
||||
"Microsoft.EntityFrameworkCore.Database.Command": "Information"
|
||||
}
|
||||
},
|
||||
"WriteTo": [
|
||||
{
|
||||
"Name": "Console",
|
||||
"Args": {
|
||||
"outputTemplate": "[{Timestamp:HH:mm:ss.fff}] [{Level:u3}] [{ThreadId}] {SourceContext}: {Message:lj}{NewLine}{Exception}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "Async",
|
||||
"Args": {
|
||||
"configure": [
|
||||
{
|
||||
"Name": "File",
|
||||
"Args": {
|
||||
"path": "%JELLYFIN_LOG_DIR%//log_.log",
|
||||
"rollingInterval": "Day",
|
||||
"retainedFileCountLimit": 3,
|
||||
"rollOnFileSizeLimit": true,
|
||||
"fileSizeLimitBytes": 100000000,
|
||||
"outputTemplate": "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level:u3}] [{ThreadId}] {SourceContext}: {Message}{NewLine}{Exception}"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"Enrich": [ "FromLogContext", "WithThreadId" ]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/jellyfin-startup",
|
||||
"// Comment": "Startup configuration defaults for Jellyfin Server",
|
||||
"// Note": "For Linux: Use /var/lib/jellyfin, For Windows: Use C:/ProgramData/jellyfin",
|
||||
"// Documentation": "These values are used when no command-line args or environment variables are set",
|
||||
"Paths": {
|
||||
"DataDir": "/var/lib/jellyfin",
|
||||
"ConfigDir": "/var/lib/jellyfin",
|
||||
"CacheDir": "/var/lib/jellyfin",
|
||||
"LogDir": "/var/lib/jellyfin",
|
||||
"TempDir": "/var/lib/jellyfin",
|
||||
"WebDir": "/var/lib/jellyfin"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/jellyfin-startup",
|
||||
"// Comment": "Linux-specific startup configuration for Jellyfin Server",
|
||||
"// Instructions": "Copy this file to 'startup.json' in the Jellyfin directory",
|
||||
"// Documentation": "These paths follow Linux FHS (Filesystem Hierarchy Standard)",
|
||||
"Paths": {
|
||||
"DataDir": "/var/lib/jellyfin",
|
||||
"ConfigDir": "/var/lib/jellyfin",
|
||||
"CacheDir": "/var/lib/jellyfin",
|
||||
"LogDir": "/var/lib/jellyfin",
|
||||
"TempDir": "/var/lib/jellyfin",
|
||||
"WebDir": "/var/lib/jellyfin"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/jellyfin-startup",
|
||||
"// Comment": "Windows-specific startup configuration for Jellyfin Server",
|
||||
"// Instructions": "Copy this file to 'startup.json' in the Jellyfin directory",
|
||||
"// Documentation": "These paths are optimized for Windows installations",
|
||||
"Paths": {
|
||||
"DataDir": "C:/ProgramData/jellyfin",
|
||||
"ConfigDir": "C:/ProgramData/jellyfin",
|
||||
"CacheDir": "C:/ProgramData/jellyfin",
|
||||
"LogDir": "C:/ProgramData/jellyfin",
|
||||
"TempDir": "C:/ProgramData/jellyfin",
|
||||
"WebDir": "C:/ProgramData/jellyfin"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>
|
||||
{{#IF isInReportingMode}}
|
||||
❌
|
||||
{{/IF}}
|
||||
Jellyfin Startup
|
||||
</title>
|
||||
<style>
|
||||
* {
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
.flex-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
align-content: normal;
|
||||
}
|
||||
|
||||
.flex-col {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
align-content: normal;
|
||||
}
|
||||
|
||||
header {
|
||||
height: 5rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
header svg {
|
||||
height: 3rem;
|
||||
width: 9rem;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
/* ol.action-list {
|
||||
list-style-type: none;
|
||||
position: relative;
|
||||
} */
|
||||
|
||||
ol.action-list * {
|
||||
font-family: monospace;
|
||||
font-weight: 300;
|
||||
font-size: clamp(18px, 100vw / var(--width), 20px);
|
||||
font-feature-settings: 'onum', 'pnum';
|
||||
line-height: 1.8;
|
||||
-webkit-text-size-adjust: none;
|
||||
}
|
||||
|
||||
/*
|
||||
ol.action-list li {
|
||||
padding-top: .5rem;
|
||||
}
|
||||
|
||||
ol.action-list li::before {
|
||||
position: absolute;
|
||||
left: -0.8em;
|
||||
font-size: 1.1em;
|
||||
} */
|
||||
|
||||
/* Attribution as heavily inspired by: https://iamkate.com/code/tree-views/ */
|
||||
.action-list {
|
||||
--spacing: 1.4rem;
|
||||
--radius: 14px;
|
||||
}
|
||||
|
||||
.action-list li {
|
||||
display: block;
|
||||
position: relative;
|
||||
padding-left: calc(2 * var(--spacing) - var(--radius) - 1px);
|
||||
}
|
||||
|
||||
.action-list ul {
|
||||
margin-left: calc(var(--radius) - var(--spacing));
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.action-list ul li {
|
||||
border-left: 2px solid #ddd;
|
||||
}
|
||||
|
||||
.action-list ul li:last-child {
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
.action-list ul li::before {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: calc(var(--spacing) / -2);
|
||||
left: -2px;
|
||||
width: calc(var(--spacing) + 2px);
|
||||
height: calc(var(--spacing) + 1px);
|
||||
border: solid #ddd;
|
||||
border-width: 0 0 2px 2px;
|
||||
}
|
||||
|
||||
.action-list summary {
|
||||
display: block;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.action-list summary::marker,
|
||||
.action-list summary::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.action-list summary:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.action-list summary:focus-visible {
|
||||
outline: 1px dotted #000;
|
||||
}
|
||||
|
||||
.action-list li::after,
|
||||
.action-list summary::before {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: calc(var(--spacing) / 2 - var(--radius) + 4px);
|
||||
left: calc(var(--spacing) - var(--radius) - -5px);
|
||||
}
|
||||
|
||||
.action-list summary::before {
|
||||
z-index: 1;
|
||||
/* background: #696 url('expand-collapse.svg') 0 0; */
|
||||
}
|
||||
|
||||
.action-list details[open]>summary::before {
|
||||
background-position: calc(-2 * var(--radius)) 0;
|
||||
}
|
||||
|
||||
.action-list li.danger-item::after,
|
||||
.action-list li.danger-strong-item::after {
|
||||
content: '❌';
|
||||
}
|
||||
|
||||
ol.action-list li span.danger-strong-item {
|
||||
text-decoration-style: solid;
|
||||
text-decoration-color: red;
|
||||
text-decoration-line: underline;
|
||||
}
|
||||
|
||||
ol.action-list li.warn-item::after {
|
||||
content: '⚠️';
|
||||
}
|
||||
|
||||
ol.action-list li.success-item::after {
|
||||
content: '✅';
|
||||
}
|
||||
|
||||
ol.action-list li.info-item::after {
|
||||
content: '🔹';
|
||||
}
|
||||
|
||||
/* End Attribution */
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div>
|
||||
<header class="flex-row">
|
||||
|
||||
{{^IF isInReportingMode}}
|
||||
<p>Jellyfin Server {{version}} still starting. Please wait.</p>
|
||||
{{#ELSE}}
|
||||
<p>Jellyfin Server has encountered an error and was not able to start.</p>
|
||||
{{/ELSE}}
|
||||
{{/IF}}
|
||||
|
||||
{{#IF localNetworkRequest}}
|
||||
<p style="margin-left: 1rem;">You can download the current log file <a href='/startup/logger'
|
||||
target="_blank">here</a>.</p>
|
||||
{{/IF}}
|
||||
</header>
|
||||
|
||||
{{#DECLARE LogEntry |--}}
|
||||
{{#LET children = Children}}
|
||||
<li class="{{FormatLogLevel(children).ToString()}}-item">
|
||||
{{--| #IF children.Count > 0}}
|
||||
<details open>
|
||||
<summary>{{DateOfCreation}} - {{Content}}</summary>
|
||||
<ul class="action-list">
|
||||
{{--| #EACH children.Reverse() |-}}
|
||||
{{#IMPORT 'LogEntry'}}
|
||||
{{--| /EACH |-}}
|
||||
</ul>
|
||||
</details>
|
||||
{{--| #ELSE |-}}
|
||||
<span class="{{FormatLogLevel(children).ToString()}}-item">{{DateOfCreation}} - {{Content}}</span>
|
||||
{{--| /ELSE |--}}
|
||||
{{--| /IF |-}}
|
||||
</li>
|
||||
{{--| /DECLARE}}
|
||||
|
||||
{{#IF localNetworkRequest}}
|
||||
<div class="flex-col">
|
||||
<ol class="action-list">
|
||||
{{#FOREACH log IN logs.Reverse()}}
|
||||
{{#IMPORT 'LogEntry' #WITH log}}
|
||||
{{/FOREACH}}
|
||||
</ol>
|
||||
</div>
|
||||
{{#ELSE}}
|
||||
{{#IF networkManagerReady}}
|
||||
<p>Please visit this page from your local network to view detailed startup logs.</p>
|
||||
{{#ELSE}}
|
||||
<p>Initializing network settings. Please wait.</p>
|
||||
{{/ELSE}}
|
||||
{{/IF}}
|
||||
{{/ELSE}}
|
||||
{{/IF}}
|
||||
</div>
|
||||
</body>
|
||||
|
||||
{{^IF isInReportingMode}}
|
||||
<script>
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, {{ retryValue.TotalMilliseconds }});
|
||||
</script>
|
||||
{{/IF}}
|
||||
|
||||
</html>
|
||||
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user