JPVenson 4 месяцев назад
Родитель
Сommit
b33810534b

+ 10 - 4
Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs

@@ -67,10 +67,16 @@ namespace Emby.Server.Implementations.Data
                 progress.Report(percent * 100);
                 progress.Report(percent * 100);
             }
             }
 
 
-            using var context = await _dbProvider.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);
-            using var transaction = await context.Database.BeginTransactionAsync(cancellationToken).ConfigureAwait(false);
-            await context.ItemValues.Where(e => e.BaseItemsMap!.Count == 0).ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false);
-            await transaction.CommitAsync(cancellationToken).ConfigureAwait(false);
+            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))
+                {
+                    await context.ItemValues.Where(e => e.BaseItemsMap!.Count == 0).ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false);
+                    await transaction.CommitAsync(cancellationToken).ConfigureAwait(false);
+                }
+            }
 
 
             progress.Report(100);
             progress.Report(100);
         }
         }

+ 9 - 3
Emby.Server.Implementations/Library/UserDataManager.cs

@@ -146,8 +146,8 @@ namespace Emby.Server.Implementations.Library
             {
             {
                 ItemId = itemId,
                 ItemId = itemId,
                 CustomDataKey = dto.Key,
                 CustomDataKey = dto.Key,
-                Item = null!,
-                User = null!,
+                Item = null,
+                User = null,
                 AudioStreamIndex = dto.AudioStreamIndex,
                 AudioStreamIndex = dto.AudioStreamIndex,
                 IsFavorite = dto.IsFavorite,
                 IsFavorite = dto.IsFavorite,
                 LastPlayedDate = dto.LastPlayedDate,
                 LastPlayedDate = dto.LastPlayedDate,
@@ -181,7 +181,13 @@ namespace Emby.Server.Implementations.Library
         private UserItemData? GetUserData(User user, Guid itemId, List<string> keys)
         private UserItemData? GetUserData(User user, Guid itemId, List<string> keys)
         {
         {
             var cacheKey = GetCacheKey(user.InternalId, itemId);
             var cacheKey = GetCacheKey(user.InternalId, itemId);
-            var data = GetUserDataInternal(user.Id, itemId, keys);
+
+            if (_userData.TryGetValue(cacheKey, out var data))
+            {
+                return data;
+            }
+
+            data = GetUserDataInternal(user.Id, itemId, keys);
 
 
             if (data is null)
             if (data is null)
             {
             {

+ 10 - 9
Jellyfin.Server.Implementations/Item/BaseItemRepository.cs

@@ -125,7 +125,7 @@ public sealed class BaseItemRepository
         transaction.Commit();
         transaction.Commit();
     }
     }
 
 
-    /// <inheritdoc cref="IItemRepository"/>
+    /// <inheritdoc />
     public IReadOnlyList<Guid> GetItemIdsList(InternalItemsQuery filter)
     public IReadOnlyList<Guid> GetItemIdsList(InternalItemsQuery filter)
     {
     {
         ArgumentNullException.ThrowIfNull(filter);
         ArgumentNullException.ThrowIfNull(filter);
@@ -201,7 +201,7 @@ public sealed class BaseItemRepository
             _itemTypeLookup.MusicGenreTypes);
             _itemTypeLookup.MusicGenreTypes);
     }
     }
 
 
-    /// <inheritdoc cref="IItemRepository"/>
+    /// <inheritdoc />
     public QueryResult<BaseItemDto> GetItems(InternalItemsQuery filter)
     public QueryResult<BaseItemDto> GetItems(InternalItemsQuery filter)
     {
     {
         ArgumentNullException.ThrowIfNull(filter);
         ArgumentNullException.ThrowIfNull(filter);
@@ -235,7 +235,7 @@ public sealed class BaseItemRepository
         return result;
         return result;
     }
     }
 
 
-    /// <inheritdoc cref="IItemRepository"/>
+    /// <inheritdoc />
     public IReadOnlyList<BaseItemDto> GetItemList(InternalItemsQuery filter)
     public IReadOnlyList<BaseItemDto> GetItemList(InternalItemsQuery filter)
     {
     {
         ArgumentNullException.ThrowIfNull(filter);
         ArgumentNullException.ThrowIfNull(filter);
@@ -354,12 +354,14 @@ public sealed class BaseItemRepository
     {
     {
         ArgumentException.ThrowIfNullOrEmpty(typeName);
         ArgumentException.ThrowIfNullOrEmpty(typeName);
 
 
+        // TODO: this isn't great. Refactor later to be both globally handled by a dedicated service not just an static variable and be loaded eagar.
+        // currently this is done so that plugins may introduce their own type of baseitems as we dont know when we are first called, before or after plugins are loaded
         return _typeMap.GetOrAdd(typeName, k => AppDomain.CurrentDomain.GetAssemblies()
         return _typeMap.GetOrAdd(typeName, k => AppDomain.CurrentDomain.GetAssemblies()
             .Select(a => a.GetType(k))
             .Select(a => a.GetType(k))
             .FirstOrDefault(t => t is not null));
             .FirstOrDefault(t => t is not null));
     }
     }
 
 
-    /// <inheritdoc cref="IItemRepository" />
+    /// <inheritdoc  />
     public void SaveImages(BaseItemDto item)
     public void SaveImages(BaseItemDto item)
     {
     {
         ArgumentNullException.ThrowIfNull(item);
         ArgumentNullException.ThrowIfNull(item);
@@ -373,13 +375,13 @@ public sealed class BaseItemRepository
         transaction.Commit();
         transaction.Commit();
     }
     }
 
 
-    /// <inheritdoc cref="IItemRepository" />
+    /// <inheritdoc  />
     public void SaveItems(IReadOnlyList<BaseItemDto> items, CancellationToken cancellationToken)
     public void SaveItems(IReadOnlyList<BaseItemDto> items, CancellationToken cancellationToken)
     {
     {
         UpdateOrInsertItems(items, cancellationToken);
         UpdateOrInsertItems(items, cancellationToken);
     }
     }
 
 
-    /// <inheritdoc cref="IItemRepository" />
+    /// <inheritdoc />
     public void UpdateOrInsertItems(IReadOnlyList<BaseItemDto> items, CancellationToken cancellationToken)
     public void UpdateOrInsertItems(IReadOnlyList<BaseItemDto> items, CancellationToken cancellationToken)
     {
     {
         ArgumentNullException.ThrowIfNull(items);
         ArgumentNullException.ThrowIfNull(items);
@@ -479,7 +481,7 @@ public sealed class BaseItemRepository
         transaction.Commit();
         transaction.Commit();
     }
     }
 
 
-    /// <inheritdoc cref="IItemRepository" />
+    /// <inheritdoc  />
     public BaseItemDto? RetrieveItem(Guid id)
     public BaseItemDto? RetrieveItem(Guid id)
     {
     {
         if (id.IsEmpty())
         if (id.IsEmpty())
@@ -890,8 +892,7 @@ public sealed class BaseItemRepository
         {
         {
             try
             try
             {
             {
-                using var dataAsStream = new MemoryStream(Encoding.UTF8.GetBytes(baseItemEntity.Data!));
-                dto = JsonSerializer.Deserialize(dataAsStream, type, JsonDefaults.Options) as BaseItemDto;
+                dto = JsonSerializer.Deserialize(baseItemEntity.Data, type, JsonDefaults.Options) as BaseItemDto;
             }
             }
             catch (JsonException ex)
             catch (JsonException ex)
             {
             {

+ 1 - 1
Jellyfin.Server.Implementations/Item/ChapterRepository.cs

@@ -71,7 +71,7 @@ public class ChapterRepository : IChapterRepository
                 chapter = e,
                 chapter = e,
                 baseItemPath = e.Item.Path
                 baseItemPath = e.Item.Path
             })
             })
-            .ToList()
+            .AsEnumerable()
             .Select(e => Map(e.chapter, e.baseItemPath!))
             .Select(e => Map(e.chapter, e.baseItemPath!))
             .ToArray();
             .ToArray();
     }
     }

+ 8 - 7
Jellyfin.Server/Migrations/Routines/MigrateLibraryDb.cs

@@ -1,3 +1,5 @@
+#pragma warning disable RS0030 // Do not use banned APIs
+
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Collections.Immutable;
 using System.Collections.Immutable;
@@ -21,7 +23,6 @@ using Microsoft.Extensions.Logging;
 using Chapter = Jellyfin.Data.Entities.Chapter;
 using Chapter = Jellyfin.Data.Entities.Chapter;
 
 
 namespace Jellyfin.Server.Migrations.Routines;
 namespace Jellyfin.Server.Migrations.Routines;
-#pragma warning disable RS0030 // Do not use banned APIs
 
 
 /// <summary>
 /// <summary>
 /// The migration routine for migrating the userdata database to EF Core.
 /// The migration routine for migrating the userdata database to EF Core.
@@ -80,7 +81,7 @@ public class MigrateLibraryDb : IMigrationRoutine
         stopwatch.Restart();
         stopwatch.Restart();
 
 
         _logger.LogInformation("Start moving TypedBaseItem.");
         _logger.LogInformation("Start moving TypedBaseItem.");
-        var typedBaseItemsQuery = """
+        const string typedBaseItemsQuery = """
          SELECT guid, type, data, StartDate, EndDate, ChannelId, IsMovie,
          SELECT guid, type, data, StartDate, EndDate, ChannelId, IsMovie,
          IsSeries, EpisodeTitle, IsRepeat, CommunityRating, CustomRating, IndexNumber, IsLocked, PreferredMetadataLanguage,
          IsSeries, EpisodeTitle, IsRepeat, CommunityRating, CustomRating, IndexNumber, IsLocked, PreferredMetadataLanguage,
          PreferredMetadataCountryCode, Width, Height, DateLastRefreshed, Name, Path, PremiereDate, Overview, ParentIndexNumber,
          PreferredMetadataCountryCode, Width, Height, DateLastRefreshed, Name, Path, PremiereDate, Overview, ParentIndexNumber,
@@ -111,7 +112,7 @@ public class MigrateLibraryDb : IMigrationRoutine
 
 
         _logger.LogInformation("Start moving ItemValues.");
         _logger.LogInformation("Start moving ItemValues.");
         // do not migrate inherited types as they are now properly mapped in search and lookup.
         // do not migrate inherited types as they are now properly mapped in search and lookup.
-        var itemValueQuery =
+        const string itemValueQuery =
         """
         """
         SELECT ItemId, Type, Value, CleanValue FROM ItemValues
         SELECT ItemId, Type, Value, CleanValue FROM ItemValues
                     WHERE Type <> 6 AND EXISTS(SELECT 1 FROM TypedBaseItems WHERE TypedBaseItems.guid = ItemValues.ItemId)
                     WHERE Type <> 6 AND EXISTS(SELECT 1 FROM TypedBaseItems WHERE TypedBaseItems.guid = ItemValues.ItemId)
@@ -187,7 +188,7 @@ public class MigrateLibraryDb : IMigrationRoutine
         dbContext.SaveChanges();
         dbContext.SaveChanges();
 
 
         _logger.LogInformation("Start moving MediaStreamInfos.");
         _logger.LogInformation("Start moving MediaStreamInfos.");
-        var mediaStreamQuery = """
+        const string mediaStreamQuery = """
         SELECT ItemId, StreamIndex, StreamType, Codec, Language, ChannelLayout, Profile, AspectRatio, Path,
         SELECT ItemId, StreamIndex, StreamType, Codec, Language, ChannelLayout, Profile, AspectRatio, Path,
         IsInterlaced, BitRate, Channels, SampleRate, IsDefault, IsForced, IsExternal, Height, Width,
         IsInterlaced, BitRate, Channels, SampleRate, IsDefault, IsForced, IsExternal, Height, Width,
         AverageFrameRate, RealFrameRate, Level, PixelFormat, BitDepth, IsAnamorphic, RefFrames, CodecTag,
         AverageFrameRate, RealFrameRate, Level, PixelFormat, BitDepth, IsAnamorphic, RefFrames, CodecTag,
@@ -211,7 +212,7 @@ public class MigrateLibraryDb : IMigrationRoutine
         stopwatch.Restart();
         stopwatch.Restart();
 
 
         _logger.LogInformation("Start moving People.");
         _logger.LogInformation("Start moving People.");
-        var personsQuery = """
+        const string personsQuery = """
         SELECT ItemId, Name, Role, PersonType, SortOrder FROM People
         SELECT ItemId, Name, Role, PersonType, SortOrder FROM People
         WHERE EXISTS(SELECT 1 FROM TypedBaseItems WHERE TypedBaseItems.guid = People.ItemId)
         WHERE EXISTS(SELECT 1 FROM TypedBaseItems WHERE TypedBaseItems.guid = People.ItemId)
         """;
         """;
@@ -268,7 +269,7 @@ public class MigrateLibraryDb : IMigrationRoutine
         stopwatch.Restart();
         stopwatch.Restart();
 
 
         _logger.LogInformation("Start moving Chapters.");
         _logger.LogInformation("Start moving Chapters.");
-        var chapterQuery = """
+        const string chapterQuery = """
         SELECT ItemId,StartPositionTicks,Name,ImagePath,ImageDateModified,ChapterIndex from Chapters2
         SELECT ItemId,StartPositionTicks,Name,ImagePath,ImageDateModified,ChapterIndex from Chapters2
         WHERE EXISTS(SELECT 1 FROM TypedBaseItems WHERE TypedBaseItems.guid = Chapters2.ItemId)
         WHERE EXISTS(SELECT 1 FROM TypedBaseItems WHERE TypedBaseItems.guid = Chapters2.ItemId)
         """;
         """;
@@ -287,7 +288,7 @@ public class MigrateLibraryDb : IMigrationRoutine
         stopwatch.Restart();
         stopwatch.Restart();
 
 
         _logger.LogInformation("Start moving AncestorIds.");
         _logger.LogInformation("Start moving AncestorIds.");
-        var ancestorIdsQuery = """
+        const string ancestorIdsQuery = """
         SELECT ItemId, AncestorId, AncestorIdText FROM AncestorIds
         SELECT ItemId, AncestorId, AncestorIdText FROM AncestorIds
         WHERE
         WHERE
         EXISTS(SELECT 1 FROM TypedBaseItems WHERE TypedBaseItems.guid = AncestorIds.ItemId)
         EXISTS(SELECT 1 FROM TypedBaseItems WHERE TypedBaseItems.guid = AncestorIds.ItemId)

+ 5 - 5
src/Jellyfin.Drawing/ImageProcessor.cs

@@ -4,6 +4,7 @@ using System.Globalization;
 using System.IO;
 using System.IO;
 using System.Linq;
 using System.Linq;
 using System.Net.Mime;
 using System.Net.Mime;
+using System.Reflection.Metadata.Ecma335;
 using System.Text;
 using System.Text;
 using System.Threading;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
@@ -410,11 +411,11 @@ public sealed class ImageProcessor : IImageProcessor, IDisposable
 
 
     /// <inheritdoc />
     /// <inheritdoc />
     public string GetImageCacheTag(BaseItem item, ItemImageInfo image)
     public string GetImageCacheTag(BaseItem item, ItemImageInfo image)
-        => (item.Path + image.DateModified.Ticks).GetMD5().ToString("N", CultureInfo.InvariantCulture);
+        => GetImageCacheTag(item.Path, image.DateModified);
 
 
     /// <inheritdoc />
     /// <inheritdoc />
     public string GetImageCacheTag(BaseItemDto item, ItemImageInfo image)
     public string GetImageCacheTag(BaseItemDto item, ItemImageInfo image)
-        => (item.Path + image.DateModified.Ticks).GetMD5().ToString("N", CultureInfo.InvariantCulture);
+        => GetImageCacheTag(item.Path, image.DateModified);
 
 
     /// <inheritdoc />
     /// <inheritdoc />
     public string? GetImageCacheTag(BaseItemDto item, ChapterInfo chapter)
     public string? GetImageCacheTag(BaseItemDto item, ChapterInfo chapter)
@@ -424,7 +425,7 @@ public sealed class ImageProcessor : IImageProcessor, IDisposable
             return null;
             return null;
         }
         }
 
 
-        return (item.Path + chapter.ImageDateModified.Ticks).GetMD5().ToString("N", CultureInfo.InvariantCulture);
+        return GetImageCacheTag(item.Path, chapter.ImageDateModified);
     }
     }
 
 
     /// <inheritdoc />
     /// <inheritdoc />
@@ -451,8 +452,7 @@ public sealed class ImageProcessor : IImageProcessor, IDisposable
             return null;
             return null;
         }
         }
 
 
-        return (user.ProfileImage.Path + user.ProfileImage.LastModified.Ticks).GetMD5()
-            .ToString("N", CultureInfo.InvariantCulture);
+        return GetImageCacheTag(user.ProfileImage.Path, user.ProfileImage.LastModified);
     }
     }
 
 
     private Task<(string Path, DateTime DateModified)> GetSupportedImage(string originalImagePath, DateTime dateModified)
     private Task<(string Path, DateTime DateModified)> GetSupportedImage(string originalImagePath, DateTime dateModified)

+ 1 - 1
tests/Jellyfin.Server.Integration.Tests/Controllers/LibraryStructureControllerTests.cs

@@ -45,7 +45,7 @@ public sealed class LibraryStructureControllerTests : IClassFixture<JellyfinAppl
     }
     }
 
 
     [Fact]
     [Fact]
-    [Priority(0)]
+    [Priority(-2)]
     public async Task UpdateLibraryOptions_Invalid_NotFound()
     public async Task UpdateLibraryOptions_Invalid_NotFound()
     {
     {
         var client = _factory.CreateClient();
         var client = _factory.CreateClient();