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:
2026-02-23 09:38:22 -05:00
parent ede6904433
commit 86883cd5c6
51 changed files with 6719 additions and 187 deletions
@@ -24,6 +24,15 @@ public interface IChapterManager
/// <param name="chapters">The set of chapters.</param>
void SaveChapters(Video video, IReadOnlyList<ChapterInfo> chapters);
/// <summary>
/// Saves the chapters asynchronously.
/// </summary>
/// <param name="video">The video.</param>
/// <param name="chapters">The set of chapters.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A task representing the asynchronous operation.</returns>
Task SaveChaptersAsync(Video video, IReadOnlyList<ChapterInfo> chapters, CancellationToken cancellationToken = default);
/// <summary>
/// Gets a single chapter of a BaseItem on a specific index.
/// </summary>
@@ -32,6 +41,15 @@ public interface IChapterManager
/// <returns>A chapter instance.</returns>
ChapterInfo? GetChapter(Guid baseItemId, int index);
/// <summary>
/// Gets a single chapter of a BaseItem on a specific index asynchronously.
/// </summary>
/// <param name="baseItemId">The BaseItems id.</param>
/// <param name="index">The index of that chapter.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A chapter instance.</returns>
Task<ChapterInfo?> GetChapterAsync(Guid baseItemId, int index, CancellationToken cancellationToken = default);
/// <summary>
/// Gets all chapters associated with the baseItem.
/// </summary>
@@ -39,6 +57,14 @@ public interface IChapterManager
/// <returns>A readonly list of chapter instances.</returns>
IReadOnlyList<ChapterInfo> GetChapters(Guid baseItemId);
/// <summary>
/// Gets all chapters associated with the baseItem asynchronously.
/// </summary>
/// <param name="baseItemId">The BaseItems id.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A readonly list of chapter instances.</returns>
Task<IReadOnlyList<ChapterInfo>> GetChaptersAsync(Guid baseItemId, CancellationToken cancellationToken = default);
/// <summary>
/// Refreshes the chapter images.
/// </summary>
+10 -2
View File
@@ -1145,8 +1145,16 @@ namespace MediaBrowser.Controller.Entities
{
Id = item.Id.ToString("N", CultureInfo.InvariantCulture),
Protocol = protocol ?? MediaProtocol.File,
MediaStreams = MediaSourceManager.GetMediaStreams(item.Id),
MediaAttachments = MediaSourceManager.GetMediaAttachments(item.Id),
// Note: Using GetAwaiter().GetResult() here as this method is synchronous by design.
// TODO: Consider making GetVersionInfo async in future refactoring.
MediaStreams = MediaSourceManager
.GetMediaStreamsAsync(item.Id, CancellationToken.None)
.GetAwaiter()
.GetResult(),
MediaAttachments = MediaSourceManager
.GetMediaAttachmentsAsync(item.Id, CancellationToken.None)
.GetAwaiter()
.GetResult(),
Name = GetMediaSourceName(item),
Path = enablePathSubstitution ? GetMappedPath(item, itemPath, protocol) : itemPath,
RunTimeTicks = item.RunTimeTicks,
@@ -16,11 +16,12 @@ using Jellyfin.MediaEncoding.Keyframes;
public interface IKeyframeManager
{
/// <summary>
/// Gets the keyframe data.
/// Gets the keyframe data asynchronously.
/// </summary>
/// <param name="itemId">The item id.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The keyframe data.</returns>
IReadOnlyList<KeyframeData> GetKeyframeData(Guid itemId);
Task<IReadOnlyList<KeyframeData>> GetKeyframeDataAsync(Guid itemId, CancellationToken cancellationToken = default);
/// <summary>
/// Saves the keyframe data.
@@ -42,6 +42,22 @@ namespace MediaBrowser.Controller.Library
/// <returns>IEnumerable&lt;MediaStream&gt;.</returns>
IReadOnlyList<MediaStream> GetMediaStreams(MediaStreamQuery query);
/// <summary>
/// Gets the media streams asynchronously.
/// </summary>
/// <param name="itemId">The item identifier.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>IReadOnlyList&lt;MediaStream&gt;.</returns>
Task<IReadOnlyList<MediaStream>> GetMediaStreamsAsync(Guid itemId, CancellationToken cancellationToken = default);
/// <summary>
/// Gets the media streams asynchronously.
/// </summary>
/// <param name="query">The query.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>IReadOnlyList&lt;MediaStream&gt;.</returns>
Task<IReadOnlyList<MediaStream>> GetMediaStreamsAsync(MediaStreamQuery query, CancellationToken cancellationToken = default);
/// <summary>
/// Gets the media attachments.
/// </summary>
@@ -56,6 +72,22 @@ namespace MediaBrowser.Controller.Library
/// <returns>IEnumerable&lt;MediaAttachment&gt;.</returns>
IReadOnlyList<MediaAttachment> GetMediaAttachments(MediaAttachmentQuery query);
/// <summary>
/// Gets the media attachments asynchronously.
/// </summary>
/// <param name="itemId">The item identifier.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>IReadOnlyList&lt;MediaAttachment&gt;.</returns>
Task<IReadOnlyList<MediaAttachment>> GetMediaAttachmentsAsync(Guid itemId, CancellationToken cancellationToken = default);
/// <summary>
/// Gets the media attachments asynchronously.
/// </summary>
/// <param name="query">The query.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>IReadOnlyList&lt;MediaAttachment&gt;.</returns>
Task<IReadOnlyList<MediaAttachment>> GetMediaAttachmentsAsync(MediaAttachmentQuery query, CancellationToken cancellationToken = default);
/// <summary>
/// Gets the playback media sources.
/// </summary>
@@ -24,24 +24,28 @@ public interface IChapterRepository
Task DeleteChaptersAsync(Guid itemId, CancellationToken cancellationToken);
/// <summary>
/// Saves the chapters.
/// Saves the chapters asynchronously.
/// </summary>
/// <param name="itemId">The item.</param>
/// <param name="chapters">The set of chapters.</param>
void SaveChapters(Guid itemId, IReadOnlyList<ChapterInfo> chapters);
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A task representing the asynchronous operation.</returns>
Task SaveChaptersAsync(Guid itemId, IReadOnlyList<ChapterInfo> chapters, CancellationToken cancellationToken = default);
/// <summary>
/// Gets all chapters associated with the baseItem.
/// Gets all chapters associated with the baseItem asynchronously.
/// </summary>
/// <param name="baseItemId">The BaseItems id.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A readonly list of chapter instances.</returns>
IReadOnlyList<ChapterInfo> GetChapters(Guid baseItemId);
Task<IReadOnlyList<ChapterInfo>> GetChaptersAsync(Guid baseItemId, CancellationToken cancellationToken = default);
/// <summary>
/// Gets a single chapter of a BaseItem on a specific index.
/// Gets a single chapter of a BaseItem on a specific index asynchronously.
/// </summary>
/// <param name="baseItemId">The BaseItems id.</param>
/// <param name="index">The index of that chapter.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A chapter instance.</returns>
ChapterInfo? GetChapter(Guid baseItemId, int index);
Task<ChapterInfo?> GetChapterAsync(Guid baseItemId, int index, CancellationToken cancellationToken = default);
}
@@ -61,6 +61,14 @@ public interface IItemRepository
/// <returns>QueryResult&lt;BaseItem&gt;.</returns>
QueryResult<BaseItem> GetItems(InternalItemsQuery filter);
/// <summary>
/// Gets the items asynchronously.
/// </summary>
/// <param name="filter">The query.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>QueryResult&lt;BaseItem&gt;.</returns>
Task<QueryResult<BaseItem>> GetItemsAsync(InternalItemsQuery filter, CancellationToken cancellationToken = default);
/// <summary>
/// Gets the item ids list.
/// </summary>
@@ -68,6 +76,14 @@ public interface IItemRepository
/// <returns>List&lt;Guid&gt;.</returns>
IReadOnlyList<Guid> GetItemIdsList(InternalItemsQuery filter);
/// <summary>
/// Gets the item ids list asynchronously.
/// </summary>
/// <param name="filter">The query.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>List&lt;Guid&gt;.</returns>
Task<IReadOnlyList<Guid>> GetItemIdsListAsync(InternalItemsQuery filter, CancellationToken cancellationToken = default);
/// <summary>
/// Gets the item list.
/// </summary>
@@ -75,6 +91,14 @@ public interface IItemRepository
/// <returns>List&lt;BaseItem&gt;.</returns>
IReadOnlyList<BaseItem> GetItemList(InternalItemsQuery filter);
/// <summary>
/// Gets the item list asynchronously.
/// </summary>
/// <param name="filter">The query.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>List&lt;BaseItem&gt;.</returns>
Task<IReadOnlyList<BaseItem>> GetItemListAsync(InternalItemsQuery filter, CancellationToken cancellationToken = default);
/// <summary>
/// Gets the item list. Used mainly by the Latest api endpoint.
/// </summary>
@@ -98,8 +122,24 @@ public interface IItemRepository
int GetCount(InternalItemsQuery filter);
/// <summary>
/// Gets the count of items asynchronously.
/// </summary>
/// <param name="filter">The query.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The count.</returns>
Task<int> GetCountAsync(InternalItemsQuery filter, CancellationToken cancellationToken = default);
ItemCounts GetItemCounts(InternalItemsQuery filter);
/// <summary>
/// Gets the item counts asynchronously.
/// </summary>
/// <param name="filter">The query.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The item counts.</returns>
Task<ItemCounts> GetItemCountsAsync(InternalItemsQuery filter, CancellationToken cancellationToken = default);
QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetGenres(InternalItemsQuery filter);
QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetMusicGenres(InternalItemsQuery filter);
@@ -16,11 +16,12 @@ using Jellyfin.MediaEncoding.Keyframes;
public interface IKeyframeRepository
{
/// <summary>
/// Gets the keyframe data.
/// Gets the keyframe data asynchronously.
/// </summary>
/// <param name="itemId">The item id.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The keyframe data.</returns>
IReadOnlyList<KeyframeData> GetKeyframeData(Guid itemId);
Task<IReadOnlyList<KeyframeData>> GetKeyframeDataAsync(Guid itemId, CancellationToken cancellationToken = default);
/// <summary>
/// Saves the keyframe data.
@@ -9,6 +9,7 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.Entities;
namespace MediaBrowser.Controller.Persistence;
@@ -16,17 +17,19 @@ namespace MediaBrowser.Controller.Persistence;
public interface IMediaAttachmentRepository
{
/// <summary>
/// Gets the media attachments.
/// Gets the media attachments asynchronously.
/// </summary>
/// <param name="filter">The query.</param>
/// <returns>IEnumerable{MediaAttachment}.</returns>
IReadOnlyList<MediaAttachment> GetMediaAttachments(MediaAttachmentQuery filter);
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>IReadOnlyList{MediaAttachment}.</returns>
Task<IReadOnlyList<MediaAttachment>> GetMediaAttachmentsAsync(MediaAttachmentQuery filter, CancellationToken cancellationToken = default);
/// <summary>
/// Saves the media attachments.
/// Saves the media attachments asynchronously.
/// </summary>
/// <param name="id">The identifier.</param>
/// <param name="attachments">The attachments.</param>
/// <param name="cancellationToken">The cancellation token.</param>
void SaveMediaAttachments(Guid id, IReadOnlyList<MediaAttachment> attachments, CancellationToken cancellationToken);
/// <returns>A task representing the asynchronous operation.</returns>
Task SaveMediaAttachmentsAsync(Guid id, IReadOnlyList<MediaAttachment> attachments, CancellationToken cancellationToken = default);
}
@@ -9,6 +9,7 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.Entities;
namespace MediaBrowser.Controller.Persistence;
@@ -19,17 +20,19 @@ namespace MediaBrowser.Controller.Persistence;
public interface IMediaStreamRepository
{
/// <summary>
/// Gets the media streams.
/// Gets the media streams asynchronously.
/// </summary>
/// <param name="filter">The query.</param>
/// <returns>IEnumerable{MediaStream}.</returns>
IReadOnlyList<MediaStream> GetMediaStreams(MediaStreamQuery filter);
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>IReadOnlyList{MediaStream}.</returns>
Task<IReadOnlyList<MediaStream>> GetMediaStreamsAsync(MediaStreamQuery filter, CancellationToken cancellationToken = default);
/// <summary>
/// Saves the media streams.
/// Saves the media streams asynchronously.
/// </summary>
/// <param name="id">The identifier.</param>
/// <param name="streams">The streams.</param>
/// <param name="cancellationToken">The cancellation token.</param>
void SaveMediaStreams(Guid id, IReadOnlyList<MediaStream> streams, CancellationToken cancellationToken);
/// <returns>A task representing the asynchronous operation.</returns>
Task SaveMediaStreamsAsync(Guid id, IReadOnlyList<MediaStream> streams, CancellationToken cancellationToken = default);
}
@@ -8,6 +8,8 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Controller.Entities;
namespace MediaBrowser.Controller.Persistence;
@@ -21,6 +23,14 @@ public interface IPeopleRepository
/// <returns>The list of people matching the filter.</returns>
IReadOnlyList<PersonInfo> GetPeople(InternalPeopleQuery filter);
/// <summary>
/// Gets the people asynchronously.
/// </summary>
/// <param name="filter">The query.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The list of people matching the filter.</returns>
Task<IReadOnlyList<PersonInfo>> GetPeopleAsync(InternalPeopleQuery filter, CancellationToken cancellationToken = default);
/// <summary>
/// Updates the people.
/// </summary>
@@ -28,10 +38,27 @@ public interface IPeopleRepository
/// <param name="people">The people.</param>
void UpdatePeople(Guid itemId, IReadOnlyList<PersonInfo> people);
/// <summary>
/// Updates the people asynchronously.
/// </summary>
/// <param name="itemId">The item identifier.</param>
/// <param name="people">The people.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A task representing the asynchronous operation.</returns>
Task UpdatePeopleAsync(Guid itemId, IReadOnlyList<PersonInfo> people, CancellationToken cancellationToken = default);
/// <summary>
/// Gets the people names.
/// </summary>
/// <param name="filter">The query.</param>
/// <returns>The list of people names matching the filter.</returns>
IReadOnlyList<string> GetPeopleNames(InternalPeopleQuery filter);
/// <summary>
/// Gets the people names asynchronously.
/// </summary>
/// <param name="filter">The query.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The list of people names matching the filter.</returns>
Task<IReadOnlyList<string>> GetPeopleNamesAsync(InternalPeopleQuery filter, CancellationToken cancellationToken = default);
}