diff --git a/FASTER/App.xaml.cs b/FASTER/App.xaml.cs index 37a0d6d..f5ac7d7 100644 --- a/FASTER/App.xaml.cs +++ b/FASTER/App.xaml.cs @@ -1,10 +1,13 @@  +using FASTER.Models; + using Microsoft.AppCenter; using Microsoft.AppCenter.Analytics; using Microsoft.AppCenter.Crashes; using System; using System.Globalization; +using System.Threading.Tasks; using System.Windows; using ControlzEx.Theming; @@ -19,6 +22,21 @@ protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); + AppDomain.CurrentDomain.UnhandledException += (_, args) => + Logger.Log($"[FATAL] Unhandled exception (CLR): {args.ExceptionObject}"); + + DispatcherUnhandledException += (_, args) => + { + Logger.Log($"[FATAL] Unhandled dispatcher exception: {args.Exception}"); + args.Handled = true; + }; + + TaskScheduler.UnobservedTaskException += (_, args) => + { + Logger.Log($"[FATAL] Unobserved task exception: {args.Exception}"); + args.SetObserved(); + }; + var countryCode = RegionInfo.CurrentRegion.TwoLetterISORegionName; var userID = AppCenter.GetInstallIdAsync(); diff --git a/FASTER/Models/ArmaMod.cs b/FASTER/Models/ArmaMod.cs index d24a20f..e710bec 100644 --- a/FASTER/Models/ArmaMod.cs +++ b/FASTER/Models/ArmaMod.cs @@ -105,6 +105,14 @@ public class ArmaMod : INotifyPropertyChanged private long _size; private bool _isLoading; private bool _isSelected; + private static bool _apiKeyWarningShown = false; + + private static bool TryShowApiKeyWarning() + { + if (_apiKeyWarningShown) return false; + _apiKeyWarningShown = true; + return true; + } public uint WorkshopId @@ -337,6 +345,12 @@ internal void UpdateInfos(bool checkFileSize = true) success = true; } while (failNum < 3 && !success); + if (!success && TryShowApiKeyWarning()) + { + MainWindow.Instance?.Dispatcher.Invoke(() => + MainWindow.Instance.DisplayMessage("Could not fetch mod info. Please check your Steam API Key in Settings.")); + } + if (checkFileSize) CheckModSize(); diff --git a/FASTER/Models/BasicCfg.cs b/FASTER/Models/BasicCfg.cs index fa3cf7f..dbdd699 100644 --- a/FASTER/Models/BasicCfg.cs +++ b/FASTER/Models/BasicCfg.cs @@ -9,6 +9,7 @@ public static class BasicCfgArrays { public static string[] PerfPresets { get; } = {"Custom", "Arma3 Defaults", "1Mb Preset", "250Mb Preset", "1Gb Preset"}; public static double[] TerrainGrids { get; } = { 50, 25, 12.5, 6.25, 3.125 }; + public static string[] Languages { get; } = { "English", "Czech", "French", "German", "Italian", "Polish", "Portuguese", "Russian", "Spanish", "Turkish", "Hungarian" }; } [Serializable] @@ -27,15 +28,26 @@ public class BasicCfg : INotifyPropertyChanged private ushort maxCustomFileSize = 1024; private ushort maxPacketSize = 1400; + private string _language = "English"; private string basicContent; + public string Language + { + get => _language; + set + { + _language = value; + RaisePropertyChanged(nameof(Language)); + } + } + public string BasicContent { get => basicContent; set { basicContent = value; - RaisePropertyChanged("BasicContent"); + RaisePropertyChanged(nameof(BasicContent)); } } @@ -45,7 +57,7 @@ public uint ViewDistance set { viewDistance = value; - RaisePropertyChanged("ViewDistance"); + RaisePropertyChanged(nameof(ViewDistance)); } } @@ -55,7 +67,7 @@ public double TerrainGrid set { terrainGrid = value; - RaisePropertyChanged("TerrainGrid"); + RaisePropertyChanged(nameof(TerrainGrid)); } } @@ -65,7 +77,7 @@ public ushort MaxSizeGuaranteed set { maxSizeGuaranteed = value; - RaisePropertyChanged("MaxSizeGuaranteed"); + RaisePropertyChanged(nameof(MaxSizeGuaranteed)); } } @@ -75,7 +87,7 @@ public ushort MaxSizeNonGuaranteed set { maxSizeNonguaranteed = value; - RaisePropertyChanged("MaxSizeNonGuaranteed"); + RaisePropertyChanged(nameof(MaxSizeNonGuaranteed)); } } @@ -85,7 +97,7 @@ public ushort MaxMsgSend set { maxMsgSend = value; - RaisePropertyChanged("MaxMsgSend"); + RaisePropertyChanged(nameof(MaxMsgSend)); } } @@ -95,7 +107,7 @@ public ulong MinBandwidth set { minBandwidth = value; - RaisePropertyChanged("MinBandwidth"); + RaisePropertyChanged(nameof(MinBandwidth)); } } @@ -105,7 +117,7 @@ public ulong MaxBandwidth set { maxBandwidth = value; - RaisePropertyChanged("MaxBandwidth"); + RaisePropertyChanged(nameof(MaxBandwidth)); } } @@ -115,7 +127,7 @@ public ushort MaxPacketSize set { maxPacketSize = value; - RaisePropertyChanged("MaxPacketSize"); + RaisePropertyChanged(nameof(MaxPacketSize)); } } @@ -125,7 +137,7 @@ public double MinErrorToSend set { minErrorToSend = value; - RaisePropertyChanged("MinErrorToSend"); + RaisePropertyChanged(nameof(MinErrorToSend)); } } @@ -135,7 +147,7 @@ public double MinErrorToSendNear set { minErrorToSendNear = value; - RaisePropertyChanged("MinErrorToSendNear"); + RaisePropertyChanged(nameof(MinErrorToSendNear)); } } @@ -145,11 +157,12 @@ public ushort MaxCustomFileSize set { maxCustomFileSize = value; - RaisePropertyChanged("MaxCustomFileSize"); + RaisePropertyChanged(nameof(MaxCustomFileSize)); } } [XmlIgnore] + [Newtonsoft.Json.JsonIgnore] public string PerfPreset { get => "Custom"; @@ -181,7 +194,7 @@ public string PerfPreset MinBandwidth = 1000000000; break; } - RaisePropertyChanged("PerfPreset"); + RaisePropertyChanged(nameof(PerfPreset)); } } @@ -191,7 +204,7 @@ public BasicCfg() public string ProcessFile() { string output = "// These options are created by default\r\n" - + "language=\"English\";\r\n" + + $"language=\"{_language}\";\r\n" + "adapter=-1;\r\n" + "3D_Performance=1.000000;\r\n" + "Resolution_W=800;\r\n" diff --git a/FASTER/Models/SteamWebApi.cs b/FASTER/Models/SteamWebApi.cs index 66b823f..a23a38c 100644 --- a/FASTER/Models/SteamWebApi.cs +++ b/FASTER/Models/SteamWebApi.cs @@ -84,9 +84,13 @@ private static JObject ApiCall(string uri) // Display the status. Console.WriteLine((response)?.StatusCode); - return response == null - ? null - : JObject.Parse(response.Content.ReadAsStringAsync().Result); + if (response == null) + return null; + + if (!response.IsSuccessStatusCode) + throw new HttpRequestException($"Steam API returned HTTP {(int)response.StatusCode} {response.StatusCode}. Please check your Steam API Key in Settings."); + + return JObject.Parse(response.Content.ReadAsStringAsync().Result); // Return the response } 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/SteamUpdaterViewModel.cs b/FASTER/ViewModel/SteamUpdaterViewModel.cs index ee740d8..a4a97c0 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( + () => ProcessModDownloadAsync(mod), + TaskCreationOptions.LongRunning).Unwrap().ContinueWith((t) => { - if (!Directory.Exists(mod.Path)) - Directory.CreateDirectory(mod.Path); + 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(); + }); + } - if (tokenSource.Token.IsCancellationRequested) - { - mod.Status = ArmaModStatus.NotComplete; - return; - } - Parameters.Output += $"\n Starting {mod.WorkshopId}"; + Logger.Log("RunModsUpdater: all tasks queued, waiting for last semaphore..."); + Parameters.Output += "\nAlmost there..."; + try + { + await maxThread.WaitAsync(); + } + finally + { + IsDlOverride = false; + } - 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..."; - return; - } - - if (!SteamClient.Credentials.IsAnonymous) //IS SYNC NEABLED - { - 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); - } - - 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(); + Logger.Log("RunModsUpdater: all done."); + Parameters.Output += "\nMods updated !"; + return UpdateState.Success; + } - mod.Status = ArmaModStatus.UpToDate; - var nx = DateTime.UnixEpoch; - var ts = DateTime.UtcNow - nx; - mod.LocalLastUpdated = (ulong)ts.TotalSeconds; - } - catch (TaskCanceledException) + private async Task ProcessModDownloadAsync(ArmaMod mod) + { + Logger.Log($" Task started: {mod.WorkshopId} ({mod.Name}), path={mod.Path}"); + try + { + if (!Directory.Exists(mod.Path)) + { + Logger.Log($" Creating dir: {mod.Path}"); + Directory.CreateDirectory(mod.Path); + } + + if (tokenSource.Token.IsCancellationRequested) + { + Logger.Log($" Cancellation requested before {mod.WorkshopId}, skipping."); + return; + } + Parameters.Output += $"\n Starting {mod.WorkshopId}"; + + Stopwatch sw = Stopwatch.StartNew(); + try + { + ManifestId manifestId = default; + + if (mod.LocalLastUpdated > mod.SteamLastUpdated && mod.Size > 0) { - sw.Stop(); - mod.Status = ArmaModStatus.NotComplete; + 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; } - catch (Exception ex) + + if (!SteamClient.Credentials.IsAnonymous) { - sw.Stop(); - mod.Status = ArmaModStatus.NotComplete; - Parameters.Output += $"\nError: {ex.Message}{(ex.InnerException != null ? $" Inner Exception: {ex.InnerException.Message}" : "")}"; + 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); } - sw.Stop(); - mod.CheckModSize(); + Logger.Log($" Requesting download handler for {mod.WorkshopId}"); + Parameters.Output += $"\n Attempting to start download of item {mod.WorkshopId}... "; - 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"; + 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}"); - }, TaskCreationOptions.LongRunning).ContinueWith((_) => + mod.Status = ArmaModStatus.UpToDate; + mod.LocalLastUpdated = (ulong)(DateTime.UtcNow - DateTime.UnixEpoch).TotalSeconds; + } + catch (TaskCanceledException) { - finished += 1; - Parameters.Output += $"\n Thread {mod.WorkshopId} complete ({finished} / {ml.Count})"; - Parameters.Progress = finished * ml.Count / 100.00; - maxThread.Release(); - }); + 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.CheckModSize(); + Parameters.Output += $"\n Download {mod.WorkshopId} completed in {sw.Elapsed.Hours * 60 + sw.Elapsed.Minutes}m {sw.Elapsed.Seconds}s {sw.Elapsed.Milliseconds}ms"; + } + catch (Exception ex) + { + Logger.Log($" UNHANDLED ERROR in task for {mod.WorkshopId}: {ex.GetType().Name}: {ex.Message}\n StackTrace: {ex.StackTrace}"); } - - Parameters.Output += "\nAlmost there..."; - await maxThread.WaitAsync(); - - - 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();