fix: double notifications
All checks were successful
Create Release PR / Create Release PR (push) Successful in 18s
All checks were successful
Create Release PR / Create Release PR (push) Successful in 18s
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
||||
.claude
|
||||
buildedplugin
|
||||
MEMORY.md
|
||||
@@ -512,11 +512,11 @@ public class SmartNotifyBackgroundService : IHostedService, IDisposable
|
||||
RefreshNotificationMetadata(notification);
|
||||
}
|
||||
|
||||
// Late upgrade detection: re-check now that metadata is fully populated.
|
||||
// Late known-item detection: re-check now that metadata is fully populated.
|
||||
// At queue time, metadata (ProviderIds, Series, Season/Episode) may not have
|
||||
// been available, causing GenerateContentKey() to return null and upgrades
|
||||
// been available, causing GenerateContentKey() to return null and known items
|
||||
// to go undetected. By now (after delay + grouping window), metadata is ready.
|
||||
if (config.SuppressUpgrades)
|
||||
// This catches reorganized items (path/metadata changes) and quality upgrades.
|
||||
{
|
||||
var suppressedIds = new List<int>();
|
||||
foreach (var notification in pendingNotifications)
|
||||
@@ -524,11 +524,22 @@ public class SmartNotifyBackgroundService : IHostedService, IDisposable
|
||||
if (Guid.TryParse(notification.JellyfinItemId, out var revalidateId))
|
||||
{
|
||||
var revalidateItem = _libraryManager.GetItemById(revalidateId);
|
||||
if (_historyService.RevalidatePendingItem(notification.JellyfinItemId, revalidateItem))
|
||||
var result = _historyService.RevalidatePendingItem(notification.JellyfinItemId, revalidateItem, _libraryManager);
|
||||
|
||||
if (result == ItemHistoryService.RevalidationResult.Reorganized)
|
||||
{
|
||||
// Always suppress reorganized items (same content, path/ID changed)
|
||||
suppressedIds.Add(notification.Id);
|
||||
_logger.LogInformation(
|
||||
"Late suppression: {Name} detected as upgrade at send time",
|
||||
"Suppressed {Name}: recognized as reorganized known item at send time",
|
||||
notification.Name);
|
||||
}
|
||||
else if (result == ItemHistoryService.RevalidationResult.Upgrade && config.SuppressUpgrades)
|
||||
{
|
||||
// Only suppress upgrades when configured to do so
|
||||
suppressedIds.Add(notification.Id);
|
||||
_logger.LogInformation(
|
||||
"Suppressed {Name}: quality upgrade detected at send time",
|
||||
notification.Name);
|
||||
}
|
||||
}
|
||||
@@ -539,8 +550,9 @@ public class SmartNotifyBackgroundService : IHostedService, IDisposable
|
||||
_historyService.RemoveNotifications(suppressedIds);
|
||||
pendingNotifications.RemoveAll(n => suppressedIds.Contains(n.Id));
|
||||
_logger.LogInformation(
|
||||
"Suppressed {Count} upgrade notifications at send time",
|
||||
suppressedIds.Count);
|
||||
"Suppressed {Count} notifications at send time ({Reason})",
|
||||
suppressedIds.Count,
|
||||
"reorganized/upgrade");
|
||||
}
|
||||
|
||||
if (pendingNotifications.Count == 0)
|
||||
|
||||
@@ -319,34 +319,74 @@ public class ItemHistoryService : IDisposable
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Re-checks if a pending notification is actually a quality upgrade.
|
||||
/// Result of revalidating a pending notification at send time.
|
||||
/// </summary>
|
||||
public enum RevalidationResult
|
||||
{
|
||||
/// <summary>Item is genuinely new content.</summary>
|
||||
New,
|
||||
|
||||
/// <summary>Item is a known item that was reorganized (path/metadata change, old ID gone).</summary>
|
||||
Reorganized,
|
||||
|
||||
/// <summary>Item is a quality upgrade (same content, old file still exists).</summary>
|
||||
Upgrade
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Re-checks if a pending notification is actually a known item (reorganized or upgraded).
|
||||
/// Called at send time when metadata is fully populated.
|
||||
/// At queue time, items often have empty ProviderIds and no season/episode numbers,
|
||||
/// so the content key couldn't be generated. Now that metadata is available, we can
|
||||
/// properly identify known items.
|
||||
/// </summary>
|
||||
/// <param name="jellyfinItemId">The Jellyfin item ID string.</param>
|
||||
/// <param name="item">The resolved library item (may have updated metadata).</param>
|
||||
/// <returns>True if this item is a late-detected upgrade that should be suppressed.</returns>
|
||||
public bool RevalidatePendingItem(string jellyfinItemId, BaseItem? item)
|
||||
/// <param name="libraryManager">The library manager to check if old items still exist.</param>
|
||||
/// <returns>The revalidation result indicating if this is new, reorganized, or an upgrade.</returns>
|
||||
public RevalidationResult RevalidatePendingItem(string jellyfinItemId, BaseItem? item, MediaBrowser.Controller.Library.ILibraryManager libraryManager)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
return false;
|
||||
return RevalidationResult.New;
|
||||
}
|
||||
|
||||
var contentKey = GenerateContentKey(item);
|
||||
if (contentKey == null)
|
||||
{
|
||||
return false;
|
||||
return RevalidationResult.New;
|
||||
}
|
||||
|
||||
// Update unresolved content key on the item's own record
|
||||
var ownRecord = _knownItems.FindOne(x => x.JellyfinItemId == jellyfinItemId);
|
||||
if (ownRecord != null && ownRecord.ContentKey.StartsWith("unresolved|", StringComparison.Ordinal))
|
||||
{
|
||||
ownRecord.ContentKey = contentKey;
|
||||
_knownItems.Update(ownRecord);
|
||||
_logger.LogDebug("Resolved content key for {Name}: {Key}", item.Name, contentKey);
|
||||
}
|
||||
|
||||
// Check if this content was already known under a different item ID
|
||||
var existing = _knownItems.FindOne(x => x.ContentKey == contentKey && x.JellyfinItemId != jellyfinItemId);
|
||||
if (existing != null)
|
||||
{
|
||||
// Determine if the old item still exists in the library
|
||||
var oldItemExists = false;
|
||||
if (Guid.TryParse(existing.JellyfinItemId, out var oldId))
|
||||
{
|
||||
oldItemExists = libraryManager.GetItemById(oldId) != null;
|
||||
}
|
||||
|
||||
var resultType = oldItemExists
|
||||
? RevalidationResult.Upgrade
|
||||
: RevalidationResult.Reorganized;
|
||||
|
||||
_logger.LogInformation(
|
||||
"Late upgrade detection for {Name}: content key {Key} already known (first seen: {FirstSeen})",
|
||||
"Late detection for {Name}: content key {Key} already known (first seen: {FirstSeen}, result: {Result})",
|
||||
item.Name,
|
||||
contentKey,
|
||||
existing.FirstSeen);
|
||||
existing.FirstSeen,
|
||||
resultType);
|
||||
|
||||
// Update the existing record to point to the new item
|
||||
existing.JellyfinItemId = jellyfinItemId;
|
||||
@@ -360,12 +400,12 @@ public class ItemHistoryService : IDisposable
|
||||
_knownItems.Delete(duplicate.Id);
|
||||
}
|
||||
|
||||
return true;
|
||||
return resultType;
|
||||
}
|
||||
|
||||
// Ensure the item is properly recorded (might have been missed at queue time due to missing metadata)
|
||||
RecordItem(item);
|
||||
return false;
|
||||
return RevalidationResult.New;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user