fix: removed notifications on reorganization
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -9,6 +10,7 @@ using MediaBrowser.Controller.Entities.Movies;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Querying;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Timer = System.Timers.Timer;
|
||||
@@ -47,6 +49,10 @@ public class SmartNotifyBackgroundService : IHostedService, IDisposable
|
||||
{
|
||||
_logger.LogInformation("SmartNotify background service starting");
|
||||
|
||||
// Pre-populate DB with all existing library items so they're recognized as "known".
|
||||
// This prevents mass notifications on first run or after DB reset.
|
||||
SeedExistingLibraryItems();
|
||||
|
||||
// Subscribe to library events
|
||||
_libraryManager.ItemAdded += OnItemAdded;
|
||||
_libraryManager.ItemRemoved += OnItemRemoved;
|
||||
@@ -62,6 +68,44 @@ public class SmartNotifyBackgroundService : IHostedService, IDisposable
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Seeds the database with all existing Episodes and Movies from the library.
|
||||
/// Runs once at startup — only records items not yet in the DB.
|
||||
/// </summary>
|
||||
private void SeedExistingLibraryItems()
|
||||
{
|
||||
try
|
||||
{
|
||||
var query = new InternalItemsQuery
|
||||
{
|
||||
IncludeItemTypes = new[] { BaseItemKind.Episode, BaseItemKind.Movie },
|
||||
IsVirtualItem = false,
|
||||
Recursive = true
|
||||
};
|
||||
|
||||
var existingItems = _libraryManager.GetItemList(query);
|
||||
var seeded = 0;
|
||||
|
||||
foreach (var item in existingItems)
|
||||
{
|
||||
if (!_historyService.IsKnownItem(item.Id))
|
||||
{
|
||||
_historyService.RecordItem(item);
|
||||
seeded++;
|
||||
}
|
||||
}
|
||||
|
||||
_logger.LogInformation(
|
||||
"Seeded {Count} existing library items into SmartNotify DB (total in library: {Total})",
|
||||
seeded,
|
||||
existingItems.Count);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error seeding existing library items");
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -136,13 +180,11 @@ public class SmartNotifyBackgroundService : IHostedService, IDisposable
|
||||
return;
|
||||
}
|
||||
|
||||
// Check 0: Is this exact item (same Jellyfin ID) already known?
|
||||
// This catches metadata changes and library re-scans where no file actually changed.
|
||||
// Jellyfin fires ItemAdded again for existing items during metadata refreshes.
|
||||
// Check 0: Is this exact item (same Jellyfin ID) already known in our DB?
|
||||
if (_historyService.IsKnownItem(item.Id))
|
||||
{
|
||||
_logger.LogDebug(
|
||||
"Item {Name} (ID: {Id}) is already known - metadata change or re-scan, skipping notification",
|
||||
"Item {Name} (ID: {Id}) is already known in DB, skipping notification",
|
||||
item.Name,
|
||||
item.Id);
|
||||
_historyService.RecordItem(item);
|
||||
@@ -222,10 +264,16 @@ public class SmartNotifyBackgroundService : IHostedService, IDisposable
|
||||
notification.EpisodeNumber = episode.IndexNumber;
|
||||
notification.Year = episode.ProductionYear;
|
||||
|
||||
// Use series image if episode doesn't have its own
|
||||
// Use series provider IDs for external links — episode provider IDs
|
||||
// (e.g. AniDB episode ID) lead to wrong URLs when used with /anime/ paths
|
||||
if (episode.SeriesId != Guid.Empty)
|
||||
{
|
||||
var series = _libraryManager.GetItemById(episode.SeriesId);
|
||||
if (series?.ProviderIds != null && series.ProviderIds.Count > 0)
|
||||
{
|
||||
notification.ProviderIdsJson = JsonSerializer.Serialize(series.ProviderIds);
|
||||
}
|
||||
|
||||
var seriesImage = series?.GetImagePath(ImageType.Primary, 0);
|
||||
if (!string.IsNullOrEmpty(seriesImage))
|
||||
{
|
||||
|
||||
@@ -64,18 +64,25 @@ public class ItemHistoryService : IDisposable
|
||||
{
|
||||
if (item is Episode episode)
|
||||
{
|
||||
// For episodes: use series provider IDs + season + episode number
|
||||
// Prefer episode's own provider ID (e.g. AniDB episode ID).
|
||||
// This is stable even when seasons are reorganized in Jellyfin.
|
||||
var episodeKey = GetProviderKey(episode.ProviderIds);
|
||||
if (!string.IsNullOrEmpty(episodeKey))
|
||||
{
|
||||
return $"episode|{episodeKey}";
|
||||
}
|
||||
|
||||
// Fallback: series provider key + season + episode number
|
||||
var series = episode.Series;
|
||||
if (series == null)
|
||||
{
|
||||
_logger.LogDebug("Episode {Name} has no series, cannot generate content key", episode.Name);
|
||||
_logger.LogDebug("Episode {Name} has no series and no provider IDs, cannot generate content key", episode.Name);
|
||||
return null;
|
||||
}
|
||||
|
||||
var seriesKey = GetProviderKey(series.ProviderIds);
|
||||
if (string.IsNullOrEmpty(seriesKey))
|
||||
{
|
||||
// Fallback to series name if no provider IDs
|
||||
seriesKey = series.Name?.ToLowerInvariant().Trim() ?? "unknown";
|
||||
}
|
||||
|
||||
@@ -84,7 +91,7 @@ public class ItemHistoryService : IDisposable
|
||||
|
||||
if (episodeNum == 0)
|
||||
{
|
||||
_logger.LogDebug("Episode {Name} has no episode number", episode.Name);
|
||||
_logger.LogDebug("Episode {Name} has no episode number and no provider IDs", episode.Name);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user