aboutsummaryrefslogtreecommitdiff
path: root/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs
diff options
context:
space:
mode:
Diffstat (limited to 'Jellyfin.Server.Implementations/Item/BaseItemRepository.cs')
-rw-r--r--Jellyfin.Server.Implementations/Item/BaseItemRepository.cs120
1 files changed, 92 insertions, 28 deletions
diff --git a/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs b/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs
index 3b3d3c4f4..5bb4494dd 100644
--- a/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs
+++ b/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs
@@ -295,6 +295,25 @@ public sealed class BaseItemRepository
dbQuery = ApplyGroupingFilter(context, dbQuery, filter);
dbQuery = ApplyQueryPaging(dbQuery, filter);
+
+ var hasRandomSort = filter.OrderBy.Any(e => e.OrderBy == ItemSortBy.Random);
+ if (hasRandomSort)
+ {
+ var orderedIds = dbQuery.Select(e => e.Id).ToList();
+ if (orderedIds.Count == 0)
+ {
+ return Array.Empty<BaseItemDto>();
+ }
+
+ var itemsById = ApplyNavigations(context.BaseItems.Where(e => orderedIds.Contains(e.Id)), filter)
+ .AsEnumerable()
+ .Select(w => DeserializeBaseItem(w, filter.SkipDeserialization))
+ .Where(dto => dto is not null)
+ .ToDictionary(i => i!.Id);
+
+ return orderedIds.Where(itemsById.ContainsKey).Select(id => itemsById[id]).ToArray()!;
+ }
+
dbQuery = ApplyNavigations(dbQuery, filter);
return dbQuery.AsEnumerable().Where(e => e is not null).Select(w => DeserializeBaseItem(w, filter.SkipDeserialization)).Where(dto => dto is not null).ToArray()!;
@@ -624,7 +643,6 @@ public sealed class BaseItemRepository
var ids = tuples.Select(f => f.Item.Id).ToArray();
var existingItems = context.BaseItems.Where(e => ids.Contains(e.Id)).Select(f => f.Id).ToArray();
- var newItems = tuples.Where(e => !existingItems.Contains(e.Item.Id)).ToArray();
foreach (var item in tuples)
{
@@ -658,19 +676,6 @@ public sealed class BaseItemRepository
context.SaveChanges();
- foreach (var item in newItems)
- {
- // reattach old userData entries
- var userKeys = item.UserDataKey.ToArray();
- var retentionDate = (DateTime?)null;
- context.UserData
- .Where(e => e.ItemId == PlaceholderId)
- .Where(e => userKeys.Contains(e.CustomDataKey))
- .ExecuteUpdate(e => e
- .SetProperty(f => f.ItemId, item.Item.Id)
- .SetProperty(f => f.RetentionDate, retentionDate));
- }
-
var itemValueMaps = tuples
.Select(e => (e.Item, Values: GetItemValuesToSave(e.Item, e.InheritedTags)))
.ToArray();
@@ -767,6 +772,43 @@ public sealed class BaseItemRepository
}
/// <inheritdoc />
+ public async Task ReattachUserDataAsync(BaseItemDto item, CancellationToken cancellationToken)
+ {
+ ArgumentNullException.ThrowIfNull(item);
+ cancellationToken.ThrowIfCancellationRequested();
+
+ var dbContext = await _dbProvider.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);
+
+ await using (dbContext.ConfigureAwait(false))
+ {
+ var transaction = await dbContext.Database.BeginTransactionAsync(cancellationToken).ConfigureAwait(false);
+ await using (transaction.ConfigureAwait(false))
+ {
+ var userKeys = item.GetUserDataKeys().ToArray();
+ var retentionDate = (DateTime?)null;
+
+ await dbContext.UserData
+ .Where(e => e.ItemId == PlaceholderId)
+ .Where(e => userKeys.Contains(e.CustomDataKey))
+ .ExecuteUpdateAsync(
+ e => e
+ .SetProperty(f => f.ItemId, item.Id)
+ .SetProperty(f => f.RetentionDate, retentionDate),
+ cancellationToken).ConfigureAwait(false);
+
+ // Rehydrate the cached userdata
+ item.UserData = await dbContext.UserData
+ .AsNoTracking()
+ .Where(e => e.ItemId == item.Id)
+ .ToArrayAsync(cancellationToken)
+ .ConfigureAwait(false);
+
+ await transaction.CommitAsync(cancellationToken).ConfigureAwait(false);
+ }
+ }
+ }
+
+ /// <inheritdoc />
public BaseItemDto? RetrieveItem(Guid id)
{
if (id.IsEmpty())
@@ -873,7 +915,7 @@ public sealed class BaseItemRepository
}
dto.ExtraIds = string.IsNullOrWhiteSpace(entity.ExtraIds) ? [] : entity.ExtraIds.Split('|').Select(e => Guid.Parse(e)).ToArray();
- dto.ProductionLocations = entity.ProductionLocations?.Split('|') ?? [];
+ dto.ProductionLocations = entity.ProductionLocations?.Split('|', StringSplitOptions.RemoveEmptyEntries) ?? [];
dto.Studios = entity.Studios?.Split('|') ?? [];
dto.Tags = string.IsNullOrWhiteSpace(entity.Tags) ? [] : entity.Tags.Split('|');
@@ -1035,7 +1077,7 @@ public sealed class BaseItemRepository
}
entity.ExtraIds = dto.ExtraIds is not null ? string.Join('|', dto.ExtraIds) : null;
- entity.ProductionLocations = dto.ProductionLocations is not null ? string.Join('|', dto.ProductionLocations) : null;
+ entity.ProductionLocations = dto.ProductionLocations is not null ? string.Join('|', dto.ProductionLocations.Where(p => !string.IsNullOrWhiteSpace(p))) : null;
entity.Studios = dto.Studios is not null ? string.Join('|', dto.Studios) : null;
entity.Tags = dto.Tags is not null ? string.Join('|', dto.Tags) : null;
entity.LockedFields = dto.LockedFields is not null ? dto.LockedFields
@@ -1606,29 +1648,36 @@ public sealed class BaseItemRepository
IOrderedQueryable<BaseItemEntity>? orderedQuery = null;
+ // When searching, prioritize by match quality: exact match > prefix match > contains
+ if (hasSearch)
+ {
+ orderedQuery = query.OrderBy(OrderMapper.MapSearchRelevanceOrder(filter.SearchTerm!));
+ }
+
var firstOrdering = orderBy.FirstOrDefault();
if (firstOrdering != default)
{
var expression = OrderMapper.MapOrderByField(firstOrdering.OrderBy, filter, context);
- if (firstOrdering.SortOrder == SortOrder.Ascending)
+ if (orderedQuery is null)
{
- orderedQuery = query.OrderBy(expression);
+ // No search relevance ordering, start fresh
+ orderedQuery = firstOrdering.SortOrder == SortOrder.Ascending
+ ? query.OrderBy(expression)
+ : query.OrderByDescending(expression);
}
else
{
- orderedQuery = query.OrderByDescending(expression);
+ // Search relevance ordering already applied, chain with ThenBy
+ orderedQuery = firstOrdering.SortOrder == SortOrder.Ascending
+ ? orderedQuery.ThenBy(expression)
+ : orderedQuery.ThenByDescending(expression);
}
if (firstOrdering.OrderBy is ItemSortBy.Default or ItemSortBy.SortName)
{
- if (firstOrdering.SortOrder is SortOrder.Ascending)
- {
- orderedQuery = orderedQuery.ThenBy(e => e.Name);
- }
- else
- {
- orderedQuery = orderedQuery.ThenByDescending(e => e.Name);
- }
+ orderedQuery = firstOrdering.SortOrder is SortOrder.Ascending
+ ? orderedQuery.ThenBy(e => e.Name)
+ : orderedQuery.ThenByDescending(e => e.Name);
}
}
@@ -2666,6 +2715,21 @@ public sealed class BaseItemRepository
.Where(e => artistNames.Contains(e.Name))
.ToArray();
- return artists.GroupBy(e => e.Name).ToDictionary(e => e.Key!, e => e.Select(f => DeserializeBaseItem(f)).Where(dto => dto is not null).Cast<MusicArtist>().ToArray());
+ var lookup = artists
+ .GroupBy(e => e.Name!)
+ .ToDictionary(
+ g => g.Key,
+ g => g.Select(f => DeserializeBaseItem(f)).Where(dto => dto is not null).Cast<MusicArtist>().ToArray());
+
+ var result = new Dictionary<string, MusicArtist[]>(artistNames.Count);
+ foreach (var name in artistNames)
+ {
+ if (lookup.TryGetValue(name, out var artistArray))
+ {
+ result[name] = artistArray;
+ }
+ }
+
+ return result;
}
}