aboutsummaryrefslogtreecommitdiff
path: root/Jellyfin.Server.Implementations
diff options
context:
space:
mode:
authorLogan Douglas <42654828+JadedRain@users.noreply.github.com>2025-10-31 13:06:17 -0600
committerGitHub <noreply@github.com>2025-10-31 13:06:17 -0600
commit490bf347cbdf8ec458996d09ba40651eb647b7b9 (patch)
tree1bdad774667bf5e69f3fe7397a27f631a5ca617a /Jellyfin.Server.Implementations
parentfd6e48603bcf143a1bbc3b1bda26a8e1664f9379 (diff)
parent23929a3e709f4324d49271c02b0b047e1149e860 (diff)
Merge branch 'jellyfin:master' into master
Diffstat (limited to 'Jellyfin.Server.Implementations')
-rw-r--r--Jellyfin.Server.Implementations/FullSystemBackup/BackupService.cs223
-rw-r--r--Jellyfin.Server.Implementations/Item/BaseItemRepository.cs69
-rw-r--r--Jellyfin.Server.Implementations/Item/ChapterRepository.cs13
-rw-r--r--Jellyfin.Server.Implementations/Item/PeopleRepository.cs6
-rw-r--r--Jellyfin.Server.Implementations/StorageHelpers/StorageHelper.cs6
5 files changed, 190 insertions, 127 deletions
diff --git a/Jellyfin.Server.Implementations/FullSystemBackup/BackupService.cs b/Jellyfin.Server.Implementations/FullSystemBackup/BackupService.cs
index e5c3cef3d3..70483c36cc 100644
--- a/Jellyfin.Server.Implementations/FullSystemBackup/BackupService.cs
+++ b/Jellyfin.Server.Implementations/FullSystemBackup/BackupService.cs
@@ -128,7 +128,8 @@ public class BackupService : IBackupService
var targetPath = Path.GetFullPath(Path.Combine(target, Path.GetRelativePath(source, item.FullName)));
if (!sourcePath.StartsWith(fullSourcePath, StringComparison.Ordinal)
- || !targetPath.StartsWith(fullTargetRoot, StringComparison.Ordinal))
+ || !targetPath.StartsWith(fullTargetRoot, StringComparison.Ordinal)
+ || Path.EndsInDirectorySeparator(item.FullName))
{
continue;
}
@@ -199,7 +200,7 @@ public class BackupService : IBackupService
var zipEntry = zipArchive.GetEntry(NormalizePathSeparator(Path.Combine("Database", $"{entityType.Type.Name}.json")));
if (zipEntry is null)
{
- _logger.LogInformation("No backup of expected table {Table} is present in backup. Continue anyway.", entityType.Type.Name);
+ _logger.LogInformation("No backup of expected table {Table} is present in backup, continuing anyway", entityType.Type.Name);
continue;
}
@@ -223,7 +224,7 @@ public class BackupService : IBackupService
}
catch (Exception ex)
{
- _logger.LogError(ex, "Could not store entity {Entity} continue anyway.", item);
+ _logger.LogError(ex, "Could not store entity {Entity}, continuing anyway", item);
}
}
@@ -233,11 +234,11 @@ public class BackupService : IBackupService
_logger.LogInformation("Try restore Database");
await dbContext.SaveChangesAsync().ConfigureAwait(false);
- _logger.LogInformation("Restored database.");
+ _logger.LogInformation("Restored database");
}
}
- _logger.LogInformation("Restored Jellyfin system from {Date}.", manifest.DateCreated);
+ _logger.LogInformation("Restored Jellyfin system from {Date}", manifest.DateCreated);
}
}
@@ -263,6 +264,8 @@ public class BackupService : IBackupService
Options = Map(backupOptions)
};
+ _logger.LogInformation("Running database optimization before backup");
+
await _jellyfinDatabaseProvider.RunScheduledOptimisation(CancellationToken.None).ConfigureAwait(false);
var backupFolder = Path.Combine(_applicationPaths.BackupPath);
@@ -281,130 +284,154 @@ public class BackupService : IBackupService
}
var backupPath = Path.Combine(backupFolder, $"jellyfin-backup-{manifest.DateCreated.ToLocalTime():yyyyMMddHHmmss}.zip");
- _logger.LogInformation("Attempt to create a new backup at {BackupPath}", backupPath);
- var fileStream = File.OpenWrite(backupPath);
- await using (fileStream.ConfigureAwait(false))
- using (var zipArchive = new ZipArchive(fileStream, ZipArchiveMode.Create, false))
+
+ try
{
- _logger.LogInformation("Start backup process.");
- var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
- await using (dbContext.ConfigureAwait(false))
+ _logger.LogInformation("Attempting to create a new backup at {BackupPath}", backupPath);
+ var fileStream = File.OpenWrite(backupPath);
+ await using (fileStream.ConfigureAwait(false))
+ using (var zipArchive = new ZipArchive(fileStream, ZipArchiveMode.Create, false))
{
- dbContext.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
- static IAsyncEnumerable<object> GetValues(IQueryable dbSet)
+ _logger.LogInformation("Starting backup process");
+ var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
+ await using (dbContext.ConfigureAwait(false))
{
- var method = dbSet.GetType().GetMethod(nameof(DbSet<object>.AsAsyncEnumerable))!;
- var enumerable = method.Invoke(dbSet, null)!;
- return (IAsyncEnumerable<object>)enumerable;
- }
+ dbContext.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
- // include the migration history as well
- var historyRepository = dbContext.GetService<IHistoryRepository>();
- var migrations = await historyRepository.GetAppliedMigrationsAsync().ConfigureAwait(false);
-
- ICollection<(Type Type, string SourceName, Func<IAsyncEnumerable<object>> ValueFactory)> entityTypes = [
- .. typeof(JellyfinDbContext)
- .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())
- ];
- manifest.DatabaseTables = entityTypes.Select(e => e.Type.Name).ToArray();
- var transaction = await dbContext.Database.BeginTransactionAsync().ConfigureAwait(false);
-
- await using (transaction.ConfigureAwait(false))
- {
- _logger.LogInformation("Begin Database backup");
+ static IAsyncEnumerable<object> GetValues(IQueryable dbSet)
+ {
+ var method = dbSet.GetType().GetMethod(nameof(DbSet<object>.AsAsyncEnumerable))!;
+ var enumerable = method.Invoke(dbSet, null)!;
+ return (IAsyncEnumerable<object>)enumerable;
+ }
- foreach (var entityType in entityTypes)
+ // include the migration history as well
+ var historyRepository = dbContext.GetService<IHistoryRepository>();
+ var migrations = await historyRepository.GetAppliedMigrationsAsync().ConfigureAwait(false);
+
+ ICollection<(Type Type, string SourceName, Func<IAsyncEnumerable<object>> ValueFactory)> entityTypes =
+ [
+ .. typeof(JellyfinDbContext)
+ .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())
+ ];
+ manifest.DatabaseTables = entityTypes.Select(e => e.Type.Name).ToArray();
+ var transaction = await dbContext.Database.BeginTransactionAsync().ConfigureAwait(false);
+
+ await using (transaction.ConfigureAwait(false))
{
- _logger.LogInformation("Begin backup of entity {Table}", entityType.SourceName);
- var zipEntry = zipArchive.CreateEntry(NormalizePathSeparator(Path.Combine("Database", $"{entityType.SourceName}.json")));
- var entities = 0;
- var zipEntryStream = zipEntry.Open();
- await using (zipEntryStream.ConfigureAwait(false))
+ _logger.LogInformation("Begin Database backup");
+
+ foreach (var entityType in entityTypes)
{
- var jsonSerializer = new Utf8JsonWriter(zipEntryStream);
- await using (jsonSerializer.ConfigureAwait(false))
+ _logger.LogInformation("Begin backup of entity {Table}", entityType.SourceName);
+ var zipEntry = zipArchive.CreateEntry(NormalizePathSeparator(Path.Combine("Database", $"{entityType.SourceName}.json")));
+ var entities = 0;
+ var zipEntryStream = zipEntry.Open();
+ await using (zipEntryStream.ConfigureAwait(false))
{
- jsonSerializer.WriteStartArray();
-
- var set = entityType.ValueFactory().ConfigureAwait(false);
- await foreach (var item in set.ConfigureAwait(false))
+ var jsonSerializer = new Utf8JsonWriter(zipEntryStream);
+ await using (jsonSerializer.ConfigureAwait(false))
{
- entities++;
- try
- {
- JsonSerializer.SerializeToDocument(item, _serializerSettings).WriteTo(jsonSerializer);
- }
- catch (Exception ex)
+ jsonSerializer.WriteStartArray();
+
+ var set = entityType.ValueFactory().ConfigureAwait(false);
+ await foreach (var item in set.ConfigureAwait(false))
{
- _logger.LogError(ex, "Could not load entity {Entity}", item);
- throw;
+ entities++;
+ try
+ {
+ using var document = JsonSerializer.SerializeToDocument(item, _serializerSettings);
+ document.WriteTo(jsonSerializer);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Could not load entity {Entity}", item);
+ throw;
+ }
}
- }
- jsonSerializer.WriteEndArray();
+ jsonSerializer.WriteEndArray();
+ }
}
- }
- _logger.LogInformation("backup of entity {Table} with {Number} created", entityType.Type.Name, entities);
+ _logger.LogInformation("Backup of entity {Table} with {Number} created", entityType.SourceName, entities);
+ }
}
}
- }
- _logger.LogInformation("Backup of folder {Table}", _applicationPaths.ConfigurationDirectoryPath);
- foreach (var item in Directory.EnumerateFiles(_applicationPaths.ConfigurationDirectoryPath, "*.xml", SearchOption.TopDirectoryOnly)
- .Union(Directory.EnumerateFiles(_applicationPaths.ConfigurationDirectoryPath, "*.json", SearchOption.TopDirectoryOnly)))
- {
- zipArchive.CreateEntryFromFile(item, NormalizePathSeparator(Path.Combine("Config", Path.GetFileName(item))));
- }
+ _logger.LogInformation("Backup of folder {Table}", _applicationPaths.ConfigurationDirectoryPath);
+ foreach (var item in Directory.EnumerateFiles(_applicationPaths.ConfigurationDirectoryPath, "*.xml", SearchOption.TopDirectoryOnly)
+ .Union(Directory.EnumerateFiles(_applicationPaths.ConfigurationDirectoryPath, "*.json", SearchOption.TopDirectoryOnly)))
+ {
+ zipArchive.CreateEntryFromFile(item, NormalizePathSeparator(Path.Combine("Config", Path.GetFileName(item))));
+ }
- void CopyDirectory(string source, string target, string filter = "*")
- {
- if (!Directory.Exists(source))
+ void CopyDirectory(string source, string target, string filter = "*")
{
- return;
+ if (!Directory.Exists(source))
+ {
+ return;
+ }
+
+ _logger.LogInformation("Backup of folder {Table}", source);
+
+ foreach (var item in Directory.EnumerateFiles(source, filter, SearchOption.AllDirectories))
+ {
+ zipArchive.CreateEntryFromFile(item, NormalizePathSeparator(Path.Combine(target, Path.GetRelativePath(source, item))));
+ }
}
- _logger.LogInformation("Backup of folder {Table}", source);
+ CopyDirectory(Path.Combine(_applicationPaths.ConfigurationDirectoryPath, "users"), Path.Combine("Config", "users"));
+ CopyDirectory(Path.Combine(_applicationPaths.ConfigurationDirectoryPath, "ScheduledTasks"), Path.Combine("Config", "ScheduledTasks"));
+ CopyDirectory(Path.Combine(_applicationPaths.RootFolderPath), "Root");
+ CopyDirectory(Path.Combine(_applicationPaths.DataPath, "collections"), Path.Combine("Data", "collections"));
+ CopyDirectory(Path.Combine(_applicationPaths.DataPath, "playlists"), Path.Combine("Data", "playlists"));
+ CopyDirectory(Path.Combine(_applicationPaths.DataPath, "ScheduledTasks"), Path.Combine("Data", "ScheduledTasks"));
+ if (backupOptions.Subtitles)
+ {
+ CopyDirectory(Path.Combine(_applicationPaths.DataPath, "subtitles"), Path.Combine("Data", "subtitles"));
+ }
- foreach (var item in Directory.EnumerateFiles(source, filter, SearchOption.AllDirectories))
+ if (backupOptions.Trickplay)
{
- zipArchive.CreateEntryFromFile(item, NormalizePathSeparator(Path.Combine(target, Path.GetRelativePath(source, item))));
+ CopyDirectory(Path.Combine(_applicationPaths.DataPath, "trickplay"), Path.Combine("Data", "trickplay"));
}
- }
- CopyDirectory(Path.Combine(_applicationPaths.ConfigurationDirectoryPath, "users"), Path.Combine("Config", "users"));
- CopyDirectory(Path.Combine(_applicationPaths.ConfigurationDirectoryPath, "ScheduledTasks"), Path.Combine("Config", "ScheduledTasks"));
- CopyDirectory(Path.Combine(_applicationPaths.RootFolderPath), "Root");
- CopyDirectory(Path.Combine(_applicationPaths.DataPath, "collections"), Path.Combine("Data", "collections"));
- CopyDirectory(Path.Combine(_applicationPaths.DataPath, "playlists"), Path.Combine("Data", "playlists"));
- CopyDirectory(Path.Combine(_applicationPaths.DataPath, "ScheduledTasks"), Path.Combine("Data", "ScheduledTasks"));
- if (backupOptions.Subtitles)
- {
- CopyDirectory(Path.Combine(_applicationPaths.DataPath, "subtitles"), Path.Combine("Data", "subtitles"));
- }
+ if (backupOptions.Metadata)
+ {
+ CopyDirectory(Path.Combine(_applicationPaths.InternalMetadataPath), Path.Combine("Data", "metadata"));
+ }
- if (backupOptions.Trickplay)
- {
- CopyDirectory(Path.Combine(_applicationPaths.DataPath, "trickplay"), Path.Combine("Data", "trickplay"));
+ var manifestStream = zipArchive.CreateEntry(ManifestEntryName).Open();
+ await using (manifestStream.ConfigureAwait(false))
+ {
+ await JsonSerializer.SerializeAsync(manifestStream, manifest).ConfigureAwait(false);
+ }
}
- if (backupOptions.Metadata)
+ _logger.LogInformation("Backup created");
+ return Map(manifest, backupPath);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Failed to create backup, removing {BackupPath}", backupPath);
+ try
{
- CopyDirectory(Path.Combine(_applicationPaths.InternalMetadataPath), Path.Combine("Data", "metadata"));
+ if (File.Exists(backupPath))
+ {
+ File.Delete(backupPath);
+ }
}
-
- var manifestStream = zipArchive.CreateEntry(ManifestEntryName).Open();
- await using (manifestStream.ConfigureAwait(false))
+ catch (Exception innerEx)
{
- await JsonSerializer.SerializeAsync(manifestStream, manifest).ConfigureAwait(false);
+ _logger.LogWarning(innerEx, "Unable to remove failed backup");
}
- }
- _logger.LogInformation("Backup created");
- return Map(manifest, backupPath);
+ throw;
+ }
}
/// <inheritdoc/>
@@ -422,7 +449,7 @@ public class BackupService : IBackupService
}
catch (Exception ex)
{
- _logger.LogError(ex, "Tried to load archive from {Path} but failed.", archivePath);
+ _logger.LogWarning(ex, "Tried to load manifest from archive {Path} but failed", archivePath);
return null;
}
@@ -459,7 +486,7 @@ public class BackupService : IBackupService
}
catch (Exception ex)
{
- _logger.LogError(ex, "Could not load {BackupArchive} path.", item);
+ _logger.LogWarning(ex, "Tried to load manifest from archive {Path} but failed", item);
}
}
diff --git a/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs b/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs
index ef444b9302..b939c4ab21 100644
--- a/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs
+++ b/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs
@@ -614,6 +614,13 @@ public sealed class BaseItemRepository
else
{
context.BaseItemProviders.Where(e => e.ItemId == entity.Id).ExecuteDelete();
+ context.BaseItemImageInfos.Where(e => e.ItemId == entity.Id).ExecuteDelete();
+
+ if (entity.Images is { Count: > 0 })
+ {
+ context.BaseItemImageInfos.AddRange(entity.Images);
+ }
+
context.BaseItems.Attach(entity).State = EntityState.Modified;
}
}
@@ -1232,8 +1239,20 @@ public sealed class BaseItemRepository
ExcludeItemIds = filter.ExcludeItemIds
};
- var query = TranslateQuery(innerQuery, context, outerQueryFilter)
- .GroupBy(e => e.PresentationUniqueKey);
+ var masterQuery = TranslateQuery(innerQuery, context, outerQueryFilter)
+ .GroupBy(e => e.PresentationUniqueKey)
+ .Select(e => e.FirstOrDefault())
+ .Select(e => e!.Id);
+
+ var query = context.BaseItems
+ .Include(e => e.TrailerTypes)
+ .Include(e => e.Provider)
+ .Include(e => e.LockedFields)
+ .Include(e => e.Images)
+ .AsSingleQuery()
+ .Where(e => masterQuery.Contains(e.Id));
+
+ query = ApplyOrder(query, filter);
var result = new QueryResult<(BaseItemDto, ItemCounts?)>();
if (filter.EnableTotalRecordCount)
@@ -1288,12 +1307,7 @@ public sealed class BaseItemRepository
var resultQuery = query.Select(e => new
{
- item = e.AsQueryable()
- .Include(e => e.TrailerTypes)
- .Include(e => e.Provider)
- .Include(e => e.LockedFields)
- .Include(e => e.Images)
- .AsSingleQuery().First(),
+ item = e,
// TODO: This is bad refactor!
itemCount = new ItemCounts()
{
@@ -1325,13 +1339,6 @@ public sealed class BaseItemRepository
result.Items =
[
.. query
- .Select(e => e.AsQueryable()
- .Include(e => e.TrailerTypes)
- .Include(e => e.Provider)
- .Include(e => e.LockedFields)
- .Include(e => e.Images)
- .AsSingleQuery()
- .First())
.AsEnumerable()
.Where(e => e is not null)
.Select<BaseItemEntity, (BaseItemDto, ItemCounts?)>(e =>
@@ -1756,7 +1763,8 @@ public sealed class BaseItemRepository
if (!string.IsNullOrWhiteSpace(filter.Path))
{
- baseQuery = baseQuery.Where(e => e.Path == filter.Path);
+ var pathToQuery = GetPathToSave(filter.Path);
+ baseQuery = baseQuery.Where(e => e.Path == pathToQuery);
}
if (!string.IsNullOrWhiteSpace(filter.PresentationUniqueKey))
@@ -2014,7 +2022,7 @@ public sealed class BaseItemRepository
if (filter.ArtistIds.Length > 0)
{
- baseQuery = baseQuery.WhereReferencedItem(context, ItemValueType.Artist, filter.ArtistIds);
+ baseQuery = baseQuery.WhereReferencedItemMultipleTypes(context, [ItemValueType.Artist, ItemValueType.AlbumArtist], filter.ArtistIds);
}
if (filter.AlbumArtistIds.Length > 0)
@@ -2024,7 +2032,18 @@ public sealed class BaseItemRepository
if (filter.ContributingArtistIds.Length > 0)
{
- baseQuery = baseQuery.WhereReferencedItem(context, ItemValueType.Artist, filter.ContributingArtistIds);
+ var contributingNames = context.BaseItems
+ .Where(b => filter.ContributingArtistIds.Contains(b.Id))
+ .Select(b => b.CleanName);
+
+ baseQuery = baseQuery.Where(e =>
+ e.ItemValues!.Any(ivm =>
+ ivm.ItemValue.Type == ItemValueType.Artist &&
+ contributingNames.Contains(ivm.ItemValue.CleanValue))
+ &&
+ !e.ItemValues!.Any(ivm =>
+ ivm.ItemValue.Type == ItemValueType.AlbumArtist &&
+ contributingNames.Contains(ivm.ItemValue.CleanValue)));
}
if (filter.AlbumIds.Length > 0)
@@ -2035,7 +2054,7 @@ public sealed class BaseItemRepository
if (filter.ExcludeArtistIds.Length > 0)
{
- baseQuery = baseQuery.WhereReferencedItem(context, ItemValueType.Artist, filter.ExcludeArtistIds, true);
+ baseQuery = baseQuery.WhereReferencedItemMultipleTypes(context, [ItemValueType.Artist, ItemValueType.AlbumArtist], filter.ExcludeArtistIds, true);
}
if (filter.GenreIds.Count > 0)
@@ -2342,17 +2361,23 @@ public sealed class BaseItemRepository
if (filter.HasImdbId.HasValue)
{
- baseQuery = baseQuery.Where(e => e.Provider!.Any(f => f.ProviderId == "imdb"));
+ baseQuery = filter.HasImdbId.Value
+ ? baseQuery.Where(e => e.Provider!.Any(f => f.ProviderId.ToLower() == MetadataProvider.Imdb.ToString().ToLower()))
+ : baseQuery.Where(e => e.Provider!.All(f => f.ProviderId.ToLower() != MetadataProvider.Imdb.ToString().ToLower()));
}
if (filter.HasTmdbId.HasValue)
{
- baseQuery = baseQuery.Where(e => e.Provider!.Any(f => f.ProviderId == "tmdb"));
+ baseQuery = filter.HasTmdbId.Value
+ ? baseQuery.Where(e => e.Provider!.Any(f => f.ProviderId.ToLower() == MetadataProvider.Tmdb.ToString().ToLower()))
+ : baseQuery.Where(e => e.Provider!.All(f => f.ProviderId.ToLower() != MetadataProvider.Tmdb.ToString().ToLower()));
}
if (filter.HasTvdbId.HasValue)
{
- baseQuery = baseQuery.Where(e => e.Provider!.Any(f => f.ProviderId == "tvdb"));
+ baseQuery = filter.HasTvdbId.Value
+ ? baseQuery.Where(e => e.Provider!.Any(f => f.ProviderId.ToLower() == MetadataProvider.Tvdb.ToString().ToLower()))
+ : baseQuery.Where(e => e.Provider!.All(f => f.ProviderId.ToLower() != MetadataProvider.Tvdb.ToString().ToLower()));
}
var queryTopParentIds = filter.TopParentIds;
diff --git a/Jellyfin.Server.Implementations/Item/ChapterRepository.cs b/Jellyfin.Server.Implementations/Item/ChapterRepository.cs
index e0d23a2613..98700f3224 100644
--- a/Jellyfin.Server.Implementations/Item/ChapterRepository.cs
+++ b/Jellyfin.Server.Implementations/Item/ChapterRepository.cs
@@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
using Jellyfin.Database.Implementations;
using Jellyfin.Database.Implementations.Entities;
using MediaBrowser.Controller.Drawing;
@@ -82,11 +84,14 @@ public class ChapterRepository : IChapterRepository
}
/// <inheritdoc />
- public void DeleteChapters(Guid itemId)
+ public async Task DeleteChaptersAsync(Guid itemId, CancellationToken cancellationToken)
{
- using var context = _dbProvider.CreateDbContext();
- context.Chapters.Where(c => c.ItemId.Equals(itemId)).ExecuteDelete();
- context.SaveChanges();
+ var dbContext = await _dbProvider.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);
+ await using (dbContext.ConfigureAwait(false))
+ {
+ await dbContext.Chapters.Where(c => c.ItemId.Equals(itemId)).ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false);
+ await dbContext.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
+ }
}
private Chapter Map(ChapterInfo chapterInfo, int index, Guid itemId)
diff --git a/Jellyfin.Server.Implementations/Item/PeopleRepository.cs b/Jellyfin.Server.Implementations/Item/PeopleRepository.cs
index e03c136915..355ed64797 100644
--- a/Jellyfin.Server.Implementations/Item/PeopleRepository.cs
+++ b/Jellyfin.Server.Implementations/Item/PeopleRepository.cs
@@ -95,6 +95,7 @@ public class PeopleRepository(IDbContextFactory<JellyfinDbContext> dbProvider, I
.ToArray();
var toAdd = people
+ .Where(e => e.Type is not PersonKind.Artist && e.Type is not PersonKind.AlbumArtist)
.Where(e => !existingPersons.Any(f => f.Name == e.Name && f.PersonType == e.Type.ToString()))
.Select(Map);
context.Peoples.AddRange(toAdd);
@@ -108,6 +109,11 @@ public class PeopleRepository(IDbContextFactory<JellyfinDbContext> dbProvider, I
foreach (var person in people)
{
+ if (person.Type == PersonKind.Artist || person.Type == PersonKind.AlbumArtist)
+ {
+ continue;
+ }
+
var entityPerson = personsEntities.First(e => e.Name == person.Name && e.PersonType == person.Type.ToString());
var existingMap = existingMaps.FirstOrDefault(e => e.People.Name == person.Name && e.People.PersonType == person.Type.ToString() && e.Role == person.Role);
if (existingMap is null)
diff --git a/Jellyfin.Server.Implementations/StorageHelpers/StorageHelper.cs b/Jellyfin.Server.Implementations/StorageHelpers/StorageHelper.cs
index b2f54be7e2..570d6cb9b7 100644
--- a/Jellyfin.Server.Implementations/StorageHelpers/StorageHelper.cs
+++ b/Jellyfin.Server.Implementations/StorageHelpers/StorageHelper.cs
@@ -14,7 +14,7 @@ public static class StorageHelper
{
private const long TwoGigabyte = 2_147_483_647L;
private const long FiveHundredAndTwelveMegaByte = 536_870_911L;
- private static readonly string[] _byteHumanizedSuffixes = ["B", "KB", "MB", "GB", "TB", "PB", "EB"];
+ private static readonly string[] _byteHumanizedSuffixes = ["B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"];
/// <summary>
/// Tests the available storage capacity on the jellyfin paths with estimated minimum values.
@@ -27,7 +27,7 @@ public static class StorageHelper
TestDataDirectorySize(applicationPaths.LogDirectoryPath, logger, FiveHundredAndTwelveMegaByte);
TestDataDirectorySize(applicationPaths.CachePath, logger, TwoGigabyte);
TestDataDirectorySize(applicationPaths.ProgramDataPath, logger, TwoGigabyte);
- TestDataDirectorySize(applicationPaths.TempDirectory, logger, TwoGigabyte);
+ TestDataDirectorySize(applicationPaths.TempDirectory, logger, FiveHundredAndTwelveMegaByte);
}
/// <summary>
@@ -77,7 +77,7 @@ public static class StorageHelper
var drive = new DriveInfo(path);
if (threshold != -1 && drive.AvailableFreeSpace < threshold)
{
- throw new InvalidOperationException($"The path `{path}` has insufficient free space. Required: at least {HumanizeStorageSize(threshold)}.");
+ throw new InvalidOperationException($"The path `{path}` has insufficient free space. Available: {HumanizeStorageSize(drive.AvailableFreeSpace)}, Required: {HumanizeStorageSize(threshold)}.");
}
logger.LogInformation(