mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-04-20 17:14:42 +01:00
Merge pull request #12798 from JPVenson/feature/EFUserData
Refactor library.db into jellyfin.db and EFCore
This commit is contained in:
@@ -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);
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user