Merge pull request #12798 from JPVenson/feature/EFUserData

Refactor library.db into jellyfin.db and EFCore
This commit is contained in:
Joshua M. Boniface
2025-01-25 02:08:44 -05:00
committed by GitHub
160 changed files with 20076 additions and 7497 deletions

View File

@@ -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,14 +2969,14 @@ namespace Emby.Server.Implementations.Library
if (saveEntity)
{
(personsToSave ??= new()).Add(personEntity);
await RunMetadataSavers(personEntity, itemUpdateType).ConfigureAwait(false);
}
}
if (createEntity)
{
CreateOrUpdateItems([personEntity], null, CancellationToken.None);
}
if (personsToSave is not null)
{
CreateItems(personsToSave, null, CancellationToken.None);
await RunMetadataSavers(personEntity, itemUpdateType).ConfigureAwait(false);
CreateOrUpdateItems([personEntity], null, CancellationToken.None);
}
}
}
@@ -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);

View File

@@ -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)

View File

@@ -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)
{
var list = new List<BaseItem>
{
item
};
list.AddRange(GetInstantMixFromGenres(item.Genres, user, dtoOptions));
return list;
}
/// <inheritdoc />
public List<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> GetInstantMixFromSong(Audio item, User? user, DtoOptions dtoOptions)
{
return GetInstantMixFromGenres(item.Genres, user, dtoOptions);
}
public List<BaseItem> GetInstantMixFromFolder(Folder item, User? user, DtoOptions dtoOptions)
/// <inheritdoc />
public IReadOnlyList<BaseItem> GetInstantMixFromArtist(MusicArtist artist, User? user, DtoOptions dtoOptions)
{
return GetInstantMixFromGenres(artist.Genres, user, dtoOptions);
}
public IReadOnlyList<BaseItem> GetInstantMixFromAlbum(MusicAlbum item, User? user, DtoOptions dtoOptions)
{
return GetInstantMixFromGenres(item.Genres, user, 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)
{

View File

@@ -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)
{

View File

@@ -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 _userData.GetOrAdd(cacheKey, _ => GetUserDataInternal(userId, keys));
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,
};
}
private UserItemData GetUserDataInternal(long internalUserId, List<string> keys)
private UserItemData Map(UserData dto)
{
var userData = _repository.GetUserData(internalUserId, keys);
if (userData is not null)
return new UserItemData()
{
return userData;
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? GetUserData(User user, Guid itemId, List<string> keys)
{
var cacheKey = GetCacheKey(user.InternalId, itemId);
if (_userData.TryGetValue(cacheKey, out var data))
{
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
};
}