-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathJavCacheCleanupTask.cs
More file actions
134 lines (115 loc) · 5.14 KB
/
Copy pathJavCacheCleanupTask.cs
File metadata and controls
134 lines (115 loc) · 5.14 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Tasks;
using Microsoft.Extensions.Logging;
namespace Jellyfin.Plugin.JavOrganizer;
/// <summary>
/// Scheduled task that prunes the plugin's cache of unused files. It runs
/// daily (default trigger: 04:00) and removes:
/// <list type="bullet">
/// <item>scrape records older than the configured cache TTL (they would be
/// re-fetched on the next scan anyway),</item>
/// <item>expired negative "not found" markers,</item>
/// <item>records for videos that no longer exist anywhere in the library —
/// covers left behind by files that were renamed, moved or deleted.</item>
/// </list>
/// The task is also runnable on demand from Dashboard → Scheduled Tasks.
/// </summary>
public sealed class JavCacheCleanupTask : IScheduledTask, IConfigurableScheduledTask
{
private readonly ILibraryManager _libraryManager;
private readonly ILogger<JavCacheCleanupTask> _logger;
/// <summary>
/// Initializes a new instance of the <see cref="JavCacheCleanupTask"/> class.
/// </summary>
/// <param name="libraryManager">Library manager used to list current video paths.</param>
/// <param name="logger">Logger scoped to this task.</param>
public JavCacheCleanupTask(ILibraryManager libraryManager, ILogger<JavCacheCleanupTask> logger)
{
_libraryManager = libraryManager;
_logger = logger;
}
/// <inheritdoc />
public string Name => "Clean JavOrganizer Cache";
/// <inheritdoc />
public string Key => "JavOrganizerCacheCleanup";
/// <inheritdoc />
public string Description => "Removes expired JavOrganizer scrape records, expired not-found markers, and cached metadata for videos that are no longer in the library.";
/// <inheritdoc />
public string Category => "Library";
/// <inheritdoc />
public bool IsHidden => false;
/// <inheritdoc />
public bool IsEnabled => true;
/// <inheritdoc />
public bool IsLogged => true;
/// <inheritdoc />
public async Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
{
var removedExpired = 0;
var removedOrphans = 0;
var removedNegatives = 0;
// Phase 1: time-based cleanup — expired records and markers.
progress.Report(5);
(removedExpired, removedNegatives) = JavCache.RemoveExpired(_logger);
// Phase 2: library-aware cleanup — records whose code no longer
// matches any file name in the library are orphans.
progress.Report(40);
removedOrphans = await RemoveLibraryOrphansAsync(progress, cancellationToken).ConfigureAwait(false);
var total = removedExpired + removedOrphans + removedNegatives;
_logger.LogInformation(
"JavOrganizer cache cleanup finished: {Expired} expired records, {Negatives} expired not-found markers, {Orphans} orphaned records removed ({Total} files total)",
removedExpired,
removedNegatives,
removedOrphans,
total);
progress.Report(100);
}
/// <summary>
/// Deletes cached records whose product code cannot be extracted from any
/// video file name currently in the library.
/// </summary>
/// <param name="progress">Progress reporter.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>The number of orphaned cache files removed.</returns>
private async Task<int> RemoveLibraryOrphansAsync(IProgress<double> progress, CancellationToken cancellationToken)
{
// Collect the codes of every video currently in the library.
var liveCodes = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
try
{
var query = new InternalItemsQuery
{
Recursive = true,
IsFolder = false,
IncludeItemTypes = [BaseItemKind.Video, BaseItemKind.Movie]
};
var items = JavApiCompat.GetItemList(_libraryManager, query).ToList();
foreach (var item in items)
{
cancellationToken.ThrowIfCancellationRequested();
var code = JavCodeParser.ExtractCode(item.Path) ?? JavCodeParser.ExtractCode(item.Name);
if (code is not null)
{
liveCodes.Add(JavCodeParser.Normalize(code));
}
}
}
catch (Exception ex)
{
// The library query is an optimization, never a hard requirement;
// on failure keep every record rather than risk deleting live ones.
_logger.LogWarning(ex, "Could not enumerate library items; skipping orphan removal this run");
return 0;
}
progress.Report(70);
return await Task.Run(() => JavCache.RemoveOrphans(liveCodes, _logger), cancellationToken).ConfigureAwait(false);
}
/// <inheritdoc />
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
{
// Daily at 04:00, when library scans are unlikely to be running.
yield return JavApiCompat.DailyTrigger(TimeSpan.FromHours(4));
}
}