Refactor SQLite Database Provider

- Removed unused classes and files related to SQLite database provider, including SqliteDesignTimeJellyfinDbFactory, ModelBuilderExtensions, PragmaConnectionInterceptor, AssemblyInfo, SqliteDatabaseProvider, DateTimeKindValueConverter.
- Updated tests to remove dependencies on removed classes and adjusted mocking for configuration sections.
- Added Microsoft.EntityFrameworkCore.Sqlite package reference to test project.
- Improved string handling in tests for better consistency and clarity.
- Refactored logging methods in JellyfinApplicationFactory for better readability and maintainability.
This commit is contained in:
2026-05-03 09:39:00 -04:00
parent e1f7a4bee9
commit 3e5d29225a
249 changed files with 72112 additions and 50310 deletions
@@ -444,7 +444,7 @@ public sealed class BaseItemRepository
// Get IDs only, without DistinctBy to avoid translation errors
var allIds = await dbQuery
.Select(e => new { e.Id, e.PresentationUniqueKey, e.SeriesPresentationUniqueKey })
.Select(e => new { e.Id, e.PresentationUniqueKey, SeriesPresentationUniqueKey = e.TvExtras!.SeriesPresentationUniqueKey })
.ToListAsync(cancellationToken)
.ConfigureAwait(false);
@@ -500,7 +500,7 @@ public sealed class BaseItemRepository
{
e.Id,
e.PresentationUniqueKey,
e.SeriesPresentationUniqueKey
SeriesPresentationUniqueKey = e.TvExtras!.SeriesPresentationUniqueKey
})
.ToListAsync(cancellationToken)
.ConfigureAwait(false);
@@ -570,7 +570,7 @@ public sealed class BaseItemRepository
subquery = TranslateQuery(subquery, context, filter);
var subqueryGrouped = subquery
.GroupBy(g => collectionType == CollectionType.tvshows ? g.SeriesName : g.Album)
.GroupBy(g => collectionType == CollectionType.tvshows ? g.TvExtras!.SeriesName : g.AudioExtras!.Album)
.Select(g => new
{
Key = g.Key,
@@ -605,7 +605,7 @@ public sealed class BaseItemRepository
// Get IDs + keys to memory (without DistinctBy in the query)
var itemsWithKeys = await mainquery
.Select(e => new { e.Id, e.PresentationUniqueKey, e.SeriesPresentationUniqueKey })
.Select(e => new { e.Id, e.PresentationUniqueKey, SeriesPresentationUniqueKey = e.TvExtras!.SeriesPresentationUniqueKey })
.ToListAsync(cancellationToken)
.ConfigureAwait(false);
@@ -655,7 +655,7 @@ public sealed class BaseItemRepository
// Get IDs + keys to memory (without DistinctBy in the query)
var itemsWithKeys = await mainquery
.Select(e => new { e.Id, e.PresentationUniqueKey, e.SeriesPresentationUniqueKey })
.Select(e => new { e.Id, e.PresentationUniqueKey, SeriesPresentationUniqueKey = e.TvExtras!.SeriesPresentationUniqueKey })
.ToListAsync(cancellationToken)
.ConfigureAwait(false);
@@ -711,7 +711,7 @@ public sealed class BaseItemRepository
i => new { UserId = filter.User.Id, ItemId = i.Id },
u => new { UserId = u.UserId, ItemId = u.ItemId },
(entity, data) => new { Item = entity, UserData = data })
.GroupBy(g => g.Item.SeriesPresentationUniqueKey)
.GroupBy(g => g.Item.TvExtras!.SeriesPresentationUniqueKey)
.Select(g => new { g.Key, LastPlayedDate = g.Max(u => u.UserData.LastPlayedDate) })
.Where(g => g.Key != null && g.LastPlayedDate != null && g.LastPlayedDate >= dateCutoff)
.OrderByDescending(g => g.LastPlayedDate)
@@ -745,7 +745,7 @@ public sealed class BaseItemRepository
{
// PERFORMANCE FIX: Use DistinctBy() instead of GroupBy().Select().FirstOrDefault()
// to prevent correlated subqueries. See docs/database-query-optimization.md
dbQuery = dbQuery.DistinctBy(e => new { e.PresentationUniqueKey, e.SeriesPresentationUniqueKey });
dbQuery = dbQuery.DistinctBy(e => new { e.PresentationUniqueKey, SeriesPresentationUniqueKey = e.TvExtras!.SeriesPresentationUniqueKey });
}
else if (enableGroupByPresentationUniqueKey)
{
@@ -756,7 +756,7 @@ public sealed class BaseItemRepository
else if (filter.GroupBySeriesPresentationUniqueKey)
{
// PERFORMANCE FIX: Use DistinctBy() instead of GroupBy().Select().FirstOrDefault()
dbQuery = dbQuery.DistinctBy(e => e.SeriesPresentationUniqueKey);
dbQuery = dbQuery.DistinctBy(e => e.TvExtras!.SeriesPresentationUniqueKey);
}
else
{
@@ -795,6 +795,11 @@ public sealed class BaseItemRepository
dbQuery = dbQuery.Include(e => e.Images);
}
dbQuery = dbQuery
.Include(e => e.TvExtras)
.Include(e => e.LiveTvExtras)
.Include(e => e.AudioExtras);
return dbQuery;
}
@@ -1100,12 +1105,20 @@ public sealed class BaseItemRepository
.ToArrayAsync(cancellationToken)
.ConfigureAwait(false);
var extensionUpserts = new List<(Guid ExtItemId, BaseItemTvExtras? TvExtras, BaseItemLiveTvExtras? LiveTvExtras, BaseItemAudioExtras? AudioExtras)>();
foreach (var item in tuples)
{
var entity = Map(item.Item);
// TODO: refactor this "inconsistency"
entity.TopParentId = item.TopParent?.Id;
// Extract extension data and clear nav props before EF Core tracking
extensionUpserts.Add((entity.Id, entity.TvExtras, entity.LiveTvExtras, entity.AudioExtras));
entity.TvExtras = null;
entity.LiveTvExtras = null;
entity.AudioExtras = null;
if (!existingItems.Any(e => e == entity.Id))
{
context.BaseItems.Add(entity);
@@ -1183,6 +1196,72 @@ public sealed class BaseItemRepository
await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
// Upsert extension table rows (BaseItemTvExtras, BaseItemLiveTvExtras, BaseItemAudioExtras)
foreach (var (extItemId, tvExtras, liveTvExtras, audioExtras) in extensionUpserts)
{
if (tvExtras is not null)
{
await context.Database.ExecuteSqlAsync(
$@"INSERT INTO library.""BaseItemTvExtras"" (""ItemId"", ""SeriesId"", ""SeasonId"", ""SeriesName"", ""SeasonName"", ""SeriesPresentationUniqueKey"")
VALUES ({extItemId}, {tvExtras.SeriesId}, {tvExtras.SeasonId}, {tvExtras.SeriesName}, {tvExtras.SeasonName}, {tvExtras.SeriesPresentationUniqueKey})
ON CONFLICT (""ItemId"") DO UPDATE SET
""SeriesId"" = EXCLUDED.""SeriesId"",
""SeasonId"" = EXCLUDED.""SeasonId"",
""SeriesName"" = EXCLUDED.""SeriesName"",
""SeasonName"" = EXCLUDED.""SeasonName"",
""SeriesPresentationUniqueKey"" = EXCLUDED.""SeriesPresentationUniqueKey""",
cancellationToken).ConfigureAwait(false);
}
else
{
await context.BaseItemTvExtras
.Where(e => e.ItemId == extItemId)
.ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false);
}
if (liveTvExtras is not null)
{
await context.Database.ExecuteSqlAsync(
$@"INSERT INTO library.""BaseItemLiveTvExtras"" (""ItemId"", ""StartDate"", ""EndDate"", ""EpisodeTitle"", ""ShowId"", ""ExternalSeriesId"", ""ExternalServiceId"", ""Audio"")
VALUES ({extItemId}, {liveTvExtras.StartDate}, {liveTvExtras.EndDate}, {liveTvExtras.EpisodeTitle}, {liveTvExtras.ShowId}, {liveTvExtras.ExternalSeriesId}, {liveTvExtras.ExternalServiceId}, {(int?)liveTvExtras.Audio})
ON CONFLICT (""ItemId"") DO UPDATE SET
""StartDate"" = EXCLUDED.""StartDate"",
""EndDate"" = EXCLUDED.""EndDate"",
""EpisodeTitle"" = EXCLUDED.""EpisodeTitle"",
""ShowId"" = EXCLUDED.""ShowId"",
""ExternalSeriesId"" = EXCLUDED.""ExternalSeriesId"",
""ExternalServiceId"" = EXCLUDED.""ExternalServiceId"",
""Audio"" = EXCLUDED.""Audio""",
cancellationToken).ConfigureAwait(false);
}
else
{
await context.BaseItemLiveTvExtras
.Where(e => e.ItemId == extItemId)
.ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false);
}
if (audioExtras is not null)
{
await context.Database.ExecuteSqlAsync(
$@"INSERT INTO library.""BaseItemAudioExtras"" (""ItemId"", ""Album"", ""Artists"", ""AlbumArtists"", ""LUFS"", ""NormalizationGain"")
VALUES ({extItemId}, {audioExtras.Album}, {audioExtras.Artists}, {audioExtras.AlbumArtists}, {audioExtras.LUFS}, {audioExtras.NormalizationGain})
ON CONFLICT (""ItemId"") DO UPDATE SET
""Album"" = EXCLUDED.""Album"",
""Artists"" = EXCLUDED.""Artists"",
""AlbumArtists"" = EXCLUDED.""AlbumArtists"",
""LUFS"" = EXCLUDED.""LUFS"",
""NormalizationGain"" = EXCLUDED.""NormalizationGain""",
cancellationToken).ConfigureAwait(false);
}
else
{
await context.BaseItemAudioExtras
.Where(e => e.ItemId == extItemId)
.ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false);
}
}
var itemValueMaps = tuples
.Select(e => (e.Item, Values: GetItemValuesToSave(e.Item, e.InheritedTags)))
.ToArray();
@@ -1357,7 +1436,10 @@ public sealed class BaseItemRepository
.Include(e => e.Provider)
.Include(e => e.LockedFields)
.Include(e => e.UserData)
.Include(e => e.Images);
.Include(e => e.Images)
.Include(e => e.TvExtras)
.Include(e => e.LiveTvExtras)
.Include(e => e.AudioExtras);
var item = await dbQuery
.FirstOrDefaultAsync(e => e.Id == id, cancellationToken)
@@ -1385,7 +1467,7 @@ public sealed class BaseItemRepository
dto.Id = entity.Id;
dto.ParentId = entity.ParentId.GetValueOrDefault();
dto.Path = appHost?.ExpandVirtualPath(entity.Path) ?? entity.Path;
dto.EndDate = entity.EndDate;
dto.EndDate = entity.LiveTvExtras?.EndDate;
dto.CommunityRating = entity.CommunityRating;
dto.CustomRating = entity.CustomRating;
dto.IndexNumber = entity.IndexNumber;
@@ -1407,11 +1489,11 @@ public sealed class BaseItemRepository
dto.CriticRating = entity.CriticRating;
dto.PresentationUniqueKey = entity.PresentationUniqueKey;
dto.OriginalTitle = entity.OriginalTitle;
dto.Album = entity.Album;
dto.LUFS = entity.LUFS;
dto.NormalizationGain = entity.NormalizationGain;
dto.Album = entity.AudioExtras?.Album;
dto.LUFS = entity.AudioExtras?.LUFS;
dto.NormalizationGain = entity.AudioExtras?.NormalizationGain;
dto.IsVirtualItem = entity.IsVirtualItem;
dto.ExternalSeriesId = entity.ExternalSeriesId;
dto.ExternalSeriesId = entity.LiveTvExtras?.ExternalSeriesId;
dto.Tagline = entity.Tagline;
dto.TotalBitrate = entity.TotalBitrate;
dto.ExternalId = entity.ExternalId;
@@ -1442,9 +1524,10 @@ public sealed class BaseItemRepository
dto.LockedFields = entity.LockedFields?.Select(e => (MetadataField)e.Id).ToArray() ?? [];
}
if (entity.Audio is not null)
var audioEnum = entity.LiveTvExtras?.Audio;
if (audioEnum is not null)
{
dto.Audio = (ProgramAudio)entity.Audio;
dto.Audio = (ProgramAudio)audioEnum;
}
dto.ExtraIds = string.IsNullOrWhiteSpace(entity.ExtraIds) ? [] : entity.ExtraIds.Split('|').Select(e => Guid.Parse(e)).ToArray();
@@ -1456,13 +1539,13 @@ public sealed class BaseItemRepository
{
hasProgramAttributes.IsMovie = entity.IsMovie;
hasProgramAttributes.IsSeries = entity.IsSeries;
hasProgramAttributes.EpisodeTitle = entity.EpisodeTitle;
hasProgramAttributes.EpisodeTitle = entity.LiveTvExtras?.EpisodeTitle;
hasProgramAttributes.IsRepeat = entity.IsRepeat;
}
if (dto is LiveTvChannel liveTvChannel)
{
liveTvChannel.ServiceName = entity.ExternalServiceId;
liveTvChannel.ServiceName = entity.LiveTvExtras?.ExternalServiceId;
}
if (dto is Trailer trailer)
@@ -1477,30 +1560,30 @@ public sealed class BaseItemRepository
if (dto is IHasSeries hasSeriesName)
{
hasSeriesName.SeriesName = entity.SeriesName;
hasSeriesName.SeriesId = entity.SeriesId.GetValueOrDefault();
hasSeriesName.SeriesPresentationUniqueKey = entity.SeriesPresentationUniqueKey;
hasSeriesName.SeriesName = entity.TvExtras?.SeriesName;
hasSeriesName.SeriesId = entity.TvExtras?.SeriesId ?? Guid.Empty;
hasSeriesName.SeriesPresentationUniqueKey = entity.TvExtras?.SeriesPresentationUniqueKey;
}
if (dto is Episode episode)
{
episode.SeasonName = entity.SeasonName;
episode.SeasonId = entity.SeasonId.GetValueOrDefault();
episode.SeasonName = entity.TvExtras?.SeasonName;
episode.SeasonId = entity.TvExtras?.SeasonId ?? Guid.Empty;
}
if (dto is IHasArtist hasArtists)
{
hasArtists.Artists = entity.Artists?.Split('|', StringSplitOptions.RemoveEmptyEntries) ?? [];
hasArtists.Artists = entity.AudioExtras?.Artists?.Split('|', StringSplitOptions.RemoveEmptyEntries) ?? [];
}
if (dto is IHasAlbumArtist hasAlbumArtists)
{
hasAlbumArtists.AlbumArtists = entity.AlbumArtists?.Split('|', StringSplitOptions.RemoveEmptyEntries) ?? [];
hasAlbumArtists.AlbumArtists = entity.AudioExtras?.AlbumArtists?.Split('|', StringSplitOptions.RemoveEmptyEntries) ?? [];
}
if (dto is LiveTvProgram program)
{
program.ShowId = entity.ShowId;
program.ShowId = entity.LiveTvExtras?.ShowId;
}
if (entity.Images is not null)
@@ -1513,7 +1596,7 @@ public sealed class BaseItemRepository
// dto.MediaType = Enum.TryParse<MediaType>(entity.MediaType);
if (dto is IHasStartDate hasStartDate)
{
hasStartDate.StartDate = entity.StartDate.GetValueOrDefault();
hasStartDate.StartDate = entity.LiveTvExtras?.StartDate ?? default;
}
// Fields that are present in the DB but are never actually used
@@ -1551,7 +1634,6 @@ public sealed class BaseItemRepository
entity.ParentId = !dto.ParentId.IsEmpty() ? dto.ParentId : null;
entity.Path = GetPathToSave(dto.Path);
entity.EndDate = dto.EndDate;
entity.CommunityRating = dto.CommunityRating;
entity.CustomRating = dto.CustomRating;
entity.IndexNumber = dto.IndexNumber;
@@ -1574,11 +1656,7 @@ public sealed class BaseItemRepository
entity.CriticRating = dto.CriticRating;
entity.PresentationUniqueKey = dto.PresentationUniqueKey;
entity.OriginalTitle = dto.OriginalTitle;
entity.Album = dto.Album;
entity.LUFS = dto.LUFS;
entity.NormalizationGain = dto.NormalizationGain;
entity.IsVirtualItem = dto.IsVirtualItem;
entity.ExternalSeriesId = dto.ExternalSeriesId;
entity.Tagline = dto.Tagline;
entity.TotalBitrate = dto.TotalBitrate;
entity.ExternalId = dto.ExternalId;
@@ -1599,11 +1677,6 @@ public sealed class BaseItemRepository
ProviderValue = e.Value
}).ToList();
if (dto.Audio.HasValue)
{
entity.Audio = (ProgramAudioEntity)dto.Audio;
}
if (dto.ExtraType.HasValue)
{
entity.ExtraType = (BaseItemExtraType)dto.ExtraType;
@@ -1626,48 +1699,14 @@ public sealed class BaseItemRepository
{
entity.IsMovie = hasProgramAttributes.IsMovie;
entity.IsSeries = hasProgramAttributes.IsSeries;
entity.EpisodeTitle = hasProgramAttributes.EpisodeTitle;
entity.IsRepeat = hasProgramAttributes.IsRepeat;
}
if (dto is LiveTvChannel liveTvChannel)
{
entity.ExternalServiceId = liveTvChannel.ServiceName;
}
if (dto is Video video)
{
entity.PrimaryVersionId = video.PrimaryVersionId;
}
if (dto is IHasSeries hasSeriesName)
{
entity.SeriesName = hasSeriesName.SeriesName;
entity.SeriesId = hasSeriesName.SeriesId;
entity.SeriesPresentationUniqueKey = hasSeriesName.SeriesPresentationUniqueKey;
}
if (dto is Episode episode)
{
entity.SeasonName = episode.SeasonName;
entity.SeasonId = episode.SeasonId;
}
if (dto is IHasArtist hasArtists)
{
entity.Artists = hasArtists.Artists is not null ? string.Join('|', hasArtists.Artists) : null;
}
if (dto is IHasAlbumArtist hasAlbumArtists)
{
entity.AlbumArtists = hasAlbumArtists.AlbumArtists is not null ? string.Join('|', hasAlbumArtists.AlbumArtists) : null;
}
if (dto is LiveTvProgram program)
{
entity.ShowId = program.ShowId;
}
if (dto.ImageInfos is not null)
{
entity.Images = dto.ImageInfos.Select(f => Map(dto.Id, f)).ToArray();
@@ -1686,16 +1725,85 @@ public sealed class BaseItemRepository
// dto.Type = entity.Type;
// dto.Data = entity.Data;
entity.MediaType = dto.MediaType.ToString();
if (dto is IHasStartDate hasStartDate)
{
entity.StartDate = hasStartDate.StartDate;
}
entity.UnratedType = dto.GetBlockUnratedType().ToString();
// Fields that are present in the DB but are never actually used
// dto.UserDataKey = entity.UserDataKey;
// Populate extension navigation properties (persisted to extension tables in UpdateOrInsertItemsAsync)
if (dto is IHasSeries seriesExt)
{
entity.TvExtras = new BaseItemTvExtras
{
ItemId = entity.Id,
SeriesName = seriesExt.SeriesName,
SeriesId = seriesExt.SeriesId,
SeriesPresentationUniqueKey = seriesExt.SeriesPresentationUniqueKey
};
}
if (dto is Episode episodeExt)
{
entity.TvExtras ??= new BaseItemTvExtras { ItemId = entity.Id };
entity.TvExtras.SeasonName = episodeExt.SeasonName;
entity.TvExtras.SeasonId = episodeExt.SeasonId;
}
if (dto.EndDate is not null || dto.ExternalSeriesId is not null || dto.Audio.HasValue
|| dto is IHasProgramAttributes || dto is LiveTvChannel || dto is LiveTvProgram || dto is IHasStartDate)
{
var liveTvExt = new BaseItemLiveTvExtras { ItemId = entity.Id };
liveTvExt.EndDate = dto.EndDate;
liveTvExt.ExternalSeriesId = dto.ExternalSeriesId;
if (dto.Audio.HasValue)
{
liveTvExt.Audio = (ProgramAudioEntity)dto.Audio;
}
if (dto is IHasProgramAttributes hpaExt)
{
liveTvExt.EpisodeTitle = hpaExt.EpisodeTitle;
}
if (dto is LiveTvChannel liveTvChExt)
{
liveTvExt.ExternalServiceId = liveTvChExt.ServiceName;
}
if (dto is LiveTvProgram progExt)
{
liveTvExt.ShowId = progExt.ShowId;
}
if (dto is IHasStartDate hasSDExt)
{
liveTvExt.StartDate = hasSDExt.StartDate;
}
entity.LiveTvExtras = liveTvExt;
}
if (dto.Album is not null || dto.LUFS is not null || dto.NormalizationGain is not null
|| dto is IHasArtist || dto is IHasAlbumArtist)
{
var audioExt = new BaseItemAudioExtras { ItemId = entity.Id };
audioExt.Album = dto.Album;
audioExt.LUFS = dto.LUFS;
audioExt.NormalizationGain = dto.NormalizationGain;
if (dto is IHasArtist hasArtistsExt)
{
audioExt.Artists = hasArtistsExt.Artists is not null ? string.Join('|', hasArtistsExt.Artists) : null;
}
if (dto is IHasAlbumArtist hasAlbumArtistsExt)
{
audioExt.AlbumArtists = hasAlbumArtistsExt.AlbumArtists is not null ? string.Join('|', hasAlbumArtistsExt.AlbumArtists) : null;
}
entity.AudioExtras = audioExt;
}
if (dto is Folder folder)
{
entity.DateLastMediaAdded = folder.DateLastMediaAdded == DateTime.MinValue ? null : folder.DateLastMediaAdded;
@@ -2696,22 +2804,22 @@ public sealed class BaseItemRepository
if (minEndDate.HasValue)
{
baseQuery = baseQuery.Where(e => e.EndDate >= minEndDate);
baseQuery = baseQuery.Where(e => e.LiveTvExtras!.EndDate >= minEndDate);
}
if (maxEndDate.HasValue)
{
baseQuery = baseQuery.Where(e => e.EndDate <= maxEndDate);
baseQuery = baseQuery.Where(e => e.LiveTvExtras!.EndDate <= maxEndDate);
}
if (filter.MinStartDate.HasValue)
{
baseQuery = baseQuery.Where(e => e.StartDate >= filter.MinStartDate.Value);
baseQuery = baseQuery.Where(e => e.LiveTvExtras!.StartDate >= filter.MinStartDate.Value);
}
if (filter.MaxStartDate.HasValue)
{
baseQuery = baseQuery.Where(e => e.StartDate <= filter.MaxStartDate.Value);
baseQuery = baseQuery.Where(e => e.LiveTvExtras!.StartDate <= filter.MaxStartDate.Value);
}
if (filter.MinPremiereDate.HasValue)
@@ -2734,11 +2842,11 @@ public sealed class BaseItemRepository
{
if (filter.IsAiring.Value)
{
baseQuery = baseQuery.Where(e => e.StartDate <= now && e.EndDate >= now);
baseQuery = baseQuery.Where(e => e.LiveTvExtras!.StartDate <= now && e.LiveTvExtras!.EndDate >= now);
}
else
{
baseQuery = baseQuery.Where(e => e.StartDate > now && e.EndDate < now);
baseQuery = baseQuery.Where(e => e.LiveTvExtras!.StartDate > now && e.LiveTvExtras!.EndDate < now);
}
}
@@ -2772,7 +2880,7 @@ public sealed class BaseItemRepository
if (!string.IsNullOrWhiteSpace(filter.ExternalSeriesId))
{
baseQuery = baseQuery.Where(e => e.ExternalSeriesId == filter.ExternalSeriesId);
baseQuery = baseQuery.Where(e => e.LiveTvExtras!.ExternalSeriesId == filter.ExternalSeriesId);
}
if (!string.IsNullOrWhiteSpace(filter.ExternalId))
@@ -2860,7 +2968,7 @@ public sealed class BaseItemRepository
baseQuery = baseQuery.Where(e => context.BaseItems.Where(e => e.Id != EF.Constant(PlaceholderId))
.Where(e => e.IsFolder == false && e.IsVirtualItem == false)
.Where(f => f.UserData!.FirstOrDefault(e => e.UserId == filter.User!.Id && e.Played)!.Played)
.Any(f => f.SeriesPresentationUniqueKey == e.PresentationUniqueKey) == filter.IsPlayed);
.Any(f => f.TvExtras!.SeriesPresentationUniqueKey == e.PresentationUniqueKey) == filter.IsPlayed);
}
else
{
@@ -2918,7 +3026,7 @@ public sealed class BaseItemRepository
if (filter.AlbumIds.Length > 0)
{
var subQuery = context.BaseItems.WhereOneOrMany(filter.AlbumIds, f => f.Id);
baseQuery = baseQuery.Where(e => subQuery.Any(f => f.Name == e.Album));
baseQuery = baseQuery.Where(e => subQuery.Any(f => f.Name == e.AudioExtras!.Album));
}
if (filter.ExcludeArtistIds.Length > 0)
@@ -3279,7 +3387,7 @@ public sealed class BaseItemRepository
if (!string.IsNullOrWhiteSpace(filter.SeriesPresentationUniqueKey))
{
baseQuery = baseQuery
.Where(e => e.SeriesPresentationUniqueKey == filter.SeriesPresentationUniqueKey);
.Where(e => e.TvExtras!.SeriesPresentationUniqueKey == filter.SeriesPresentationUniqueKey);
}
if (filter.ExcludeInheritedTags.Length > 0)
@@ -3287,7 +3395,7 @@ public sealed class BaseItemRepository
var excludedTags = filter.ExcludeInheritedTags;
baseQuery = baseQuery.Where(e =>
!e.ItemValues!.Any(f => f.ItemValue.Type == ItemValueType.Tags && excludedTags.Contains(f.ItemValue.CleanValue))
&& (!e.SeriesId.HasValue || !context.ItemValuesMap.Any(f => f.ItemId == e.SeriesId.Value && f.ItemValue.Type == ItemValueType.Tags && excludedTags.Contains(f.ItemValue.CleanValue))));
&& (!e.TvExtras!.SeriesId.HasValue || !context.ItemValuesMap.Any(f => f.ItemId == e.TvExtras!.SeriesId.Value && f.ItemValue.Type == ItemValueType.Tags && excludedTags.Contains(f.ItemValue.CleanValue))));
}
if (filter.IncludeInheritedTags.Length > 0)
@@ -3298,7 +3406,7 @@ public sealed class BaseItemRepository
e.ItemValues!.Any(f => f.ItemValue.Type == ItemValueType.Tags && includeTags.Contains(f.ItemValue.CleanValue))
// For seasons and episodes, we also need to check the parent series' tags.
|| (e.SeriesId.HasValue && context.ItemValuesMap.Any(f => f.ItemId == e.SeriesId.Value && f.ItemValue.Type == ItemValueType.Tags && includeTags.Contains(f.ItemValue.CleanValue)))
|| (e.TvExtras!.SeriesId.HasValue && context.ItemValuesMap.Any(f => f.ItemId == e.TvExtras!.SeriesId.Value && f.ItemValue.Type == ItemValueType.Tags && includeTags.Contains(f.ItemValue.CleanValue)))
// A playlist should be accessible to its owner regardless of allowed tags
|| (isPlaylistOnlyQuery && e.Data!.Contains($"OwnerUserId\":\"{filter.User!.Id:N}\"")));
@@ -46,11 +46,11 @@ public static class OrderMapper
(ItemSortBy.AlbumArtist, _) => e => e.ItemValues!.Where(f => f.ItemValue.Type == ItemValueType.AlbumArtist).Select(f => f.ItemValue.CleanValue).FirstOrDefault(),
(ItemSortBy.Studio, _) => e => e.ItemValues!.Where(f => f.ItemValue.Type == ItemValueType.Studios).Select(f => f.ItemValue.CleanValue).FirstOrDefault(),
(ItemSortBy.OfficialRating, _) => e => e.InheritedParentalRatingValue,
(ItemSortBy.SeriesSortName, _) => e => e.SeriesName,
(ItemSortBy.Album, _) => e => e.Album,
(ItemSortBy.SeriesSortName, _) => e => e.TvExtras!.SeriesName,
(ItemSortBy.Album, _) => e => e.AudioExtras!.Album,
(ItemSortBy.DateCreated, _) => e => e.DateCreated,
(ItemSortBy.PremiereDate, _) => e => (e.PremiereDate ?? (e.ProductionYear.HasValue ? DateTime.MinValue.AddYears(e.ProductionYear.Value - 1) : null)),
(ItemSortBy.StartDate, _) => e => e.StartDate,
(ItemSortBy.StartDate, _) => e => e.LiveTvExtras!.StartDate,
(ItemSortBy.Name, _) => e => e.CleanName,
(ItemSortBy.CommunityRating, _) => e => e.CommunityRating,
(ItemSortBy.ProductionYear, _) => e => e.ProductionYear,
@@ -60,10 +60,10 @@ public static class OrderMapper
(ItemSortBy.IndexNumber, _) => e => e.IndexNumber,
(ItemSortBy.SeriesDatePlayed, not null) => e =>
jellyfinDbContext.BaseItems
.Where(w => w.SeriesPresentationUniqueKey == e.PresentationUniqueKey)
.Where(w => w.TvExtras!.SeriesPresentationUniqueKey == e.PresentationUniqueKey)
.Join(jellyfinDbContext.UserData.Where(w => w.UserId == query.User.Id && w.Played), f => f.Id, f => f.ItemId, (item, userData) => userData.LastPlayedDate)
.Max(f => f),
(ItemSortBy.SeriesDatePlayed, null) => e => jellyfinDbContext.BaseItems.Where(w => w.SeriesPresentationUniqueKey == e.PresentationUniqueKey)
(ItemSortBy.SeriesDatePlayed, null) => e => jellyfinDbContext.BaseItems.Where(w => w.TvExtras!.SeriesPresentationUniqueKey == e.PresentationUniqueKey)
.Join(jellyfinDbContext.UserData.Where(w => w.Played), f => f.Id, f => f.ItemId, (item, userData) => userData.LastPlayedDate)
.Max(f => f),
// ItemSortBy.SeriesDatePlayed => e => jellyfinDbContext.UserData
@@ -149,7 +149,7 @@ namespace Jellyfin.Server.Implementations.Security
return authInfo;
}
var tokenPreview = token?.Length > 8 ? token.Substring(0, 8) + "..." : token ?? "null";
var tokenPreview = token?.Length > 8 ? string.Concat(token.AsSpan(0, 8), "...") : token ?? "null";
_logger.LogDebug(
"Validating authentication token. Client: {Client}, Device: {Device}, DeviceId: {DeviceId}, Token: {Token}",
client ?? "unknown",
@@ -255,7 +255,7 @@ namespace Jellyfin.Server.Implementations.Security
}
else
{
var tokenPreview2 = token?.Length > 8 ? token.Substring(0, 8) + "..." : token ?? "null";
var tokenPreview2 = token?.Length > 8 ? string.Concat(token.AsSpan(0, 8), "...") : token ?? "null";
_logger.LogDebug(
"Token validation failed: Token not found in devices or API keys. Token: {Token}",
tokenPreview2);