aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/ci-tests.yml2
-rw-r--r--Directory.Packages.props8
-rw-r--r--Emby.Server.Implementations/Library/UserDataManager.cs2
-rw-r--r--Emby.Server.Implementations/Localization/Core/be.json74
-rw-r--r--Emby.Server.Implementations/Localization/Core/es_DO.json8
-rw-r--r--Emby.Server.Implementations/Localization/Core/gsw.json2
-rw-r--r--Jellyfin.Server.Implementations/Item/BaseItemRepository.cs115
-rw-r--r--Jellyfin.Server.Implementations/Item/PeopleRepository.cs23
-rw-r--r--Jellyfin.Server/Filters/RetryOnTemporarilyUnavailableFilter.cs53
9 files changed, 172 insertions, 115 deletions
diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml
index 8e3ab1022..10cc6af3a 100644
--- a/.github/workflows/ci-tests.yml
+++ b/.github/workflows/ci-tests.yml
@@ -35,7 +35,7 @@ jobs:
--verbosity minimal
- name: Merge code coverage results
- uses: danielpalme/ReportGenerator-GitHub-Action@e15fac9a0be12d67bc1edd3a7b379b298cc186f5 # v5.4.13
+ uses: danielpalme/ReportGenerator-GitHub-Action@18e0cd4c1bebd0c8b3978b380a6a4ea61c51178e # v5.4.15
with:
reports: "**/coverage.cobertura.xml"
targetdir: "merged/"
diff --git a/Directory.Packages.props b/Directory.Packages.props
index d2c1dc401..78a5b7603 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -18,7 +18,7 @@
<PackageVersion Include="DiscUtils.Udf" Version="0.16.13" />
<PackageVersion Include="DotNet.Glob" Version="3.1.3" />
<PackageVersion Include="FsCheck.Xunit" Version="3.3.1" />
- <PackageVersion Include="HarfBuzzSharp.NativeAssets.Linux" Version="8.3.1.1" />
+ <PackageVersion Include="HarfBuzzSharp.NativeAssets.Linux" Version="8.3.1.2" />
<PackageVersion Include="ICU4N.Transliterator" Version="60.1.0-alpha.356" />
<PackageVersion Include="IDisposableAnalyzers" Version="4.0.8" />
<PackageVersion Include="Ignore" Version="0.2.1" />
@@ -74,9 +74,9 @@
<PackageVersion Include="SerilogAnalyzer" Version="0.15.0" />
<PackageVersion Include="SharpFuzz" Version="2.2.0" />
<!-- Pinned to 3.116.1 because https://github.com/jellyfin/jellyfin/pull/14255 -->
- <PackageVersion Include="SkiaSharp" Version="3.116.1" />
- <PackageVersion Include="SkiaSharp.HarfBuzz" Version="3.116.1" />
- <PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="3.116.1" />
+ <PackageVersion Include="SkiaSharp" Version="3.119.1" />
+ <PackageVersion Include="SkiaSharp.HarfBuzz" Version="3.119.1" />
+ <PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="3.119.1" />
<PackageVersion Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" />
<PackageVersion Include="StyleCop.Analyzers" Version="1.2.0-beta.556" />
<PackageVersion Include="Svg.Skia" Version="3.2.1" />
diff --git a/Emby.Server.Implementations/Library/UserDataManager.cs b/Emby.Server.Implementations/Library/UserDataManager.cs
index a83ba1570..72c8d7a9d 100644
--- a/Emby.Server.Implementations/Library/UserDataManager.cs
+++ b/Emby.Server.Implementations/Library/UserDataManager.cs
@@ -238,7 +238,7 @@ namespace Emby.Server.Implementations.Library
/// <inheritdoc />
public UserItemData? GetUserData(User user, BaseItem item)
{
- return item.UserData.Where(e => e.UserId.Equals(user.Id)).Select(Map).FirstOrDefault() ?? new UserItemData()
+ return item.UserData?.Where(e => e.UserId.Equals(user.Id)).Select(Map).FirstOrDefault() ?? new UserItemData()
{
Key = item.GetUserDataKeys()[0],
};
diff --git a/Emby.Server.Implementations/Localization/Core/be.json b/Emby.Server.Implementations/Localization/Core/be.json
index dec491d08..29847048c 100644
--- a/Emby.Server.Implementations/Localization/Core/be.json
+++ b/Emby.Server.Implementations/Localization/Core/be.json
@@ -1,16 +1,16 @@
{
"Sync": "Сінхранізаваць",
- "Playlists": "Спісы прайгравання",
- "Latest": "Апошні",
+ "Playlists": "Плэй-лісты",
+ "Latest": "Апошняе",
"LabelIpAddressValue": "IP-адрас: {0}",
- "ItemAddedWithName": "{0} быў дададзены ў бібліятэку",
+ "ItemAddedWithName": "{0} даданы ў бібліятэку",
"MessageApplicationUpdated": "Сервер Jellyfin абноўлены",
- "NotificationOptionApplicationUpdateInstalled": "Абнаўленне прыкладання ўсталявана",
+ "NotificationOptionApplicationUpdateInstalled": "Абнаўленне праграмы ўсталявана",
"PluginInstalledWithName": "{0} быў усталяваны",
"UserCreatedWithName": "Карыстальнік {0} быў створаны",
"Albums": "Альбомы",
- "Application": "Прыкладанне",
- "AuthenticationSucceededWithUserName": "{0} паспяхова аўтэнтыфікаваны",
+ "Application": "Праграма",
+ "AuthenticationSucceededWithUserName": "{0} паспяхова аўтарызаваны",
"Channels": "Каналы",
"ChapterNameValue": "Раздзел {0}",
"Collections": "Калекцыі",
@@ -29,18 +29,18 @@
"HeaderAlbumArtists": "Выканаўцы альбома",
"LabelRunningTimeValue": "Працягласць: {0}",
"HomeVideos": "Хатнія відэа",
- "ItemRemovedWithName": "{0} быў выдалены з бібліятэкі",
- "MessageApplicationUpdatedTo": "Сервер Jellyfin абноўлены да {0}",
+ "ItemRemovedWithName": "{0} выдалены з бібліятэкі",
+ "MessageApplicationUpdatedTo": "Сервер Jellyfin абноўлены да версіі {0}",
"Movies": "Фільмы",
"Music": "Музыка",
"MusicVideos": "Музычныя кліпы",
- "NameInstallFailed": "Устаноўка {0} не атрымалася",
+ "NameInstallFailed": "Усталяванне {0} не атрымалася",
"NameSeasonNumber": "Сезон {0}",
- "NotificationOptionApplicationUpdateAvailable": "Даступна абнаўленне прыкладання",
+ "NotificationOptionApplicationUpdateAvailable": "Даступна абнаўленне праграмы",
"NotificationOptionPluginInstalled": "Плагін усталяваны",
- "NotificationOptionPluginUpdateInstalled": "Абнаўленне плагіна усталявана",
+ "NotificationOptionPluginUpdateInstalled": "Абнаўленне плагіна ўсталявана",
"NotificationOptionServerRestartRequired": "Патрабуецца перазапуск сервера",
- "Photos": "Фатаграфіі",
+ "Photos": "Фотаздымкі",
"Plugin": "Плагін",
"PluginUninstalledWithName": "{0} быў выдалены",
"PluginUpdatedWithName": "{0} быў абноўлены",
@@ -54,16 +54,16 @@
"Artists": "Выканаўцы",
"UserOfflineFromDevice": "{0} адлучыўся ад {1}",
"UserPolicyUpdatedWithName": "Палітыка карыстальніка абноўлена для {0}",
- "TaskCleanActivityLogDescription": "Выдаляе старэйшыя за зададзены ўзрост запісы ў журнале актыўнасці.",
+ "TaskCleanActivityLogDescription": "Выдаляе запісы старэйшыя за зададзены ўзрост ў журнале актыўнасці.",
"TaskRefreshChapterImagesDescription": "Стварае мініяцюры для відэа, якія маюць раздзелы.",
"TaskCleanLogsDescription": "Выдаляе файлы журналу, якім больш за {0} дзён.",
- "TaskUpdatePluginsDescription": "Спампоўвае і ўсталёўвае абнаўленні для плагінаў, якія настроены на аўтаматычнае абнаўленне.",
+ "TaskUpdatePluginsDescription": "Спампоўвае і ўсталёўвае абнаўленні для плагінаў, якія сканфігураваныя на аўтаматычнае абнаўленне.",
"TaskRefreshChannelsDescription": "Абнаўляе інфармацыю аб інтэрнэт-канале.",
- "TaskDownloadMissingSubtitlesDescription": "Шукае ў інтэрнэце адсутныя субтытры на аснове канфігурацыі метададзеных.",
- "TaskOptimizeDatabaseDescription": "Ушчыльняе базу дадзеных і скарачае вольную прастору. Выкананне гэтай задачы пасля сканавання бібліятэкі або ўнясення іншых змяненняў, якія прадугледжваюць мадыфікацыю базы дадзеных, можа палепшыць прадукцыйнасць.",
+ "TaskDownloadMissingSubtitlesDescription": "Шукае ў інтэрнэце адсутныя субцітры на аснове канфігурацыі метададзеных.",
+ "TaskOptimizeDatabaseDescription": "Ушчыльняе базу дадзеных і скарачае вольную прастору. Выкананне гэтай задачы пасля сканавання бібліятэкі або ўнясення іншых зменаў, якія прадугледжваюць мадыфікацыю базы дадзеных, можа палепшыць выдайнасць.",
"TaskKeyframeExtractor": "Экстрактар ключавых кадраў",
- "TasksApplicationCategory": "Прыкладанне",
- "AppDeviceValues": "Прыкладанне: {0}, Прылада: {1}",
+ "TasksApplicationCategory": "Праграма",
+ "AppDeviceValues": "Праграма: {0}, Прылада: {1}",
"Books": "Кнігі",
"CameraImageUploadedFrom": "Новая выява камеры была загружана з {0}",
"DeviceOfflineWithName": "{0} адлучыўся",
@@ -74,7 +74,7 @@
"HeaderFavoriteArtists": "Абраныя выканаўцы",
"HearingImpaired": "Са слабым слыхам",
"Inherit": "Атрымаць у спадчыну",
- "MessageNamedServerConfigurationUpdatedWithValue": "Канфігурацыя сервера {0} абноўлена",
+ "MessageNamedServerConfigurationUpdatedWithValue": "Канфігурацыя сервера (секцыя {0}) абноўлена",
"MessageServerConfigurationUpdated": "Канфігурацыя сервера абноўлена",
"MixedContent": "Змешаны змест",
"NameSeasonUnknown": "Невядомы сезон",
@@ -92,48 +92,48 @@
"NotificationOptionVideoPlaybackStopped": "Прайграванне відэа спынена",
"ScheduledTaskFailedWithName": "{0} не атрымалася",
"ScheduledTaskStartedWithName": "{0} пачалося",
- "ServerNameNeedsToBeRestarted": "{0} трэба перазапусціць",
+ "ServerNameNeedsToBeRestarted": "{0} патрабуе перазапуску",
"Shows": "Шоу",
"StartupEmbyServerIsLoading": "Jellyfin Server загружаецца. Калі ласка, паўтарыце спробу крыху пазней.",
"SubtitleDownloadFailureFromForItem": "Не атрымалася спампаваць субтытры з {0} для {1}",
- "TvShows": "ТБ-шоу",
+ "TvShows": "Тэлепраграма",
"Undefined": "Нявызначана",
"UserLockedOutWithName": "Карыстальнік {0} быў заблакіраваны",
"UserOnlineFromDevice": "{0} падключаны з {1}",
"UserPasswordChangedWithName": "Пароль быў зменены для карыстальніка {0}",
- "UserStartedPlayingItemWithValues": "{0} грае {1} на {2}",
+ "UserStartedPlayingItemWithValues": "{0} прайграваецца {1} на {2}",
"UserStoppedPlayingItemWithValues": "{0} скончыў прайграванне {1} на {2}",
"ValueHasBeenAddedToLibrary": "{0} быў дададзены ў вашу медыятэку",
"ValueSpecialEpisodeName": "Спецэпізод - {0}",
"VersionNumber": "Версія {0}",
"TasksMaintenanceCategory": "Абслугоўванне",
- "TasksLibraryCategory": "Медыятэка",
+ "TasksLibraryCategory": "Бібліятэка",
"TasksChannelsCategory": "Інтэрнэт-каналы",
"TaskCleanActivityLog": "Ачысціць журнал актыўнасці",
"TaskCleanCache": "Ачысціць кэш",
"TaskCleanCacheDescription": "Выдаляе файлы кэша, якія больш не патрэбныя сістэме.",
- "TaskRefreshChapterImages": "Выняць выявы раздзелаў",
- "TaskRefreshLibrary": "Сканіраваць медыятэку",
- "TaskRefreshLibraryDescription": "Сканіруе вашу медыятэку на наяўнасць новых файлаў і абнаўляе метададзеныя.",
- "TaskCleanLogs": "Ачысціць часопіс",
- "TaskRefreshPeople": "Абнавіць людзей",
+ "TaskRefreshChapterImages": "Вынуць выявы раздзелаў",
+ "TaskRefreshLibrary": "Сканаваць бібліятэку",
+ "TaskRefreshLibraryDescription": "Скануе вашу медыятэку на наяўнасць новых файлаў і абнаўляе метададзеныя.",
+ "TaskCleanLogs": "Ачысціць журнал",
+ "TaskRefreshPeople": "Абнавіць выканаўцаў",
"TaskRefreshPeopleDescription": "Абнаўленне метаданых для акцёраў і рэжысёраў у вашай медыятэцы.",
"TaskUpdatePlugins": "Абнавіць плагіны",
"TaskCleanTranscode": "Ачысціць каталог перакадзіравання",
"TaskCleanTranscodeDescription": "Выдаляе перакадзіраваныя файлы, старэйшыя за адзін дзень.",
"TaskRefreshChannels": "Абнавіць каналы",
- "TaskDownloadMissingSubtitles": "Спампаваць адсутныя субтытры",
- "TaskKeyframeExtractorDescription": "Выдае ключавыя кадры з відэафайлаў для стварэння больш дакладных спісаў прайгравання HLS. Гэта задача можа працаваць у працягу доўгага часу.",
- "TaskRefreshTrickplayImages": "Стварыце выявы Trickplay",
- "TaskRefreshTrickplayImagesDescription": "Стварае прагляд відэаролікаў для Trickplay у падключаных бібліятэках.",
- "TaskCleanCollectionsAndPlaylists": "Ачысціце калекцыі і спісы прайгравання",
- "TaskCleanCollectionsAndPlaylistsDescription": "Выдаляе элементы з калекцый і спісаў прайгравання, якія больш не існуюць.",
- "TaskAudioNormalizationDescription": "Сканіруе файлы на прадмет нармалізацыі гуку.",
+ "TaskDownloadMissingSubtitles": "Спампаваць адсутныя субцітры",
+ "TaskKeyframeExtractorDescription": "Выдае ключавыя кадры з відэафайлаў для стварэння больш дакладных плэй-лістоў HLS. Гэта задача можа працягнуцца шмат часу.",
+ "TaskRefreshTrickplayImages": "Стварыць выявы Trickplay",
+ "TaskRefreshTrickplayImagesDescription": "Стварае перадпрагляды відэаролікаў для Trickplay у падключаных бібліятэках.",
+ "TaskCleanCollectionsAndPlaylists": "Ачысціце калекцыі і плэй-лісты",
+ "TaskCleanCollectionsAndPlaylistsDescription": "Выдаляе элементы з калекцый і плэй-лістоў, якія больш не існуюць.",
+ "TaskAudioNormalizationDescription": "Скануе файлы на прадмет нармалізацыі гуку.",
"TaskAudioNormalization": "Нармалізацыя гуку",
"TaskExtractMediaSegmentsDescription": "Выдае або атрымлівае медыясегменты з убудоў з падтрымкай MediaSegment.",
"TaskMoveTrickplayImagesDescription": "Перамяшчае існуючыя файлы trickplay у адпаведнасці з наладамі бібліятэкі.",
- "TaskDownloadMissingLyrics": "Спампаваць зніклыя тэксты песень",
- "TaskDownloadMissingLyricsDescription": "Спампоўвае тэксты для песень",
+ "TaskDownloadMissingLyrics": "Спампаваць адсутныя тэксты песняў",
+ "TaskDownloadMissingLyricsDescription": "Спампоўвае тэксты для песняў",
"TaskExtractMediaSegments": "Сканіраванне медыя-сегмента",
"TaskMoveTrickplayImages": "Перанесці месцазнаходжанне выявы Trickplay",
"CleanupUserDataTask": "Задача па ачыстцы дадзеных карыстальніка",
diff --git a/Emby.Server.Implementations/Localization/Core/es_DO.json b/Emby.Server.Implementations/Localization/Core/es_DO.json
index 8cdd06b7c..f98a5e5b2 100644
--- a/Emby.Server.Implementations/Localization/Core/es_DO.json
+++ b/Emby.Server.Implementations/Localization/Core/es_DO.json
@@ -125,5 +125,11 @@
"Undefined": "Sin definir",
"TaskCleanActivityLogDescription": "Elimina las entradas del registro de actividad anteriores al periodo configurado.",
"TaskCleanCacheDescription": "Elimina archivos caché que ya no son necesarios para el sistema.",
- "TaskCleanLogsDescription": "Elimina archivos de registro con más de {0} días de antigüedad."
+ "TaskCleanLogsDescription": "Elimina archivos de registro con más de {0} días de antigüedad.",
+ "NotificationOptionApplicationUpdateAvailable": "actualización disponible",
+ "TaskDownloadMissingLyrics": "Descargue letras desaparecidas",
+ "TaskDownloadMissingLyricsDescription": "Decarga letras para canciones",
+ "TaskMoveTrickplayImages": "Mover localización de foto vista previa",
+ "NotificationOptionApplicationUpdateInstalled": "Aplicación actualización disponible",
+ "CleanupUserDataTask": "Tarea de limpieza de los datos del usuario"
}
diff --git a/Emby.Server.Implementations/Localization/Core/gsw.json b/Emby.Server.Implementations/Localization/Core/gsw.json
index b3ee2a4f6..f847d83d1 100644
--- a/Emby.Server.Implementations/Localization/Core/gsw.json
+++ b/Emby.Server.Implementations/Localization/Core/gsw.json
@@ -15,7 +15,7 @@
"Favorites": "Favorite",
"Folders": "Ordner",
"Genres": "Genre",
- "HeaderAlbumArtists": "Album-Künstler",
+ "HeaderAlbumArtists": "Album-Künschtler",
"HeaderContinueWatching": "weiter schauen",
"HeaderFavoriteAlbums": "Lieblingsalben",
"HeaderFavoriteArtists": "Lieblings-Künstler",
diff --git a/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs b/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs
index 30de711ea..a34e95c4d 100644
--- a/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs
+++ b/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs
@@ -271,7 +271,7 @@ public sealed class BaseItemRepository
result.TotalRecordCount = dbQuery.Count();
}
- dbQuery = ApplyGroupingFilter(dbQuery, filter);
+ dbQuery = ApplyGroupingFilter(context, dbQuery, filter);
dbQuery = ApplyQueryPaging(dbQuery, filter);
result.Items = dbQuery.AsEnumerable().Where(e => e is not null).Select(w => DeserializeBaseItem(w, filter.SkipDeserialization)).ToArray();
@@ -290,7 +290,7 @@ public sealed class BaseItemRepository
dbQuery = TranslateQuery(dbQuery, context, filter);
- dbQuery = ApplyGroupingFilter(dbQuery, filter);
+ dbQuery = ApplyGroupingFilter(context, dbQuery, filter);
dbQuery = ApplyQueryPaging(dbQuery, filter);
return dbQuery.AsEnumerable().Where(e => e is not null).Select(w => DeserializeBaseItem(w, filter.SkipDeserialization)).ToArray();
@@ -332,7 +332,7 @@ public sealed class BaseItemRepository
var mainquery = PrepareItemQuery(context, filter);
mainquery = TranslateQuery(mainquery, context, filter);
mainquery = mainquery.Where(g => g.DateCreated >= subqueryGrouped.Min(s => s.MaxDateCreated));
- mainquery = ApplyGroupingFilter(mainquery, filter);
+ mainquery = ApplyGroupingFilter(context, mainquery, filter);
mainquery = ApplyQueryPaging(mainquery, filter);
return mainquery.AsEnumerable().Where(e => e is not null).Select(w => DeserializeBaseItem(w, filter.SkipDeserialization)).ToArray();
@@ -369,37 +369,53 @@ public sealed class BaseItemRepository
return query.ToArray();
}
- private IQueryable<BaseItemEntity> ApplyGroupingFilter(IQueryable<BaseItemEntity> dbQuery, InternalItemsQuery filter)
+ private IQueryable<BaseItemEntity> ApplyGroupingFilter(JellyfinDbContext context, IQueryable<BaseItemEntity> dbQuery, InternalItemsQuery filter)
{
// This whole block is needed to filter duplicate entries on request
// for the time being it cannot be used because it would destroy the ordering
// this results in "duplicate" responses for queries that try to lookup individual series or multiple versions but
// for that case the invoker has to run a DistinctBy(e => e.PresentationUniqueKey) on their own
- // var enableGroupByPresentationUniqueKey = EnableGroupByPresentationUniqueKey(filter);
- // if (enableGroupByPresentationUniqueKey && filter.GroupBySeriesPresentationUniqueKey)
- // {
- // dbQuery = ApplyOrder(dbQuery, filter);
- // dbQuery = dbQuery.GroupBy(e => new { e.PresentationUniqueKey, e.SeriesPresentationUniqueKey }).Select(e => e.First());
- // }
- // else if (enableGroupByPresentationUniqueKey)
- // {
- // dbQuery = ApplyOrder(dbQuery, filter);
- // dbQuery = dbQuery.GroupBy(e => e.PresentationUniqueKey).Select(e => e.First());
- // }
- // else if (filter.GroupBySeriesPresentationUniqueKey)
- // {
- // dbQuery = ApplyOrder(dbQuery, filter);
- // dbQuery = dbQuery.GroupBy(e => e.SeriesPresentationUniqueKey).Select(e => e.First());
- // }
- // else
- // {
- // dbQuery = dbQuery.Distinct();
- // dbQuery = ApplyOrder(dbQuery, filter);
- // }
- dbQuery = dbQuery.Distinct();
+ var enableGroupByPresentationUniqueKey = EnableGroupByPresentationUniqueKey(filter);
+ if (enableGroupByPresentationUniqueKey && filter.GroupBySeriesPresentationUniqueKey)
+ {
+ var tempQuery = dbQuery.GroupBy(e => new { e.PresentationUniqueKey, e.SeriesPresentationUniqueKey }).Select(e => e.FirstOrDefault()).Select(e => e!.Id);
+ dbQuery = context.BaseItems.Where(e => tempQuery.Contains(e.Id));
+ }
+ else if (enableGroupByPresentationUniqueKey)
+ {
+ var tempQuery = dbQuery.GroupBy(e => e.PresentationUniqueKey).Select(e => e.FirstOrDefault()).Select(e => e!.Id);
+ dbQuery = context.BaseItems.Where(e => tempQuery.Contains(e.Id));
+ }
+ else if (filter.GroupBySeriesPresentationUniqueKey)
+ {
+ var tempQuery = dbQuery.GroupBy(e => e.SeriesPresentationUniqueKey).Select(e => e.FirstOrDefault()).Select(e => e!.Id);
+ dbQuery = context.BaseItems.Where(e => tempQuery.Contains(e.Id));
+ }
+ else
+ {
+ dbQuery = dbQuery.Distinct();
+ }
+
dbQuery = ApplyOrder(dbQuery, filter);
+ dbQuery = ApplyNavigations(dbQuery, filter);
+
+ return dbQuery;
+ }
+
+ private static IQueryable<BaseItemEntity> ApplyNavigations(IQueryable<BaseItemEntity> dbQuery, InternalItemsQuery filter)
+ {
+ dbQuery = dbQuery.Include(e => e.TrailerTypes)
+ .Include(e => e.Provider)
+ .Include(e => e.LockedFields)
+ .Include(e => e.UserData);
+
+ if (filter.DtoOptions.EnableImages)
+ {
+ dbQuery = dbQuery.Include(e => e.Images);
+ }
+
return dbQuery;
}
@@ -426,8 +442,7 @@ public sealed class BaseItemRepository
private IQueryable<BaseItemEntity> ApplyQueryFilter(IQueryable<BaseItemEntity> dbQuery, JellyfinDbContext context, InternalItemsQuery filter)
{
dbQuery = TranslateQuery(dbQuery, context, filter);
- dbQuery = ApplyOrder(dbQuery, filter);
- dbQuery = ApplyGroupingFilter(dbQuery, filter);
+ dbQuery = ApplyGroupingFilter(context, dbQuery, filter);
dbQuery = ApplyQueryPaging(dbQuery, filter);
return dbQuery;
}
@@ -435,16 +450,7 @@ public sealed class BaseItemRepository
private IQueryable<BaseItemEntity> PrepareItemQuery(JellyfinDbContext context, InternalItemsQuery filter)
{
IQueryable<BaseItemEntity> dbQuery = context.BaseItems.AsNoTracking();
- dbQuery = dbQuery.AsSingleQuery()
- .Include(e => e.TrailerTypes)
- .Include(e => e.Provider)
- .Include(e => e.LockedFields)
- .Include(e => e.UserData);
-
- if (filter.DtoOptions.EnableImages)
- {
- dbQuery = dbQuery.Include(e => e.Images);
- }
+ dbQuery = dbQuery.AsSingleQuery();
return dbQuery;
}
@@ -729,13 +735,20 @@ public sealed class BaseItemRepository
}
using var context = _dbProvider.CreateDbContext();
- var item = PrepareItemQuery(context, new()
+ var dbQuery = PrepareItemQuery(context, new()
{
DtoOptions = new()
{
EnableImages = true
}
- }).FirstOrDefault(e => e.Id == id);
+ });
+ dbQuery = dbQuery.Include(e => e.TrailerTypes)
+ .Include(e => e.Provider)
+ .Include(e => e.LockedFields)
+ .Include(e => e.UserData)
+ .Include(e => e.Images);
+
+ var item = dbQuery.FirstOrDefault(e => e.Id == id);
if (item is null)
{
return null;
@@ -1310,7 +1323,13 @@ public sealed class BaseItemRepository
result.Items =
[
.. query
- .Select(e => e.First())
+ .Select(e => e.AsQueryable()
+ .Include(e => e.TrailerTypes)
+ .Include(e => e.Provider)
+ .Include(e => e.LockedFields)
+ .Include(e => e.Images)
+ .AsSingleQuery()
+ .First())
.AsEnumerable()
.Where(e => e is not null)
.Select<BaseItemEntity, (BaseItemDto, ItemCounts?)>(e =>
@@ -2278,8 +2297,18 @@ public sealed class BaseItemRepository
if (filter.HasAnyProviderId is not null && filter.HasAnyProviderId.Count > 0)
{
- var include = filter.HasAnyProviderId.Select(e => $"{e.Key}:{e.Value}").ToArray();
- baseQuery = baseQuery.Where(e => e.Provider!.Select(f => f.ProviderId + ":" + f.ProviderValue)!.Any(f => include.Contains(f)));
+ // Allow setting a null or empty value to get all items that have the specified provider set.
+ var includeAny = filter.HasAnyProviderId.Where(e => string.IsNullOrEmpty(e.Value)).Select(e => e.Key).ToArray();
+ if (includeAny.Length > 0)
+ {
+ baseQuery = baseQuery.Where(e => e.Provider!.Any(f => includeAny.Contains(f.ProviderId)));
+ }
+
+ var includeSelected = filter.HasAnyProviderId.Where(e => !string.IsNullOrEmpty(e.Value)).Select(e => $"{e.Key}:{e.Value}").ToArray();
+ if (includeSelected.Length > 0)
+ {
+ baseQuery = baseQuery.Where(e => e.Provider!.Select(f => f.ProviderId + ":" + f.ProviderValue)!.Any(f => includeSelected.Contains(f)));
+ }
}
if (filter.HasImdbId.HasValue)
diff --git a/Jellyfin.Server.Implementations/Item/PeopleRepository.cs b/Jellyfin.Server.Implementations/Item/PeopleRepository.cs
index b52de5dd1..24afaea55 100644
--- a/Jellyfin.Server.Implementations/Item/PeopleRepository.cs
+++ b/Jellyfin.Server.Implementations/Item/PeopleRepository.cs
@@ -35,16 +35,22 @@ public class PeopleRepository(IDbContextFactory<JellyfinDbContext> dbProvider, I
using var context = _dbProvider.CreateDbContext();
var dbQuery = TranslateQuery(context.Peoples.AsNoTracking(), context, filter);
- // dbQuery = dbQuery.OrderBy(e => e.ListOrder);
- if (filter.Limit > 0)
+ // Include PeopleBaseItemMap
+ if (!filter.ItemId.IsEmpty())
{
- dbQuery = dbQuery.Take(filter.Limit);
+ dbQuery = dbQuery.Include(p => p.BaseItems!.Where(m => m.ItemId == filter.ItemId))
+ .OrderBy(e => e.BaseItems!.First(e => e.ItemId == filter.ItemId).ListOrder)
+ .ThenBy(e => e.PersonType)
+ .ThenBy(e => e.Name);
+ }
+ else
+ {
+ dbQuery = dbQuery.OrderBy(e => e.Name);
}
- // Include PeopleBaseItemMap
- if (!filter.ItemId.IsEmpty())
+ if (filter.Limit > 0)
{
- dbQuery = dbQuery.Include(p => p.BaseItems!.Where(m => m.ItemId == filter.ItemId));
+ dbQuery = dbQuery.Take(filter.Limit);
}
return dbQuery.AsEnumerable().Select(Map).ToArray();
@@ -84,14 +90,15 @@ public class PeopleRepository(IDbContextFactory<JellyfinDbContext> dbProvider, I
var existingMap = maps.FirstOrDefault(e => e.PeopleId == person.Id);
if (existingMap is null)
{
+ var sortOrder = (person.SortOrder ?? context.PeopleBaseItemMap.Where(e => e.ItemId == itemId).Max(e => e.SortOrder) ?? 0) + 1;
context.PeopleBaseItemMap.Add(new PeopleBaseItemMap()
{
Item = null!,
ItemId = itemId,
People = null!,
PeopleId = person.Id,
- ListOrder = person.SortOrder,
- SortOrder = person.SortOrder,
+ ListOrder = sortOrder,
+ SortOrder = sortOrder,
Role = person.Role
});
}
diff --git a/Jellyfin.Server/Filters/RetryOnTemporarilyUnavailableFilter.cs b/Jellyfin.Server/Filters/RetryOnTemporarilyUnavailableFilter.cs
index fef5577a1..08caac0d3 100644
--- a/Jellyfin.Server/Filters/RetryOnTemporarilyUnavailableFilter.cs
+++ b/Jellyfin.Server/Filters/RetryOnTemporarilyUnavailableFilter.cs
@@ -1,6 +1,4 @@
-using System;
using System.Collections.Generic;
-using System.Net.Http.Headers;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
@@ -10,27 +8,44 @@ internal class RetryOnTemporarilyUnavailableFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
- operation.Responses.Add("503", new OpenApiResponse()
- {
- Description = "The server is currently starting or is temporarily not available.",
- Headers = new Dictionary<string, OpenApiHeader>()
+ operation.Responses.Add(
+ "503",
+ new OpenApiResponse
{
+ Description = "The server is currently starting or is temporarily not available.",
+ Headers = new Dictionary<string, OpenApiHeader>
{
- "Retry-After",
- new() { AllowEmptyValue = true, Required = false, Description = "A hint for when to retry the operation in full seconds." }
+ {
+ "Retry-After", new OpenApiHeader
+ {
+ AllowEmptyValue = true,
+ Required = false,
+ Description = "A hint for when to retry the operation in full seconds.",
+ Schema = new OpenApiSchema
+ {
+ Type = "integer",
+ Format = "int32"
+ }
+ }
+ },
+ {
+ "Message", new OpenApiHeader
+ {
+ AllowEmptyValue = true,
+ Required = false,
+ Description = "A short plain-text reason why the server is not available.",
+ Schema = new OpenApiSchema
+ {
+ Type = "string",
+ Format = "text"
+ }
+ }
+ }
},
+ Content = new Dictionary<string, OpenApiMediaType>()
{
- "Message",
- new() { AllowEmptyValue = true, Required = false, Description = "A short plain-text reason why the server is not available." }
+ { "text/html", new OpenApiMediaType() }
}
- },
- Content = new Dictionary<string, OpenApiMediaType>()
- {
- {
- "text/html",
- new OpenApiMediaType()
- }
- }
- });
+ });
}
}