Skip to content

Commit

Permalink
Merge pull request #23 from lay295/master
Browse files Browse the repository at this point in the history
sync
  • Loading branch information
superbonaci authored Feb 7, 2024
2 parents 4d607b3 + 765d800 commit a11aa5b
Show file tree
Hide file tree
Showing 19 changed files with 523 additions and 61 deletions.
9 changes: 9 additions & 0 deletions TwitchDownloaderCLI/Modes/DownloadVideo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,15 @@ internal static void Download(VideoDownloadArgs inputOptions)
progress.ProgressChanged += ProgressHandler.Progress_ProgressChanged;

var downloadOptions = GetDownloadOptions(inputOptions);
downloadOptions.CacheCleanerCallback = directoryInfos =>
{
Console.WriteLine(
$"[LOG] - {directoryInfos.Length} unmanaged video caches were found at '{downloadOptions.TempFolder}' and can be safely deleted. " +
"Run 'TwitchDownloaderCLI cache help' for more information.");

return Array.Empty<DirectoryInfo>();
};

VideoDownloader videoDownloader = new(downloadOptions, progress);
videoDownloader.DownloadAsync(new CancellationToken()).Wait();
}
Expand Down
4 changes: 4 additions & 0 deletions TwitchDownloaderCLI/Tools/CacheHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ public static void ParseArgs(CacheArgs args)
{
PromptClearCache();
}

// TODO: Add option to print out cache information (i.e. individual sub-directory size, maybe in table form?)
// TODO: Add interactive cache delete mode (i.e. loop over each sub-directory with Yes/No delete prompts)
// TODO: Allow the user to specify a cache folder so it can be managed with the aforementioned tools
}

private static void PromptClearCache()
Expand Down
27 changes: 9 additions & 18 deletions TwitchDownloaderCore/Extensions/M3U8Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -110,35 +110,26 @@ public static string GetResolutionFramerateString(this M3U8.Stream stream)

if (streamInfo.Resolution == default)
{
if (stream.IsSource())
{
return "Source";
}

return "";
return stream.IsSource()
? "Source"
: "";
}

var frameHeight = streamInfo.Resolution.Height;

if (streamInfo.Framerate == default)
{
if (stream.IsSource())
{
return $"{frameHeight}p (Source)";
}

return $"{frameHeight}p";
return stream.IsSource()
? $"{frameHeight}p (Source)"
: $"{frameHeight}p";
}

// Some M3U8 responses have framerate values up to 2fps more/less than the typical framerate.
var frameRate = (uint)(Math.Round(streamInfo.Framerate / 10) * 10);

if (stream.IsSource())
{
return $"{frameHeight}p{frameRate} (Source)";
}

return $"{frameHeight}p{frameRate}";
return stream.IsSource()
? $"{frameHeight}p{frameRate} (Source)"
: $"{frameHeight}p{frameRate}";
}

/// <summary>
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>
Loading

0 comments on commit a11aa5b

Please sign in to comment.