Refactor PostgreSQL provider: multi-schema & async prep
- Refactor migrations and provider to use multiple PostgreSQL schemas, each matching a legacy SQLite database (activitylog, authentication, displaypreferences, library, users). - All tables, foreign keys, and indexes are now schema-qualified; Down migration drops tables by schema. - Provider ensures schemas exist before migrations; entities are mapped to correct schemas in OnModelCreating. - Add support for max-pool-size, min-pool-size, and multiplexing connection options; update logging accordingly. - VACUUM ANALYZE now runs per schema during scheduled optimization. - TruncateAllTablesAsync now truncates tables with schema qualification. - README updated with schema structure, new options, and multiplexing warnings. - CacheDecorator now calls async repository methods using .GetAwaiter().GetResult(), with documentation. - Lays groundwork for full async/await and multiplexing support in the database layer.
This commit is contained in:
@@ -176,12 +176,23 @@ public sealed class BaseItemRepository
|
||||
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyList<Guid> GetItemIdsList(InternalItemsQuery filter)
|
||||
{
|
||||
return GetItemIdsListAsync(filter, CancellationToken.None)
|
||||
.GetAwaiter()
|
||||
.GetResult();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<IReadOnlyList<Guid>> GetItemIdsListAsync(InternalItemsQuery filter, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(filter);
|
||||
PrepareFilterQuery(filter);
|
||||
|
||||
using var context = _dbProvider.CreateDbContext();
|
||||
return ApplyQueryFilter(context.BaseItems.AsNoTracking().Where(e => e.Id != EF.Constant(PlaceholderId)), context, filter).Select(e => e.Id).ToArray();
|
||||
await using var context = _dbProvider.CreateDbContext();
|
||||
return await ApplyQueryFilter(context.BaseItems.AsNoTracking().Where(e => e.Id != EF.Constant(PlaceholderId)), context, filter)
|
||||
.Select(e => e.Id)
|
||||
.ToArrayAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -252,11 +263,19 @@ public sealed class BaseItemRepository
|
||||
|
||||
/// <inheritdoc />
|
||||
public QueryResult<BaseItemDto> GetItems(InternalItemsQuery filter)
|
||||
{
|
||||
return GetItemsAsync(filter, CancellationToken.None)
|
||||
.GetAwaiter()
|
||||
.GetResult();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<QueryResult<BaseItemDto>> GetItemsAsync(InternalItemsQuery filter, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(filter);
|
||||
if (!filter.EnableTotalRecordCount || ((filter.Limit ?? 0) == 0 && (filter.StartIndex ?? 0) == 0))
|
||||
{
|
||||
var returnList = GetItemList(filter);
|
||||
var returnList = await GetItemListAsync(filter, cancellationToken).ConfigureAwait(false);
|
||||
return new QueryResult<BaseItemDto>(
|
||||
filter.StartIndex,
|
||||
returnList.Count,
|
||||
@@ -266,7 +285,7 @@ public sealed class BaseItemRepository
|
||||
PrepareFilterQuery(filter);
|
||||
var result = new QueryResult<BaseItemDto>();
|
||||
|
||||
using var context = _dbProvider.CreateDbContext();
|
||||
await using var context = _dbProvider.CreateDbContext();
|
||||
|
||||
IQueryable<BaseItemEntity> dbQuery = PrepareItemQuery(context, filter);
|
||||
|
||||
@@ -275,24 +294,42 @@ public sealed class BaseItemRepository
|
||||
|
||||
if (filter.EnableTotalRecordCount)
|
||||
{
|
||||
result.TotalRecordCount = dbQuery.Count();
|
||||
result.TotalRecordCount = await dbQuery
|
||||
.CountAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
dbQuery = ApplyQueryPaging(dbQuery, filter);
|
||||
dbQuery = ApplyNavigations(dbQuery, filter);
|
||||
|
||||
result.Items = dbQuery.AsEnumerable().Where(e => e is not null).Select(w => DeserializeBaseItem(w, filter.SkipDeserialization)).Where(dto => dto is not null).ToArray()!;
|
||||
var items = await dbQuery
|
||||
.ToListAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
result.Items = items
|
||||
.Where(e => e is not null)
|
||||
.Select(w => DeserializeBaseItem(w, filter.SkipDeserialization))
|
||||
.Where(dto => dto is not null)
|
||||
.ToArray()!;
|
||||
result.StartIndex = filter.StartIndex ?? 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyList<BaseItemDto> GetItemList(InternalItemsQuery filter)
|
||||
{
|
||||
return GetItemListAsync(filter, CancellationToken.None)
|
||||
.GetAwaiter()
|
||||
.GetResult();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<IReadOnlyList<BaseItemDto>> GetItemListAsync(InternalItemsQuery filter, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(filter);
|
||||
PrepareFilterQuery(filter);
|
||||
|
||||
using var context = _dbProvider.CreateDbContext();
|
||||
await using var context = _dbProvider.CreateDbContext();
|
||||
IQueryable<BaseItemEntity> dbQuery = PrepareItemQuery(context, filter);
|
||||
|
||||
dbQuery = TranslateQuery(dbQuery, context, filter);
|
||||
@@ -303,14 +340,21 @@ public sealed class BaseItemRepository
|
||||
var hasRandomSort = filter.OrderBy.Any(e => e.OrderBy == ItemSortBy.Random);
|
||||
if (hasRandomSort)
|
||||
{
|
||||
var orderedIds = dbQuery.Select(e => e.Id).ToList();
|
||||
var orderedIds = await dbQuery
|
||||
.Select(e => e.Id)
|
||||
.ToListAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (orderedIds.Count == 0)
|
||||
{
|
||||
return Array.Empty<BaseItemDto>();
|
||||
}
|
||||
|
||||
var itemsById = ApplyNavigations(context.BaseItems.Where(e => orderedIds.Contains(e.Id)), filter)
|
||||
.AsEnumerable()
|
||||
var items = await ApplyNavigations(context.BaseItems.Where(e => orderedIds.Contains(e.Id)), filter)
|
||||
.ToListAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
var itemsById = items
|
||||
.Select(w => DeserializeBaseItem(w, filter.SkipDeserialization))
|
||||
.Where(dto => dto is not null)
|
||||
.ToDictionary(i => i!.Id);
|
||||
@@ -320,7 +364,15 @@ public sealed class BaseItemRepository
|
||||
|
||||
dbQuery = ApplyNavigations(dbQuery, filter);
|
||||
|
||||
return dbQuery.AsEnumerable().Where(e => e is not null).Select(w => DeserializeBaseItem(w, filter.SkipDeserialization)).Where(dto => dto is not null).ToArray()!;
|
||||
var results = await dbQuery
|
||||
.ToListAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return results
|
||||
.Where(e => e is not null)
|
||||
.Select(w => DeserializeBaseItem(w, filter.SkipDeserialization))
|
||||
.Where(dto => dto is not null)
|
||||
.ToArray()!;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -496,30 +548,47 @@ public sealed class BaseItemRepository
|
||||
/// <inheritdoc/>
|
||||
public int GetCount(InternalItemsQuery filter)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(filter);
|
||||
// Hack for right now since we currently don't support filtering out these duplicates within a query
|
||||
PrepareFilterQuery(filter);
|
||||
|
||||
using var context = _dbProvider.CreateDbContext();
|
||||
var dbQuery = TranslateQuery(context.BaseItems.AsNoTracking(), context, filter);
|
||||
|
||||
return dbQuery.Count();
|
||||
return GetCountAsync(filter, CancellationToken.None)
|
||||
.GetAwaiter()
|
||||
.GetResult();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ItemCounts GetItemCounts(InternalItemsQuery filter)
|
||||
/// <inheritdoc/>
|
||||
public async Task<int> GetCountAsync(InternalItemsQuery filter, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(filter);
|
||||
// Hack for right now since we currently don't support filtering out these duplicates within a query
|
||||
PrepareFilterQuery(filter);
|
||||
|
||||
using var context = _dbProvider.CreateDbContext();
|
||||
await using var context = _dbProvider.CreateDbContext();
|
||||
var dbQuery = TranslateQuery(context.BaseItems.AsNoTracking(), context, filter);
|
||||
|
||||
var counts = dbQuery
|
||||
return await dbQuery.CountAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ItemCounts GetItemCounts(InternalItemsQuery filter)
|
||||
{
|
||||
return GetItemCountsAsync(filter, CancellationToken.None)
|
||||
.GetAwaiter()
|
||||
.GetResult();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<ItemCounts> GetItemCountsAsync(InternalItemsQuery filter, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(filter);
|
||||
// Hack for right now since we currently don't support filtering out these duplicates within a query
|
||||
PrepareFilterQuery(filter);
|
||||
|
||||
await using var context = _dbProvider.CreateDbContext();
|
||||
var dbQuery = TranslateQuery(context.BaseItems.AsNoTracking(), context, filter);
|
||||
|
||||
var counts = await dbQuery
|
||||
.GroupBy(x => x.Type)
|
||||
.Select(x => new { x.Key, Count = x.Count() })
|
||||
.ToArray();
|
||||
.ToArrayAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
var lookup = _itemTypeLookup.BaseItemKindNames;
|
||||
var result = new ItemCounts();
|
||||
|
||||
@@ -36,16 +36,18 @@ public class ChapterRepository : IChapterRepository
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ChapterInfo? GetChapter(Guid baseItemId, int index)
|
||||
public async Task<ChapterInfo?> GetChapterAsync(Guid baseItemId, int index, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var context = _dbProvider.CreateDbContext();
|
||||
var chapter = context.Chapters.AsNoTracking()
|
||||
await using var context = _dbProvider.CreateDbContext();
|
||||
var chapter = await context.Chapters.AsNoTracking()
|
||||
.Select(e => new
|
||||
{
|
||||
chapter = e,
|
||||
baseItemPath = e.Item.Path
|
||||
})
|
||||
.FirstOrDefault(e => e.chapter.ItemId.Equals(baseItemId) && e.chapter.ChapterIndex == index);
|
||||
.FirstOrDefaultAsync(e => e.chapter.ItemId.Equals(baseItemId) && e.chapter.ChapterIndex == index, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (chapter is not null)
|
||||
{
|
||||
return Map(chapter.chapter, chapter.baseItemPath!);
|
||||
@@ -55,36 +57,41 @@ public class ChapterRepository : IChapterRepository
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyList<ChapterInfo> GetChapters(Guid baseItemId)
|
||||
public async Task<IReadOnlyList<ChapterInfo>> GetChaptersAsync(Guid baseItemId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var context = _dbProvider.CreateDbContext();
|
||||
return context.Chapters.AsNoTracking().Where(e => e.ItemId.Equals(baseItemId))
|
||||
await using var context = _dbProvider.CreateDbContext();
|
||||
var chapters = await context.Chapters.AsNoTracking()
|
||||
.Where(e => e.ItemId.Equals(baseItemId))
|
||||
.Select(e => new
|
||||
{
|
||||
chapter = e,
|
||||
baseItemPath = e.Item.Path
|
||||
})
|
||||
.AsEnumerable()
|
||||
.Select(e => Map(e.chapter, e.baseItemPath!))
|
||||
.ToArray();
|
||||
.ToArrayAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return chapters.Select(e => Map(e.chapter, e.baseItemPath!)).ToArray();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SaveChapters(Guid itemId, IReadOnlyList<ChapterInfo> chapters)
|
||||
public async Task SaveChaptersAsync(Guid itemId, IReadOnlyList<ChapterInfo> chapters, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var context = _dbProvider.CreateDbContext();
|
||||
using (var transaction = context.Database.BeginTransaction())
|
||||
{
|
||||
context.Chapters.Where(e => e.ItemId.Equals(itemId)).ExecuteDelete();
|
||||
for (var i = 0; i < chapters.Count; i++)
|
||||
{
|
||||
var chapter = chapters[i];
|
||||
context.Chapters.Add(Map(chapter, i, itemId));
|
||||
}
|
||||
await using var context = _dbProvider.CreateDbContext();
|
||||
await using var transaction = await context.Database.BeginTransactionAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
context.SaveChanges();
|
||||
transaction.Commit();
|
||||
await context.Chapters
|
||||
.Where(e => e.ItemId.Equals(itemId))
|
||||
.ExecuteDeleteAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
for (var i = 0; i < chapters.Count; i++)
|
||||
{
|
||||
var chapter = chapters[i];
|
||||
await context.Chapters.AddAsync(Map(chapter, i, itemId), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
|
||||
await transaction.CommitAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -48,31 +48,34 @@ public class KeyframeRepository : IKeyframeRepository
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyList<MediaEncoding.Keyframes.KeyframeData> GetKeyframeData(Guid itemId)
|
||||
public async Task<IReadOnlyList<MediaEncoding.Keyframes.KeyframeData>> GetKeyframeDataAsync(Guid itemId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var context = _dbProvider.CreateDbContext();
|
||||
await using var context = _dbProvider.CreateDbContext();
|
||||
|
||||
return context.KeyframeData.AsNoTracking().Where(e => e.ItemId.Equals(itemId)).Select(e => Map(e)).ToList();
|
||||
return await context.KeyframeData
|
||||
.AsNoTracking()
|
||||
.Where(e => e.ItemId.Equals(itemId))
|
||||
.Select(e => Map(e))
|
||||
.ToListAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task SaveKeyframeDataAsync(Guid itemId, MediaEncoding.Keyframes.KeyframeData data, CancellationToken cancellationToken)
|
||||
public async Task SaveKeyframeDataAsync(Guid itemId, MediaEncoding.Keyframes.KeyframeData data, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var context = _dbProvider.CreateDbContext();
|
||||
var transaction = await context.Database.BeginTransactionAsync(cancellationToken).ConfigureAwait(false);
|
||||
await using (transaction.ConfigureAwait(false))
|
||||
{
|
||||
await context.KeyframeData.Where(e => e.ItemId.Equals(itemId)).ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false);
|
||||
await context.KeyframeData.AddAsync(Map(data, itemId), cancellationToken).ConfigureAwait(false);
|
||||
await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
|
||||
await transaction.CommitAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
await using var context = _dbProvider.CreateDbContext();
|
||||
await using var transaction = await context.Database.BeginTransactionAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
await context.KeyframeData.Where(e => e.ItemId.Equals(itemId)).ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false);
|
||||
await context.KeyframeData.AddAsync(Map(data, itemId), cancellationToken).ConfigureAwait(false);
|
||||
await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
|
||||
await transaction.CommitAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task DeleteKeyframeDataAsync(Guid itemId, CancellationToken cancellationToken)
|
||||
public async Task DeleteKeyframeDataAsync(Guid itemId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var context = _dbProvider.CreateDbContext();
|
||||
await using var context = _dbProvider.CreateDbContext();
|
||||
await context.KeyframeData.Where(e => e.ItemId.Equals(itemId)).ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false);
|
||||
await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Database.Implementations;
|
||||
using Jellyfin.Database.Implementations.Entities;
|
||||
using MediaBrowser.Controller.Persistence;
|
||||
@@ -22,38 +23,53 @@ using Microsoft.EntityFrameworkCore;
|
||||
public class MediaAttachmentRepository(IDbContextFactory<JellyfinDbContext> dbProvider) : IMediaAttachmentRepository
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public void SaveMediaAttachments(
|
||||
public async Task SaveMediaAttachmentsAsync(
|
||||
Guid id,
|
||||
IReadOnlyList<MediaAttachment> attachments,
|
||||
CancellationToken cancellationToken)
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var context = dbProvider.CreateDbContext();
|
||||
using var transaction = context.Database.BeginTransaction();
|
||||
await using var context = dbProvider.CreateDbContext();
|
||||
await using var transaction = await context.Database.BeginTransactionAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// Users may replace a media with a version that includes attachments to one without them.
|
||||
// So when saving attachments is triggered by a library scan, we always unconditionally
|
||||
// clear the old ones, and then add the new ones if given.
|
||||
context.AttachmentStreamInfos.Where(e => e.ItemId.Equals(id)).ExecuteDelete();
|
||||
await context.AttachmentStreamInfos
|
||||
.Where(e => e.ItemId.Equals(id))
|
||||
.ExecuteDeleteAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (attachments.Any())
|
||||
{
|
||||
context.AttachmentStreamInfos.AddRange(attachments.Select(e => Map(e, id)));
|
||||
await context.AttachmentStreamInfos
|
||||
.AddRangeAsync(attachments.Select(e => Map(e, id)), cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
context.SaveChanges();
|
||||
transaction.Commit();
|
||||
await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
|
||||
await transaction.CommitAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyList<MediaAttachment> GetMediaAttachments(MediaAttachmentQuery filter)
|
||||
public async Task<IReadOnlyList<MediaAttachment>> GetMediaAttachmentsAsync(
|
||||
MediaAttachmentQuery filter,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var context = dbProvider.CreateDbContext();
|
||||
var query = context.AttachmentStreamInfos.AsNoTracking().Where(e => e.ItemId.Equals(filter.ItemId));
|
||||
await using var context = dbProvider.CreateDbContext();
|
||||
var query = context.AttachmentStreamInfos
|
||||
.AsNoTracking()
|
||||
.Where(e => e.ItemId.Equals(filter.ItemId));
|
||||
|
||||
if (filter.Index.HasValue)
|
||||
{
|
||||
query = query.Where(e => e.Index == filter.Index);
|
||||
}
|
||||
|
||||
return query.AsEnumerable().Select(Map).ToArray();
|
||||
var attachments = await query
|
||||
.ToArrayAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return attachments.Select(Map).ToArray();
|
||||
}
|
||||
|
||||
private MediaAttachment Map(AttachmentStreamInfo attachment)
|
||||
|
||||
@@ -9,6 +9,7 @@ using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Database.Implementations;
|
||||
using Jellyfin.Database.Implementations.Entities;
|
||||
using MediaBrowser.Controller;
|
||||
@@ -40,23 +41,33 @@ public class MediaStreamRepository : IMediaStreamRepository
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SaveMediaStreams(Guid id, IReadOnlyList<MediaStream> streams, CancellationToken cancellationToken)
|
||||
public async Task SaveMediaStreamsAsync(Guid id, IReadOnlyList<MediaStream> streams, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var context = _dbProvider.CreateDbContext();
|
||||
using var transaction = context.Database.BeginTransaction();
|
||||
await using var context = _dbProvider.CreateDbContext();
|
||||
await using var transaction = await context.Database.BeginTransactionAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
context.MediaStreamInfos.Where(e => e.ItemId.Equals(id)).ExecuteDelete();
|
||||
context.MediaStreamInfos.AddRange(streams.Select(f => Map(f, id)));
|
||||
context.SaveChanges();
|
||||
await context.MediaStreamInfos
|
||||
.Where(e => e.ItemId.Equals(id))
|
||||
.ExecuteDeleteAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
transaction.Commit();
|
||||
await context.MediaStreamInfos
|
||||
.AddRangeAsync(streams.Select(f => Map(f, id)), cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
|
||||
await transaction.CommitAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyList<MediaStream> GetMediaStreams(MediaStreamQuery filter)
|
||||
public async Task<IReadOnlyList<MediaStream>> GetMediaStreamsAsync(MediaStreamQuery filter, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var context = _dbProvider.CreateDbContext();
|
||||
return TranslateQuery(context.MediaStreamInfos.AsNoTracking(), filter).AsEnumerable().Select(Map).ToArray();
|
||||
await using var context = _dbProvider.CreateDbContext();
|
||||
var streams = await TranslateQuery(context.MediaStreamInfos.AsNoTracking(), filter)
|
||||
.ToArrayAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return streams.Select(Map).ToArray();
|
||||
}
|
||||
|
||||
private string? GetPathToSave(string? path)
|
||||
|
||||
@@ -8,6 +8,8 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Database.Implementations;
|
||||
using Jellyfin.Database.Implementations.Entities;
|
||||
@@ -37,7 +39,15 @@ public class PeopleRepository(IDbContextFactory<JellyfinDbContext> dbProvider, I
|
||||
/// <inheritdoc/>
|
||||
public IReadOnlyList<PersonInfo> GetPeople(InternalPeopleQuery filter)
|
||||
{
|
||||
using var context = _dbProvider.CreateDbContext();
|
||||
return GetPeopleAsync(filter, CancellationToken.None)
|
||||
.GetAwaiter()
|
||||
.GetResult();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<IReadOnlyList<PersonInfo>> GetPeopleAsync(InternalPeopleQuery filter, CancellationToken cancellationToken = default)
|
||||
{
|
||||
await using var context = _dbProvider.CreateDbContext();
|
||||
var dbQuery = TranslateQuery(context.Peoples.AsNoTracking(), context, filter);
|
||||
|
||||
// Include PeopleBaseItemMap
|
||||
@@ -58,26 +68,49 @@ public class PeopleRepository(IDbContextFactory<JellyfinDbContext> dbProvider, I
|
||||
dbQuery = dbQuery.Take(filter.Limit);
|
||||
}
|
||||
|
||||
return dbQuery.AsEnumerable().Select(Map).ToArray();
|
||||
var results = await dbQuery
|
||||
.ToArrayAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return results.Select(Map).ToArray();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IReadOnlyList<string> GetPeopleNames(InternalPeopleQuery filter)
|
||||
{
|
||||
using var context = _dbProvider.CreateDbContext();
|
||||
var dbQuery = TranslateQuery(context.Peoples.AsNoTracking(), context, filter).Select(e => e.Name).Distinct();
|
||||
return GetPeopleNamesAsync(filter, CancellationToken.None)
|
||||
.GetAwaiter()
|
||||
.GetResult();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<IReadOnlyList<string>> GetPeopleNamesAsync(InternalPeopleQuery filter, CancellationToken cancellationToken = default)
|
||||
{
|
||||
await using var context = _dbProvider.CreateDbContext();
|
||||
var dbQuery = TranslateQuery(context.Peoples.AsNoTracking(), context, filter)
|
||||
.Select(e => e.Name)
|
||||
.Distinct();
|
||||
|
||||
// dbQuery = dbQuery.OrderBy(e => e.ListOrder);
|
||||
if (filter.Limit > 0)
|
||||
{
|
||||
dbQuery = dbQuery.Take(filter.Limit);
|
||||
}
|
||||
|
||||
return dbQuery.ToArray();
|
||||
return await dbQuery
|
||||
.ToArrayAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void UpdatePeople(Guid itemId, IReadOnlyList<PersonInfo> people)
|
||||
{
|
||||
UpdatePeopleAsync(itemId, people, CancellationToken.None)
|
||||
.GetAwaiter()
|
||||
.GetResult();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task UpdatePeopleAsync(Guid itemId, IReadOnlyList<PersonInfo> people, CancellationToken cancellationToken = default)
|
||||
{
|
||||
foreach (var person in people)
|
||||
{
|
||||
@@ -89,27 +122,35 @@ public class PeopleRepository(IDbContextFactory<JellyfinDbContext> dbProvider, I
|
||||
people = people.DistinctBy(e => e.Name + "-" + e.Type).ToArray();
|
||||
var personKeys = people.Select(e => e.Name + "-" + e.Type).ToArray();
|
||||
|
||||
using var context = _dbProvider.CreateDbContext();
|
||||
using var transaction = context.Database.BeginTransaction();
|
||||
var existingPersons = context.Peoples.Select(e => new
|
||||
await using var context = _dbProvider.CreateDbContext();
|
||||
await using var transaction = await context.Database.BeginTransactionAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var existingPersons = await context.Peoples
|
||||
.Select(e => new
|
||||
{
|
||||
item = e,
|
||||
SelectionKey = e.Name + "-" + e.PersonType
|
||||
})
|
||||
.Where(p => personKeys.Contains(p.SelectionKey))
|
||||
.Select(f => f.item)
|
||||
.ToArray();
|
||||
.ToArrayAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
var toAdd = people
|
||||
.Where(e => e.Type is not PersonKind.Artist && e.Type is not PersonKind.AlbumArtist)
|
||||
.Where(e => !existingPersons.Any(f => f.Name == e.Name && f.PersonType == e.Type.ToString()))
|
||||
.Select(Map);
|
||||
context.Peoples.AddRange(toAdd);
|
||||
context.SaveChanges();
|
||||
|
||||
await context.Peoples.AddRangeAsync(toAdd, cancellationToken).ConfigureAwait(false);
|
||||
await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var personsEntities = toAdd.Concat(existingPersons).ToArray();
|
||||
|
||||
var existingMaps = context.PeopleBaseItemMap.Include(e => e.People).Where(e => e.ItemId == itemId).ToList();
|
||||
var existingMaps = await context.PeopleBaseItemMap
|
||||
.Include(e => e.People)
|
||||
.Where(e => e.ItemId == itemId)
|
||||
.ToListAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
var listOrder = 0;
|
||||
|
||||
@@ -124,7 +165,7 @@ public class PeopleRepository(IDbContextFactory<JellyfinDbContext> dbProvider, I
|
||||
var existingMap = existingMaps.FirstOrDefault(e => e.People.Name == person.Name && e.People.PersonType == person.Type.ToString() && e.Role == person.Role);
|
||||
if (existingMap is null)
|
||||
{
|
||||
context.PeopleBaseItemMap.Add(new PeopleBaseItemMap()
|
||||
await context.PeopleBaseItemMap.AddAsync(new PeopleBaseItemMap()
|
||||
{
|
||||
Item = null!,
|
||||
ItemId = itemId,
|
||||
@@ -133,7 +174,7 @@ public class PeopleRepository(IDbContextFactory<JellyfinDbContext> dbProvider, I
|
||||
ListOrder = listOrder,
|
||||
SortOrder = person.SortOrder,
|
||||
Role = person.Role
|
||||
});
|
||||
}, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -149,8 +190,8 @@ public class PeopleRepository(IDbContextFactory<JellyfinDbContext> dbProvider, I
|
||||
|
||||
context.PeopleBaseItemMap.RemoveRange(existingMaps);
|
||||
|
||||
context.SaveChanges();
|
||||
transaction.Commit();
|
||||
await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
|
||||
await transaction.CommitAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private PersonInfo Map(People people)
|
||||
|
||||
Reference in New Issue
Block a user