From dac80511836864c8429c4a30afcec1f84ff4d4c6 Mon Sep 17 00:00:00 2001 From: Scrub <72096833+ScrubN@users.noreply.github.com> Date: Mon, 5 Feb 2024 22:27:28 -0500 Subject: [PATCH] Prompt user for old cache deletion (#963) * Switch method to use callback * Use better callback * Add window for deleting old video caches instead of deleting them all automatically * Update translations * Restore 7 day directory age minimum * Use ticks instead of millis to avoid potential clashes from the URL mass downloader * Add basic CLI callback * Fix total size not working * Fix theming * Remove select all button because it doesn't work apparently :/ * Default ShouldDelete to true * Forgot to make it recursive * Fixed the select all button! * Forgot to use block scope namespace * Microsoft would be happy about this commit * Pull out cache cleaner callback into dedicated method * `Delete?` -> `Delete` * Add minimum window size * Add TODO * Move string representation responsibility from C# to XAML --- TwitchDownloaderCLI/Modes/DownloadVideo.cs | 7 + .../Options/VideoDownloadOptions.cs | 6 +- TwitchDownloaderCore/TwitchHelper.cs | 72 +++++---- TwitchDownloaderCore/VideoDownloader.cs | 4 +- TwitchDownloaderWPF/PageVodDownload.xaml.cs | 16 ++ .../Translations/Strings.Designer.cs | 63 ++++++++ .../Translations/Strings.es.resx | 21 +++ .../Translations/Strings.fr.resx | 21 +++ .../Translations/Strings.it.resx | 24 ++- .../Translations/Strings.pl.resx | 21 +++ TwitchDownloaderWPF/Translations/Strings.resx | 21 +++ .../Translations/Strings.ru.resx | 21 +++ .../Translations/Strings.tr.resx | 21 +++ .../Translations/Strings.uk.resx | 21 +++ .../Translations/Strings.zh.resx | 21 +++ .../WindowOldVideoCacheManager.xaml | 51 +++++++ .../WindowOldVideoCacheManager.xaml.cs | 140 ++++++++++++++++++ 17 files changed, 508 insertions(+), 43 deletions(-) create mode 100644 TwitchDownloaderWPF/WindowOldVideoCacheManager.xaml create mode 100644 TwitchDownloaderWPF/WindowOldVideoCacheManager.xaml.cs diff --git a/TwitchDownloaderCLI/Modes/DownloadVideo.cs b/TwitchDownloaderCLI/Modes/DownloadVideo.cs index f2edcf07..2b4426c0 100644 --- a/TwitchDownloaderCLI/Modes/DownloadVideo.cs +++ b/TwitchDownloaderCLI/Modes/DownloadVideo.cs @@ -19,6 +19,13 @@ internal static void Download(VideoDownloadArgs inputOptions) progress.ProgressChanged += ProgressHandler.Progress_ProgressChanged; var downloadOptions = GetDownloadOptions(inputOptions); + downloadOptions.CacheCleanerCallback += directoryInfos => + { + // TODO: Poll for user input with a timeout for scripts + ((IProgress)progress).Report(new ProgressReport(ReportType.Log, $"{directoryInfos.Length} unmanaged video caches were found in {downloadOptions.TempFolder} and can be safely deleted.")); + return Array.Empty(); + }; + VideoDownloader videoDownloader = new(downloadOptions, progress); videoDownloader.DownloadAsync(new CancellationToken()).Wait(); } diff --git a/TwitchDownloaderCore/Options/VideoDownloadOptions.cs b/TwitchDownloaderCore/Options/VideoDownloadOptions.cs index 7ac21df3..89c1c22f 100644 --- a/TwitchDownloaderCore/Options/VideoDownloadOptions.cs +++ b/TwitchDownloaderCore/Options/VideoDownloadOptions.cs @@ -1,4 +1,7 @@ -namespace TwitchDownloaderCore.Options +using System; +using System.IO; + +namespace TwitchDownloaderCore.Options { public class VideoDownloadOptions { @@ -14,5 +17,6 @@ public class VideoDownloadOptions public string Oauth { get; set; } public string FfmpegPath { get; set; } public string TempFolder { get; set; } + public Func CacheCleanerCallback { get; set; } } } diff --git a/TwitchDownloaderCore/TwitchHelper.cs b/TwitchDownloaderCore/TwitchHelper.cs index e58eabf0..e89aa104 100644 --- a/TwitchDownloaderCore/TwitchHelper.cs +++ b/TwitchDownloaderCore/TwitchHelper.cs @@ -1,5 +1,6 @@ using SkiaSharp; using System; +using System.Buffers; using System.Collections.Generic; using System.IO; using System.IO.Compression; @@ -813,63 +814,58 @@ public static void SetDirectoryPermissions(string path) /// /// Cleans up any unmanaged cache files from previous runs that were interrupted before cleaning up /// - public static void CleanupUnmanagedCacheFiles(string cacheFolder, IProgress progress) + public static async Task CleanupAbandonedVideoCaches(string cacheFolder, Func itemsToDeleteCallback, IProgress progress) { if (!Directory.Exists(cacheFolder)) { return; } - // Let's delete any video download cache folders older than 24 hours - var videoFolderRegex = new Regex(@"\d+_(\d+)$", RegexOptions.RightToLeft); // Matches "...###_###" and captures the 2nd ### - var directories = Directory.GetDirectories(cacheFolder); - var directoriesDeleted = (from directory in directories - let videoFolderMatch = videoFolderRegex.Match(directory) - where videoFolderMatch.Success - where DeleteOldDirectory(directory, videoFolderMatch.Groups[1].ValueSpan) - select directory).Count(); - - if (directoriesDeleted > 0) + if (itemsToDeleteCallback == null) { - progress.Report(new ProgressReport(ReportType.Log, $"{directoriesDeleted} old video caches were deleted.")); + // TODO: Log this + return; } - } - private static bool DeleteOldDirectory(string directory, ReadOnlySpan directoryCreationMillis) - { - var downloadTime = long.Parse(directoryCreationMillis); - var currentTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); + var videoFolderRegex = new Regex(@"\d+_\d+$", RegexOptions.RightToLeft); + var allCacheDirectories = Directory.GetDirectories(cacheFolder); + + var oldVideoCaches = (from directory in allCacheDirectories + where videoFolderRegex.IsMatch(directory) + let directoryInfo = new DirectoryInfo(directory) + where DateTime.UtcNow.Ticks - directoryInfo.LastWriteTimeUtc.Ticks > TimeSpan.TicksPerDay * 7 + select directoryInfo) + .ToArray(); - const int TWENTY_FOUR_HOURS_MILLIS = 86_400_000; - if (currentTime - downloadTime > TWENTY_FOUR_HOURS_MILLIS * 7) + if (oldVideoCaches.Length == 0) { - try - { - Directory.Delete(directory, true); - return true; - } - catch { /* Eat the exception */ } + return; } - return false; - } - private static bool DeleteColdDirectory(string directory) - { - // Directory.GetLastWriteTimeUtc() works as expected on both Windows and MacOS. Assuming it does on Linux too - var directoryWriteTimeMillis = Directory.GetLastWriteTimeUtc(directory).Ticks / TimeSpan.TicksPerMillisecond; - var currentTimeMillis = DateTimeOffset.UtcNow.Ticks / TimeSpan.TicksPerMillisecond; + var toDelete = await Task.Run(() => itemsToDeleteCallback(oldVideoCaches)); + + if (toDelete == null || toDelete.Length == 0) + { + return; + } - const int SIX_HOURS_MILLIS = 21_600_000; - if (currentTimeMillis - directoryWriteTimeMillis > SIX_HOURS_MILLIS) + var wasDeleted = 0; + foreach (var directory in toDelete) { try { - Directory.Delete(directory, true); - return true; + Directory.Delete(directory.FullName, true); + wasDeleted++; + } + catch + { + // Oh well } - catch { /* Eat the exception */ } } - return false; + + progress.Report(toDelete.Length == wasDeleted + ? new ProgressReport(ReportType.Log, $"{wasDeleted} old video caches were deleted.") + : new ProgressReport(ReportType.Log, $"{wasDeleted} old video caches were deleted, {toDelete.Length - wasDeleted} could not be deleted.")); } public static int TimestampToSeconds(string input) diff --git a/TwitchDownloaderCore/VideoDownloader.cs b/TwitchDownloaderCore/VideoDownloader.cs index a78686d7..00800416 100644 --- a/TwitchDownloaderCore/VideoDownloader.cs +++ b/TwitchDownloaderCore/VideoDownloader.cs @@ -36,11 +36,11 @@ public VideoDownloader(VideoDownloadOptions videoDownloadOptions, IProgress downloadProgress = new Progress(OnProgressChanged); VideoDownloader currentDownload = new VideoDownloader(options, downloadProgress); @@ -461,6 +462,21 @@ private async void SplitBtnDownloader_Click(object sender, RoutedEventArgs e) GC.Collect(); } + private DirectoryInfo[] HandleCacheCleanerCallback(DirectoryInfo[] directories) + { + return Dispatcher.Invoke(() => + { + var window = new WindowOldVideoCacheManager(directories) + { + Owner = Application.Current.MainWindow, + WindowStartupLocation = WindowStartupLocation.CenterOwner + }; + window.ShowDialog(); + + return window.GetItemsToDelete(); + }); + } + private void BtnCancel_Click(object sender, RoutedEventArgs e) { statusMessage.Text = Translations.Strings.StatusCanceling; diff --git a/TwitchDownloaderWPF/Translations/Strings.Designer.cs b/TwitchDownloaderWPF/Translations/Strings.Designer.cs index 6adeb3d0..dbe3ccd6 100644 --- a/TwitchDownloaderWPF/Translations/Strings.Designer.cs +++ b/TwitchDownloaderWPF/Translations/Strings.Designer.cs @@ -527,6 +527,15 @@ public static string DateCustomFormattingHyperlink { } } + /// + /// Looks up a localized string similar to Delete. + /// + public static string DeleteCacheColumnHeader { + get { + return ResourceManager.GetString("DeleteCacheColumnHeader", resourceCulture); + } + } + /// /// Looks up a localized string similar to Delete Confirmation. /// @@ -824,6 +833,24 @@ public static string FfzEmotes { } } + /// + /// Looks up a localized string similar to Age. + /// + public static string FileAgeColumnHeader { + get { + return ResourceManager.GetString("FileAgeColumnHeader", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0:N0} days. + /// + public static string FileAgeInDays { + get { + return ResourceManager.GetString("FileAgeInDays", resourceCulture); + } + } + /// /// Looks up a localized string similar to {title} {id} {date} {channel} {date_custom=""} {random_string} {crop_start} {crop_end} {crop_start_custom=""} {crop_end_custom=""} {length} {length_custom=""} {views} {game}. /// @@ -842,6 +869,24 @@ public static string FileNotFound { } } + /// + /// Looks up a localized string similar to Path. + /// + public static string FilePathColumnHeader { + get { + return ResourceManager.GetString("FilePathColumnHeader", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Size. + /// + public static string FileSizeColumnHeader { + get { + return ResourceManager.GetString("FileSizeColumnHeader", resourceCulture); + } + } + /// /// Looks up a localized string similar to Font Color:. /// @@ -1454,6 +1499,15 @@ public static string SidePaddingScale { } } + /// + /// Looks up a localized string similar to Total Size:. + /// + public static string SizeOfAllFiles { + get { + return ResourceManager.GetString("SizeOfAllFiles", resourceCulture); + } + } + /// /// Looks up a localized string similar to Sort:. /// @@ -1814,6 +1868,15 @@ public static string TitleVideoMassDownloader { } } + /// + /// Looks up a localized string similar to Select Caches To Delete. + /// + public static string TitleWindowOldVideoCacheManager { + get { + return ResourceManager.GetString("TitleWindowOldVideoCacheManager", resourceCulture); + } + } + /// /// Looks up a localized string similar to Top All Time. /// diff --git a/TwitchDownloaderWPF/Translations/Strings.es.resx b/TwitchDownloaderWPF/Translations/Strings.es.resx index 7453d9b3..0edd0018 100644 --- a/TwitchDownloaderWPF/Translations/Strings.es.resx +++ b/TwitchDownloaderWPF/Translations/Strings.es.resx @@ -818,4 +818,25 @@ Preferred Quality: + + Select Caches To Delete + + + Total Size: + + + Delete + + + Path + + + Age + + + Size + + + {0:N0} days + \ No newline at end of file diff --git a/TwitchDownloaderWPF/Translations/Strings.fr.resx b/TwitchDownloaderWPF/Translations/Strings.fr.resx index 0da702d6..95062a0d 100644 --- a/TwitchDownloaderWPF/Translations/Strings.fr.resx +++ b/TwitchDownloaderWPF/Translations/Strings.fr.resx @@ -816,4 +816,25 @@ Qualité préférée: + + Select Caches To Delete + + + Total Size: + + + Delete + + + Path + + + Age + + + Size + + + {0:N0} days + \ No newline at end of file diff --git a/TwitchDownloaderWPF/Translations/Strings.it.resx b/TwitchDownloaderWPF/Translations/Strings.it.resx index f9794270..a040150e 100644 --- a/TwitchDownloaderWPF/Translations/Strings.it.resx +++ b/TwitchDownloaderWPF/Translations/Strings.it.resx @@ -812,10 +812,30 @@ Remove - Open folder - + Open folder Preferred Quality: + + Select Caches To Delete + + + Total Size: + + + Delete + + + Path + + + Age + + + Size + + + {0:N0} days + diff --git a/TwitchDownloaderWPF/Translations/Strings.pl.resx b/TwitchDownloaderWPF/Translations/Strings.pl.resx index b3020592..4e9f87cf 100644 --- a/TwitchDownloaderWPF/Translations/Strings.pl.resx +++ b/TwitchDownloaderWPF/Translations/Strings.pl.resx @@ -816,4 +816,25 @@ Preferred Quality: + + Select Caches To Delete + + + Total Size: + + + Delete + + + Path + + + Age + + + Size + + + {0:N0} days + \ No newline at end of file diff --git a/TwitchDownloaderWPF/Translations/Strings.resx b/TwitchDownloaderWPF/Translations/Strings.resx index 27e42be7..a1b693c4 100644 --- a/TwitchDownloaderWPF/Translations/Strings.resx +++ b/TwitchDownloaderWPF/Translations/Strings.resx @@ -815,4 +815,25 @@ Preferred Quality: + + Select Caches To Delete + + + Total Size: + + + Delete + + + Path + + + Age + + + Size + + + {0:N0} days + \ No newline at end of file diff --git a/TwitchDownloaderWPF/Translations/Strings.ru.resx b/TwitchDownloaderWPF/Translations/Strings.ru.resx index c428d3ea..e6708c79 100644 --- a/TwitchDownloaderWPF/Translations/Strings.ru.resx +++ b/TwitchDownloaderWPF/Translations/Strings.ru.resx @@ -816,4 +816,25 @@ Preferred Quality: + + Select Caches To Delete + + + Total Size: + + + Delete + + + Path + + + Age + + + Size + + + {0:N0} days + \ No newline at end of file diff --git a/TwitchDownloaderWPF/Translations/Strings.tr.resx b/TwitchDownloaderWPF/Translations/Strings.tr.resx index fea52a76..baa86d39 100644 --- a/TwitchDownloaderWPF/Translations/Strings.tr.resx +++ b/TwitchDownloaderWPF/Translations/Strings.tr.resx @@ -817,4 +817,25 @@ Preferred Quality: + + Select Caches To Delete + + + Total Size: + + + Delete + + + Path + + + Age + + + Size + + + {0:N0} days + \ No newline at end of file diff --git a/TwitchDownloaderWPF/Translations/Strings.uk.resx b/TwitchDownloaderWPF/Translations/Strings.uk.resx index 16415bc6..47696b89 100644 --- a/TwitchDownloaderWPF/Translations/Strings.uk.resx +++ b/TwitchDownloaderWPF/Translations/Strings.uk.resx @@ -816,4 +816,25 @@ Бажана якість: + + Select Caches To Delete + + + Total Size: + + + Delete + + + Path + + + Age + + + Size + + + {0:N0} days + diff --git a/TwitchDownloaderWPF/Translations/Strings.zh.resx b/TwitchDownloaderWPF/Translations/Strings.zh.resx index ac16b721..aaf1d6d1 100644 --- a/TwitchDownloaderWPF/Translations/Strings.zh.resx +++ b/TwitchDownloaderWPF/Translations/Strings.zh.resx @@ -815,4 +815,25 @@ Preferred Quality: + + Select Caches To Delete + + + Total Size: + + + Delete + + + Path + + + Age + + + Size + + + {0:N0} days + \ No newline at end of file diff --git a/TwitchDownloaderWPF/WindowOldVideoCacheManager.xaml b/TwitchDownloaderWPF/WindowOldVideoCacheManager.xaml new file mode 100644 index 00000000..1c8b8043 --- /dev/null +++ b/TwitchDownloaderWPF/WindowOldVideoCacheManager.xaml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + +