Skip to content

Commit

Permalink
Prompt user for old cache deletion (#963)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
ScrubN authored Feb 6, 2024
1 parent 94971a4 commit dac8051
Show file tree
Hide file tree
Showing 17 changed files with 508 additions and 43 deletions.
7 changes: 7 additions & 0 deletions TwitchDownloaderCLI/Modes/DownloadVideo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<ProgressReport>)progress).Report(new ProgressReport(ReportType.Log, $"{directoryInfos.Length} unmanaged video caches were found in {downloadOptions.TempFolder} and can be safely deleted."));
return Array.Empty<DirectoryInfo>();
};

VideoDownloader videoDownloader = new(downloadOptions, progress);
videoDownloader.DownloadAsync(new CancellationToken()).Wait();
}
Expand Down
6 changes: 5 additions & 1 deletion TwitchDownloaderCore/Options/VideoDownloadOptions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
namespace TwitchDownloaderCore.Options
using System;
using System.IO;

namespace TwitchDownloaderCore.Options
{
public class VideoDownloadOptions
{
Expand All @@ -14,5 +17,6 @@ public class VideoDownloadOptions
public string Oauth { get; set; }
public string FfmpegPath { get; set; }
public string TempFolder { get; set; }
public Func<DirectoryInfo[], DirectoryInfo[]> CacheCleanerCallback { get; set; }
}
}
72 changes: 34 additions & 38 deletions TwitchDownloaderCore/TwitchHelper.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using SkiaSharp;
using System;
using System.Buffers;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
Expand Down Expand Up @@ -813,63 +814,58 @@ public static void SetDirectoryPermissions(string path)
/// <summary>
/// Cleans up any unmanaged cache files from previous runs that were interrupted before cleaning up
/// </summary>
public static void CleanupUnmanagedCacheFiles(string cacheFolder, IProgress<ProgressReport> progress)
public static async Task CleanupAbandonedVideoCaches(string cacheFolder, Func<DirectoryInfo[], DirectoryInfo[]> itemsToDeleteCallback, IProgress<ProgressReport> 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<char> 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)
Expand Down
4 changes: 2 additions & 2 deletions TwitchDownloaderCore/VideoDownloader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,11 @@ public VideoDownloader(VideoDownloadOptions videoDownloadOptions, IProgress<Prog

public async Task DownloadAsync(CancellationToken cancellationToken)
{
TwitchHelper.CleanupUnmanagedCacheFiles(downloadOptions.TempFolder, _progress);
await TwitchHelper.CleanupAbandonedVideoCaches(downloadOptions.TempFolder, downloadOptions.CacheCleanerCallback, _progress);

string downloadFolder = Path.Combine(
downloadOptions.TempFolder,
$"{downloadOptions.Id}_{DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()}");
$"{downloadOptions.Id}_{DateTimeOffset.UtcNow.Ticks}");

_progress.Report(new ProgressReport(ReportType.SameLineStatus, "Fetching Video Info [1/5]"));

Expand Down
16 changes: 16 additions & 0 deletions TwitchDownloaderWPF/PageVodDownload.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,7 @@ private async void SplitBtnDownloader_Click(object sender, RoutedEventArgs e)
btnGetInfo.IsEnabled = false;

VideoDownloadOptions options = GetOptions(saveFileDialog.FileName, null);
options.CacheCleanerCallback = HandleCacheCleanerCallback;

Progress<ProgressReport> downloadProgress = new Progress<ProgressReport>(OnProgressChanged);
VideoDownloader currentDownload = new VideoDownloader(options, downloadProgress);
Expand Down Expand Up @@ -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;
Expand Down
63 changes: 63 additions & 0 deletions TwitchDownloaderWPF/Translations/Strings.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 21 additions & 0 deletions TwitchDownloaderWPF/Translations/Strings.es.resx
Original file line number Diff line number Diff line change
Expand Up @@ -818,4 +818,25 @@
<data name="PreferredQuality" xml:space="preserve">
<value>Preferred Quality:</value>
</data>
<data name="TitleWindowOldVideoCacheManager" xml:space="preserve">
<value>Select Caches To Delete</value>
</data>
<data name="SizeOfAllFiles" xml:space="preserve">
<value>Total Size:</value>
</data>
<data name="DeleteCacheColumnHeader" xml:space="preserve">
<value>Delete</value>
</data>
<data name="FilePathColumnHeader" xml:space="preserve">
<value>Path</value>
</data>
<data name="FileAgeColumnHeader" xml:space="preserve">
<value>Age</value>
</data>
<data name="FileSizeColumnHeader" xml:space="preserve">
<value>Size</value>
</data>
<data name="FileAgeInDays" xml:space="preserve">
<value>{0:N0} days</value>
</data>
</root>
21 changes: 21 additions & 0 deletions TwitchDownloaderWPF/Translations/Strings.fr.resx
Original file line number Diff line number Diff line change
Expand Up @@ -816,4 +816,25 @@
<data name="PreferredQuality" xml:space="preserve">
<value>Qualité préférée:</value>
</data>
<data name="TitleWindowOldVideoCacheManager" xml:space="preserve">
<value>Select Caches To Delete</value>
</data>
<data name="SizeOfAllFiles" xml:space="preserve">
<value>Total Size:</value>
</data>
<data name="DeleteCacheColumnHeader" xml:space="preserve">
<value>Delete</value>
</data>
<data name="FilePathColumnHeader" xml:space="preserve">
<value>Path</value>
</data>
<data name="FileAgeColumnHeader" xml:space="preserve">
<value>Age</value>
</data>
<data name="FileSizeColumnHeader" xml:space="preserve">
<value>Size</value>
</data>
<data name="FileAgeInDays" xml:space="preserve">
<value>{0:N0} days</value>
</data>
</root>
24 changes: 22 additions & 2 deletions TwitchDownloaderWPF/Translations/Strings.it.resx
Original file line number Diff line number Diff line change
Expand Up @@ -812,10 +812,30 @@
<value>Remove</value>
</data>
<data name="ContextMenuOpenTaskFolder" xml:space="preserve">
<value>Open folder
</value>
<value>Open folder</value>
</data>
<data name="PreferredQuality" xml:space="preserve">
<value>Preferred Quality:</value>
</data>
<data name="TitleWindowOldVideoCacheManager" xml:space="preserve">
<value>Select Caches To Delete</value>
</data>
<data name="SizeOfAllFiles" xml:space="preserve">
<value>Total Size:</value>
</data>
<data name="DeleteCacheColumnHeader" xml:space="preserve">
<value>Delete</value>
</data>
<data name="FilePathColumnHeader" xml:space="preserve">
<value>Path</value>
</data>
<data name="FileAgeColumnHeader" xml:space="preserve">
<value>Age</value>
</data>
<data name="FileSizeColumnHeader" xml:space="preserve">
<value>Size</value>
</data>
<data name="FileAgeInDays" xml:space="preserve">
<value>{0:N0} days</value>
</data>
</root>
21 changes: 21 additions & 0 deletions TwitchDownloaderWPF/Translations/Strings.pl.resx
Original file line number Diff line number Diff line change
Expand Up @@ -816,4 +816,25 @@
<data name="PreferredQuality" xml:space="preserve">
<value>Preferred Quality:</value>
</data>
<data name="TitleWindowOldVideoCacheManager" xml:space="preserve">
<value>Select Caches To Delete</value>
</data>
<data name="SizeOfAllFiles" xml:space="preserve">
<value>Total Size:</value>
</data>
<data name="DeleteCacheColumnHeader" xml:space="preserve">
<value>Delete</value>
</data>
<data name="FilePathColumnHeader" xml:space="preserve">
<value>Path</value>
</data>
<data name="FileAgeColumnHeader" xml:space="preserve">
<value>Age</value>
</data>
<data name="FileSizeColumnHeader" xml:space="preserve">
<value>Size</value>
</data>
<data name="FileAgeInDays" xml:space="preserve">
<value>{0:N0} days</value>
</data>
</root>
Loading

0 comments on commit dac8051

Please sign in to comment.