diff --git a/PKHeX.Core/Saves/Util/SaveFinder.cs b/PKHeX.Core/Saves/Util/SaveFinder.cs
index 8d8ece247..7d43bac36 100644
--- a/PKHeX.Core/Saves/Util/SaveFinder.cs
+++ b/PKHeX.Core/Saves/Util/SaveFinder.cs
@@ -18,27 +18,70 @@ public static class SaveFinder
/// List of drives on the host machine.
/// Optional parameter to skip the first drive.
/// The first drive is usually the system hard drive, or can be a floppy disk drive (slower to check, never has expected data).
+ /// Cancellation token to cancel the operation.
/// Folder path pointing to the Nintendo 3DS folder.
- public static string? Get3DSLocation(IEnumerable drives, bool skipFirstDrive = true) =>
- FindConsoleRootFolder(drives, "Nintendo 3DS", skipFirstDrive);
+ public static string? Get3DSLocation(IEnumerable drives, bool skipFirstDrive, CancellationToken token) =>
+ FindConsoleRootFolder(drives, "Nintendo 3DS", skipFirstDrive, token);
///
/// Searches the provided to find a valid Switch drive, usually from an inserted SD card.
///
/// List of drives on the host machine.
- /// Optional parameter to skip the first drive.
- /// The first drive is usually the system hard drive, or can be a floppy disk drive (slower to check, never has expected data).
+ /// Optional parameter to skip the first drive.
+ /// Cancellation token to cancel the operation.
/// Folder path pointing to the Nintendo folder.
- public static string? GetSwitchLocation(IEnumerable drives, bool skipFirstDrive = true) =>
- FindConsoleRootFolder(drives, "Nintendo", skipFirstDrive);
+ public static string? GetSwitchLocation(IEnumerable drives, bool skipFirstDrive, CancellationToken token) =>
+ FindConsoleRootFolder(drives, "Nintendo", skipFirstDrive, token);
- private static string? FindConsoleRootFolder(IEnumerable drives, [ConstantExpected] string path, bool skipFirstDrive)
+ private static string? FindConsoleRootFolder(IEnumerable drives, [ConstantExpected] string path, bool skipFirstDrive, CancellationToken token)
{
- if (skipFirstDrive)
- drives = drives.Skip(1);
+ foreach (var drive in GetUsableDrives(drives, skipFirstDrive, token))
+ {
+ if (token.IsCancellationRequested)
+ break;
- var paths = drives.Select(drive => Path.Combine(drive, path));
- return paths.FirstOrDefault(Directory.Exists);
+ var candidate = Path.Combine(drive, path);
+ // Directory.Exists is synchronous and cannot itself be cancelled.
+ // Slow/unresponsive external drives can really drag execution.
+ if (Directory.Exists(candidate))
+ return candidate;
+ }
+
+ return null;
+ }
+
+ private static IEnumerable GetUsableDrives(IEnumerable drives, bool skipFirstDrive, CancellationToken token)
+ {
+ var first = true;
+ foreach (var drive in drives)
+ {
+ if (token.IsCancellationRequested)
+ yield break;
+
+ if (skipFirstDrive && first)
+ {
+ first = false;
+ continue;
+ }
+
+ first = false;
+ if (IsUsableDrive(drive))
+ yield return drive;
+ }
+ }
+
+ private static bool IsUsableDrive(string drive)
+ {
+ try
+ {
+ var type = new DriveInfo(drive).DriveType;
+ return type is DriveType.Fixed or DriveType.Removable or DriveType.Ram;
+ // ignore Network, CDRom, NoRootDirectory, and Unknown
+ }
+ catch
+ {
+ return false;
+ }
}
///
@@ -92,15 +135,28 @@ public static IEnumerable GetSwitchBackupPaths(string root)
/// Reference to a valid save file, if any.
public static SaveFile? FindMostRecentSaveFile(IReadOnlyList drives, IEnumerable extra, CancellationToken token)
{
+ if (token.IsCancellationRequested)
+ return null;
+
var foldersToCheck = GetFoldersToCheck(drives, extra, token);
var result = GetSaveFilePathsFromFolders(foldersToCheck, true, out var possiblePaths, token);
if (!result)
throw new FileNotFoundException(string.Join(Environment.NewLine, possiblePaths)); // `possiblePaths` contains the error message
- // return newest save file path that is valid
- var byMostRecent = possiblePaths.OrderByDescending(File.GetLastWriteTimeUtc);
- var saves = byMostRecent.Select(SaveUtil.GetSaveFile);
- return saves.FirstOrDefault(z => z?.ChecksumsValid == true);
+ if (token.IsCancellationRequested)
+ return null;
+
+ foreach (var path in possiblePaths.OrderByDescending(File.GetLastWriteTimeUtc))
+ {
+ if (token.IsCancellationRequested)
+ break;
+
+ var save = SaveUtil.GetSaveFile(path);
+ if (save?.ChecksumsValid == true)
+ return save;
+ }
+
+ return null;
}
///
@@ -119,9 +175,10 @@ public static IEnumerable GetSaveFiles(IReadOnlyList drives, b
if (!result)
yield break;
- var byMostRecent = possiblePaths.OrderByDescending(File.GetLastWriteTimeUtc);
- foreach (var s in byMostRecent)
+ foreach (var s in possiblePaths.OrderByDescending(File.GetLastWriteTimeUtc))
{
+ if (token.IsCancellationRequested)
+ yield break;
if (SaveUtil.TryGetSaveFile(s, out var sav))
yield return sav;
}
@@ -129,15 +186,27 @@ public static IEnumerable GetSaveFiles(IReadOnlyList drives, b
public static IEnumerable GetFoldersToCheck(IReadOnlyList drives, IEnumerable extra, CancellationToken token)
{
- var foldersToCheck = extra.Where(f => !string.IsNullOrWhiteSpace(f)).Concat(CustomBackupPaths);
+ if (token.IsCancellationRequested)
+ return [];
- string? path3DS = Path.GetPathRoot(Get3DSLocation(drives));
+ var foldersToCheck = new List();
+ foreach (var folder in extra)
+ {
+ if (!string.IsNullOrWhiteSpace(folder))
+ foldersToCheck.Add(folder);
+ }
+
+ foldersToCheck.AddRange(CustomBackupPaths);
+ string? path3DS = Path.GetPathRoot(Get3DSLocation(drives, true, token));
if (!string.IsNullOrEmpty(path3DS)) // check for Homebrew/CFW backups
- foldersToCheck = foldersToCheck.Concat(Get3DSBackupPaths(path3DS));
+ foldersToCheck.AddRange(Get3DSBackupPaths(path3DS));
- string? pathNX = Path.GetPathRoot(GetSwitchLocation(drives));
+ if (token.IsCancellationRequested)
+ return foldersToCheck;
+
+ string? pathNX = Path.GetPathRoot(GetSwitchLocation(drives, true, token));
if (!string.IsNullOrEmpty(pathNX)) // check for Homebrew/CFW backups
- foldersToCheck = foldersToCheck.Concat(GetSwitchBackupPaths(pathNX));
+ foldersToCheck.AddRange(GetSwitchBackupPaths(pathNX));
return foldersToCheck;
}
diff --git a/PKHeX.WinForms/MainWindow/Main.cs b/PKHeX.WinForms/MainWindow/Main.cs
index 305e412c8..4d87511a1 100644
--- a/PKHeX.WinForms/MainWindow/Main.cs
+++ b/PKHeX.WinForms/MainWindow/Main.cs
@@ -1389,7 +1389,7 @@ private void ClickSaveFileName(object sender, EventArgs e)
{
try
{
- var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
+ using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
if (!SaveFinder.TryDetectSaveFile(cts.Token, out var sav))
return;
diff --git a/PKHeX.WinForms/Subforms/SAV_FolderList.cs b/PKHeX.WinForms/Subforms/SAV_FolderList.cs
index c6210967d..0705bfcae 100644
--- a/PKHeX.WinForms/Subforms/SAV_FolderList.cs
+++ b/PKHeX.WinForms/Subforms/SAV_FolderList.cs
@@ -29,7 +29,7 @@ public SAV_FolderList(Action openSaveFile)
var backups = Main.BackupPath;
var drives = Environment.GetLogicalDrives();
- Paths = GetPathList(drives, backups);
+ Paths = GetPathList(drives, backups, cts.Token);
components ??= new System.ComponentModel.Container();
dgDataRecent.ContextMenuStrip = GetContextMenu(dgDataRecent);
@@ -80,12 +80,13 @@ private void OnKeyDown(object sender, KeyEventArgs e)
Close();
}
- private static List GetPathList(IReadOnlyList drives, string backupPath)
+ private static List GetPathList(IReadOnlyList drives, string backupPath,
+ CancellationToken token)
{
List locs =
[
new CustomFolderPath(backupPath, DisplayText: "PKHeX Backups"),
- ..GetUserPaths(), ..GetPaths3DS(drives), ..GetPathsSwitch(drives),
+ ..GetUserPaths(), ..GetPaths3DS(drives, token), ..GetPathsSwitch(drives, token),
];
var filtered = locs
.DistinctBy(z => z.Path)
@@ -129,9 +130,9 @@ private static IEnumerable GetUserPaths()
return paths.Select(x => new CustomFolderPath(x, FolderPathGroup.Custom));
}
- private static IEnumerable GetPaths3DS(IEnumerable drives)
+ private static IEnumerable GetPaths3DS(IEnumerable drives, CancellationToken token)
{
- var path3DS = SaveFinder.Get3DSLocation(drives);
+ var path3DS = SaveFinder.Get3DSLocation(drives, true, token);
if (path3DS is null)
return [];
@@ -143,9 +144,9 @@ private static IEnumerable GetPaths3DS(IEnumerable dri
return paths.Select(z => new CustomFolderPath(z, FolderPathGroup.Nintendo3DS));
}
- private static IEnumerable GetPathsSwitch(IEnumerable drives)
+ private static IEnumerable GetPathsSwitch(IEnumerable drives, CancellationToken token)
{
- var pathNX = SaveFinder.GetSwitchLocation(drives);
+ var pathNX = SaveFinder.GetSwitchLocation(drives, true, token);
if (pathNX is null)
return [];
diff --git a/PKHeX.WinForms/Util/WinFormsUtil.cs b/PKHeX.WinForms/Util/WinFormsUtil.cs
index fc2cbe7b8..54eb91b9d 100644
--- a/PKHeX.WinForms/Util/WinFormsUtil.cs
+++ b/PKHeX.WinForms/Util/WinFormsUtil.cs
@@ -338,7 +338,7 @@ public static bool OpenSAVPKMDialog(IEnumerable extensions, [NotNullWhen
{
try
{
- var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
+ using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
var sav = SaveFinder.FindMostRecentSaveFile(cts.Token);
return sav?.Metadata.FilePath;
}