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 @@ - + + + + + + + +