aboutsummaryrefslogtreecommitdiff
path: root/Emby.Server.Implementations/Library
diff options
context:
space:
mode:
authorJoshua M. Boniface <joshua@boniface.me>2025-01-25 02:08:44 -0500
committerGitHub <noreply@github.com>2025-01-25 02:08:44 -0500
commit93b8eade617dbce16979bbada63b7faf44c5ce82 (patch)
tree5ef83a80b4ef1f713961d32ee6311c6033a3a9a9 /Emby.Server.Implementations/Library
parent679ee960d346f24d7df559cbbaf95cf1c9567345 (diff)
parent64cf67f1ac758acd36db61432c97e434d9f9225d (diff)
Merge pull request #12798 from JPVenson/feature/EFUserData
Refactor library.db into jellyfin.db and EFCore
Diffstat (limited to 'Emby.Server.Implementations/Library')
-rw-r--r--Emby.Server.Implementations/Library/LibraryManager.cs62
-rw-r--r--Emby.Server.Implementations/Library/MediaSourceManager.cs42
-rw-r--r--Emby.Server.Implementations/Library/MusicManager.cs26
-rw-r--r--Emby.Server.Implementations/Library/SearchEngine.cs2
-rw-r--r--Emby.Server.Implementations/Library/UserDataManager.cs139
5 files changed, 175 insertions, 96 deletions
diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs
index 2d1af82b3..93ee47fe8 100644
--- a/Emby.Server.Implementations/Library/LibraryManager.cs
+++ b/Emby.Server.Implementations/Library/LibraryManager.cs
@@ -76,6 +76,7 @@ namespace Emby.Server.Implementations.Library
private readonly IItemRepository _itemRepository;
private readonly IImageProcessor _imageProcessor;
private readonly NamingOptions _namingOptions;
+ private readonly IPeopleRepository _peopleRepository;
private readonly ExtraResolver _extraResolver;
/// <summary>
@@ -112,6 +113,7 @@ namespace Emby.Server.Implementations.Library
/// <param name="imageProcessor">The image processor.</param>
/// <param name="namingOptions">The naming options.</param>
/// <param name="directoryService">The directory service.</param>
+ /// <param name="peopleRepository">The People Repository.</param>
public LibraryManager(
IServerApplicationHost appHost,
ILoggerFactory loggerFactory,
@@ -127,7 +129,8 @@ namespace Emby.Server.Implementations.Library
IItemRepository itemRepository,
IImageProcessor imageProcessor,
NamingOptions namingOptions,
- IDirectoryService directoryService)
+ IDirectoryService directoryService,
+ IPeopleRepository peopleRepository)
{
_appHost = appHost;
_logger = loggerFactory.CreateLogger<LibraryManager>();
@@ -144,7 +147,7 @@ namespace Emby.Server.Implementations.Library
_imageProcessor = imageProcessor;
_cache = new ConcurrentDictionary<Guid, BaseItem>();
_namingOptions = namingOptions;
-
+ _peopleRepository = peopleRepository;
_extraResolver = new ExtraResolver(loggerFactory.CreateLogger<ExtraResolver>(), namingOptions, directoryService);
_configurationManager.ConfigurationUpdated += ConfigurationUpdated;
@@ -1274,7 +1277,7 @@ namespace Emby.Server.Implementations.Library
return ItemIsVisible(item, user) ? item : null;
}
- public List<BaseItem> GetItemList(InternalItemsQuery query, bool allowExternalContent)
+ public IReadOnlyList<BaseItem> GetItemList(InternalItemsQuery query, bool allowExternalContent)
{
if (query.Recursive && !query.ParentId.IsEmpty())
{
@@ -1300,7 +1303,7 @@ namespace Emby.Server.Implementations.Library
return itemList;
}
- public List<BaseItem> GetItemList(InternalItemsQuery query)
+ public IReadOnlyList<BaseItem> GetItemList(InternalItemsQuery query)
{
return GetItemList(query, true);
}
@@ -1324,7 +1327,7 @@ namespace Emby.Server.Implementations.Library
return _itemRepository.GetCount(query);
}
- public List<BaseItem> GetItemList(InternalItemsQuery query, List<BaseItem> parents)
+ public IReadOnlyList<BaseItem> GetItemList(InternalItemsQuery query, List<BaseItem> parents)
{
SetTopParentIdsOrAncestors(query, parents);
@@ -1357,7 +1360,7 @@ namespace Emby.Server.Implementations.Library
_itemRepository.GetItemList(query));
}
- public List<Guid> GetItemIds(InternalItemsQuery query)
+ public IReadOnlyList<Guid> GetItemIds(InternalItemsQuery query)
{
if (query.User is not null)
{
@@ -1807,11 +1810,11 @@ namespace Emby.Server.Implementations.Library
/// <inheritdoc />
public void CreateItem(BaseItem item, BaseItem? parent)
{
- CreateItems(new[] { item }, parent, CancellationToken.None);
+ CreateOrUpdateItems(new[] { item }, parent, CancellationToken.None);
}
/// <inheritdoc />
- public void CreateItems(IReadOnlyList<BaseItem> items, BaseItem? parent, CancellationToken cancellationToken)
+ public void CreateOrUpdateItems(IReadOnlyList<BaseItem> items, BaseItem? parent, CancellationToken cancellationToken)
{
_itemRepository.SaveItems(items, cancellationToken);
@@ -1955,13 +1958,13 @@ namespace Emby.Server.Implementations.Library
/// <inheritdoc />
public async Task UpdateItemsAsync(IReadOnlyList<BaseItem> items, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken)
{
+ _itemRepository.SaveItems(items, cancellationToken);
+
foreach (var item in items)
{
await RunMetadataSavers(item, updateReason).ConfigureAwait(false);
}
- _itemRepository.SaveItems(items, cancellationToken);
-
if (ItemUpdated is not null)
{
foreach (var item in items)
@@ -2736,12 +2739,12 @@ namespace Emby.Server.Implementations.Library
return path;
}
- public List<PersonInfo> GetPeople(InternalPeopleQuery query)
+ public IReadOnlyList<PersonInfo> GetPeople(InternalPeopleQuery query)
{
- return _itemRepository.GetPeople(query);
+ return _peopleRepository.GetPeople(query);
}
- public List<PersonInfo> GetPeople(BaseItem item)
+ public IReadOnlyList<PersonInfo> GetPeople(BaseItem item)
{
if (item.SupportsPeople)
{
@@ -2756,12 +2759,12 @@ namespace Emby.Server.Implementations.Library
}
}
- return new List<PersonInfo>();
+ return [];
}
- public List<Person> GetPeopleItems(InternalPeopleQuery query)
+ public IReadOnlyList<Person> GetPeopleItems(InternalPeopleQuery query)
{
- return _itemRepository.GetPeopleNames(query)
+ return _peopleRepository.GetPeopleNames(query)
.Select(i =>
{
try
@@ -2779,9 +2782,9 @@ namespace Emby.Server.Implementations.Library
.ToList()!; // null values are filtered out
}
- public List<string> GetPeopleNames(InternalPeopleQuery query)
+ public IReadOnlyList<string> GetPeopleNames(InternalPeopleQuery query)
{
- return _itemRepository.GetPeopleNames(query);
+ return _peopleRepository.GetPeopleNames(query);
}
public void UpdatePeople(BaseItem item, List<PersonInfo> people)
@@ -2790,16 +2793,17 @@ namespace Emby.Server.Implementations.Library
}
/// <inheritdoc />
- public async Task UpdatePeopleAsync(BaseItem item, List<PersonInfo> people, CancellationToken cancellationToken)
+ public async Task UpdatePeopleAsync(BaseItem item, IReadOnlyList<PersonInfo> people, CancellationToken cancellationToken)
{
if (!item.SupportsPeople)
{
return;
}
- _itemRepository.UpdatePeople(item.Id, people);
if (people is not null)
{
+ people = people.Where(e => e is not null).ToArray();
+ _peopleRepository.UpdatePeople(item.Id, people);
await SavePeopleMetadataAsync(people, cancellationToken).ConfigureAwait(false);
}
}
@@ -2914,14 +2918,13 @@ namespace Emby.Server.Implementations.Library
private async Task SavePeopleMetadataAsync(IEnumerable<PersonInfo> people, CancellationToken cancellationToken)
{
- List<BaseItem>? personsToSave = null;
-
foreach (var person in people)
{
cancellationToken.ThrowIfCancellationRequested();
var itemUpdateType = ItemUpdateType.MetadataDownload;
var saveEntity = false;
+ var createEntity = false;
var personEntity = GetPerson(person.Name);
if (personEntity is null)
@@ -2938,6 +2941,7 @@ namespace Emby.Server.Implementations.Library
personEntity.PresentationUniqueKey = personEntity.CreatePresentationUniqueKey();
saveEntity = true;
+ createEntity = true;
}
foreach (var id in person.ProviderIds)
@@ -2965,15 +2969,15 @@ namespace Emby.Server.Implementations.Library
if (saveEntity)
{
- (personsToSave ??= new()).Add(personEntity);
+ if (createEntity)
+ {
+ CreateOrUpdateItems([personEntity], null, CancellationToken.None);
+ }
+
await RunMetadataSavers(personEntity, itemUpdateType).ConfigureAwait(false);
+ CreateOrUpdateItems([personEntity], null, CancellationToken.None);
}
}
-
- if (personsToSave is not null)
- {
- CreateItems(personsToSave, null, CancellationToken.None);
- }
}
private void StartScanInBackground()
@@ -3027,7 +3031,7 @@ namespace Emby.Server.Implementations.Library
{
var libraryOptions = CollectionFolder.GetLibraryOptions(virtualFolderPath);
- libraryOptions.PathInfos = [..libraryOptions.PathInfos, pathInfo];
+ libraryOptions.PathInfos = [.. libraryOptions.PathInfos, pathInfo];
SyncLibraryOptionsToLocations(virtualFolderPath, libraryOptions);
diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs
index 90a01c052..d0f5e60f7 100644
--- a/Emby.Server.Implementations/Library/MediaSourceManager.cs
+++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs
@@ -5,6 +5,7 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
+using System.Collections.Immutable;
using System.Globalization;
using System.IO;
using System.Linq;
@@ -51,7 +52,8 @@ namespace Emby.Server.Implementations.Library
private readonly ILocalizationManager _localizationManager;
private readonly IApplicationPaths _appPaths;
private readonly IDirectoryService _directoryService;
-
+ private readonly IMediaStreamRepository _mediaStreamRepository;
+ private readonly IMediaAttachmentRepository _mediaAttachmentRepository;
private readonly ConcurrentDictionary<string, ILiveStream> _openStreams = new ConcurrentDictionary<string, ILiveStream>(StringComparer.OrdinalIgnoreCase);
private readonly AsyncNonKeyedLocker _liveStreamLocker = new(1);
private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
@@ -69,7 +71,9 @@ namespace Emby.Server.Implementations.Library
IFileSystem fileSystem,
IUserDataManager userDataManager,
IMediaEncoder mediaEncoder,
- IDirectoryService directoryService)
+ IDirectoryService directoryService,
+ IMediaStreamRepository mediaStreamRepository,
+ IMediaAttachmentRepository mediaAttachmentRepository)
{
_appHost = appHost;
_itemRepo = itemRepo;
@@ -82,6 +86,8 @@ namespace Emby.Server.Implementations.Library
_localizationManager = localizationManager;
_appPaths = applicationPaths;
_directoryService = directoryService;
+ _mediaStreamRepository = mediaStreamRepository;
+ _mediaAttachmentRepository = mediaAttachmentRepository;
}
public void AddParts(IEnumerable<IMediaSourceProvider> providers)
@@ -89,9 +95,9 @@ namespace Emby.Server.Implementations.Library
_providers = providers.ToArray();
}
- public List<MediaStream> GetMediaStreams(MediaStreamQuery query)
+ public IReadOnlyList<MediaStream> GetMediaStreams(MediaStreamQuery query)
{
- var list = _itemRepo.GetMediaStreams(query);
+ var list = _mediaStreamRepository.GetMediaStreams(query);
foreach (var stream in list)
{
@@ -121,7 +127,7 @@ namespace Emby.Server.Implementations.Library
return false;
}
- public List<MediaStream> GetMediaStreams(Guid itemId)
+ public IReadOnlyList<MediaStream> GetMediaStreams(Guid itemId)
{
var list = GetMediaStreams(new MediaStreamQuery
{
@@ -131,7 +137,7 @@ namespace Emby.Server.Implementations.Library
return GetMediaStreamsForItem(list);
}
- private List<MediaStream> GetMediaStreamsForItem(List<MediaStream> streams)
+ private IReadOnlyList<MediaStream> GetMediaStreamsForItem(IReadOnlyList<MediaStream> streams)
{
foreach (var stream in streams)
{
@@ -145,13 +151,13 @@ namespace Emby.Server.Implementations.Library
}
/// <inheritdoc />
- public List<MediaAttachment> GetMediaAttachments(MediaAttachmentQuery query)
+ public IReadOnlyList<MediaAttachment> GetMediaAttachments(MediaAttachmentQuery query)
{
- return _itemRepo.GetMediaAttachments(query);
+ return _mediaAttachmentRepository.GetMediaAttachments(query);
}
/// <inheritdoc />
- public List<MediaAttachment> GetMediaAttachments(Guid itemId)
+ public IReadOnlyList<MediaAttachment> GetMediaAttachments(Guid itemId)
{
return GetMediaAttachments(new MediaAttachmentQuery
{
@@ -159,7 +165,7 @@ namespace Emby.Server.Implementations.Library
});
}
- public async Task<List<MediaSourceInfo>> GetPlaybackMediaSources(BaseItem item, User user, bool allowMediaProbe, bool enablePathSubstitution, CancellationToken cancellationToken)
+ public async Task<IReadOnlyList<MediaSourceInfo>> GetPlaybackMediaSources(BaseItem item, User user, bool allowMediaProbe, bool enablePathSubstitution, CancellationToken cancellationToken)
{
var mediaSources = GetStaticMediaSources(item, enablePathSubstitution, user);
@@ -212,7 +218,7 @@ namespace Emby.Server.Implementations.Library
list.Add(source);
}
- return SortMediaSources(list);
+ return SortMediaSources(list).ToArray();
}
/// <inheritdoc />>
@@ -332,7 +338,7 @@ namespace Emby.Server.Implementations.Library
return sources.FirstOrDefault(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase));
}
- public List<MediaSourceInfo> GetStaticMediaSources(BaseItem item, bool enablePathSubstitution, User user = null)
+ public IReadOnlyList<MediaSourceInfo> GetStaticMediaSources(BaseItem item, bool enablePathSubstitution, User user = null)
{
ArgumentNullException.ThrowIfNull(item);
@@ -453,7 +459,7 @@ namespace Emby.Server.Implementations.Library
}
}
- private static List<MediaSourceInfo> SortMediaSources(IEnumerable<MediaSourceInfo> sources)
+ private static IEnumerable<MediaSourceInfo> SortMediaSources(IEnumerable<MediaSourceInfo> sources)
{
return sources.OrderBy(i =>
{
@@ -470,8 +476,7 @@ namespace Emby.Server.Implementations.Library
return stream?.Width ?? 0;
})
- .Where(i => i.Type != MediaSourceType.Placeholder)
- .ToList();
+ .Where(i => i.Type != MediaSourceType.Placeholder);
}
public async Task<Tuple<LiveStreamResponse, IDirectStreamProvider>> OpenLiveStreamInternal(LiveStreamRequest request, CancellationToken cancellationToken)
@@ -806,7 +811,7 @@ namespace Emby.Server.Implementations.Library
return result.Item1;
}
- public async Task<List<MediaSourceInfo>> GetRecordingStreamMediaSources(ActiveRecordingInfo info, CancellationToken cancellationToken)
+ public async Task<IReadOnlyList<MediaSourceInfo>> GetRecordingStreamMediaSources(ActiveRecordingInfo info, CancellationToken cancellationToken)
{
var stream = new MediaSourceInfo
{
@@ -829,10 +834,7 @@ namespace Emby.Server.Implementations.Library
await new LiveStreamHelper(_mediaEncoder, _logger, _appPaths)
.AddMediaInfoWithProbe(stream, false, false, cancellationToken).ConfigureAwait(false);
- return new List<MediaSourceInfo>
- {
- stream
- };
+ return [stream];
}
public async Task CloseLiveStream(string id)
diff --git a/Emby.Server.Implementations/Library/MusicManager.cs b/Emby.Server.Implementations/Library/MusicManager.cs
index a69a0f33f..71c69ec50 100644
--- a/Emby.Server.Implementations/Library/MusicManager.cs
+++ b/Emby.Server.Implementations/Library/MusicManager.cs
@@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
+using System.Collections.Immutable;
using System.Linq;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
@@ -24,30 +25,23 @@ namespace Emby.Server.Implementations.Library
_libraryManager = libraryManager;
}
- public List<BaseItem> GetInstantMixFromSong(Audio item, User? user, DtoOptions dtoOptions)
+ public IReadOnlyList<BaseItem> GetInstantMixFromSong(Audio item, User? user, DtoOptions dtoOptions)
{
- var list = new List<BaseItem>
- {
- item
- };
-
- list.AddRange(GetInstantMixFromGenres(item.Genres, user, dtoOptions));
-
- return list;
+ return GetInstantMixFromGenres(item.Genres, user, dtoOptions);
}
/// <inheritdoc />
- public List<BaseItem> GetInstantMixFromArtist(MusicArtist artist, User? user, DtoOptions dtoOptions)
+ public IReadOnlyList<BaseItem> GetInstantMixFromArtist(MusicArtist artist, User? user, DtoOptions dtoOptions)
{
return GetInstantMixFromGenres(artist.Genres, user, dtoOptions);
}
- public List<BaseItem> GetInstantMixFromAlbum(MusicAlbum item, User? user, DtoOptions dtoOptions)
+ public IReadOnlyList<BaseItem> GetInstantMixFromAlbum(MusicAlbum item, User? user, DtoOptions dtoOptions)
{
return GetInstantMixFromGenres(item.Genres, user, dtoOptions);
}
- public List<BaseItem> GetInstantMixFromFolder(Folder item, User? user, DtoOptions dtoOptions)
+ public IReadOnlyList<BaseItem> GetInstantMixFromFolder(Folder item, User? user, DtoOptions dtoOptions)
{
var genres = item
.GetRecursiveChildren(user, new InternalItemsQuery(user)
@@ -63,12 +57,12 @@ namespace Emby.Server.Implementations.Library
return GetInstantMixFromGenres(genres, user, dtoOptions);
}
- public List<BaseItem> GetInstantMixFromPlaylist(Playlist item, User? user, DtoOptions dtoOptions)
+ public IReadOnlyList<BaseItem> GetInstantMixFromPlaylist(Playlist item, User? user, DtoOptions dtoOptions)
{
return GetInstantMixFromGenres(item.Genres, user, dtoOptions);
}
- public List<BaseItem> GetInstantMixFromGenres(IEnumerable<string> genres, User? user, DtoOptions dtoOptions)
+ public IReadOnlyList<BaseItem> GetInstantMixFromGenres(IEnumerable<string> genres, User? user, DtoOptions dtoOptions)
{
var genreIds = genres.DistinctNames().Select(i =>
{
@@ -85,7 +79,7 @@ namespace Emby.Server.Implementations.Library
return GetInstantMixFromGenreIds(genreIds, user, dtoOptions);
}
- public List<BaseItem> GetInstantMixFromGenreIds(Guid[] genreIds, User? user, DtoOptions dtoOptions)
+ public IReadOnlyList<BaseItem> GetInstantMixFromGenreIds(Guid[] genreIds, User? user, DtoOptions dtoOptions)
{
return _libraryManager.GetItemList(new InternalItemsQuery(user)
{
@@ -97,7 +91,7 @@ namespace Emby.Server.Implementations.Library
});
}
- public List<BaseItem> GetInstantMixFromItem(BaseItem item, User? user, DtoOptions dtoOptions)
+ public IReadOnlyList<BaseItem> GetInstantMixFromItem(BaseItem item, User? user, DtoOptions dtoOptions)
{
if (item is MusicGenre)
{
diff --git a/Emby.Server.Implementations/Library/SearchEngine.cs b/Emby.Server.Implementations/Library/SearchEngine.cs
index 7f3f8615e..3ac1d0219 100644
--- a/Emby.Server.Implementations/Library/SearchEngine.cs
+++ b/Emby.Server.Implementations/Library/SearchEngine.cs
@@ -171,7 +171,7 @@ namespace Emby.Server.Implementations.Library
}
};
- List<BaseItem> mediaItems;
+ IReadOnlyList<BaseItem> mediaItems;
if (searchQuery.IncludeItemTypes.Length == 1 && searchQuery.IncludeItemTypes[0] == BaseItemKind.MusicArtist)
{
diff --git a/Emby.Server.Implementations/Library/UserDataManager.cs b/Emby.Server.Implementations/Library/UserDataManager.cs
index ceb3d65a4..a41ef888b 100644
--- a/Emby.Server.Implementations/Library/UserDataManager.cs
+++ b/Emby.Server.Implementations/Library/UserDataManager.cs
@@ -1,17 +1,21 @@
+#pragma warning disable RS0030 // Do not use banned APIs
+
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
-using System.Diagnostics;
using System.Globalization;
+using System.Linq;
using System.Threading;
using Jellyfin.Data.Entities;
+using Jellyfin.Extensions;
+using Jellyfin.Server.Implementations;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
+using Microsoft.EntityFrameworkCore;
using AudioBook = MediaBrowser.Controller.Entities.AudioBook;
using Book = MediaBrowser.Controller.Entities.Book;
@@ -26,22 +30,18 @@ namespace Emby.Server.Implementations.Library
new ConcurrentDictionary<string, UserItemData>(StringComparer.OrdinalIgnoreCase);
private readonly IServerConfigurationManager _config;
- private readonly IUserManager _userManager;
- private readonly IUserDataRepository _repository;
+ private readonly IDbContextFactory<JellyfinDbContext> _repository;
/// <summary>
/// Initializes a new instance of the <see cref="UserDataManager"/> class.
/// </summary>
/// <param name="config">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
- /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
- /// <param name="repository">Instance of the <see cref="IUserDataRepository"/> interface.</param>
+ /// <param name="repository">Instance of the <see cref="IDbContextFactory{JellyfinDbContext}"/> interface.</param>
public UserDataManager(
IServerConfigurationManager config,
- IUserManager userManager,
- IUserDataRepository repository)
+ IDbContextFactory<JellyfinDbContext> repository)
{
_config = config;
- _userManager = userManager;
_repository = repository;
}
@@ -59,13 +59,27 @@ namespace Emby.Server.Implementations.Library
var keys = item.GetUserDataKeys();
- var userId = user.InternalId;
+ using var dbContext = _repository.CreateDbContext();
+ using var transaction = dbContext.Database.BeginTransaction();
foreach (var key in keys)
{
- _repository.SaveUserData(userId, key, userData, cancellationToken);
+ 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);
_userData.AddOrUpdate(cacheKey, userData, (_, _) => userData);
@@ -86,7 +100,7 @@ namespace Emby.Server.Implementations.Library
ArgumentNullException.ThrowIfNull(item);
ArgumentNullException.ThrowIfNull(userDataDto);
- var userData = GetUserData(user, item);
+ var userData = GetUserData(user, item) ?? throw new InvalidOperationException("UserData should not be null.");
if (userDataDto.PlaybackPositionTicks.HasValue)
{
@@ -126,33 +140,91 @@ namespace Emby.Server.Implementations.Library
SaveUserData(user, item, userData, reason, CancellationToken.None);
}
- private UserItemData GetUserData(User user, Guid itemId, List<string> keys)
+ private UserData Map(UserItemData dto, Guid userId, Guid itemId)
{
- var userId = user.InternalId;
-
- var cacheKey = GetCacheKey(userId, itemId);
+ return new UserData()
+ {
+ ItemId = itemId,
+ CustomDataKey = dto.Key,
+ Item = null,
+ User = null,
+ AudioStreamIndex = dto.AudioStreamIndex,
+ IsFavorite = dto.IsFavorite,
+ LastPlayedDate = dto.LastPlayedDate,
+ Likes = dto.Likes,
+ PlaybackPositionTicks = dto.PlaybackPositionTicks,
+ PlayCount = dto.PlayCount,
+ Played = dto.Played,
+ Rating = dto.Rating,
+ UserId = userId,
+ SubtitleStreamIndex = dto.SubtitleStreamIndex,
+ };
+ }
- return _userData.GetOrAdd(cacheKey, _ => GetUserDataInternal(userId, keys));
+ private UserItemData Map(UserData dto)
+ {
+ return new UserItemData()
+ {
+ Key = dto.CustomDataKey!,
+ AudioStreamIndex = dto.AudioStreamIndex,
+ IsFavorite = dto.IsFavorite,
+ LastPlayedDate = dto.LastPlayedDate,
+ Likes = dto.Likes,
+ PlaybackPositionTicks = dto.PlaybackPositionTicks,
+ PlayCount = dto.PlayCount,
+ Played = dto.Played,
+ Rating = dto.Rating,
+ SubtitleStreamIndex = dto.SubtitleStreamIndex,
+ };
}
- private UserItemData GetUserDataInternal(long internalUserId, List<string> keys)
+ private UserItemData? GetUserData(User user, Guid itemId, List<string> keys)
{
- var userData = _repository.GetUserData(internalUserId, keys);
+ var cacheKey = GetCacheKey(user.InternalId, itemId);
- if (userData is not null)
+ if (_userData.TryGetValue(cacheKey, out var data))
{
- return userData;
+ return data;
}
- if (keys.Count > 0)
+ data = GetUserDataInternal(user.Id, itemId, keys);
+
+ if (data is null)
{
- return new UserItemData
+ return new UserItemData()
{
- Key = keys[0]
+ Key = keys[0],
};
}
- throw new UnreachableException();
+ return _userData.GetOrAdd(cacheKey, data);
+ }
+
+ private UserItemData? GetUserDataInternal(Guid userId, Guid itemId, List<string> keys)
+ {
+ if (keys.Count == 0)
+ {
+ return null;
+ }
+
+ using var context = _repository.CreateDbContext();
+ var userData = context.UserData.AsNoTracking().Where(e => e.ItemId == itemId && keys.Contains(e.CustomDataKey) && e.UserId.Equals(userId)).ToArray();
+
+ if (userData.Length > 0)
+ {
+ var directDataReference = userData.FirstOrDefault(e => e.CustomDataKey == itemId.ToString("N"));
+ if (directDataReference is not null)
+ {
+ return Map(directDataReference);
+ }
+
+ return Map(userData.First());
+ }
+
+ return new UserItemData
+ {
+ Key = keys.Last()!
+ };
}
/// <summary>
@@ -165,20 +237,25 @@ namespace Emby.Server.Implementations.Library
}
/// <inheritdoc />
- public UserItemData GetUserData(User user, BaseItem item)
+ public UserItemData? GetUserData(User user, BaseItem item)
{
return GetUserData(user, item.Id, item.GetUserDataKeys());
}
/// <inheritdoc />
- public UserItemDataDto GetUserDataDto(BaseItem item, User user)
+ public UserItemDataDto? GetUserDataDto(BaseItem item, User user)
=> GetUserDataDto(item, null, user, new DtoOptions());
/// <inheritdoc />
- public UserItemDataDto GetUserDataDto(BaseItem item, BaseItemDto? itemDto, User user, DtoOptions options)
+ public UserItemDataDto? GetUserDataDto(BaseItem item, BaseItemDto? itemDto, User user, DtoOptions options)
{
var userData = GetUserData(user, item);
- var dto = GetUserItemDataDto(userData);
+ if (userData is null)
+ {
+ return null;
+ }
+
+ var dto = GetUserItemDataDto(userData, item.Id);
item.FillUserDataDtoValues(dto, userData, itemDto, user, options);
return dto;
@@ -188,9 +265,10 @@ namespace Emby.Server.Implementations.Library
/// Converts a UserItemData to a DTOUserItemData.
/// </summary>
/// <param name="data">The data.</param>
+ /// <param name="itemId">The reference key to an Item.</param>
/// <returns>DtoUserItemData.</returns>
/// <exception cref="ArgumentNullException"><paramref name="data"/> is <c>null</c>.</exception>
- private UserItemDataDto GetUserItemDataDto(UserItemData data)
+ private UserItemDataDto GetUserItemDataDto(UserItemData data, Guid itemId)
{
ArgumentNullException.ThrowIfNull(data);
@@ -203,6 +281,7 @@ namespace Emby.Server.Implementations.Library
Rating = data.Rating,
Played = data.Played,
LastPlayedDate = data.LastPlayedDate,
+ ItemId = itemId,
Key = data.Key
};
}