diff --git a/ADB.cs b/ADB.cs
index 24ae682..e293481 100644
--- a/ADB.cs
+++ b/ADB.cs
@@ -357,7 +357,7 @@ namespace AndroidSideloader
string directoryToDelete = Path.Combine(Environment.CurrentDirectory, packagename);
if (Directory.Exists(directoryToDelete) && directoryToDelete != Environment.CurrentDirectory)
{
- Directory.Delete(directoryToDelete, true);
+ FileSystemUtilities.TryDeleteDirectory(directoryToDelete);
}
progressCallback?.Invoke(100, null);
@@ -732,7 +732,7 @@ namespace AndroidSideloader
{
if (directoryToDelete != Environment.CurrentDirectory)
{
- Directory.Delete(directoryToDelete, true);
+ FileSystemUtilities.TryDeleteDirectory(directoryToDelete);
}
}
diff --git a/AndroidSideloader.csproj b/AndroidSideloader.csproj
index 3185b2c..229460c 100644
--- a/AndroidSideloader.csproj
+++ b/AndroidSideloader.csproj
@@ -244,6 +244,7 @@
+
Form
diff --git a/MainForm.cs b/MainForm.cs
index 4743e62..bf91441 100755
--- a/MainForm.cs
+++ b/MainForm.cs
@@ -434,6 +434,20 @@ namespace AndroidSideloader
changeTitle(isOffline ? "Starting in Offline Mode..." : "Initializing...");
+ // Non-blocking WebView cleanup
+ _ = Task.Run(() =>
+ {
+ try
+ {
+ string webViewDirectoryPath = Path.Combine(Path.GetPathRoot(Environment.SystemDirectory), "RSL", "EBWebView");
+ if (Directory.Exists(webViewDirectoryPath))
+ {
+ FileSystemUtilities.TryDeleteDirectory(webViewDirectoryPath);
+ }
+ }
+ catch { }
+ });
+
// Non-blocking background cleanup
_ = Task.Run(() =>
{
@@ -441,7 +455,7 @@ namespace AndroidSideloader
{
if (Directory.Exists(Sideloader.TempFolder))
{
- Directory.Delete(Sideloader.TempFolder, true);
+ FileSystemUtilities.TryDeleteDirectory(Sideloader.TempFolder);
_ = Directory.CreateDirectory(Sideloader.TempFolder);
}
}
@@ -588,20 +602,6 @@ namespace AndroidSideloader
}
}
- // WebView cleanup in background
- _ = Task.Run(() =>
- {
- try
- {
- string webViewDirectoryPath = Path.Combine(Path.GetPathRoot(Environment.SystemDirectory), "RSL", "EBWebView");
- if (Directory.Exists(webViewDirectoryPath))
- {
- Directory.Delete(webViewDirectoryPath, true);
- }
- }
- catch { }
- });
-
// Pre-initialize trailer player in background
try
{
@@ -831,7 +831,8 @@ namespace AndroidSideloader
try
{
string titleSuffix = string.IsNullOrWhiteSpace(txt) ? "" : " | " + txt;
- this.Invoke(() => {
+ this.Invoke(() =>
+ {
Text = "Rookie Sideloader " + Updater.LocalVersion + titleSuffix;
rookieStatusLabel.Text = txt;
});
@@ -843,8 +844,9 @@ namespace AndroidSideloader
await Task.Delay(TimeSpan.FromSeconds(5));
// Reset to base title without any status message
- this.Invoke(() => {
- Text = "Rookie Sideloader " + Updater.LocalVersion;
+ this.Invoke(() =>
+ {
+ Text = "Rookie Sideloader " + Updater.LocalVersion;
rookieStatusLabel.Text = "";
});
}
@@ -985,14 +987,16 @@ namespace AndroidSideloader
output = await ADB.CopyOBBWithProgressAsync(
path,
- (progress, eta) => this.Invoke(() => {
+ (progress, eta) => this.Invoke(() =>
+ {
progressBar.Value = progress;
string etaStr = eta.HasValue && eta.Value.TotalSeconds > 0
? $" · ETA: {eta.Value:mm\\:ss}"
: "";
speedLabel.Text = $"Progress: {progress}%{etaStr}";
}),
- status => this.Invoke(() => {
+ status => this.Invoke(() =>
+ {
progressBar.StatusText = status;
}),
folderName);
@@ -1489,7 +1493,7 @@ namespace AndroidSideloader
File.Delete($"{settings.MainDir}\\{gameZipName}");
this.Invoke(() => FlexibleMessageBox.Show(Program.form, $"Upload of {currentlyUploading} is complete! Thank you for your contribution!"));
- Directory.Delete($"{settings.MainDir}\\{packageName}", true);
+ FileSystemUtilities.TryDeleteDirectory($"{settings.MainDir}\\{packageName}");
})
{
IsBackground = true
@@ -1750,7 +1754,7 @@ namespace AndroidSideloader
await Task.Delay(100);
}
- Directory.Delete($"{zippath}\\{datazip}", true);
+ FileSystemUtilities.TryDeleteDirectory($"{zippath}\\{datazip}");
}
}
}
@@ -1907,7 +1911,7 @@ namespace AndroidSideloader
await Task.Delay(100);
}
- Directory.Delete(foldername, true);
+ FileSystemUtilities.TryDeleteDirectory(foldername);
changeTitle("");
}
// BMBF Zip extraction then push to BMBF song folder on Quest.
@@ -1937,7 +1941,7 @@ namespace AndroidSideloader
await Task.Delay(100);
}
- Directory.Delete($"{zippath}\\{datazip}", true);
+ FileSystemUtilities.TryDeleteDirectory($"{zippath}\\{datazip}");
}
else if (extension == ".txt")
{
@@ -2635,7 +2639,7 @@ namespace AndroidSideloader
_ = ADB.RunCommandToString(cmd, path);
if (Directory.Exists($"{settings.MainDir}\\{game.Pckgcommand}"))
{
- Directory.Delete($"{settings.MainDir}\\{game.Pckgcommand}", true);
+ FileSystemUtilities.TryDeleteDirectory($"{settings.MainDir}\\{game.Pckgcommand}");
}
Program.form.changeTitle("Uploading to server, you can continue to use Rookie while it uploads.");
@@ -3740,7 +3744,7 @@ If the problem persists, visit our Telegram (https://t.me/VRPirates) or Discord
// only delete after extraction; allows for resume if the fetch fails midway.
if (Directory.Exists($"{settings.DownloadDir}\\{gameName}"))
{
- Directory.Delete($"{settings.DownloadDir}\\{gameName}", true);
+ FileSystemUtilities.TryDeleteDirectory($"{settings.DownloadDir}\\{gameName}");
}
}
}
@@ -3909,14 +3913,14 @@ If the problem persists, visit our Telegram (https://t.me/VRPirates) or Discord
if (UsingPublicConfig)
{
if (Directory.Exists($"{settings.DownloadDir}\\{cancelledHash}"))
- Directory.Delete($"{settings.DownloadDir}\\{cancelledHash}", true);
+ FileSystemUtilities.TryDeleteDirectory($"{settings.DownloadDir}\\{cancelledHash}");
if (Directory.Exists($"{settings.DownloadDir}\\{cancelledGame}"))
- Directory.Delete($"{settings.DownloadDir}\\{cancelledGame}", true);
+ FileSystemUtilities.TryDeleteDirectory($"{settings.DownloadDir}\\{cancelledGame}");
}
else
{
if (Directory.Exists($"{settings.DownloadDir}\\{cancelledGame}"))
- Directory.Delete($"{settings.DownloadDir}\\{cancelledGame}", true);
+ FileSystemUtilities.TryDeleteDirectory($"{settings.DownloadDir}\\{cancelledGame}");
}
}
}
@@ -4024,7 +4028,7 @@ If the problem persists, visit our Telegram (https://t.me/VRPirates) or Discord
if (Directory.Exists($"{settings.DownloadDir}\\{gameNameHash}"))
{
- Directory.Delete($"{settings.DownloadDir}\\{gameNameHash}", true);
+ FileSystemUtilities.TryDeleteDirectory($"{settings.DownloadDir}\\{gameNameHash}");
}
}
@@ -4099,7 +4103,8 @@ If the problem persists, visit our Telegram (https://t.me/VRPirates) or Discord
// Use async method with progress
output += await ADB.SideloadWithProgressAsync(
apkFile,
- (progress, eta) => this.Invoke(() => {
+ (progress, eta) => this.Invoke(() =>
+ {
if (progress == 0)
{
progressBar.IsIndeterminate = true;
@@ -4113,7 +4118,8 @@ If the problem persists, visit our Telegram (https://t.me/VRPirates) or Discord
UpdateProgressStatus("Installing", percent: (int)Math.Round(progress), eta: eta);
progressBar.StatusText = $"Installing · {progress:0.0}%";
}),
- status => this.Invoke(() => {
+ status => this.Invoke(() =>
+ {
if (!string.IsNullOrEmpty(status))
{
if (status.Contains("Completing Installation"))
@@ -4192,7 +4198,7 @@ If the problem persists, visit our Telegram (https://t.me/VRPirates) or Discord
if (settings.DeleteAllAfterInstall && !nodeviceonstart && DeviceConnected)
{
changeTitle("Deleting game files");
- try { Directory.Delete(settings.DownloadDir + "\\" + gameName, true); } catch (Exception ex) { _ = FlexibleMessageBox.Show(Program.form, $"Error deleting game files: {ex.Message}"); }
+ try { FileSystemUtilities.TryDeleteDirectory(settings.DownloadDir + "\\" + gameName); } catch (Exception ex) { _ = FlexibleMessageBox.Show(Program.form, $"Error deleting game files: {ex.Message}"); }
}
// Update device space after successful installation
@@ -4405,7 +4411,7 @@ If the problem persists, visit our Telegram (https://t.me/VRPirates) or Discord
timerticked = false;
if (Directory.Exists(Path.Combine(Environment.CurrentDirectory, CurrPCKG)))
{
- Directory.Delete(Path.Combine(Environment.CurrentDirectory, CurrPCKG), true);
+ FileSystemUtilities.TryDeleteDirectory(Path.Combine(Environment.CurrentDirectory, CurrPCKG));
}
changeTitle("");
@@ -5753,10 +5759,10 @@ function onYouTubeIframeAPIReady() {
score += extraWords * -3; // -3 per extra word
// Hard penalties for junk
- if (title.Contains("review") ||
- title.Contains("tutorial") ||
- title.Contains("how to") ||
- title.Contains("reaction"))
+ if (title.Contains("review") ||
+ title.Contains("tutorial") ||
+ title.Contains("how to") ||
+ title.Contains("reaction"))
score -= 30;
if (score > bestScore)
@@ -6053,7 +6059,7 @@ function onYouTubeIframeAPIReady() {
ulong VersionInt = ulong.Parse(Utilities.StringUtilities.KeepOnlyNumbers(InstalledVersionCode));
if (Directory.Exists($"{settings.MainDir}\\{packageName}"))
{
- Directory.Delete($"{settings.MainDir}\\{packageName}", true);
+ FileSystemUtilities.TryDeleteDirectory($"{settings.MainDir}\\{packageName}");
}
ProcessOutput output = new ProcessOutput("", "");
@@ -6119,7 +6125,7 @@ function onYouTubeIframeAPIReady() {
File.Copy($"{settings.MainDir}\\{GameName} v{VersionInt} {packageName}.zip", $"{Environment.GetFolderPath(Environment.SpecialFolder.Desktop)}\\{GameName} v{VersionInt} {packageName}.zip");
File.Delete($"{settings.MainDir}\\{GameName} v{VersionInt} {packageName}.zip");
- Directory.Delete($"{settings.MainDir}\\{packageName}", true);
+ FileSystemUtilities.TryDeleteDirectory($"{settings.MainDir}\\{packageName}");
isworking = false;
changeTitle("");
progressBar.IsIndeterminate = false;
@@ -6386,7 +6392,7 @@ function onYouTubeIframeAPIReady() {
gamesListView.SelectedItems.Clear();
_rightClickedItem.Selected = true;
-
+
UpdateFavoriteMenuItemText();
favoriteGame.Show(gamesListView, e.Location);
}
@@ -6494,7 +6500,9 @@ function onYouTubeIframeAPIReady() {
if (string.IsNullOrEmpty(firmware))
{
firmware = string.Empty;
- } else {
+ }
+ else
+ {
firmware = Utilities.StringUtilities.RemoveEverythingBeforeFirst(firmware, "-v");
firmware = Utilities.StringUtilities.KeepOnlyNumbers(firmware);
}
@@ -6506,7 +6514,7 @@ function onYouTubeIframeAPIReady() {
long totalSpace = 0;
long usedSpace = 0;
long freeSpace = 0;
-
+
if (lines.Length > 1)
{
string[] parts = lines[1].Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
@@ -8123,7 +8131,7 @@ function onYouTubeIframeAPIReady() {
// Confirm uninstall
DialogResult dialogresult = FlexibleMessageBox.Show(
- $"Are you sure you want to uninstall {gameName}?",
+ $"Are you sure you want to uninstall {gameName}?",
"Proceed with uninstall?", MessageBoxButtons.YesNo);
if (dialogresult == DialogResult.No)
@@ -8140,7 +8148,7 @@ function onYouTubeIframeAPIReady() {
}
DialogResult dialogresult2 = FlexibleMessageBox.Show(
- $"Do you want to attempt to automatically backup any saves to {backupFolder}\\{DateTime.Today.ToString("yyyy.MM.dd")}\\",
+ $"Do you want to attempt to automatically backup any saves to {backupFolder}\\{DateTime.Today.ToString("yyyy.MM.dd")}\\",
"Attempt Game Backup?", MessageBoxButtons.YesNo);
if (dialogresult2 == DialogResult.Yes)
@@ -8153,7 +8161,8 @@ function onYouTubeIframeAPIReady() {
progressBar.IsIndeterminate = true;
progressBar.OperationType = "";
- await Task.Run(() => {
+ await Task.Run(() =>
+ {
output += Sideloader.UninstallGame(packageName);
});
diff --git a/Sideloader.cs b/Sideloader.cs
index e39b671..7de688e 100644
--- a/Sideloader.cs
+++ b/Sideloader.cs
@@ -204,7 +204,7 @@ namespace AndroidSideloader
if (Directory.Exists($"{settings.MainDir}\\{packageName}"))
{
- Directory.Delete($"{settings.MainDir}\\{packageName}", true);
+ FileSystemUtilities.TryDeleteDirectory($"{settings.MainDir}\\{packageName}");
}
_ = Directory.CreateDirectory($"{settings.MainDir}\\{packageName}");
diff --git a/Sideloader/GetDependencies.cs b/Sideloader/GetDependencies.cs
index 3671f23..0b901f4 100644
--- a/Sideloader/GetDependencies.cs
+++ b/Sideloader/GetDependencies.cs
@@ -309,7 +309,7 @@ namespace AndroidSideloader
}
File.Move(file, destFile);
}
- Directory.Delete(dirExtractedRclone, true);
+ FileSystemUtilities.TryDeleteDirectory(dirExtractedRclone);
// Restore vrp.download.config if it was backed up
if (hasConfig && File.Exists(tempConfigPath))
diff --git a/Sideloader/RCLONE.cs b/Sideloader/RCLONE.cs
index 9228005..a0247d1 100644
--- a/Sideloader/RCLONE.cs
+++ b/Sideloader/RCLONE.cs
@@ -290,7 +290,7 @@ namespace AndroidSideloader
{
if (Directory.Exists(path))
{
- Directory.Delete(path, true);
+ FileSystemUtilities.TryDeleteDirectory(path);
}
}
catch (Exception ex)
diff --git a/Utilities/FileSystemUtilities.cs b/Utilities/FileSystemUtilities.cs
new file mode 100644
index 0000000..f16b660
--- /dev/null
+++ b/Utilities/FileSystemUtilities.cs
@@ -0,0 +1,91 @@
+using System;
+using System.IO;
+using System.Threading;
+
+namespace AndroidSideloader.Utilities
+{
+ internal static class FileSystemUtilities
+ {
+ public static bool TryDeleteDirectory(string directoryPath, int maxRetries = 3, int delayMs = 150) // 3x 150ms = 450ms total
+ {
+ if (string.IsNullOrWhiteSpace(directoryPath))
+ return true;
+
+ if (!Directory.Exists(directoryPath))
+ return true;
+
+ Exception lastError = null;
+
+ // Retry deletion several times in case of lock ups
+ for (int attempt = 0; attempt <= maxRetries; attempt++)
+ {
+ try
+ {
+ StripReadOnlyAttributes(directoryPath);
+ Directory.Delete(directoryPath, true);
+ return true;
+ }
+ catch (DirectoryNotFoundException)
+ {
+ return true;
+ }
+ catch (Exception ex) when (ex is UnauthorizedAccessException || ex is IOException)
+ {
+ lastError = ex;
+
+ if (attempt < maxRetries)
+ {
+ Thread.Sleep(delayMs);
+ continue;
+ }
+
+ break;
+ }
+ catch (Exception ex)
+ {
+ // Non-retryable error
+ lastError = ex;
+ break;
+ }
+ }
+
+ // Last resort: rename then delete
+ try
+ {
+ string renamedPath = directoryPath.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)
+ + ".deleting." + DateTime.UtcNow.Ticks;
+
+ Directory.Move(directoryPath, renamedPath);
+
+ StripReadOnlyAttributes(renamedPath);
+ Directory.Delete(renamedPath, true);
+ return true;
+ }
+ catch (Exception ex)
+ {
+ lastError = ex;
+ }
+
+ Logger.Log($"Failed to delete directory: {directoryPath}. Error: {lastError}", LogLevel.WARNING);
+ return false;
+ }
+
+ private static void StripReadOnlyAttributes(string directoryPath)
+ {
+ var root = new DirectoryInfo(directoryPath);
+ if (!root.Exists) return;
+
+ root.Attributes &= ~FileAttributes.ReadOnly;
+
+ foreach (var dir in root.EnumerateDirectories("*", SearchOption.AllDirectories))
+ {
+ dir.Attributes &= ~FileAttributes.ReadOnly;
+ }
+
+ foreach (var file in root.EnumerateFiles("*", SearchOption.AllDirectories))
+ {
+ file.Attributes &= ~FileAttributes.ReadOnly;
+ }
+ }
+ }
+}
\ No newline at end of file