diff --git a/Celeste.Mod.mm/Content/Dialog/English.txt b/Celeste.Mod.mm/Content/Dialog/English.txt index 4eef8c25a..d93dea167 100755 --- a/Celeste.Mod.mm/Content/Dialog/English.txt +++ b/Celeste.Mod.mm/Content/Dialog/English.txt @@ -153,6 +153,7 @@ MODOPTIONS_COREMODULE_MIRRORPREFERENCES_WEGFAN= China MODOPTIONS_COREMODULE_MIRRORPREFERENCES_OTOBOT= North America MODOPTIONS_COREMODULE_MIRRORPREFERENCES_RISINGSUNLIGHT= France + MODOPTIONS_COREMODULE_MIRRORTESTING= Determine the best mirror MODOPTIONS_COREMODULE_USEAPIMIRROR= Use Database Mirror MODOPTIONS_COREMODULE_USEAPIMIRROR_DESC= This option can help if you get "Downloading the database failed" errors @@ -337,6 +338,15 @@ MODOPTIONS_MODTOGGLE_BINS= Map Bin Files MODOPTIONS_MODTOGGLE_SEARCHBOX_PLACEHOLDER= Press 'Tab' or 'Enter' to scroll to the next match +# Mirror Testing Menu + MODOPTIONS_MIRRORTESTING= TEST MIRRORS + MODOPTIONS_MIRRORTESTING_EXPLANATION= All mirrors will be used to download{n}a same mod and Everest will choose the mirror with the best download speed. + MODOPTIONS_MIRRORTESTING_DOWNLOADING= Downloading mod from ((mirror)) + MODOPTIONS_MIRRORTESTING_DOWNLOADED= Downloaded mod from ((mirror)) in + MODOPTIONS_MIRRORTESTING_TIMEDOUT= Downloaded mod from ((mirror)) timed out + MODOPTIONS_MIRRORTESTING_CANCELED= Canceled + MODOPTIONS_MIRRORTESTING_ERROR= An error occured + # Asset Reload Helper ASSETRELOADHELPER_RELOADINGMAP= Reloading map ASSETRELOADHELPER_RELOADINGLEVEL= Reloading level diff --git a/Celeste.Mod.mm/Content/Dialog/French.txt b/Celeste.Mod.mm/Content/Dialog/French.txt index bf08970db..72b3d816f 100755 --- a/Celeste.Mod.mm/Content/Dialog/French.txt +++ b/Celeste.Mod.mm/Content/Dialog/French.txt @@ -318,6 +318,12 @@ MODOPTIONS_MODTOGGLE_BINS= Fichiers .bin de maps MODOPTIONS_MODTOGGLE_SEARCHBOX_PLACEHOLDER= Appuie sur Tab ou Entrée pour lancer la recherche +# Mirror Testing Menu + MODOPTIONS_MIRRORTESTING= TEST DES MIROIRS + MODOPTIONS_MIRRORTESTING_EXPLANATION= Tout les miroirs vont être utilisés pour télécharger{n}un même mod puis Everest va choisir le miroir avec la meilleur vitesse de téléchargement. + MODOPTIONS_MIRRORTESTING_DOWNLOADING= Téléchargement du mod via le miroir ((mirror)) + MODOPTIONS_MIRRORTESTING_DOWNLOADED= Téléchargement du mod via le miroir ((mirror)) fini en + # Asset Reload Helper ASSETRELOADHELPER_RELOADINGMAP= Rechargement de la map... ASSETRELOADHELPER_RELOADINGLEVEL= Rechargement du niveau... diff --git a/Celeste.Mod.mm/Mod/Core/CoreModule.cs b/Celeste.Mod.mm/Mod/Core/CoreModule.cs index 1bf80352d..180e661d9 100644 --- a/Celeste.Mod.mm/Mod/Core/CoreModule.cs +++ b/Celeste.Mod.mm/Mod/Core/CoreModule.cs @@ -327,6 +327,14 @@ public override void CreateModMenuSection(patch_TextMenu menu, bool inGame, Even menu.Insert(items.Count - 2, new TextMenu.Button(Dialog.Clean("modoptions_coremodule_togglemods")).Pressed(() => { OuiModOptions.Instance.Overworld.Goto(); })); + + // insert mirror test under "Use Mirror to Download Mods" + int MirrorPreferenceEntryIndex = items.FindIndex(item => (item is TextMenu.Slider) && (item as TextMenu.Slider).Label == Dialog.Clean("MODOPTIONS_COREMODULE_MIRRORPREFERENCES")); + if (MirrorPreferenceEntryIndex != -1) { + menu.Insert(MirrorPreferenceEntryIndex + 1, new TextMenu.Button(Dialog.Clean("MODOPTIONS_COREMODULE_MIRRORTESTING")).Pressed(() => { + OuiModOptions.Instance.Overworld.Goto(); + })); + } } } diff --git a/Celeste.Mod.mm/Mod/UI/OuiMirrorTester.cs b/Celeste.Mod.mm/Mod/UI/OuiMirrorTester.cs new file mode 100755 index 000000000..89e42dd5d --- /dev/null +++ b/Celeste.Mod.mm/Mod/UI/OuiMirrorTester.cs @@ -0,0 +1,135 @@ +using Celeste.Mod.Core; +using Celeste.Mod.Helpers; +using Microsoft.Xna.Framework; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Threading.Tasks; + +namespace Celeste.Mod.UI { + public class OuiMirrorTester : OuiGenericMenu, OuiModOptions.ISubmenu { + public override string MenuName => Dialog.Clean("MODOPTIONS_MIRRORTESTING"); + private Task mirrorLoadingTask; + private bool ongoingUpdateCancelled = false; + public OuiMirrorTester() { + backToParentMenu = onBackPressed; + } + + protected override void addOptionsToMenu(patch_TextMenu menu) { + TextMenuExt.SubHeaderExt loading = new TextMenuExt.SubHeaderExt(Dialog.Clean("MODOPTIONS_MIRRORTESTING_EXPLANATION")); + menu.Add(loading); + + mirrorLoadingTask = new Task(() => { + int mirrorIndex = 0; + List mirrorNames = CoreModule.Settings.MirrorPreferences.Split(',').ToList(); + string downloadDestination = Path.Combine(Everest.PathTmp, $"mirror-tester.zip"); + Dictionary speedForMirror = new(); + + foreach (string mirrorUrl in ModUpdaterHelper.GetAllMirrorUrls("https://gamebanana.com/dl/1778093")) { + if (ongoingUpdateCancelled) + continue; + string mirrorName = Dialog.Clean("MODOPTIONS_COREMODULE_MIRRORPREFERENCES_" + mirrorNames[mirrorIndex]); + if (mirrorNames[mirrorIndex] == "gb") { + mirrorName = "GameBanana"; + } + TextMenu.Button loadingMirror = new TextMenuExt.ButtonExt(Dialog.Clean("MODOPTIONS_MIRRORTESTING_DOWNLOADING").Replace("((mirror))", mirrorName)) { Disabled = true, TextColorDisabled = Color.LightGray }; + menu.Add(loadingMirror); + Func progressCallback = (position, length, speed) => { + string speedString; + if (ongoingUpdateCancelled) + return false; + if (length > 0) { + speedString = $"{(int) Math.Floor(100D * (position / (double) length))}% @ {speed} KiB/s"; + } else { + speedString = $"{(int) Math.Floor(position / 1000D)}KiB @ {speed} KiB/s"; + } + + loadingMirror.Label = Dialog.Clean("MODOPTIONS_MIRRORTESTING_DOWNLOADING").Replace("((mirror))", mirrorName) + $": {speedString}"; + return true; + }; + + + Logger.Verbose("OuiMirrorTester", $"Downloading from {mirrorUrl}"); + bool finishedSuccessfully = false; + Stopwatch testerStopwatch = Stopwatch.StartNew(); + try { + Everest.Updater.DownloadFileWithProgress(mirrorUrl, downloadDestination, progressCallback); + finishedSuccessfully = true; + } catch (TimeoutException) { + } + testerStopwatch.Stop(); + + if (finishedSuccessfully) { + loadingMirror.Label = Dialog.Clean("MODOPTIONS_MIRRORTESTING_DOWNLOADED").Replace("((mirror))", mirrorName) + $" {Math.Round((double) (testerStopwatch.ElapsedMilliseconds / 10)) / 100}s"; + speedForMirror[mirrorNames[mirrorIndex]] = testerStopwatch.ElapsedMilliseconds; + } else { + loadingMirror.Label = Dialog.Clean("MODOPTIONS_MIRRORTESTING_TIMEDOUT").Replace("((mirror))", mirrorName); + speedForMirror[mirrorNames[mirrorIndex]] = long.MaxValue; + } + ModUpdaterHelper.TryDelete(downloadDestination); + mirrorIndex++; + }; + if (!ongoingUpdateCancelled) { + mirrorNames.Sort((p1, p2) => speedForMirror[p1].CompareTo(speedForMirror[p2])); + + List mirrorPreferences = new List { + "gb,jade,risingsunlight,otobot,wegfan", + "jade,risingsunlight,otobot,wegfan,gb", + "wegfan,otobot,jade,risingsunlight,gb", + "otobot,jade,risingsunlight,wegfan,gb", + "risingsunlight,jade,otobot,wegfan,gb" + }; + + CoreModule.Settings.MirrorPreferences = mirrorPreferences.Find(mirrors => mirrors.StartsWith(mirrorNames[0])); + } + Overworld.Goto(); + }); + + mirrorLoadingTask.Start(); + } + + private bool MessageDisplayed = false; + public override void Update() { + base.Update(); + + if (mirrorLoadingTask != null && mirrorLoadingTask.IsCompleted && !mirrorLoadingTask.IsCompletedSuccessfully && !MessageDisplayed) { + if (ongoingUpdateCancelled) { + menu.Add(new TextMenuExt.SubHeaderExt(Dialog.Clean("MODOPTIONS_MIRRORTESTING_CANCELED"))); + } else { + menu.Add(new TextMenuExt.SubHeaderExt(Dialog.Clean("MODOPTIONS_MIRRORTESTING_ERROR"))); + + throw mirrorLoadingTask.Exception; + } + MessageDisplayed = true; + } + } + + + private void onBackPressed(Overworld overworld) { + if (mirrorLoadingTask.IsCompleted) { + overworld.Goto(); + } else { + ongoingUpdateCancelled = true; + } + } + + public override void Render() { + base.Render(); + } + + public override IEnumerator Leave(Oui next) { + IEnumerator orig = base.Leave(next); + while (orig.MoveNext()) { + yield return orig.Current; + } + + // we left the screen: clean up all variables. + mirrorLoadingTask = null; + ongoingUpdateCancelled = false; + MessageDisplayed = false; + } + } +}