From c9f87c58cfe34725c1af60be188fff6059c70d26 Mon Sep 17 00:00:00 2001 From: rlauuzo <46294892+rlauuzo@users.noreply.github.com> Date: Fri, 7 Jun 2024 17:09:48 +0200 Subject: [PATCH] Delete Cache (#192) Co-authored-by: rlauu <46294892+rlauu@users.noreply.github.com> --- .../Configuration/configPage.html | 56 ++++++++++++++-- .../Controllers/SkipIntroController.cs | 21 +++++- .../Controllers/VisualizationController.cs | 7 +- .../FFmpegWrapper.cs | 67 +++++++++++++++++-- 4 files changed, 135 insertions(+), 16 deletions(-) diff --git a/ConfusedPolarBear.Plugin.IntroSkipper/Configuration/configPage.html b/ConfusedPolarBear.Plugin.IntroSkipper/Configuration/configPage.html index 6c6dc14..8e03f1a 100644 --- a/ConfusedPolarBear.Plugin.IntroSkipper/Configuration/configPage.html +++ b/ConfusedPolarBear.Plugin.IntroSkipper/Configuration/configPage.html @@ -593,9 +593,14 @@

- +
+
+ + + +
+ + @@ -630,6 +644,7 @@ var support = document.querySelector("details#support"); var btnEraseIntroTimestamps = document.querySelector("button#btnEraseIntroTimestamps"); var btnEraseCreditTimestamps = document.querySelector("button#btnEraseCreditTimestamps"); + var btnCleanCache = document.querySelector("button#btnCleanCache"); // all plugin configuration fields that can be get or set with .value (i.e. strings or numbers). var configurationFields = [ @@ -682,6 +697,7 @@ var txtOffset = document.querySelector("input#offset"); var txtSuggested = document.querySelector("span#suggestedShifts"); var btnSeasonEraseTimestamps = document.querySelector("button#btnEraseSeasonTimestamps"); + var eraseSeasonContainer = document.getElementById("eraseSeasonContainer"); var timestampError = document.querySelector("textarea#timestampError"); var timestampEditor = document.querySelector("#timestampEditor"); var btnUpdateTimestamps = document.querySelector("button#btnUpdateTimestamps"); @@ -791,7 +807,7 @@ // show changed, populate seasons async function showChanged() { clearSelect(selectSeason); - btnSeasonEraseTimestamps.style.display = "none"; + eraseSeasonContainer.style.display = "none"; clearSelect(selectEpisode1); clearSelect(selectEpisode2); @@ -810,7 +826,7 @@ clearSelect(selectEpisode1); clearSelect(selectEpisode2); - btnSeasonEraseTimestamps.style.display = "unset"; + eraseSeasonContainer.style.display = "unset"; let i = 1; for (let episode of episodes) { @@ -1028,6 +1044,7 @@ const body = "Are you sure you want to erase all previously discovered " + mode.toLocaleLowerCase() + " timestamps?"; + const eraseCacheChecked = document.getElementById("eraseModeCacheCheckbox").checked; Dashboard.confirm( body, @@ -1037,12 +1054,31 @@ return; } - fetchWithAuth("Intros/EraseTimestamps?mode=" + mode, "POST", null); + fetchWithAuth("Intros/EraseTimestamps?mode=" + mode + "&eraseCache=" + eraseCacheChecked, "POST", null); Dashboard.alert(mode + " timestamps erased"); }); } + // erase all intro/credits cache + function cleanCache() { + const title = "Confirm Cache erasure"; + const body = "Are you sure you want to erase all unused fingerprints?"; + + Dashboard.confirm( + body, + title, + (result) => { + if (!result) { + return; + } + + fetchWithAuth("Intros/CleanCache", "POST", null); + + Dashboard.alert("Cache cleaned"); + }); + } + document.querySelector('#TemplateConfigPage') .addEventListener('pageshow', function () { Dashboard.showLoadingMsg(); @@ -1100,6 +1136,10 @@ eraseTimestamps("Credits"); e.preventDefault(); }); + btnCleanCache.addEventListener("click", (e) => { + cleanCache(); + e.preventDefault(); + }); btnSeasonEraseTimestamps.addEventListener("click", () => { Dashboard.confirm( "Are you sure you want to erase all timestamps for this season?", @@ -1111,11 +1151,13 @@ const show = selectShow.value; const season = selectSeason.value; + const eraseCacheChecked = document.getElementById("eraseSeasonCacheCheckbox").checked; const url = "Intros/Show/" + encodeURIComponent(show) + "/" + encodeURIComponent(season); - fetchWithAuth(url, "DELETE", null); + fetchWithAuth(url + "?eraseCache=" + eraseCacheChecked, "DELETE", null); Dashboard.alert("Erased timestamps for " + season + " of " + show); + document.getElementById("eraseSeasonCacheCheckbox").checked = false; } ); }); diff --git a/ConfusedPolarBear.Plugin.IntroSkipper/Controllers/SkipIntroController.cs b/ConfusedPolarBear.Plugin.IntroSkipper/Controllers/SkipIntroController.cs index 4e65f22..a88e551 100644 --- a/ConfusedPolarBear.Plugin.IntroSkipper/Controllers/SkipIntroController.cs +++ b/ConfusedPolarBear.Plugin.IntroSkipper/Controllers/SkipIntroController.cs @@ -114,11 +114,12 @@ public class SkipIntroController : ControllerBase /// Erases all previously discovered introduction timestamps. /// /// Mode. + /// Erase cache. /// Operation successful. /// No content. [Authorize(Policy = Policies.RequiresElevation)] [HttpPost("Intros/EraseTimestamps")] - public ActionResult ResetIntroTimestamps([FromQuery] AnalysisMode mode) + public ActionResult ResetIntroTimestamps([FromQuery] AnalysisMode mode, [FromQuery] bool eraseCache = false) { if (mode == AnalysisMode.Introduction) { @@ -129,10 +130,28 @@ public class SkipIntroController : ControllerBase Plugin.Instance!.Credits.Clear(); } + if (eraseCache) + { + FFmpegWrapper.DeleteCacheFiles(mode); + } + Plugin.Instance!.SaveTimestamps(mode); return NoContent(); } + /// + /// Erases all previously cached introduction fingerprints. + /// + /// Operation successful. + /// No content. + [Authorize(Policy = "RequiresElevation")] + [HttpPost("Intros/CleanCache")] + public ActionResult CleanIntroCache() + { + FFmpegWrapper.CleanCacheFiles(); + return NoContent(); + } + /// /// Get all introductions or credits. Only used by the end to end testing script. /// diff --git a/ConfusedPolarBear.Plugin.IntroSkipper/Controllers/VisualizationController.cs b/ConfusedPolarBear.Plugin.IntroSkipper/Controllers/VisualizationController.cs index 9cb1266..a9e2569 100644 --- a/ConfusedPolarBear.Plugin.IntroSkipper/Controllers/VisualizationController.cs +++ b/ConfusedPolarBear.Plugin.IntroSkipper/Controllers/VisualizationController.cs @@ -125,11 +125,12 @@ public class VisualizationController : ControllerBase /// /// Show name. /// Season name. + /// Erase cache. /// Season timestamps erased. /// Unable to find season in provided series. /// No content. [HttpDelete("Show/{Series}/{Season}")] - public ActionResult EraseSeason([FromRoute] string series, [FromRoute] string season) + public ActionResult EraseSeason([FromRoute] string series, [FromRoute] string season, [FromQuery] bool eraseCache = false) { if (!LookupSeasonByName(series, season, out var episodes)) { @@ -142,6 +143,10 @@ public class VisualizationController : ControllerBase { Plugin.Instance!.Intros.TryRemove(e.EpisodeId, out _); Plugin.Instance!.Credits.TryRemove(e.EpisodeId, out _); + if (eraseCache) + { + FFmpegWrapper.DeleteEpisodeCache(e.EpisodeId); + } } Plugin.Instance!.SaveTimestamps(AnalysisMode.Introduction); diff --git a/ConfusedPolarBear.Plugin.IntroSkipper/FFmpegWrapper.cs b/ConfusedPolarBear.Plugin.IntroSkipper/FFmpegWrapper.cs index 1731db6..71ae5b5 100644 --- a/ConfusedPolarBear.Plugin.IntroSkipper/FFmpegWrapper.cs +++ b/ConfusedPolarBear.Plugin.IntroSkipper/FFmpegWrapper.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; +using System.Linq; using System.Text; using System.Text.RegularExpressions; using Microsoft.Extensions.Logging; @@ -607,20 +608,72 @@ public static class FFmpegWrapper /// /// Remove a cached episode fingerprint from disk. /// - /// Episode to remove from cache. - /// Analysis mode. - public static void DeleteEpisodeCache(string episodeId, AnalysisMode mode) + /// Episode to remove from cache. + public static void DeleteEpisodeCache(Guid id) { var cachePath = Path.Join( Plugin.Instance!.FingerprintCachePath, - episodeId); + id.ToString("N")); - if (mode == AnalysisMode.Credits) + // File.Delete(cachePath); + // File.Delete(cachePath + "-intro-silence-v1"); + // File.Delete(cachePath + "-credits"); + + var filePattern = Path.GetFileName(cachePath) + "*"; + foreach (var filePath in Directory.EnumerateFiles(Plugin.Instance!.FingerprintCachePath, filePattern)) { - cachePath += "-credits"; + File.Delete(filePath); } + } - File.Delete(cachePath); + /// + /// Remove cached fingerprints from disk by mode. + /// + /// Analysis mode. + public static void DeleteCacheFiles(AnalysisMode mode) + { + foreach (var filePath in Directory.EnumerateFiles(Plugin.Instance!.FingerprintCachePath)) + { + var shouldDelete = (mode == AnalysisMode.Introduction) + ? !filePath.Contains("credit", StringComparison.OrdinalIgnoreCase) + && !filePath.Contains("blackframes", StringComparison.OrdinalIgnoreCase) + : filePath.Contains("credit", StringComparison.OrdinalIgnoreCase) + || filePath.Contains("blackframes", StringComparison.OrdinalIgnoreCase); + + if (shouldDelete) + { + File.Delete(filePath); + } + } + } + + /// + /// Remove a cached episode fingerprint from disk. + /// + public static void CleanCacheFiles() + { + // Get valid episode IDs from the dictionaries + HashSet validEpisodeIds = new HashSet(Plugin.Instance!.Intros.Keys.Concat(Plugin.Instance!.Credits.Keys)); // Or use GetMediaItems instead? + + // Delete invalid cache files + foreach (string filePath in Directory.EnumerateFiles(Plugin.Instance!.FingerprintCachePath)) + { + string fileName = Path.GetFileNameWithoutExtension(filePath); + + int dashIndex = fileName.IndexOf('-', StringComparison.Ordinal); // Find the index of the first '-' character + if (dashIndex > 0) + { + fileName = fileName.Substring(0, dashIndex); + } + + if (Guid.TryParse(fileName, out Guid episodeId)) + { + if (!validEpisodeIds.Contains(episodeId)) + { + DeleteEpisodeCache(episodeId); + } + } + } } ///