From db113299a116b26d61a963f9d30b861edd4d2fd9 Mon Sep 17 00:00:00 2001 From: "Muhammad A." <16158433+kopihitam@users.noreply.github.com> Date: Sat, 31 Oct 2026 12:27:25 +0800 Subject: [PATCH] Let the user choose where temporary work files go Converting an image, converting a package to an image, and editing the details of an FFPFSC image or a package unpack the whole game first. That always went to the system temporary folder, which could fill the C: drive. - Settings > Temporary folder picks another folder; blank keeps the system temporary folder. "Keep temporary files next to the output instead" puts them in a .Packwright-* folder beside the output, on the same drive. - The command line follows the setting, and --temp overrides it. - A folder that cannot be created fails the job with a message naming it, instead of falling back to the system drive. Only each job's own subfolder is deleted, never the chosen folder. - The library scan skips .Packwright-* work folders, so a half-unpacked game next to an output is not listed as a title. - New smoke test: SmokeTests temp-folders . Closes itsmemac/Packwright#2 Co-Authored-By: Claude Opus 5.5 --- README.md | 2 +- src/Packwright.App/Cli.cs | 11 +- .../Infrastructure/AppSettingsNormalizer.cs | 1 + .../Infrastructure/AppStateStore.cs | 4 + src/Packwright.App/Services/AppServices.cs | 3 + src/Packwright.App/Services/MetadataEditor.cs | 3 +- src/Packwright.App/Views/SettingsView.axaml | 8 ++ .../Views/SettingsView.axaml.cs | 25 ++++ .../ImageConversionService.cs | 9 +- src/Packwright.Containers/TemporaryFolders.cs | 57 +++++++++ .../Services/Ps5DumpLocator.cs | 11 ++ .../Services/Ps5PackageImageConversion.cs | 3 +- tests/Packwright.SmokeTests/Program.cs | 1 + .../Packwright.SmokeTests/TempFolderTests.cs | 120 ++++++++++++++++++ 14 files changed, 248 insertions(+), 10 deletions(-) create mode 100644 src/Packwright.Containers/TemporaryFolders.cs create mode 100644 tests/Packwright.SmokeTests/TempFolderTests.cs diff --git a/README.md b/README.md index 434ef1d..16e7a3d 100644 --- a/README.md +++ b/README.md @@ -240,7 +240,7 @@ Example: `id:PPSA01 size:>20GB -dlc | category:patch`. | Settings (including consoles and presets), library cache, task history | Windows `%LOCALAPPDATA%\Packwright`, Linux and macOS `~/.local/share/Packwright` | | Library-only edits (`metadata-overrides.json`, `overrides/` for custom icons) | the same folder | | Logs | `logs/` inside that folder (also shown on the **Log** page) | -| Temporary work files | your system temporary folder (only used when an image has to be unpacked first) | +| Temporary work files | your system temporary folder, or the one chosen under **Settings > Temporary folder** (or next to the output); only used when an image has to be unpacked first. The command line takes `--temp ` too | Delete the folder to reset the app. Setting the `PACKWRIGHT_DATA` environment variable to a folder makes a separate copy of the app use that folder instead (handy for testing). diff --git a/src/Packwright.App/Cli.cs b/src/Packwright.App/Cli.cs index 51023c9..cc3e522 100644 --- a/src/Packwright.App/Cli.cs +++ b/src/Packwright.App/Cli.cs @@ -29,7 +29,8 @@ internal static class Cli private static readonly HashSet ValueOptions = new(StringComparer.OrdinalIgnoreCase) { "to", "o", "output", "preset", "passcode", "level", "gain", "cluster", "block", "fragment", "density", "min-free", - "compression", "kraken-level", "threads", "playgo", "drm", "format", "folder", "host", "port", "user", "password" + "compression", "kraken-level", "threads", "playgo", "drm", "format", "folder", "host", "port", "user", "password", + "temp" }; public static bool IsCommand(string[] args) => @@ -165,6 +166,7 @@ version Print the version. convert options: -o, --output Where to write it (default: next to the source). --overwrite replaces an existing file. + --temp Where to unpack work files (default: the folder chosen in Settings, or the system temp). --preset Use a saved or built-in preset from the Tools page (its target and options). --level 1-9 --gain 0-100 FFPFSC compression --cluster 32|64 --no-ampr exFAT @@ -357,6 +359,13 @@ private static async Task JobAsync(string command, Options options, Cancell Func, CancellationToken, Task>? run = spec.Executor(); if (run is null) return Fail("That job cannot run with these arguments.", Usage); + // Work files go where Settings says, unless --temp names a folder for this run. + if (options.Get("temp") is { } temp) TemporaryFolders.Configure(temp, nextToOutput: false); + else + { + AppSettings saved = new AppStateStore().LoadSettings(); + TemporaryFolders.Configure(saved.TempDirectory, saved.TempNextToOutput); + } var printer = new ConsoleProgress(); Console.Error.WriteLine($"{command}: {source}"); if (spec.Output.Length > 0) Console.Error.WriteLine(" -> " + spec.Output); diff --git a/src/Packwright.App/Infrastructure/AppSettingsNormalizer.cs b/src/Packwright.App/Infrastructure/AppSettingsNormalizer.cs index 4d2149e..f2bcc33 100644 --- a/src/Packwright.App/Infrastructure/AppSettingsNormalizer.cs +++ b/src/Packwright.App/Infrastructure/AppSettingsNormalizer.cs @@ -52,6 +52,7 @@ public static AppSettings Normalize(AppSettings? settings) settings.ThumbnailCacheCount = Math.Clamp(settings.ThumbnailCacheCount, MinThumbnailCache, MaxThumbnailCache); settings.OutputDirectory = (settings.OutputDirectory ?? string.Empty).Trim(); + settings.TempDirectory = (settings.TempDirectory ?? string.Empty).Trim(); // A passcode is either blank (meaning the built-in all-zero default) or exactly 32 printable // ASCII characters. Anything else is discarded rather than carried into a build. diff --git a/src/Packwright.App/Infrastructure/AppStateStore.cs b/src/Packwright.App/Infrastructure/AppStateStore.cs index 05ad840..b9f7542 100644 --- a/src/Packwright.App/Infrastructure/AppStateStore.cs +++ b/src/Packwright.App/Infrastructure/AppStateStore.cs @@ -118,6 +118,10 @@ public sealed class AppSettings // Paths & outputs public string OutputDirectory { get; set; } = string.Empty; public bool OpenOutputAfterTask { get; set; } + /// Where conversions and edits unpack their work files; empty is the system temporary folder. + public string TempDirectory { get; set; } = string.Empty; + /// Keep work files next to the output instead, on the same drive. + public bool TempNextToOutput { get; set; } // Tasks workspace /// Remembered vertical split between the task list and the details panel. diff --git a/src/Packwright.App/Services/AppServices.cs b/src/Packwright.App/Services/AppServices.cs index 375d6b8..de2927b 100644 --- a/src/Packwright.App/Services/AppServices.cs +++ b/src/Packwright.App/Services/AppServices.cs @@ -1,3 +1,4 @@ +using Packwright.Containers; using Packwright.Core.Models; using Packwright.Core.Services; using Packwright.Core.Tasks; @@ -19,6 +20,7 @@ private AppServices() SettingsLoadResult loaded = Store.LoadSettingsWithDiagnostics(); Settings = loaded.Settings; SettingsWarning = loaded.Warning; + TemporaryFolders.Configure(Settings.TempDirectory, Settings.TempNextToOutput); Queue = new PackageTaskQueue(); Queue.TasksChanged += (_, _) => LogTaskChanges(); string taskFile = Path.Combine(Store.AppDataDirectory, "studio-tasks.json"); @@ -160,6 +162,7 @@ public void SaveSettings() public void ReplaceSettings(AppSettings settings) { Settings = AppSettingsNormalizer.Normalize(settings); + TemporaryFolders.Configure(Settings.TempDirectory, Settings.TempNextToOutput); SaveSettings(); } diff --git a/src/Packwright.App/Services/MetadataEditor.cs b/src/Packwright.App/Services/MetadataEditor.cs index 9bd22fa..a75aedd 100644 --- a/src/Packwright.App/Services/MetadataEditor.cs +++ b/src/Packwright.App/Services/MetadataEditor.cs @@ -236,8 +236,7 @@ public static async Task ApplyAsync(string source, string output, bool fromPacka if (!inPlace && (File.Exists(target) || Directory.Exists(target))) throw new IOException("The output already exists: " + target); - string work = Path.Combine(Path.GetTempPath(), "Packwright-edit-" + Guid.NewGuid().ToString("N")); - Directory.CreateDirectory(work); + string work = TemporaryFolders.Create("edit", target); try { byte[]? icon = edit.IconPath.Length > 0 ? await File.ReadAllBytesAsync(edit.IconPath, token).ConfigureAwait(false) : null; diff --git a/src/Packwright.App/Views/SettingsView.axaml b/src/Packwright.App/Views/SettingsView.axaml index 497199f..a54e9ab 100644 --- a/src/Packwright.App/Views/SettingsView.axaml +++ b/src/Packwright.App/Views/SettingsView.axaml @@ -45,6 +45,14 @@