From ad0bca5192d72979bfa4afa0988e832d9ae3d292 Mon Sep 17 00:00:00 2001 From: Salvador Cipolla Date: Sun, 22 Mar 2026 12:09:54 -0300 Subject: [PATCH] Implement download anti-stuck check --- Knossos.NET/Models/GlobalSettings.cs | 3 ++ .../ViewModels/GlobalSettingsViewModel.cs | 9 +++++ .../Templates/Tasks/DownloadFile.cs | 33 +++++++++++++++++++ Knossos.NET/Views/GlobalSettingsView.axaml | 5 +++ 4 files changed, 50 insertions(+) diff --git a/Knossos.NET/Models/GlobalSettings.cs b/Knossos.NET/Models/GlobalSettings.cs index 574e3815..6bd7712d 100644 --- a/Knossos.NET/Models/GlobalSettings.cs +++ b/Knossos.NET/Models/GlobalSettings.cs @@ -127,6 +127,8 @@ public StandaloneServerSettings(MultiCfg multiCfg, string id, string version, in public int logLevel { get; set; } = 1; [JsonPropertyName("global_cmdline")] public string? globalCmdLine { get; set; } = null; + [JsonPropertyName("anti-stuck")] + public bool antiStuck { get; set; } = true; [JsonPropertyName("force_sse2")] public bool forceSSE2 { get; set; } = false; [JsonPropertyName("max_concurrent_subtasks")] @@ -724,6 +726,7 @@ public void Load() closeToTray = tempSettings.closeToTray; ignoredLauncherUpdates = tempSettings.ignoredLauncherUpdates; hiddenModIds = tempSettings.hiddenModIds; + antiStuck = tempSettings.antiStuck; if (hiddenModIds.Any()) { foreach (var hiddenMod in hiddenModIds) diff --git a/Knossos.NET/ViewModels/GlobalSettingsViewModel.cs b/Knossos.NET/ViewModels/GlobalSettingsViewModel.cs index 59a3200f..8e9188eb 100644 --- a/Knossos.NET/ViewModels/GlobalSettingsViewModel.cs +++ b/Knossos.NET/ViewModels/GlobalSettingsViewModel.cs @@ -71,6 +71,13 @@ public partial class GlobalSettingsViewModel : ViewModelBase [ObservableProperty] internal string basePath = string.Empty; //When this is changed settings are saved immediately. + private bool antiStuck = true; + internal bool AntiStuck + { + get { return antiStuck; } + set { if (antiStuck != value) { this.SetProperty(ref antiStuck, value); UnCommitedChanges = true; } } + } + private bool blCfNebula = false; internal bool BlCfNebula { @@ -669,6 +676,7 @@ public void LoadData() EnvVars = Knossos.globalSettings.envVars; ShowDevOptions = Knossos.globalSettings.showDevOptions || NoSystemCMD; CloseToTray = Knossos.globalSettings.closeToTray; + AntiStuck = Knossos.globalSettings.antiStuck; /* VIDEO SETTINGS */ //RESOLUTION @@ -1249,6 +1257,7 @@ internal void SaveCommand() Knossos.globalSettings.envVars = EnvVars; Knossos.globalSettings.showDevOptions = ShowDevOptions; Knossos.globalSettings.closeToTray = CloseToTray; + Knossos.globalSettings.antiStuck = AntiStuck; /* VIDEO */ //Resolution diff --git a/Knossos.NET/ViewModels/Templates/Tasks/DownloadFile.cs b/Knossos.NET/ViewModels/Templates/Tasks/DownloadFile.cs index 8570ec91..5e7b341b 100644 --- a/Knossos.NET/ViewModels/Templates/Tasks/DownloadFile.cs +++ b/Knossos.NET/ViewModels/Templates/Tasks/DownloadFile.cs @@ -171,6 +171,15 @@ private async Task Download(Uri downloadUrl, string destinationFilePath, F Log.Add(Log.LogSeverity.Information, "TaskItemViewModel.Download()", "Downloading file: " + downloadUrl); System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch(); + // ANTI-STUCK CONFIG + // If less ANTI_STUCK_MIN_BYTES are downloaded in ANTI_STUCK_CHECK_SECONDS + // The download is restarted with a mirror swap + const int ANTI_STUCK_CHECK_SECONDS = 30; + const long ANTI_STUCK_MIN_BYTES = 102400; // 100 KB (3.33 KB/s) + DateTime lastStallCheck = DateTime.UtcNow; + long bytesAtLastCheck = 0; + // ================================= + var httpClient = KnUtils.GetHttpClient(); if (downloadUrl.ToString().ToLower().Contains(".json")) { @@ -219,6 +228,9 @@ private async Task Download(Uri downloadUrl, string destinationFilePath, F { throw new TaskCanceledException(); } + // Reset anti-stuck counter on paused downloads + lastStallCheck = DateTime.UtcNow; + bytesAtLastCheck = totalBytesRead; } if (cancellationTokenSource!.IsCancellationRequested) @@ -253,6 +265,27 @@ private async Task Download(Uri downloadUrl, string destinationFilePath, F stopwatch.Restart(); } + // Anti-Stuck check + if (Knossos.globalSettings.antiStuck && (DateTime.UtcNow - lastStallCheck).TotalSeconds >= ANTI_STUCK_CHECK_SECONDS) + { + long bytesInWindow = totalBytesRead - bytesAtLastCheck; + + if (bytesInWindow < ANTI_STUCK_MIN_BYTES) + { + string speedStr = KnUtils.FormatBytes(bytesInWindow / ANTI_STUCK_CHECK_SECONDS) + "/s"; + Log.Add(Log.LogSeverity.Warning, "TaskItemViewModel.Download", + $"[ANTI-STUCK] Mirror {CurrentMirror ?? downloadUrl.Host} is stuck (too slow). " + + $"Only {KnUtils.FormatBytes(bytesInWindow)} in {ANTI_STUCK_CHECK_SECONDS}s ({speedStr}). " + + "Changing to a diferent mirror..."); + + return false; + } + + // reset counter + lastStallCheck = DateTime.UtcNow; + bytesAtLastCheck = totalBytesRead; + } + // ================================= if (readCount % 100 == 0) { diff --git a/Knossos.NET/Views/GlobalSettingsView.axaml b/Knossos.NET/Views/GlobalSettingsView.axaml index 6359e469..55fa3fa8 100644 --- a/Knossos.NET/Views/GlobalSettingsView.axaml +++ b/Knossos.NET/Views/GlobalSettingsView.axaml @@ -128,6 +128,11 @@ hestia.feralhosting.com + + + + +