Skip to content

Commit 0892eff

Browse files
committed
fix: more through refresh dispatching logic
Made the refresh dispatching logic in the event dispatcher service more through, by distincting the input lists we get from the core, and also tracking which entities has been updated and their parents, and only firing the event if the entity or it's parents haven't been updated since the last usage stalled event or if the clear plugin cache task is ran.
1 parent dc584c5 commit 0892eff

File tree

2 files changed

+112
-35
lines changed

2 files changed

+112
-35
lines changed

Shokofin/Events/EventDispatchService.cs

Lines changed: 107 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Collections.Concurrent;
23
using System.Collections.Generic;
34
using System.IO;
45
using System.Linq;
@@ -25,7 +26,11 @@
2526
using ImageType = MediaBrowser.Model.Entities.ImageType;
2627
using LibraryOptions = MediaBrowser.Model.Configuration.LibraryOptions;
2728
using MetadataRefreshMode = MediaBrowser.Controller.Providers.MetadataRefreshMode;
29+
using Movie = MediaBrowser.Controller.Entities.Movies.Movie;
2830
using Timer = System.Timers.Timer;
31+
using TvEpisode = MediaBrowser.Controller.Entities.TV.Episode;
32+
using TvSeason = MediaBrowser.Controller.Entities.TV.Season;
33+
using TvSeries = MediaBrowser.Controller.Entities.TV.Series;
2934

3035
namespace Shokofin.Events;
3136

@@ -51,6 +56,8 @@ public class EventDispatchService
5156

5257
private readonly ILogger<EventDispatchService> Logger;
5358

59+
private readonly UsageTracker UsageTracker;
60+
5461
private int ChangesDetectionSubmitterCount = 0;
5562

5663
private readonly Timer ChangesDetectionTimer;
@@ -64,6 +71,8 @@ public class EventDispatchService
6471
// It's so magical that it matches the magical value in the library monitor in JF core. 🪄
6572
private const int MagicalDelayValue = 45000;
6673

74+
private readonly ConcurrentDictionary<Guid, bool> RecentlyUpdatedEntitiesDict = new();
75+
6776
private static readonly TimeSpan DetectChangesThreshold = TimeSpan.FromSeconds(5);
6877

6978
public EventDispatchService(
@@ -76,7 +85,8 @@ public EventDispatchService(
7685
LibraryScanWatcher libraryScanWatcher,
7786
IFileSystem fileSystem,
7887
IDirectoryService directoryService,
79-
ILogger<EventDispatchService> logger
88+
ILogger<EventDispatchService> logger,
89+
UsageTracker usageTracker
8090
)
8191
{
8292
ApiManager = apiManager;
@@ -89,16 +99,25 @@ ILogger<EventDispatchService> logger
8999
FileSystem = fileSystem;
90100
DirectoryService = directoryService;
91101
Logger = logger;
102+
UsageTracker = usageTracker;
103+
UsageTracker.Stalled += OnStalled;
92104
ChangesDetectionTimer = new() { AutoReset = true, Interval = TimeSpan.FromSeconds(4).TotalMilliseconds };
93105
ChangesDetectionTimer.Elapsed += OnIntervalElapsed;
94106
}
95107

96108
~EventDispatchService()
97109
{
98-
110+
UsageTracker.Stalled -= OnStalled;
99111
ChangesDetectionTimer.Elapsed -= OnIntervalElapsed;
100112
}
101113

114+
private void OnStalled(object? sender, EventArgs eventArgs)
115+
{
116+
Clear();
117+
}
118+
119+
public void Clear() => RecentlyUpdatedEntitiesDict.Clear();
120+
102121
#region Event Detection
103122

104123
public IDisposable RegisterEventSubmitter()
@@ -596,16 +615,25 @@ private async Task<int> ProcessSeriesEvents(ShowInfo showInfo, List<IMetadataUpd
596615
var animeEvent = changes.Find(e => e.Kind is BaseItemKind.Series || e.Kind is BaseItemKind.Episode && e.Reason is UpdateReason.Removed);
597616
if (animeEvent is not null) {
598617
var shows = LibraryManager
599-
.GetItemList(
600-
new() {
601-
IncludeItemTypes = [BaseItemKind.Series],
602-
HasAnyProviderId = new Dictionary<string, string> { { ShokoSeriesId.Name, showInfo.Id } },
603-
DtoOptions = new(true),
604-
},
605-
true
606-
)
618+
.GetItemList(new() {
619+
IncludeItemTypes = [BaseItemKind.Series],
620+
HasAnyProviderId = new Dictionary<string, string> { { ShokoSeriesId.Name, showInfo.Id } },
621+
DtoOptions = new(true),
622+
})
623+
.DistinctBy(s => s.Id)
624+
.OfType<TvSeries>()
607625
.ToList();
608626
foreach (var show in shows) {
627+
if (RecentlyUpdatedEntitiesDict.ContainsKey(show.Id)) {
628+
Logger.LogTrace("Show {ShowName} is already being updated. (Check=1,Show={ShowId},Series={SeriesId})", show.Name, show.Id, showInfo.Id);
629+
continue;
630+
}
631+
632+
if (!RecentlyUpdatedEntitiesDict.TryAdd(show.Id, true)) {
633+
Logger.LogTrace("Show {ShowName} is already being updated. (Check=2,Show={ShowId},Series={SeriesId})", show.Name, show.Id, showInfo.Id);
634+
continue;
635+
}
636+
609637
Logger.LogInformation("Refreshing show {ShowName}. (Show={ShowId},Series={SeriesId})", show.Name, show.Id, showInfo.Id);
610638
await show.RefreshMetadata(new(DirectoryService) {
611639
MetadataRefreshMode = MetadataRefreshMode.FullRefresh,
@@ -635,16 +663,31 @@ await show.RefreshMetadata(new(DirectoryService) {
635663
.ToList();
636664
foreach (var seasonInfo in seasonList) {
637665
var seasons = LibraryManager
638-
.GetItemList(
639-
new() {
640-
IncludeItemTypes = [BaseItemKind.Season],
641-
HasAnyProviderId = new Dictionary<string, string> { { ShokoSeriesId.Name, seasonInfo.Id } },
642-
DtoOptions = new(true),
643-
},
644-
true
645-
)
666+
.GetItemList(new() {
667+
IncludeItemTypes = [BaseItemKind.Season],
668+
HasAnyProviderId = new Dictionary<string, string> { { ShokoSeriesId.Name, seasonInfo.Id } },
669+
DtoOptions = new(true),
670+
})
671+
.DistinctBy(s => s.Id)
672+
.OfType<TvSeason>()
646673
.ToList();
647674
foreach (var season in seasons) {
675+
var showId = season.SeriesId;
676+
if (RecentlyUpdatedEntitiesDict.ContainsKey(showId)) {
677+
Logger.LogTrace("Show is already being updated. (Check=1,Show={ShowId},Season={SeasonId},Series={SeriesId})", showId, season.Id, seasonInfo.Id);
678+
continue;
679+
}
680+
681+
if (RecentlyUpdatedEntitiesDict.ContainsKey(season.Id)) {
682+
Logger.LogTrace("Season is already being updated. (Check=2,Show={ShowId},Season={SeasonId},Series={SeriesId})", showId, season.Id, seasonInfo.Id);
683+
continue;
684+
}
685+
686+
if (!RecentlyUpdatedEntitiesDict.TryAdd(season.Id, true)) {
687+
Logger.LogTrace("Season is already being updated. (Check=3,Show={ShowId},Season={SeasonId},Series={SeriesId})", showId, season.Id, seasonInfo.Id);
688+
continue;
689+
}
690+
648691
Logger.LogInformation("Refreshing season {SeasonName}. (Season={SeasonId},Series={SeriesId},ExtraSeries={ExtraIds})", season.Name, season.Id, seasonInfo.Id, seasonInfo.ExtraIds);
649692
await season.RefreshMetadata(new(DirectoryService) {
650693
MetadataRefreshMode = MetadataRefreshMode.FullRefresh,
@@ -666,16 +709,37 @@ await season.RefreshMetadata(new(DirectoryService) {
666709
.ToList();
667710
foreach (var episodeInfo in episodeList) {
668711
var episodes = LibraryManager
669-
.GetItemList(
670-
new() {
671-
IncludeItemTypes = [BaseItemKind.Episode],
672-
HasAnyProviderId = new Dictionary<string, string> { { ShokoEpisodeId.Name, episodeInfo.Id } },
673-
DtoOptions = new(true),
674-
},
675-
true
676-
)
712+
.GetItemList(new() {
713+
IncludeItemTypes = [BaseItemKind.Episode],
714+
HasAnyProviderId = new Dictionary<string, string> { { ShokoEpisodeId.Name, episodeInfo.Id } },
715+
DtoOptions = new(true),
716+
})
717+
.DistinctBy(e => e.Id)
718+
.OfType<TvEpisode>()
677719
.ToList();
678720
foreach (var episode in episodes) {
721+
var showId = episode.SeriesId;
722+
var seasonId = episode.SeasonId;
723+
if (RecentlyUpdatedEntitiesDict.ContainsKey(showId)) {
724+
Logger.LogTrace("Show is already being updated. (Check=1,Show={ShowId},Season={SeasonId},Episode={EpisodeId},Episode={EpisodeId},Series={SeriesId})", showId, seasonId, episode.Id, episodeInfo.Id, episodeInfo.SeriesId);
725+
continue;
726+
}
727+
728+
if (RecentlyUpdatedEntitiesDict.ContainsKey(seasonId)) {
729+
Logger.LogTrace("Season is already being updated. (Check=2,Show={ShowId},Season={SeasonId},Episode={EpisodeId},Episode={EpisodeId},Series={SeriesId})", showId, seasonId, episode.Id, episodeInfo.Id, episodeInfo.SeriesId);
730+
continue;
731+
}
732+
733+
if (RecentlyUpdatedEntitiesDict.ContainsKey(episode.Id)) {
734+
Logger.LogTrace("Episode is already being updated. (Check=3,Show={ShowId},Season={SeasonId},Episode={EpisodeId},Episode={EpisodeId},Series={SeriesId})", showId, seasonId, episode.Id, episodeInfo.Id, episodeInfo.SeriesId);
735+
continue;
736+
}
737+
738+
if (!RecentlyUpdatedEntitiesDict.TryAdd(episode.Id, true)) {
739+
Logger.LogTrace("Episode is already being updated. (Check=4,Show={ShowId},Season={SeasonId},Episode={EpisodeId},Episode={EpisodeId},Series={SeriesId})", showId, seasonId, episode.Id, episodeInfo.Id, episodeInfo.SeriesId);
740+
continue;
741+
}
742+
679743
Logger.LogInformation("Refreshing episode {EpisodeName}. (Episode={EpisodeId},Episode={EpisodeId},Series={SeriesId})", episode.Name, episode.Id, episodeInfo.Id, episodeInfo.SeriesId);
680744
await episode.RefreshMetadata(new(DirectoryService) {
681745
MetadataRefreshMode = MetadataRefreshMode.FullRefresh,
@@ -709,16 +773,25 @@ private async Task<int> ProcessMovieEvents(SeasonInfo seasonInfo, List<IMetadata
709773
.ToList();
710774
foreach (var episodeInfo in episodeList) {
711775
var movies = LibraryManager
712-
.GetItemList(
713-
new() {
714-
IncludeItemTypes = [BaseItemKind.Movie],
715-
HasAnyProviderId = new Dictionary<string, string> { { ShokoEpisodeId.Name, episodeInfo.Id } },
716-
DtoOptions = new(true),
717-
},
718-
true
719-
)
776+
.GetItemList(new() {
777+
IncludeItemTypes = [BaseItemKind.Movie],
778+
HasAnyProviderId = new Dictionary<string, string> { { ShokoEpisodeId.Name, episodeInfo.Id } },
779+
DtoOptions = new(true),
780+
})
781+
.DistinctBy(e => e.Id)
782+
.OfType<Movie>()
720783
.ToList();
721784
foreach (var movie in movies) {
785+
if (RecentlyUpdatedEntitiesDict.ContainsKey(movie.Id)) {
786+
Logger.LogTrace("Movie is already being updated. (Check=1,Movie={MovieId},Episode={EpisodeId},Series={SeriesId},ExtraSeries={ExtraIds})", movie.Id, episodeInfo.Id, seasonInfo.Id, seasonInfo.ExtraIds);
787+
continue;
788+
}
789+
790+
if (!RecentlyUpdatedEntitiesDict.TryAdd(movie.Id, true)) {
791+
Logger.LogTrace("Movie is already being updated. (Check=2,Movie={MovieId},Episode={EpisodeId},Series={SeriesId},ExtraSeries={ExtraIds})", movie.Id, episodeInfo.Id, seasonInfo.Id, seasonInfo.ExtraIds);
792+
continue;
793+
}
794+
722795
Logger.LogInformation("Refreshing movie {MovieName}. (Movie={MovieId},Episode={EpisodeId},Series={SeriesId},ExtraSeries={ExtraIds})", movie.Name, movie.Id, episodeInfo.Id, seasonInfo.Id, seasonInfo.ExtraIds);
723796
await movie.RefreshMetadata(new(DirectoryService) {
724797
MetadataRefreshMode = MetadataRefreshMode.FullRefresh,

Shokofin/Tasks/ClearPluginCacheTask.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,15 @@
44
using System.Threading.Tasks;
55
using MediaBrowser.Model.Tasks;
66
using Shokofin.API;
7+
using Shokofin.Events;
78
using Shokofin.Resolvers;
89

910
namespace Shokofin.Tasks;
1011

1112
/// <summary>
1213
/// Forcefully clear the plugin cache. For debugging and troubleshooting. DO NOT RUN THIS TASK WHILE A LIBRARY SCAN IS RUNNING.
1314
/// </summary>
14-
public class ClearPluginCacheTask(ShokoAPIManager apiManager, ShokoAPIClient apiClient, VirtualFileSystemService vfsService) : IScheduledTask, IConfigurableScheduledTask
15+
public class ClearPluginCacheTask(ShokoAPIManager apiManager, ShokoAPIClient apiClient, VirtualFileSystemService vfsService, EventDispatchService eventDispatchService) : IScheduledTask, IConfigurableScheduledTask
1516
{
1617
/// <inheritdoc />
1718
public string Name => "Clear Plugin Cache";
@@ -40,6 +41,8 @@ public class ClearPluginCacheTask(ShokoAPIManager apiManager, ShokoAPIClient api
4041

4142
private readonly VirtualFileSystemService _vfsService = vfsService;
4243

44+
private readonly EventDispatchService _eventDispatchService = eventDispatchService;
45+
4346
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
4447
=> [];
4548

@@ -48,6 +51,7 @@ public Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellat
4851
_apiClient.Clear();
4952
_apiManager.Clear();
5053
_vfsService.Clear();
54+
_eventDispatchService.Clear();
5155
return Task.CompletedTask;
5256
}
5357
}

0 commit comments

Comments
 (0)