diff --git a/Emby.Server.Implementations/Playlists/PlaylistsFolder.cs b/Emby.Server.Implementations/Playlists/PlaylistsFolder.cs
index f65d609c71..db3aeaaf31 100644
--- a/Emby.Server.Implementations/Playlists/PlaylistsFolder.cs
+++ b/Emby.Server.Implementations/Playlists/PlaylistsFolder.cs
@@ -5,12 +5,14 @@ using System.Linq;
using System.Text.Json.Serialization;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
+using MediaBrowser.Common;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Playlists;
using MediaBrowser.Model.Querying;
namespace Emby.Server.Implementations.Playlists
{
+ [RequiresSourceSerialisation]
public class PlaylistsFolder : BasePluginFolder
{
public PlaylistsFolder()
diff --git a/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs b/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs
index 7e7873f6d4..f92f526bc7 100644
--- a/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs
+++ b/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs
@@ -3,14 +3,21 @@ using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Globalization;
+using System.IO;
using System.Linq;
using System.Linq.Expressions;
+using System.Reflection;
using System.Text;
+using System.Text.Json;
using System.Threading;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
+using Jellyfin.Extensions.Json;
+using MediaBrowser.Common;
using MediaBrowser.Controller;
+using MediaBrowser.Controller.Channels;
+using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.TV;
@@ -21,7 +28,7 @@ using MediaBrowser.Model.Entities;
using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.Querying;
using Microsoft.EntityFrameworkCore;
-using SQLitePCL;
+using Microsoft.Extensions.Logging;
using BaseItemDto = MediaBrowser.Controller.Entities.BaseItem;
using BaseItemEntity = Jellyfin.Data.Entities.BaseItemEntity;
#pragma warning disable RS0030 // Do not use banned APIs
@@ -37,7 +44,14 @@ namespace Jellyfin.Server.Implementations.Item;
/// The db factory.
/// The Application host.
/// The static type lookup.
-public sealed class BaseItemRepository(IDbContextFactory dbProvider, IServerApplicationHost appHost, IItemTypeLookup itemTypeLookup)
+/// The server Configuration manager.
+/// System logger.
+public sealed class BaseItemRepository(
+ IDbContextFactory dbProvider,
+ IServerApplicationHost appHost,
+ IItemTypeLookup itemTypeLookup,
+ IServerConfigurationManager serverConfigurationManager,
+ ILogger logger)
: IItemRepository, IDisposable
{
///
@@ -244,7 +258,7 @@ public sealed class BaseItemRepository(IDbContextFactory dbPr
}
}
- result.Items = dbQuery.ToList().Select(DeserialiseBaseItem).ToImmutableArray();
+ result.Items = dbQuery.ToList().Select(w => DeserialiseBaseItem(w, filter.SkipDeserialization)).ToImmutableArray();
result.StartIndex = filter.StartIndex ?? 0;
return result;
}
@@ -272,7 +286,7 @@ public sealed class BaseItemRepository(IDbContextFactory dbPr
}
}
- return dbQuery.ToList().Select(DeserialiseBaseItem).ToImmutableArray();
+ return dbQuery.ToList().Select(w => DeserialiseBaseItem(w, filter.SkipDeserialization)).ToImmutableArray();
}
///
@@ -1675,10 +1689,42 @@ public sealed class BaseItemRepository(IDbContextFactory dbPr
return query.Select(e => e.ItemValue.CleanValue).ToImmutableArray();
}
- private BaseItemDto DeserialiseBaseItem(BaseItemEntity baseItemEntity)
+ private bool TypeRequiresDeserialization(Type type)
+ {
+ if (serverConfigurationManager.Configuration.SkipDeserializationForBasicTypes)
+ {
+ if (type == typeof(Channel)
+ || type == typeof(UserRootFolder))
+ {
+ return false;
+ }
+ }
+
+ return type.GetCustomAttribute() == null;
+ }
+
+ private BaseItemDto DeserialiseBaseItem(BaseItemEntity baseItemEntity, bool skipDeserialization = false)
{
var type = GetType(baseItemEntity.Type) ?? throw new InvalidOperationException("Cannot deserialise unkown type.");
- var dto = Activator.CreateInstance(type) as BaseItemDto ?? throw new InvalidOperationException("Cannot deserialise unkown type.");
+ BaseItemDto? dto = null;
+ if (TypeRequiresDeserialization(type) && baseItemEntity.Data is not null && !skipDeserialization)
+ {
+ try
+ {
+ using var dataAsStream = new MemoryStream(Encoding.UTF8.GetBytes(baseItemEntity.Data!));
+ dto = JsonSerializer.Deserialize(dataAsStream, type, JsonDefaults.Options) as BaseItemDto;
+ }
+ catch (JsonException ex)
+ {
+ logger.LogError(ex, "Error deserializing item with JSON: {Data}", baseItemEntity.Data);
+ }
+ }
+
+ if (dto is null)
+ {
+ dto = Activator.CreateInstance(type) as BaseItemDto ?? throw new InvalidOperationException("Cannot deserialise unkown type.");
+ }
+
return Map(baseItemEntity, dto);
}
@@ -1764,7 +1810,7 @@ public sealed class BaseItemRepository(IDbContextFactory dbPr
result.StartIndex = filter.StartIndex ?? 0;
result.Items = resultQuery.ToImmutableArray().Select(e =>
{
- return (DeserialiseBaseItem(e.item), e.itemCount);
+ return (DeserialiseBaseItem(e.item, filter.SkipDeserialization), e.itemCount);
}).ToImmutableArray();
return result;
diff --git a/MediaBrowser.Common/RequiresSourceSerialisationAttribute.cs b/MediaBrowser.Common/RequiresSourceSerialisationAttribute.cs
new file mode 100644
index 0000000000..b22e7cba17
--- /dev/null
+++ b/MediaBrowser.Common/RequiresSourceSerialisationAttribute.cs
@@ -0,0 +1,11 @@
+using System;
+
+namespace MediaBrowser.Common;
+
+///
+/// Marks a BaseItem as needing custom serialisation from the Data field of the db.
+///
+[System.AttributeUsage(System.AttributeTargets.Class, Inherited = true, AllowMultiple = false)]
+public sealed class RequiresSourceSerialisationAttribute : System.Attribute
+{
+}
diff --git a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs
index a0aae8769c..f3873775b9 100644
--- a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs
+++ b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs
@@ -21,6 +21,7 @@ namespace MediaBrowser.Controller.Entities.Audio
///
/// Class MusicAlbum.
///
+ [Common.RequiresSourceSerialisation]
public class MusicAlbum : Folder, IHasAlbumArtist, IHasArtist, IHasMusicGenres, IHasLookupInfo, IMetadataContainer
{
public MusicAlbum()
diff --git a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs
index 6d3249399b..5375509256 100644
--- a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs
+++ b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs
@@ -21,6 +21,7 @@ namespace MediaBrowser.Controller.Entities.Audio
///
/// Class MusicArtist.
///
+ [Common.RequiresSourceSerialisation]
public class MusicArtist : Folder, IItemByName, IHasMusicGenres, IHasDualAccess, IHasLookupInfo
{
[JsonIgnore]
diff --git a/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs b/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs
index 80f3902be7..65669e6804 100644
--- a/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs
+++ b/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs
@@ -14,6 +14,7 @@ namespace MediaBrowser.Controller.Entities.Audio
///
/// Class MusicGenre.
///
+ [Common.RequiresSourceSerialisation]
public class MusicGenre : BaseItem, IItemByName
{
[JsonIgnore]
diff --git a/MediaBrowser.Controller/Entities/AudioBook.cs b/MediaBrowser.Controller/Entities/AudioBook.cs
index 782481fbcd..666bf2a750 100644
--- a/MediaBrowser.Controller/Entities/AudioBook.cs
+++ b/MediaBrowser.Controller/Entities/AudioBook.cs
@@ -9,6 +9,7 @@ using MediaBrowser.Controller.Providers;
namespace MediaBrowser.Controller.Entities
{
+ [Common.RequiresSourceSerialisation]
public class AudioBook : Audio.Audio, IHasSeries, IHasLookupInfo
{
[JsonIgnore]
diff --git a/MediaBrowser.Controller/Entities/Book.cs b/MediaBrowser.Controller/Entities/Book.cs
index 66dea1084c..5187669373 100644
--- a/MediaBrowser.Controller/Entities/Book.cs
+++ b/MediaBrowser.Controller/Entities/Book.cs
@@ -10,6 +10,7 @@ using MediaBrowser.Controller.Providers;
namespace MediaBrowser.Controller.Entities
{
+ [Common.RequiresSourceSerialisation]
public class Book : BaseItem, IHasLookupInfo, IHasSeries
{
public Book()
diff --git a/MediaBrowser.Controller/Entities/Genre.cs b/MediaBrowser.Controller/Entities/Genre.cs
index e5353d7bd9..6ec78a270e 100644
--- a/MediaBrowser.Controller/Entities/Genre.cs
+++ b/MediaBrowser.Controller/Entities/Genre.cs
@@ -14,6 +14,7 @@ namespace MediaBrowser.Controller.Entities
///
/// Class Genre.
///
+ [Common.RequiresSourceSerialisation]
public class Genre : BaseItem, IItemByName
{
///
diff --git a/MediaBrowser.Controller/Entities/Person.cs b/MediaBrowser.Controller/Entities/Person.cs
index b0933d23f4..5cc4d322f7 100644
--- a/MediaBrowser.Controller/Entities/Person.cs
+++ b/MediaBrowser.Controller/Entities/Person.cs
@@ -14,6 +14,7 @@ namespace MediaBrowser.Controller.Entities
///
/// This is the full Person object that can be retrieved with all of it's data.
///
+ [Common.RequiresSourceSerialisation]
public class Person : BaseItem, IItemByName, IHasLookupInfo
{
///
diff --git a/MediaBrowser.Controller/Entities/PhotoAlbum.cs b/MediaBrowser.Controller/Entities/PhotoAlbum.cs
index a7ecb9061c..5b31b4f116 100644
--- a/MediaBrowser.Controller/Entities/PhotoAlbum.cs
+++ b/MediaBrowser.Controller/Entities/PhotoAlbum.cs
@@ -4,6 +4,7 @@ using System.Text.Json.Serialization;
namespace MediaBrowser.Controller.Entities
{
+ [Common.RequiresSourceSerialisation]
public class PhotoAlbum : Folder
{
[JsonIgnore]
diff --git a/MediaBrowser.Controller/Entities/Studio.cs b/MediaBrowser.Controller/Entities/Studio.cs
index b46a3d1bcf..9103b09a95 100644
--- a/MediaBrowser.Controller/Entities/Studio.cs
+++ b/MediaBrowser.Controller/Entities/Studio.cs
@@ -13,6 +13,7 @@ namespace MediaBrowser.Controller.Entities
///
/// Class Studio.
///
+ [Common.RequiresSourceSerialisation]
public class Studio : BaseItem, IItemByName
{
///
diff --git a/MediaBrowser.Controller/Entities/TV/Season.cs b/MediaBrowser.Controller/Entities/TV/Season.cs
index 181b9be2bf..8e9f5818d0 100644
--- a/MediaBrowser.Controller/Entities/TV/Season.cs
+++ b/MediaBrowser.Controller/Entities/TV/Season.cs
@@ -10,6 +10,7 @@ using System.Text.Json.Serialization;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
+using MediaBrowser.Common;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Querying;
@@ -19,6 +20,7 @@ namespace MediaBrowser.Controller.Entities.TV
///
/// Class Season.
///
+ [RequiresSourceSerialisation]
public class Season : Folder, IHasSeries, IHasLookupInfo
{
[JsonIgnore]
diff --git a/MediaBrowser.Controller/Entities/Year.cs b/MediaBrowser.Controller/Entities/Year.cs
index 587d7ce7e5..37820296cc 100644
--- a/MediaBrowser.Controller/Entities/Year.cs
+++ b/MediaBrowser.Controller/Entities/Year.cs
@@ -13,6 +13,7 @@ namespace MediaBrowser.Controller.Entities
///
/// Class Year.
///
+ [Common.RequiresSourceSerialisation]
public class Year : BaseItem, IItemByName
{
[JsonIgnore]
diff --git a/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs b/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs
index 2ac6f99633..83944f741c 100644
--- a/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs
+++ b/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs
@@ -18,6 +18,7 @@ using MediaBrowser.Model.Providers;
namespace MediaBrowser.Controller.LiveTv
{
+ [Common.RequiresSourceSerialisation]
public class LiveTvProgram : BaseItem, IHasLookupInfo, IHasStartDate, IHasProgramAttributes
{
private const string EmbyServiceName = "Emby";