diff options
| -rw-r--r-- | .github/workflows/ci-tests.yml | 2 | ||||
| -rw-r--r-- | Directory.Packages.props | 8 | ||||
| -rw-r--r-- | Emby.Server.Implementations/Library/UserDataManager.cs | 2 | ||||
| -rw-r--r-- | Emby.Server.Implementations/Localization/Core/be.json | 74 | ||||
| -rw-r--r-- | Emby.Server.Implementations/Localization/Core/es_DO.json | 8 | ||||
| -rw-r--r-- | Emby.Server.Implementations/Localization/Core/gsw.json | 2 | ||||
| -rw-r--r-- | Jellyfin.Server.Implementations/Item/BaseItemRepository.cs | 115 | ||||
| -rw-r--r-- | Jellyfin.Server.Implementations/Item/PeopleRepository.cs | 23 | ||||
| -rw-r--r-- | Jellyfin.Server/Filters/RetryOnTemporarilyUnavailableFilter.cs | 53 |
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() - } - } - }); + }); } } |
