Skip to content

Commit

Permalink
Save state pruning (#330)
Browse files Browse the repository at this point in the history
* prune save states

* readme update

* fixed date format

* comment

* woops, removed unneeded string replace

* delete
  • Loading branch information
mattpannella authored Sep 11, 2024
1 parent 605326c commit a15fca1
Show file tree
Hide file tree
Showing 6 changed files with 98 additions and 2 deletions.
3 changes: 2 additions & 1 deletion MENU.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
2. Reinstall Select Cores
3. Uninstall Select Cores
4. Remove JT Beta Keys
5. Go Back
5. Prune Save States
6. Go Back
8. Pocket Extras
1. Additional Assets
1. Download for Eric Lewis's Donkey Kong
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,10 @@ This contains a list of alternate core setups. These take existing cores and mak
display-modes Enable all Display Modes
-p, --path Absolute path to install location
prune-memories Delete all but the latest save states for each game (defaults to all cores)
-p, --path Absolute path to install location
-c, --core The core you want to prune memories for
update-self Check for updates to pupdate
help Display more information on a specific command.
Expand Down
6 changes: 5 additions & 1 deletion src/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ private static void Main(string[] args)
var parserResult = parser.ParseArguments<MenuOptions, FundOptions, UpdateOptions,
AssetsOptions, FirmwareOptions, ImagesOptions, InstanceGeneratorOptions,
UpdateSelfOptions, UninstallOptions, BackupSavesOptions, GameBoyPalettesOptions,
PocketLibraryImagesOptions, PocketExtrasOptions, DisplayModesOptions>(args)
PocketLibraryImagesOptions, PocketExtrasOptions, DisplayModesOptions, PruneMemoriesOptions>(args)
.WithNotParsed(errors =>
{
foreach (var error in errors)
Expand Down Expand Up @@ -208,6 +208,10 @@ private static void Main(string[] args)
EnableDisplayModes();
break;

case PruneMemoriesOptions options:
AssetsService.PruneSaveStates(ServiceHelper.UpdateDirectory, options.CoreName);
break;

default:
DisplayMenu(coreUpdaterService);
break;
Expand Down
10 changes: 10 additions & 0 deletions src/options/PruneMemoriesOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using CommandLine;

namespace Pannella.Options;

[Verb("prune-memories", HelpText = "Prune old memories")]
public class PruneMemoriesOptions : BaseOptions
{
[Option ('c', "core", Required = false, HelpText = "The core you want to prune save states for.")]
public string CoreName { get; set; }
}
5 changes: 5 additions & 0 deletions src/partials/Program.Menus.cs
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,11 @@ private static void DisplayMenu(CoreUpdaterService coreUpdaterService)

Pause();
})
.Add("Prune Save States", _ =>
{
AssetsService.PruneSaveStates(ServiceHelper.UpdateDirectory);
Pause();
})
.Add("Go Back", ConsoleMenu.Close);

var additionalAssetsMenu = new ConsoleMenu().Configure(menuConfig);
Expand Down
72 changes: 72 additions & 0 deletions src/services/AssetsService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Security.Cryptography;
using System.Text;
using Newtonsoft.Json;
using System.Text.RegularExpressions;
using Pannella.Helpers;

namespace Pannella.Services;
Expand Down Expand Up @@ -128,4 +129,75 @@ private static string ComputeDirectoryHash(string directoryPath)
return BitConverter.ToString(finalHashBytes).Replace("-", "").ToLowerInvariant();
}
}

public static void PruneSaveStates(string rootDirectory, string coreName = null)
{
AssetsService.BackupMemories(ServiceHelper.UpdateDirectory, ServiceHelper.SettingsService.GetConfig().backup_saves_location);
string savesPath = Path.Combine(rootDirectory, "Memories", "Save States");

//YYYYMMDD_HHMMSS_SOMETHING_SOMETHING_GAMETITLE.STA
string pattern = @"^(\d\d\d\d\d\d\d\d_\d\d\d\d\d\d)_[A-Za-z]+_[A-Za-z0-9]+_(.*)\.sta$";
Regex regex = new Regex(pattern);

foreach (var dir in Directory.EnumerateDirectories(savesPath) )
{
//just skip it if it's not the requested core
if (coreName != null && dir != Path.Combine(savesPath, coreName))
{
continue;
}

// Dictionary to store the most recent file per game
var mostRecentFiles = new Dictionary<string, (string fileName, long timestamp)>();

// Get all .sta files in the directory
var files = Directory.GetFiles(dir, "*.sta");

foreach (var file in files)
{
string fileName = Path.GetFileName(file);

// Match the filename with the regex
Match match = regex.Match(fileName);

if (match.Success)
{
// Extract the timestamp (group 1) and game name (group 2)
long timestamp = long.Parse(match.Groups[1].Value.Replace("_", String.Empty));
string applicationName = match.Groups[2].Value;

// Check if this game already has a file in the dictionary
if (!mostRecentFiles.ContainsKey(applicationName) || mostRecentFiles[applicationName].timestamp < timestamp)
{
// If the file is more recent, or the first one for this game, store it
mostRecentFiles[applicationName] = (fileName, timestamp);
}
}
}

// Now prune the folder by deleting older files
foreach (var file in files)
{
string fileName = Path.GetFileName(file);

// Match the filename with the regex
Match match = regex.Match(fileName);

if (match.Success)
{
// Extract the game name (Group 2)
string applicationName = match.Groups[2].Value;

// Check if the file is the most recent for this game
if (mostRecentFiles.ContainsKey(applicationName) && mostRecentFiles[applicationName].fileName != fileName)
{
// If it's not the most recent file for this game, delete it
File.Delete(file);
Console.WriteLine($"Deleted file: {fileName}");
}
}
}
}
Console.WriteLine("Pruning completed. Most recent save state for each game retained.");
}
}

0 comments on commit a15fca1

Please sign in to comment.