diff options
Diffstat (limited to 'Emby.Server.Implementations/Library/UserDataManager.cs')
| -rw-r--r-- | Emby.Server.Implementations/Library/UserDataManager.cs | 149 |
1 files changed, 113 insertions, 36 deletions
diff --git a/Emby.Server.Implementations/Library/UserDataManager.cs b/Emby.Server.Implementations/Library/UserDataManager.cs index ceb3d65a4..be1d96bf0 100644 --- a/Emby.Server.Implementations/Library/UserDataManager.cs +++ b/Emby.Server.Implementations/Library/UserDataManager.cs @@ -1,17 +1,20 @@ +#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 BitFaster.Caching.Lru; +using Jellyfin.Database.Implementations; +using Jellyfin.Database.Implementations.Entities; 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; @@ -22,27 +25,22 @@ namespace Emby.Server.Implementations.Library /// </summary> public class UserDataManager : IUserDataManager { - private readonly ConcurrentDictionary<string, UserItemData> _userData = - new ConcurrentDictionary<string, UserItemData>(StringComparer.OrdinalIgnoreCase); - private readonly IServerConfigurationManager _config; - private readonly IUserManager _userManager; - private readonly IUserDataRepository _repository; + private readonly IDbContextFactory<JellyfinDbContext> _repository; + private readonly FastConcurrentLru<string, UserItemData> _cache; /// <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; + _cache = new FastConcurrentLru<string, UserItemData>(Environment.ProcessorCount, _config.Configuration.CacheSize, StringComparer.OrdinalIgnoreCase); } /// <inheritdoc /> @@ -59,15 +57,29 @@ 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); + _cache.AddOrUpdate(cacheKey, userData); UserDataSaved?.Invoke(this, new UserDataSaveEventArgs { @@ -86,7 +98,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 +138,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 (_cache.TryGet(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 _cache.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 +235,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 +263,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 +279,7 @@ namespace Emby.Server.Implementations.Library Rating = data.Rating, Played = data.Played, LastPlayedDate = data.LastPlayedDate, + ItemId = itemId, Key = data.Key }; } |
