diff --git a/FASTER/Models/Logger.cs b/FASTER/Models/Logger.cs
new file mode 100644
index 0000000..93d24ad
--- /dev/null
+++ b/FASTER/Models/Logger.cs
@@ -0,0 +1,27 @@
+using System;
+using System.IO;
+
+namespace FASTER.Models
+{
+ public static class Logger
+ {
+ private static readonly string LogPath = Path.Combine(
+ Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
+ "FASTER", "faster.log");
+
+ public static bool IsEnabled => Properties.Settings.Default.enableDebugLog;
+
+ public static string LogFilePath => LogPath;
+
+ public static void Log(string message)
+ {
+ if (!IsEnabled) return;
+ try
+ {
+ Directory.CreateDirectory(Path.GetDirectoryName(LogPath)!);
+ File.AppendAllText(LogPath, $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] {message}{Environment.NewLine}");
+ }
+ catch { }
+ }
+ }
+}
diff --git a/FASTER/Properties/Settings.Designer.cs b/FASTER/Properties/Settings.Designer.cs
index 2d01c64..bdf6f6f 100644
--- a/FASTER/Properties/Settings.Designer.cs
+++ b/FASTER/Properties/Settings.Designer.cs
@@ -199,6 +199,18 @@ public bool excludeServerFolder {
}
}
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("False")]
+ public bool enableDebugLog {
+ get {
+ return ((bool)(this["enableDebugLog"]));
+ }
+ set {
+ this["enableDebugLog"] = value;
+ }
+ }
+
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("True")]
diff --git a/FASTER/Properties/Settings.settings b/FASTER/Properties/Settings.settings
index cf92712..c041c40 100644
--- a/FASTER/Properties/Settings.settings
+++ b/FASTER/Properties/Settings.settings
@@ -95,5 +95,8 @@
False
+
+ False
+
diff --git a/FASTER/ViewModel/DeploymentViewModel.cs b/FASTER/ViewModel/DeploymentViewModel.cs
index 2a38f41..465d856 100644
--- a/FASTER/ViewModel/DeploymentViewModel.cs
+++ b/FASTER/ViewModel/DeploymentViewModel.cs
@@ -108,12 +108,25 @@ public void DeployAll()
{"Name", Settings.Default.steamUserName}
});
+ Logger.Log($"DeployAll: installPath={Deployment.InstallPath}, mods={Deployment.DeployMods.Count}");
+
+ if (!Directory.Exists(Deployment.InstallPath))
+ {
+ Logger.Log("DeployAll: install path not found, aborting.");
+ DisplayMessage("Arma Install Path is empty.\nMake sure you have entered a valid path before deploying mods.");
+ return;
+ }
+
foreach (var mod in Deployment.DeployMods)
{
var linkPath = Path.Combine(Deployment.InstallPath, $"@{Functions.SafeName(mod.Name)}");
mod.Marked = true;
+ Logger.Log($" Linking {mod.Name}: {mod.Path} -> {linkPath}");
LinkMod(mod, linkPath);
}
+ Settings.Default.Deployments = Deployment;
+ Settings.Default.Save();
+ Logger.Log("DeployAll: done.");
}
///
@@ -204,15 +217,36 @@ public void OpenModPage(DeploymentMod mod)
///
private void LinkMod(DeploymentMod mod, string linkPath)
{
+ Logger.Log($"LinkMod: {mod.Name} ({mod.WorkshopId}) -> {linkPath}");
try
{
if(Directory.Exists(linkPath))
- Directory.Delete(linkPath, true);
+ {
+ if (new DirectoryInfo(linkPath).Attributes.HasFlag(FileAttributes.ReparsePoint))
+ {
+ Logger.Log($" Removing existing symlink: {linkPath}");
+ Directory.Delete(linkPath);
+ }
+ else
+ {
+ Logger.Log($" Removing existing real dir: {linkPath}");
+ Directory.Delete(linkPath, true);
+ }
+ }
Directory.CreateSymbolicLink(linkPath ?? throw new ArgumentNullException(nameof(linkPath)), mod.Path);
+ Logger.Log($" Symlink created OK.");
+ }
+ catch (UnauthorizedAccessException)
+ {
+ Logger.Log($" ERROR: UnauthorizedAccessException creating symlink.");
+ DisplayMessage("Could not create symlink: Access denied.\n\nTo deploy mods, enable Windows Developer Mode in Settings → Update & Security → For Developers, or run FASTER as Administrator.");
}
catch (Exception ex)
- { DisplayMessage("An exception occurred: \n\n" + ex.Message); }
+ {
+ Logger.Log($" ERROR: {ex.Message}");
+ DisplayMessage("An exception occurred: \n\n" + ex.Message);
+ }
}
///
@@ -224,7 +258,12 @@ private void DeleteLink(string linkPath)
try
{
if (Directory.Exists(linkPath))
- Directory.Delete(linkPath, true);
+ {
+ if (new DirectoryInfo(linkPath).Attributes.HasFlag(FileAttributes.ReparsePoint))
+ Directory.Delete(linkPath);
+ else
+ Directory.Delete(linkPath, true);
+ }
}
catch (Exception ex)
{ DisplayMessage("An exception occurred: \n\n" + ex.Message); }
diff --git a/FASTER/ViewModel/ModsViewModel.cs b/FASTER/ViewModel/ModsViewModel.cs
index f560b6e..f2f5f25 100644
--- a/FASTER/ViewModel/ModsViewModel.cs
+++ b/FASTER/ViewModel/ModsViewModel.cs
@@ -230,10 +230,16 @@ public void OpenModFolder(ArmaMod mod)
Process.Start(startInfo);
}
- public void CheckForUpdates()
+ public async Task CheckForUpdates()
{
+ Logger.Log("CheckForUpdates started.");
foreach (ArmaMod mod in ModsCollection.ArmaMods)
- { Task.Run(() => mod.UpdateInfos()); }
+ {
+ Logger.Log($" Checking mod {mod.WorkshopId} ({mod.Name})...");
+ await Task.Run(() => mod.UpdateInfos());
+ await Task.Delay(300);
+ }
+ Logger.Log("CheckForUpdates finished.");
}
public async Task UpdateSelectedMods()
@@ -253,8 +259,117 @@ public async Task UpdateAll()
MainWindow.Instance.NavigateToConsole();
var ans = await MainWindow.Instance.SteamUpdaterViewModel.RunModsUpdater(ModsCollection.ArmaMods);
- if(ans == UpdateState.LoginFailed)
+ if(ans == UpdateState.LoginFailed)
+ DisplayMessage("Steam Login Failed");
+ }
+
+ public void PurgeAndReinstallMod(ArmaMod mod)
+ {
+ if (mod == null) return;
+
+ Logger.Log($"PurgeAndReinstallMod: {mod.WorkshopId} ({mod.Name}) path={mod.Path}");
+ try
+ {
+ if (Directory.Exists(mod.Path))
+ {
+ Directory.Delete(mod.Path, true);
+ Logger.Log($" Deleted folder: {mod.Path}");
+ }
+ else
+ Logger.Log($" Folder not found, skipping delete: {mod.Path}");
+ }
+ catch (Exception ex)
+ {
+ Logger.Log($" ERROR deleting folder: {ex.Message}");
+ DisplayMessage($"Could not delete folder for mod {mod.WorkshopId}");
+ }
+
+ mod.Status = ArmaModStatus.UpdateRequired;
+ mod.LocalLastUpdated = 0;
+ mod.Size = 0;
+ Properties.Settings.Default.Save();
+ }
+
+ public void PurgeAndReinstallSelectedMods()
+ {
+ var selectedMods = new List(ModsCollection.ArmaMods.Where(m => m.IsSelected && !m.IsLocal));
+ foreach (var mod in selectedMods)
+ PurgeAndReinstallMod(mod);
+ }
+
+ public async Task PurgeAndReinstallAll()
+ {
+ var answer = await DialogCoordinator.ShowInputAsync(this, "Are you sure you want to purge all mods?", "Write \"yes\" and press OK to delete all folders in the Mod Staging Directory and re-download everything.");
+
+ if (string.IsNullOrEmpty(answer) || !answer.Equals("yes"))
+ return;
+
+ Analytics.TrackEvent("Mods - Clicked PurgeAndReinstallAll", new Dictionary
+ {
+ {"Name", Properties.Settings.Default.steamUserName}
+ });
+
+ var stagingDir = Properties.Settings.Default.modStagingDirectory;
+ Logger.Log($"PurgeAndReinstallAll: staging dir={stagingDir}");
+ if (Directory.Exists(stagingDir))
+ {
+ foreach (var dir in Directory.GetDirectories(stagingDir))
+ {
+ try
+ {
+ Directory.Delete(dir, true);
+ Logger.Log($" Deleted: {dir}");
+ }
+ catch (Exception ex)
+ {
+ Logger.Log($" ERROR deleting {dir}: {ex.Message}");
+ DisplayMessage($"Could not delete folder: {dir}");
+ }
+ }
+ }
+ else
+ Logger.Log(" Staging dir does not exist, nothing deleted.");
+
+ foreach (var mod in ModsCollection.ArmaMods.Where(m => !m.IsLocal).ToList())
+ {
+ mod.Status = ArmaModStatus.UpdateRequired;
+ mod.LocalLastUpdated = 0;
+ mod.Size = 0;
+ Logger.Log($" Reset mod {mod.WorkshopId} ({mod.Name})");
+ }
+ Properties.Settings.Default.Save();
+
+ Logger.Log("PurgeAndReinstallAll: launching UpdateAll...");
+ MainWindow.Instance.NavigateToConsole();
+ var ans = await MainWindow.Instance.SteamUpdaterViewModel.RunModsUpdater(ModsCollection.ArmaMods);
+ if (ans == UpdateState.LoginFailed)
DisplayMessage("Steam Login Failed");
}
+
+ public async Task PurgeUnusedMods()
+ {
+ var usedIds = Properties.Settings.Default.Profiles
+ .SelectMany(p => p.ProfileMods ?? Enumerable.Empty())
+ .Select(m => m.Id)
+ .ToHashSet();
+
+ var unusedMods = ModsCollection.ArmaMods
+ .Where(m => !m.IsLocal && !usedIds.Contains(m.WorkshopId))
+ .ToList();
+
+ if (unusedMods.Count == 0)
+ {
+ DisplayMessage("No unused mods found.");
+ return;
+ }
+
+ var result = await DialogCoordinator.ShowInputAsync(this,
+ "Purge Unused Mods",
+ $"Found {unusedMods.Count} unused mod(s). Type \"yes\" to confirm deletion.");
+ if (result?.ToLower() != "yes") return;
+
+ foreach (var mod in unusedMods)
+ DeleteMod(mod);
+ }
}
}
diff --git a/FASTER/ViewModel/SteamUpdaterViewModel.cs b/FASTER/ViewModel/SteamUpdaterViewModel.cs
index ee740d8..e2d451e 100644
--- a/FASTER/ViewModel/SteamUpdaterViewModel.cs
+++ b/FASTER/ViewModel/SteamUpdaterViewModel.cs
@@ -415,13 +415,22 @@ public async Task RunModUpdater(ulong modId, string path)
public async Task RunModsUpdater(ObservableCollection mods)
{
+ Logger.Log($"RunModsUpdater: starting, {mods.Count} mods total");
tokenSource = new CancellationTokenSource();
- if(!await SteamLogin())
- {
+
+ Logger.Log("RunModsUpdater: calling SteamLogin...");
+ bool loginOk = false;
+ try { loginOk = await SteamLogin(); }
+ catch (Exception ex) { Logger.Log($"RunModsUpdater: SteamLogin threw exception: {ex}"); }
+
+ if (!loginOk)
+ {
+ Logger.Log("RunModsUpdater: SteamLogin failed, aborting.");
IsLoggingIn = false;
return UpdateState.LoginFailed;
}
+ Logger.Log("RunModsUpdater: SteamLogin OK");
Parameters.Output += "\nAdding mods to download list...";
@@ -429,91 +438,122 @@ public async Task RunModsUpdater(ObservableCollection mods)
var ml = mods.Where(m => !m.IsLocal).ToList();
uint finished = 0;
IsDlOverride = true;
+ Logger.Log($"RunModsUpdater: {ml.Count} non-local mods to update");
foreach (ArmaMod mod in ml)
{
+ Logger.Log($"RunModsUpdater: waiting semaphore for mod {mod.WorkshopId} ({mod.Name})");
await maxThread.WaitAsync();
- _ = Task.Factory.StartNew(() =>
+ _ = Task.Factory.StartNew(async () =>
{
- if (!Directory.Exists(mod.Path))
- Directory.CreateDirectory(mod.Path);
-
- if (tokenSource.Token.IsCancellationRequested)
- {
- mod.Status = ArmaModStatus.NotComplete;
- return;
- }
- Parameters.Output += $"\n Starting {mod.WorkshopId}";
-
- Stopwatch sw = Stopwatch.StartNew();
+ Logger.Log($" Task started: {mod.WorkshopId} ({mod.Name}), path={mod.Path}");
try
{
- ManifestId manifestId = default;
-
- if(mod.LocalLastUpdated > mod.SteamLastUpdated && mod.Size > 0)
+ if (!Directory.Exists(mod.Path))
{
- mod.Status = ArmaModStatus.UpToDate;
- Parameters.Output += $"\n Mod{mod.WorkshopId} already up to date. Ignoring...";
- return;
+ Logger.Log($" Creating dir: {mod.Path}");
+ Directory.CreateDirectory(mod.Path);
}
- if (!SteamClient.Credentials.IsAnonymous) //IS SYNC NEABLED
+ if (tokenSource.Token.IsCancellationRequested)
{
- Parameters.Output += $"\n Getting manifest for {mod.WorkshopId}";
- manifestId = SteamContentClient.GetPublishedFileDetailsAsync(mod.WorkshopId).Result.hcontent_file;
- Manifest manifest = SteamContentClient.GetManifestAsync(107410, 107410, manifestId).Result;
- Parameters.Output += $"\n Manifest retrieved {mod.WorkshopId}";
- SyncDeleteRemovedFiles(mod.Path, manifest);
+ Logger.Log($" Cancellation requested before {mod.WorkshopId}, skipping.");
+ return;
}
+ Parameters.Output += $"\n Starting {mod.WorkshopId}";
- Parameters.Output += $"\n Attempting to start download of item {mod.WorkshopId}... ";
-
- var downloadHandler = SteamContentClient.GetPublishedFileDataAsync(mod.WorkshopId, manifestId, tokenSource.Token);
- DownloadForMultiple(downloadHandler.Result, mod.Path).Wait();
+ Stopwatch sw = Stopwatch.StartNew();
+ try
+ {
+ ManifestId manifestId = default;
+
+ if(mod.LocalLastUpdated > mod.SteamLastUpdated && mod.Size > 0)
+ {
+ mod.Status = ArmaModStatus.UpToDate;
+ Parameters.Output += $"\n Mod{mod.WorkshopId} already up to date. Ignoring...";
+ Logger.Log($" {mod.WorkshopId} already up to date, skipping.");
+ return;
+ }
+
+ if (!SteamClient.Credentials.IsAnonymous)
+ {
+ Logger.Log($" Getting manifest for {mod.WorkshopId}");
+ Parameters.Output += $"\n Getting manifest for {mod.WorkshopId}";
+ manifestId = (await SteamContentClient.GetPublishedFileDetailsAsync(mod.WorkshopId)).hcontent_file;
+ Manifest manifest = await SteamContentClient.GetManifestAsync(107410, 107410, manifestId);
+ Parameters.Output += $"\n Manifest retrieved {mod.WorkshopId}";
+ Logger.Log($" Manifest retrieved for {mod.WorkshopId}, syncing deleted files...");
+ SyncDeleteRemovedFiles(mod.Path, manifest);
+ }
+
+ Logger.Log($" Requesting download handler for {mod.WorkshopId}");
+ Parameters.Output += $"\n Attempting to start download of item {mod.WorkshopId}... ";
+
+ var downloadHandler = await SteamContentClient.GetPublishedFileDataAsync(mod.WorkshopId, manifestId, tokenSource.Token);
+ Logger.Log($" Download handler obtained for {mod.WorkshopId}, starting download...");
+ await DownloadForMultiple(downloadHandler, mod.Path);
+ Logger.Log($" Download complete for {mod.WorkshopId}");
- mod.Status = ArmaModStatus.UpToDate;
- var nx = DateTime.UnixEpoch;
- var ts = DateTime.UtcNow - nx;
- mod.LocalLastUpdated = (ulong)ts.TotalSeconds;
- }
- catch (TaskCanceledException)
- {
+ mod.Status = ArmaModStatus.UpToDate;
+ var nx = DateTime.UnixEpoch;
+ var ts = DateTime.UtcNow - nx;
+ mod.LocalLastUpdated = (ulong)ts.TotalSeconds;
+ }
+ catch (TaskCanceledException)
+ {
+ Logger.Log($" {mod.WorkshopId} task cancelled.");
+ sw.Stop();
+ mod.Status = ArmaModStatus.NotComplete;
+ }
+ catch (Exception ex)
+ {
+ Logger.Log($" ERROR downloading {mod.WorkshopId}: {ex.GetType().Name}: {ex.Message}{(ex.InnerException != null ? $" | Inner: {ex.InnerException.Message}" : "")}\n StackTrace: {ex.StackTrace}");
+ sw.Stop();
+ mod.Status = ArmaModStatus.NotComplete;
+ Parameters.Output += $"\nError: {ex.Message}{(ex.InnerException != null ? $" Inner Exception: {ex.InnerException.Message}" : "")}";
+ }
sw.Stop();
- mod.Status = ArmaModStatus.NotComplete;
+
+ mod.CheckModSize();
+ Parameters.Output += $"\n Download {mod.WorkshopId} completed, it took {sw.Elapsed.Minutes + sw.Elapsed.Hours*60}m {sw.Elapsed.Seconds}s {sw.Elapsed.Milliseconds}ms";
}
catch (Exception ex)
{
- sw.Stop();
- mod.Status = ArmaModStatus.NotComplete;
- Parameters.Output += $"\nError: {ex.Message}{(ex.InnerException != null ? $" Inner Exception: {ex.InnerException.Message}" : "")}";
+ Logger.Log($" UNHANDLED ERROR in task for {mod.WorkshopId}: {ex.GetType().Name}: {ex.Message}\n StackTrace: {ex.StackTrace}");
}
- sw.Stop();
-
- mod.CheckModSize();
-
- Parameters.Output += $"\n Download {mod.WorkshopId} completed, it took {sw.Elapsed.Minutes + sw.Elapsed.Hours*60}m {sw.Elapsed.Seconds}s {sw.Elapsed.Milliseconds}ms";
- }, TaskCreationOptions.LongRunning).ContinueWith((_) =>
+ }, TaskCreationOptions.LongRunning).Unwrap().ContinueWith((t) =>
{
+ if (t.IsFaulted)
+ Logger.Log($" ContinueWith: task for {mod.WorkshopId} faulted: {t.Exception}");
finished += 1;
Parameters.Output += $"\n Thread {mod.WorkshopId} complete ({finished} / {ml.Count})";
Parameters.Progress = finished * ml.Count / 100.00;
+ Logger.Log($" ContinueWith: mod {mod.WorkshopId} done ({finished}/{ml.Count}), releasing semaphore.");
maxThread.Release();
});
}
+ Logger.Log("RunModsUpdater: all tasks queued, waiting for last semaphore...");
Parameters.Output += "\nAlmost there...";
- await maxThread.WaitAsync();
-
+ try
+ {
+ await maxThread.WaitAsync();
+ }
+ finally
+ {
+ IsDlOverride = false;
+ }
+ Logger.Log("RunModsUpdater: all done.");
Parameters.Output += "\nMods updated !";
- IsDlOverride = false;
return UpdateState.Success;
}
internal async Task SteamLogin()
{
+ Logger.Log("SteamLogin: start");
if (tokenSource.IsCancellationRequested)
tokenSource = new CancellationTokenSource();
IsLoggingIn = true;
@@ -538,14 +578,17 @@ internal async Task SteamLogin()
try
{ await SteamClient.ConnectAsync(tokenSource.Token); }
catch (SteamClientAlreadyRunningException)
- {
- Parameters.Output += $"\nClient already logged in.";
+ {
+ Logger.Log("SteamLogin: SteamClientAlreadyRunningException - client already running");
+ Parameters.Output += $"\nClient already logged in.";
IsLoggingIn = false;
return false;
}
catch (Exception ex)
{
+ Logger.Log($"SteamLogin: ConnectAsync failed: {ex.GetType().Name}: {ex.Message}\nStackTrace: {ex.StackTrace}");
Parameters.Output += $"\nFailed! Error: {ex.Message}";
+ var savedUsername = SteamClient?.Credentials.Username;
SteamClient.Shutdown();
SteamClient.Dispose();
SteamClient = null;
@@ -553,7 +596,7 @@ internal async Task SteamLogin()
if (ex.GetBaseException() is SteamAuthenticationException)
{
Parameters.Output += "\nWarning: The logon may have failed due to expired sentry-data."
- + $"\nIf you are sure that the provided username and password are correct, consider deleting the token file for the user \"{SteamClient?.Credentials.Username}\" in the sentries directory."
+ + $"\nIf you are sure that the provided username and password are correct, consider deleting the token file for the user \"{savedUsername}\" in the sentries directory."
+ $"{path}";
}
IsLoggingIn = false;
@@ -561,8 +604,10 @@ internal async Task SteamLogin()
}
}
+ Logger.Log($"SteamLogin: creating SteamContentClient with {Properties.Settings.Default.CliWorkers} workers");
SteamContentClient = new SteamContentClient(SteamClient, Properties.Settings.Default.CliWorkers);
Parameters.Output += "\nConnected !";
+ Logger.Log("SteamLogin: connected OK");
IsLoggingIn = false;
return SteamClient.IsConnected;
}
@@ -685,28 +730,64 @@ private async Task Download(IDownloadHandler downloadHandler, string targetDir)
private async Task DownloadForMultiple(IDownloadHandler downloadHandler, string targetDir)
{
+ Logger.Log($"DownloadForMultiple: targetDir={targetDir}");
if (targetDir == null)
+ {
+ Logger.Log("DownloadForMultiple: targetDir is null, aborting.");
return;
+ }
if (!Directory.Exists(targetDir))
+ {
+ Logger.Log($"DownloadForMultiple: creating dir {targetDir}");
Directory.CreateDirectory(targetDir);
+ }
tokenSource.Token.ThrowIfCancellationRequested();
ulong downloadedSize = 0;
- downloadHandler.FileVerified += (_, args) => Parameters.Output += $"{(args.RequiresDownload ? $"\n File verified : {args.ManifestFile.FileName} ({Functions.ParseFileSize(args.ManifestFile.TotalSize)})" : "")}";
- downloadHandler.VerificationCompleted += (_, args) => Parameters.Output += $"\n Verification completed, {args.QueuedFiles.Count} files queued for download. ({args.QueuedFiles.Sum(f => (double)f.TotalSize)} bytes)";
+ downloadHandler.FileVerified += (_, args) =>
+ {
+ if (args.RequiresDownload)
+ {
+ Logger.Log($" File verified (needs download): {args.ManifestFile.FileName} ({Functions.ParseFileSize(args.ManifestFile.TotalSize)})");
+ Parameters.Output += $"\n File verified : {args.ManifestFile.FileName} ({Functions.ParseFileSize(args.ManifestFile.TotalSize)})";
+ }
+ };
+ downloadHandler.VerificationCompleted += (_, args) =>
+ {
+ Logger.Log($" Verification completed: {args.QueuedFiles.Count} files queued ({args.QueuedFiles.Sum(f => (double)f.TotalSize)} bytes)");
+ Parameters.Output += $"\n Verification completed, {args.QueuedFiles.Count} files queued for download. ({args.QueuedFiles.Sum(f => (double)f.TotalSize)} bytes)";
+ };
downloadHandler.FileDownloaded += (_, args) =>
- {
- downloadedSize += args.TotalSize;
- Parameters.Output += $"\n Progress {downloadHandler.TotalProgress * 100:00.00}% ({Functions.ParseFileSize(downloadedSize)} / {Functions.ParseFileSize(downloadHandler.TotalFileSize)})";
- };
- downloadHandler.DownloadComplete += (_, _) => Parameters.Output += "\n Download completed";
+ {
+ downloadedSize += args.TotalSize;
+ Logger.Log($" File downloaded: progress {downloadHandler.TotalProgress * 100:00.00}% ({Functions.ParseFileSize(downloadedSize)}/{Functions.ParseFileSize(downloadHandler.TotalFileSize)})");
+ Parameters.Output += $"\n Progress {downloadHandler.TotalProgress * 100:00.00}% ({Functions.ParseFileSize(downloadedSize)} / {Functions.ParseFileSize(downloadHandler.TotalFileSize)})";
+ };
+ downloadHandler.DownloadComplete += (_, _) =>
+ {
+ Logger.Log(" DownloadComplete event fired.");
+ Parameters.Output += "\n Download completed";
+ };
+ Logger.Log($"DownloadForMultiple: starting Task.Run (Setup/Verify/Download), totalFiles={downloadHandler.TotalFileCount}, totalSize={Functions.ParseFileSize(downloadHandler.TotalFileSize)}");
Task downloadTask = Task.Run(async () =>
{
- await downloadHandler.SetupAsync(targetDir, file => true, tokenSource.Token);
- await downloadHandler.VerifyAsync(tokenSource.Token);
- await downloadHandler.DownloadAsync(tokenSource.Token);
+ try
+ {
+ Logger.Log(" SetupAsync starting...");
+ await downloadHandler.SetupAsync(targetDir, file => true, tokenSource.Token);
+ Logger.Log(" SetupAsync done. VerifyAsync starting...");
+ await downloadHandler.VerifyAsync(tokenSource.Token);
+ Logger.Log(" VerifyAsync done. DownloadAsync starting...");
+ await downloadHandler.DownloadAsync(tokenSource.Token);
+ Logger.Log(" DownloadAsync done.");
+ }
+ catch (Exception ex)
+ {
+ Logger.Log($" ERROR inside download Task.Run: {ex.GetType().Name}: {ex.Message}\n StackTrace: {ex.StackTrace}");
+ throw;
+ }
});
Parameters.Output += "\n OK.";
@@ -717,7 +798,6 @@ private async Task DownloadForMultiple(IDownloadHandler downloadHandler, string
Parameters.Progress = 0;
while (!downloadTask.IsCompleted && !downloadTask.IsCanceled && !tokenSource.IsCancellationRequested)
{
-
var delayTask = Task.Delay(500, tokenSource.Token);
await Task.WhenAny(delayTask, downloadTask);
@@ -727,6 +807,7 @@ private async Task DownloadForMultiple(IDownloadHandler downloadHandler, string
if (downloadTask.IsCanceled)
{
+ Logger.Log("DownloadForMultiple: task was cancelled.");
Parameters.Output += "\n Task Cancelled";
DownloadTasks.Remove(downloadTask);
await downloadHandler.DisposeAsync();
@@ -737,11 +818,18 @@ private async Task DownloadForMultiple(IDownloadHandler downloadHandler, string
{ await downloadTask; }
catch (TaskCanceledException)
{
+ Logger.Log("DownloadForMultiple: TaskCanceledException caught on await.");
Parameters.Output += "\n Task Cancelled";
throw;
}
+ catch (Exception ex)
+ {
+ Logger.Log($"DownloadForMultiple: exception on await downloadTask: {ex.GetType().Name}: {ex.Message}\n StackTrace: {ex.StackTrace}");
+ throw;
+ }
finally
{
+ Logger.Log("DownloadForMultiple: finalizing, disposing handler.");
DownloadTasks.Remove(downloadTask);
await downloadHandler.DisposeAsync();
downloadTask.Dispose();
diff --git a/FASTER/Views/Settings.xaml b/FASTER/Views/Settings.xaml
index 0b4fdd3..4e79e4b 100644
--- a/FASTER/Views/Settings.xaml
+++ b/FASTER/Views/Settings.xaml
@@ -39,6 +39,7 @@
+
@@ -50,7 +51,17 @@
-
+
+
+
+
+
+
+
+
+
+
+
@@ -59,7 +70,7 @@
-
+
@@ -68,7 +79,7 @@
-
+
diff --git a/FASTER/Views/Settings.xaml.cs b/FASTER/Views/Settings.xaml.cs
index 5f2a8b7..5c71a1e 100644
--- a/FASTER/Views/Settings.xaml.cs
+++ b/FASTER/Views/Settings.xaml.cs
@@ -7,6 +7,8 @@
using FASTER.Models;
using System;
+using System.Diagnostics;
+using System.IO;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
@@ -129,6 +131,7 @@ private void Settings_Initialized(object sender, EventArgs e)
{
IModUpdatesOnLaunch.IsChecked = Properties.Settings.Default?.checkForModUpdates;
IAppUpdatesOnLaunch.IsChecked = Properties.Settings.Default?.checkForAppUpdates;
+ IEnableDebugLog.IsChecked = Properties.Settings.Default?.enableDebugLog;
IAPIKeyBox.Text = Properties.Settings.Default?.SteamAPIKey ?? string.Empty;
Slider.Value = Properties.Settings.Default.CliWorkers;
NumericUpDown.Value = Slider.Value;
@@ -146,6 +149,22 @@ private void IAppUpdatesOnLaunch_Checked(object sender, RoutedEventArgs e)
Properties.Settings.Default.Save();
}
+ private void IEnableDebugLog_Checked(object sender, RoutedEventArgs e)
+ {
+ Properties.Settings.Default.enableDebugLog = IEnableDebugLog.IsChecked ?? false;
+ Properties.Settings.Default.Save();
+ Logger.Log("Debug logging enabled.");
+ }
+
+ private void IOpenLogFile_Click(object sender, RoutedEventArgs e)
+ {
+ var path = Logger.LogFilePath;
+ if (File.Exists(path))
+ Process.Start(new ProcessStartInfo(path) { UseShellExecute = true });
+ else
+ MainWindow.Instance.DisplayMessage($"No log file found at:\n{path}");
+ }
+
private void IUpdateApp_OnClick(object sender, RoutedEventArgs e)
{ AutoUpdater.Start("https://raw.githubusercontent.com/Foxlider/FASTER/master/FASTER_Version.xml"); }
@@ -158,6 +177,7 @@ private void ISaveSettings_Click(object sender, RoutedEventArgs e)
Properties.Settings.Default.SteamAPIKey = IAPIKeyBox.Text;
Properties.Settings.Default.checkForAppUpdates = IAppUpdatesOnLaunch.IsChecked ?? true;
Properties.Settings.Default.checkForModUpdates = IModUpdatesOnLaunch.IsChecked ?? true;
+ Properties.Settings.Default.enableDebugLog = IEnableDebugLog.IsChecked ?? false;
Properties.Settings.Default.Save();
}