aboutsummaryrefslogtreecommitdiff
path: root/Emby.Server.Implementations/Library/SimilarItems
diff options
context:
space:
mode:
Diffstat (limited to 'Emby.Server.Implementations/Library/SimilarItems')
-rw-r--r--Emby.Server.Implementations/Library/SimilarItems/MovieSimilarItemsProvider.cs16
-rw-r--r--Emby.Server.Implementations/Library/SimilarItems/SimilarItemsManager.cs29
2 files changed, 40 insertions, 5 deletions
diff --git a/Emby.Server.Implementations/Library/SimilarItems/MovieSimilarItemsProvider.cs b/Emby.Server.Implementations/Library/SimilarItems/MovieSimilarItemsProvider.cs
index b1547e72fe..cc8f0fd24e 100644
--- a/Emby.Server.Implementations/Library/SimilarItems/MovieSimilarItemsProvider.cs
+++ b/Emby.Server.Implementations/Library/SimilarItems/MovieSimilarItemsProvider.cs
@@ -1,3 +1,5 @@
+#pragma warning disable RS0030 // Do not use banned APIs: Guid == is required inside EF expression trees.
+
using System;
using System.Collections.Generic;
using System.Linq;
@@ -172,7 +174,7 @@ public sealed class MovieSimilarItemsProvider : ILocalSimilarItemsProvider<Movie
var allCandidateIdsList = allCandidateIds.ToList();
var accessibleItems = await baseQuery
.WhereOneOrMany(allCandidateIdsList, e => e.Id)
- .Select(e => new { e.Id, e.PresentationUniqueKey })
+ .Select(e => new { e.Id, e.PresentationUniqueKey, e.PrimaryVersionId })
.ToListAsync(cancellationToken).ConfigureAwait(false);
// Phase 3: Pick top IDs per source, dedup by PresentationUniqueKey
@@ -189,6 +191,9 @@ public sealed class MovieSimilarItemsProvider : ILocalSimilarItemsProvider<Movie
var orderedIds = accessibleItems
.Where(x => scores.ContainsKey(x.Id))
.OrderByDescending(x => scores.GetValueOrDefault(x.Id))
+ // Two versions of one movie score the same, so name the primary as the
+ // representative of the group rather than whichever came back first.
+ .ThenBy(x => x.PrimaryVersionId.HasValue)
.DistinctBy(x => x.PresentationUniqueKey)
.Take(limit)
.Select(x => x.Id)
@@ -245,6 +250,11 @@ public sealed class MovieSimilarItemsProvider : ILocalSimilarItemsProvider<Movie
result[id] = [];
}
+ var hiddenVersionIds = context.BaseItems.AsNoTracking()
+ .Where(e => e.PrimaryVersionId != null
+ && context.BaseItems.Any(p => p.Id == e.PrimaryVersionId && p.TopParentId == e.TopParentId))
+ .Select(e => e.Id);
+
foreach (var (valueType, weight) in _itemValueDimensions)
{
var sourceRows = await context.ItemValuesMap.AsNoTracking()
@@ -260,7 +270,7 @@ public sealed class MovieSimilarItemsProvider : ILocalSimilarItemsProvider<Movie
}
var candidateRows = await context.ItemValuesMap.AsNoTracking()
- .Where(m => !m.Item.PrimaryVersionId.HasValue && m.ItemValue.Type == valueType && allKeys.Contains(m.ItemValue.CleanValue))
+ .Where(m => !hiddenVersionIds.Contains(m.ItemId) && m.ItemValue.Type == valueType && allKeys.Contains(m.ItemValue.CleanValue))
.Select(m => new { m.ItemId, Key = m.ItemValue.CleanValue })
.ToListAsync(cancellationToken).ConfigureAwait(false);
@@ -276,7 +286,7 @@ public sealed class MovieSimilarItemsProvider : ILocalSimilarItemsProvider<Movie
if (personSourceRows.Count > 0)
{
var personCandidateRows = await context.PeopleBaseItemMap.AsNoTracking()
- .Where(m => !m.Item.PrimaryVersionId.HasValue)
+ .Where(m => !hiddenVersionIds.Contains(m.ItemId))
.Where(m => context.PeopleBaseItemMap
.Where(s => sourceIds.Contains(s.ItemId) && _scoredPersonTypes.Contains(s.People.PersonType))
.Select(s => s.PeopleId)
diff --git a/Emby.Server.Implementations/Library/SimilarItems/SimilarItemsManager.cs b/Emby.Server.Implementations/Library/SimilarItems/SimilarItemsManager.cs
index fd5f292ebe..a18a17b593 100644
--- a/Emby.Server.Implementations/Library/SimilarItems/SimilarItemsManager.cs
+++ b/Emby.Server.Implementations/Library/SimilarItems/SimilarItemsManager.cs
@@ -651,7 +651,13 @@ public class SimilarItemsManager : ISimilarItemsManager
try
{
- var stream = File.OpenRead(cachePath);
+ var stream = new FileStream(
+ cachePath,
+ FileMode.Open,
+ FileAccess.Read,
+ FileShare.ReadWrite | FileShare.Delete,
+ IODefaults.FileStreamBufferSize,
+ FileOptions.Asynchronous | FileOptions.SequentialScan);
await using (stream.ConfigureAwait(false))
{
var cache = await JsonSerializer.DeserializeAsync<SimilarItemsCache>(stream, JsonDefaults.Options, cancellationToken).ConfigureAwait(false);
@@ -675,6 +681,7 @@ public class SimilarItemsManager : ISimilarItemsManager
private async Task SaveSimilarItemsCacheAsync(string cachePath, List<SimilarItemReference> references, TimeSpan cacheDuration, CancellationToken cancellationToken)
{
+ string? tempPath = null;
try
{
var directory = Path.GetDirectoryName(cachePath);
@@ -689,16 +696,34 @@ public class SimilarItemsManager : ISimilarItemsManager
ExpiresAt = DateTime.UtcNow.Add(cacheDuration)
};
- var stream = File.Create(cachePath);
+ tempPath = cachePath + "." + Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture) + ".tmp";
+ var stream = File.Create(tempPath);
await using (stream.ConfigureAwait(false))
{
await JsonSerializer.SerializeAsync(stream, cache, JsonDefaults.Options, cancellationToken).ConfigureAwait(false);
}
+
+ File.Move(tempPath, cachePath, true);
+ tempPath = null;
}
catch (IOException ex)
{
_logger.LogWarning(ex, "Failed to save similar items cache to {CachePath}", cachePath);
}
+ finally
+ {
+ if (tempPath is not null)
+ {
+ try
+ {
+ File.Delete(tempPath);
+ }
+ catch (IOException ex)
+ {
+ _logger.LogDebug(ex, "Failed to delete temporary similar items cache file {TempPath}", tempPath);
+ }
+ }
+ }
}
private sealed class SimilarItemsCache