Add `TryDeleteDirectoryAsync` and remove `DeleteRecursivelyWithMagicDustAsync`.
MODIFY
WalletWasabi.Packager/MacSignTools.cs
WalletWasabi.Packager/MacSignTools.cs
@@ -393,7 +393,7 @@ namespace WalletWasabi.Packager
process.WaitForExit();
}
- IoHelpers.DeleteRecursivelyWithMagicDustAsync(path).GetAwaiter().GetResult();
+ IoHelpers.TryDeleteDirectoryAsync(path).GetAwaiter().GetResult();
}
private static void UnlockKeychain()

MODIFY
WalletWasabi.Packager/Program.cs
WalletWasabi.Packager/Program.cs
@@ -180,7 +180,7 @@ namespace WalletWasabi.Packager
private static void CreateDigests()
{
var tempDir = "DigestTempDir";
- IoHelpers.DeleteRecursivelyWithMagicDustAsync(tempDir).GetAwaiter().GetResult();
+ IoHelpers.TryDeleteDirectoryAsync(tempDir).GetAwaiter().GetResult();
Directory.CreateDirectory(tempDir);
var torDaemonsDir = Path.Combine(LibraryProjectDirectory, "TorDaemons");
@@ -206,7 +206,7 @@ namespace WalletWasabi.Packager
Console.WriteLine($"{file.Name}: {hash}");
}
- IoHelpers.DeleteRecursivelyWithMagicDustAsync(tempDir).GetAwaiter().GetResult();
+ IoHelpers.TryDeleteDirectoryAsync(tempDir).GetAwaiter().GetResult();
}
private static void ReportStatus()
@@ -278,7 +278,7 @@ namespace WalletWasabi.Packager
process.WaitForExit();
}
- IoHelpers.DeleteRecursivelyWithMagicDustAsync(publishedFolder).GetAwaiter().GetResult();
+ IoHelpers.TryDeleteDirectoryAsync(publishedFolder).GetAwaiter().GetResult();
Console.WriteLine($"Deleted {publishedFolder}");
}
else if (target.StartsWith("osx", StringComparison.OrdinalIgnoreCase))
@@ -331,7 +331,7 @@ namespace WalletWasabi.Packager
{
if (Directory.Exists(BinDistDirectory))
{
- IoHelpers.DeleteRecursivelyWithMagicDustAsync(BinDistDirectory).GetAwaiter().GetResult();
+ IoHelpers.TryDeleteDirectoryAsync(BinDistDirectory).GetAwaiter().GetResult();
Console.WriteLine($"Deleted {BinDistDirectory}");
}
@@ -350,12 +350,12 @@ namespace WalletWasabi.Packager
var libraryBinReleaseDirectory = Path.GetFullPath(Path.Combine(LibraryProjectDirectory, "bin\\Release"));
if (Directory.Exists(guiBinReleaseDirectory))
{
- IoHelpers.DeleteRecursivelyWithMagicDustAsync(guiBinReleaseDirectory).GetAwaiter().GetResult();
+ IoHelpers.TryDeleteDirectoryAsync(guiBinReleaseDirectory).GetAwaiter().GetResult();
Console.WriteLine($"Deleted {guiBinReleaseDirectory}");
}
if (Directory.Exists(libraryBinReleaseDirectory))
{
- IoHelpers.DeleteRecursivelyWithMagicDustAsync(libraryBinReleaseDirectory).GetAwaiter().GetResult();
+ IoHelpers.TryDeleteDirectoryAsync(libraryBinReleaseDirectory).GetAwaiter().GetResult();
Console.WriteLine($"Deleted {libraryBinReleaseDirectory}");
}
@@ -453,7 +453,7 @@ namespace WalletWasabi.Packager
{
if (!dir.Name.Contains(toNotRemove, StringComparison.OrdinalIgnoreCase))
{
- IoHelpers.DeleteRecursivelyWithMagicDustAsync(dir.FullName).GetAwaiter().GetResult();
+ IoHelpers.TryDeleteDirectoryAsync(dir.FullName).GetAwaiter().GetResult();
}
}
@@ -512,7 +512,7 @@ namespace WalletWasabi.Packager
ZipFile.CreateFromDirectory(currentBinDistDirectory, Path.Combine(BinDistDirectory, $"Wasabi-osx-{VersionPrefix}.zip"));
- IoHelpers.DeleteRecursivelyWithMagicDustAsync(currentBinDistDirectory).GetAwaiter().GetResult();
+ IoHelpers.TryDeleteDirectoryAsync(currentBinDistDirectory).GetAwaiter().GetResult();
Console.WriteLine($"Deleted {currentBinDistDirectory}");
}
else if (target.StartsWith("linux"))
@@ -660,13 +660,13 @@ namespace WalletWasabi.Packager
process.WaitForExit();
}
- IoHelpers.DeleteRecursivelyWithMagicDustAsync(debFolderPath).GetAwaiter().GetResult();
+ IoHelpers.TryDeleteDirectoryAsync(debFolderPath).GetAwaiter().GetResult();
string oldDeb = Path.Combine(BinDistDirectory, $"{ExecutableName}_{VersionPrefix}_amd64.deb");
string newDeb = Path.Combine(BinDistDirectory, $"Wasabi-{VersionPrefix}.deb");
File.Move(oldDeb, newDeb);
- IoHelpers.DeleteRecursivelyWithMagicDustAsync(publishedFolder).GetAwaiter().GetResult();
+ IoHelpers.TryDeleteDirectoryAsync(publishedFolder).GetAwaiter().GetResult();
Console.WriteLine($"Deleted {publishedFolder}");
}
}

MODIFY
WalletWasabi.Tests/RegressionTests/CoinJoinTests.cs
WalletWasabi.Tests/RegressionTests/CoinJoinTests.cs
@@ -80,7 +80,7 @@ namespace WalletWasabi.Tests.RegressionTests
var mempoolTxId = await rpc.SendToAddressAsync(new Key().PubKey.GetSegwitAddress(network), Money.Coins(1));
var folder = Common.GetWorkDir();
- await IoHelpers.DeleteRecursivelyWithMagicDustAsync(folder);
+ await IoHelpers.TryDeleteDirectoryAsync(folder);
Directory.CreateDirectory(folder);
var cjfile = Path.Combine(folder, $"CoinJoins{network}.txt");
File.WriteAllLines(cjfile, new[] { coinbaseTxId.ToString(), offchainTxId.ToString(), mempoolTxId.ToString() });
@@ -93,7 +93,7 @@ namespace WalletWasabi.Tests.RegressionTests
Assert.Contains(mempoolTxId.ToString(), txIds);
Assert.DoesNotContain(offchainTxId.ToString(), txIds);
- await IoHelpers.DeleteRecursivelyWithMagicDustAsync(folder);
+ await IoHelpers.TryDeleteDirectoryAsync(folder);
Directory.CreateDirectory(folder);
File.WriteAllLines(cjfile, new[] { coinbaseTxId.ToString(), "This line is invalid (the file is corrupted)", offchainTxId.ToString() });

MODIFY
WalletWasabi.Tests/UnitTests/Filters/IndexStoreTests.cs
WalletWasabi.Tests/UnitTests/Filters/IndexStoreTests.cs
@@ -167,7 +167,7 @@ namespace WalletWasabi.Tests.UnitTests.Filters
private async Task<(string dir, string matureFilters, string immatureFilters)> GetIndexStorePathsAsync([CallerFilePath]string callerFilePath = null, [CallerMemberName] string callerName = "")
{
var dir = Path.Combine(Global.Instance.DataDir, EnvironmentHelpers.ExtractFileName(callerFilePath), callerName, "IndexStore");
- await IoHelpers.DeleteRecursivelyWithMagicDustAsync(dir);
+ await IoHelpers.TryDeleteDirectoryAsync(dir);
var matureFilters = Path.Combine(dir, "MatureIndex.dat");
var immatureFilters = Path.Combine(dir, "ImmatureIndex.dat");
IoHelpers.EnsureContainingDirectoryExists(matureFilters);

MODIFY
WalletWasabi.Tests/UnitTests/Transactions/TransactionProcessorTests.cs
WalletWasabi.Tests/UnitTests/Transactions/TransactionProcessorTests.cs
@@ -1110,7 +1110,7 @@ namespace WalletWasabi.Tests.UnitTests.Transactions
var txStore = new AllTransactionStore();
var dir = Path.Combine(Global.Instance.DataDir, EnvironmentHelpers.ExtractFileName(callerFilePath), callerName, "TransactionStore");
- await IoHelpers.DeleteRecursivelyWithMagicDustAsync(dir);
+ await IoHelpers.TryDeleteDirectoryAsync(dir);
await txStore.InitializeAsync(dir, Network.RegTest);
return new TransactionProcessor(

MODIFY
WalletWasabi.Tests/UnitTests/WalletDirTests.cs
WalletWasabi.Tests/UnitTests/WalletDirTests.cs
@@ -16,8 +16,8 @@ namespace WalletWasabi.Tests.UnitTests
{
var walletsPath = Path.Combine(baseDir, WalletDirectories.WalletsDirName);
var walletsBackupPath = Path.Combine(baseDir, WalletDirectories.WalletsBackupDirName);
- await IoHelpers.DeleteRecursivelyWithMagicDustAsync(walletsPath);
- await IoHelpers.DeleteRecursivelyWithMagicDustAsync(walletsBackupPath);
+ await IoHelpers.TryDeleteDirectoryAsync(walletsPath);
+ await IoHelpers.TryDeleteDirectoryAsync(walletsBackupPath);
return (walletsPath, walletsBackupPath);
}

MODIFY
WalletWasabi.Tests/XunitConfiguration/RegTestFixture.cs
WalletWasabi.Tests/XunitConfiguration/RegTestFixture.cs
@@ -33,7 +33,7 @@ namespace WalletWasabi.Tests.XunitConfiguration
BackendRegTestNode = TestNodeBuilder.CreateAsync(hostedServices, callerFilePath: "RegTests", callerMemberName: "BitcoinCoreData").GetAwaiter().GetResult();
var testnetBackendDir = EnvironmentHelpers.GetDataDir(Path.Combine("WalletWasabi", "Tests", "RegTests", "Backend"));
- IoHelpers.DeleteRecursivelyWithMagicDustAsync(testnetBackendDir).GetAwaiter().GetResult();
+ IoHelpers.TryDeleteDirectoryAsync(testnetBackendDir).GetAwaiter().GetResult();
Thread.Sleep(100);
Directory.CreateDirectory(testnetBackendDir);
Thread.Sleep(100);

MODIFY
WalletWasabi/BitcoinCore/CoreNode.cs
WalletWasabi/BitcoinCore/CoreNode.cs
@@ -106,7 +106,7 @@ namespace WalletWasabi.BitcoinCore
if (coreNodeParams.TryDeleteDataDir)
{
- await IoHelpers.DeleteRecursivelyWithMagicDustAsync(coreNode.DataDir).ConfigureAwait(false);
+ await IoHelpers.TryDeleteDirectoryAsync(coreNode.DataDir).ConfigureAwait(false);
}
cancel.ThrowIfCancellationRequested();

MODIFY
WalletWasabi/Helpers/IoHelpers.cs
WalletWasabi/Helpers/IoHelpers.cs
@@ -11,49 +11,57 @@ namespace System.IO
{
public static class IoHelpers
{
- // http://stackoverflow.com/a/14933880/2061103
- public static async Task DeleteRecursivelyWithMagicDustAsync(string destinationDir)
+ /// <summary>
+ /// Attempts to delete <paramref name="directory"/> with retry feature to allow File Explorer or any other
+ /// application that holds the directory handle of the <paramref name="directory"/> to release it.
+ /// </summary>
+ /// <see href="https://stackoverflow.com/questions/329355/cannot-delete-directory-with-directory-deletepath-true/14933880#44324346"/>
+ public static async Task<bool> TryDeleteDirectoryAsync(string directory, int maxRetries = 10, int millisecondsDelay = 100)
{
- const int MagicDust = 10;
- for (var gnomes = 1; gnomes <= MagicDust; gnomes++)
+ if (directory == null)
{
- try
+ throw new ArgumentNullException(directory);
+ }
+
+ if (maxRetries < 1)
{
- Directory.Delete(destinationDir, true);
+ throw new ArgumentOutOfRangeException(nameof(maxRetries));
}
- catch (DirectoryNotFoundException)
+
+ if (millisecondsDelay < 1)
{
- return; // good!
+ throw new ArgumentOutOfRangeException(nameof(millisecondsDelay));
}
- catch (IOException)
+
+ for (int i = 0; i < maxRetries; ++i)
+ {
+ try
{
- if (gnomes == MagicDust)
+ if (Directory.Exists(directory))
{
- throw;
+ Directory.Delete(directory, recursive: true);
}
- // System.IO.IOException: The directory is not empty
- Logger.LogDebug($"Gnomes prevent deletion of {destinationDir}! Applying magic dust, attempt #{gnomes}.");
- // see http://stackoverflow.com/questions/329355/cannot-delete-directory-with-directory-deletepath-true for more magic
- await Task.Delay(100);
- continue;
+ return true;
}
- catch (UnauthorizedAccessException)
+ catch (DirectoryNotFoundException e)
{
- if (gnomes == MagicDust)
+ Logger.LogDebug($"Directory was not found: {e}");
+
+ // Directory does not exist. So the operation is trivially done.
+ return true;
+ }
+ catch (IOException)
{
- throw;
+ await Task.Delay(millisecondsDelay);
}
- // Wait, maybe another software make us authorized a little later
- Logger.LogDebug($"Gnomes prevent deletion of {destinationDir}! Applying magic dust, attempt #{gnomes}.");
-
- // see http://stackoverflow.com/questions/329355/cannot-delete-directory-with-directory-deletepath-true for more magic
- await Task.Delay(100);
- continue;
+ catch (UnauthorizedAccessException)
+ {
+ await Task.Delay(millisecondsDelay);
}
- return;
}
- // depending on your use case, consider throwing an exception here
+
+ return false;
}
public static async Task BetterExtractZipToDirectoryAsync(string src, string dest)

Add `TryDeleteDirectoryAsync` and remove `DeleteRecursivelyWithMagicDustAsync`.