diff --git a/Source/HedgeModManager.UI/Controls/MainWindow/Test.axaml b/Source/HedgeModManager.UI/Controls/MainWindow/Test.axaml index 9075ca4..12da53f 100644 --- a/Source/HedgeModManager.UI/Controls/MainWindow/Test.axaml +++ b/Source/HedgeModManager.UI/Controls/MainWindow/Test.axaml @@ -7,19 +7,19 @@ mc:Ignorable="d" d:DesignWidth="920" d:DesignHeight="390" x:Class="HedgeModManager.UI.Controls.MainWindow.Test" x:DataType="vm:MainWindowViewModel" - Loaded="OnLoaded" + Loaded="OnLoaded" Unloaded="OnUnloaded" Background="{DynamicResource BackgroundL0Brush}"> - - + + - - - + + + @@ -64,11 +64,11 @@ - - + Export - - + diff --git a/Source/HedgeModManager.UI/Controls/MainWindow/Test.axaml.cs b/Source/HedgeModManager.UI/Controls/MainWindow/Test.axaml.cs index 38b99d6..81301dd 100644 --- a/Source/HedgeModManager.UI/Controls/MainWindow/Test.axaml.cs +++ b/Source/HedgeModManager.UI/Controls/MainWindow/Test.axaml.cs @@ -5,9 +5,11 @@ using Avalonia.Layout; using Avalonia.Markup.Xaml; using Avalonia.Styling; +using Avalonia.Threading; using HedgeModManager.UI.Controls.Modals; using HedgeModManager.UI.Models; using HedgeModManager.UI.ViewModels; +using System.Collections.Specialized; using System.Diagnostics; namespace HedgeModManager.UI.Controls.MainWindow; @@ -25,6 +27,9 @@ private void OnLoaded(object? sender, RoutedEventArgs e) if (viewModel == null) return; + if (viewModel.LoggerInstance != null) + viewModel.LoggerInstance.Logs.CollectionChanged += OnLogCollectionChanged; + // Add buttons if (viewModel.CurrentTabInfo != null) { @@ -114,6 +119,19 @@ void createStringEditor(string name) } } + private void OnUnloaded(object? sender, RoutedEventArgs e) + { + if (DataContext is not MainWindowViewModel viewModel || viewModel.LoggerInstance == null) + throw new Exception("View model or logger is missing. It is not safe to continue."); + + viewModel.LoggerInstance.Logs.CollectionChanged += OnLogCollectionChanged; + } + + private void OnLogCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) + { + Dispatcher.UIThread.Invoke(LogScrollViewer.ScrollToEnd); + } + private async void SaveConfig_Click(object? sender, RoutedEventArgs e) { await (DataContext as MainWindowViewModel)!.Config.SaveAsync(); diff --git a/Source/HedgeModManager.UI/Controls/Settings/Settings.axaml b/Source/HedgeModManager.UI/Controls/Settings/Settings.axaml index c654ca6..196c58e 100644 --- a/Source/HedgeModManager.UI/Controls/Settings/Settings.axaml +++ b/Source/HedgeModManager.UI/Controls/Settings/Settings.axaml @@ -78,6 +78,28 @@ + + + + + + + + + diff --git a/Source/HedgeModManager.UI/Controls/Settings/Settings.axaml.cs b/Source/HedgeModManager.UI/Controls/Settings/Settings.axaml.cs index 9c6d85c..7943f51 100644 --- a/Source/HedgeModManager.UI/Controls/Settings/Settings.axaml.cs +++ b/Source/HedgeModManager.UI/Controls/Settings/Settings.axaml.cs @@ -3,6 +3,7 @@ using Avalonia.Interactivity; using Avalonia.Markup.Xaml; using Avalonia.Platform.Storage; +using HedgeModManager.CoreLib; using HedgeModManager.UI.Controls.Modals; using HedgeModManager.UI.Languages; using HedgeModManager.UI.Models; @@ -142,18 +143,55 @@ private void OnGameDirClick(object? sender, RoutedEventArgs e) private async void OnInstallMLClick(object? sender, RoutedEventArgs e) { - if (_isInstalling) - return; - _isInstalling = true; if (DataContext is not MainWindowViewModel mainViewModel || mainViewModel.SelectedGame is not UIGame uiGame) return; - await uiGame.Game.InstallModLoaderAsync(); + if (_isInstalling) + return; + _isInstalling = true; + await mainViewModel.InstallModLoader(); ViewModel.Update(); _isInstalling = false; } - + + private async void OnPrefixReinstallClick(object? sender, RoutedEventArgs e) + { + if (DataContext is not MainWindowViewModel mainViewModel) + return; + + mainViewModel.IsBusy = true; + await MainWindowViewModel.CreateSimpleDownload("Download.Text.InstallRuntime", "Failed to install runtime", + async (d, p, c) => + { + if (mainViewModel.SelectedGame == null) + return; + + await LinuxCompatibility.InstallRuntimeToPrefix( + mainViewModel.SelectedGame.Game.PrefixRoot); + }).OnFinally((d) => + { + mainViewModel.IsBusy = false; + return Task.CompletedTask; + }).RunAsync(mainViewModel); + } + + private void OnPrefixOpenClick(object? sender, RoutedEventArgs e) + { + if (DataContext is not MainWindowViewModel mainViewModel) + return; + + if (mainViewModel.SelectedGame == null) + return; + + Process.Start(new ProcessStartInfo + { + FileName = mainViewModel.SelectedGame.Game.PrefixRoot ?? string.Empty, + UseShellExecute = true + }); + + } + private async void OnCheckManagerUpdatesClick(object? sender, RoutedEventArgs e) { ViewModel.CheckManagerUpdatesText = "Settings.Button.CheckingUpdates"; diff --git a/Source/HedgeModManager.UI/Languages/en-AU.axaml b/Source/HedgeModManager.UI/Languages/en-AU.axaml index f9bf8a1..5ebe5e8 100644 --- a/Source/HedgeModManager.UI/Languages/en-AU.axaml +++ b/Source/HedgeModManager.UI/Languages/en-AU.axaml @@ -49,6 +49,8 @@ Downloading {0}... Installing {0}... Installing {0} mods... + Installing mod loader... + Installing runtime... Downloading updates... Extracting update... Cool Dark @@ -73,15 +75,19 @@ Change Check Checking... + Delete Install + Reinstall Uninstall Open Game Directory Open + Open Game & Mod Loader Hedge Mod Manager Settings Profile Quickly change the enabled mods using user-defined presets. + Tools for managing the Windows compatibility layer. Enable and install to load mods. Enable to display mod loader debug messages. Launch the game using the associated game launcher. @@ -89,11 +95,12 @@ Automatically check for Hedge Mod Manager updates on startup. Automatically check for mod loader updates on startup and game change. Automatically check for mod updates on startup and game change. - Mods Directory - Mod Loader Enable Debug Console Language Use Game Launcher + Mods Directory + Mod Loader + Proton Theme Check for code updates Check for Hedge Mod Manager updates @@ -126,17 +133,19 @@ Select Mods Directory... Invalid Mods Directory Update Error + Update Available Hedge Mod Manager Update Launch Error Configure {0} Are you sure you want to delete "{0}"? This action cannot be undone! Failed to download mod." Please try again later. - Please update Hedge Mod Manager using your system's package manager. An error occurred when Hedge Mod Manager was trying to load mod data. Hedge Mod Manager is unable to install "{0}" due to {1} not being found. Hedge Mod Manager does not have write access to the game files. Please make sure the game files are writable. Failed to install mod. Check log for exception. + Please update Hedge Mod Manager using your system's package manager. Hedge Mod Manager does not have permissions to the selected directory. Please select another directory. + There is an update available for {0}. Would you like to update the mod? Hedge Mod Manager was unable to save due to an unknown error. An error occurred when trying to prepare for updating. Please try again later. Hedge Mod Manager was unable to launch the game due to an unknown error. diff --git a/Source/HedgeModManager.UI/Logger.cs b/Source/HedgeModManager.UI/Logger.cs index 082dff9..38a43fe 100644 --- a/Source/HedgeModManager.UI/Logger.cs +++ b/Source/HedgeModManager.UI/Logger.cs @@ -1,4 +1,5 @@ -using CommunityToolkit.Mvvm.ComponentModel; +using Avalonia.Threading; +using CommunityToolkit.Mvvm.ComponentModel; using HedgeModManager.Foundation; using System.Collections.ObjectModel; using System.Text; @@ -8,6 +9,7 @@ namespace HedgeModManager.UI; public partial class UILogger : ObservableObject, ILogger { [ObservableProperty] private ObservableCollection _logs = []; + private List _backupLogs = []; public UILogger() { @@ -16,7 +18,12 @@ public UILogger() public void WriteLine(LogType type, string message) { - Logs.Add(new Log(type, message)); + var log = new Log(type, message); + _backupLogs.Add(log); + Dispatcher.UIThread.Invoke(() => + { + Logs.Add(new Log(type, message)); + }); } public static UILogger GetInstance() @@ -27,11 +34,12 @@ public static UILogger GetInstance() public static void Clear() { GetInstance().Logs.Clear(); + GetInstance()._backupLogs.Clear(); } public static string Export() { - return GetInstance().Logs + return GetInstance()._backupLogs .Aggregate(new StringBuilder(), (sb, log) => sb.AppendLine(log.ToString())) .ToString(); } diff --git a/Source/HedgeModManager.UI/Program.cs b/Source/HedgeModManager.UI/Program.cs index 2924015..96783c8 100644 --- a/Source/HedgeModManager.UI/Program.cs +++ b/Source/HedgeModManager.UI/Program.cs @@ -75,14 +75,10 @@ public static void Main(string[] args) sb.AppendLine("-- Logger Dump End --"); sb.AppendLine(); - var thrownException = (Exception)e.ExceptionObject; + var thrownException = e.ExceptionObject as Exception; sb.AppendLine("Unhandled Exception:"); - sb.AppendLine(thrownException.ToString()); - sb.AppendLine(); - - sb.AppendLine("Unhandled Inner Exception:"); - sb.AppendLine(thrownException.InnerException?.ToString() ?? "None"); + sb.AppendLine(thrownException?.ToString() ?? "NULL EXCEPTION"); sb.AppendLine(); string text = sb.ToString(); diff --git a/Source/HedgeModManager.UI/Updater.cs b/Source/HedgeModManager.UI/Updater.cs index c325228..d89a20e 100644 --- a/Source/HedgeModManager.UI/Updater.cs +++ b/Source/HedgeModManager.UI/Updater.cs @@ -16,7 +16,7 @@ public class Updater public static string GetUpdatePackageName() { if (OperatingSystem.IsWindows()) - return $"HedgeModManager-win-x64.zip"; + return $"HedgeModManager.exe"; else if (OperatingSystem.IsLinux()) return $"HedgeModManager-linux-x64.zip"; else @@ -33,7 +33,9 @@ public static string GetUpdatePackageName() if (app == null) { Logger.Error("Failed to check for updates"); - return (null, UpdateCheckStatus.Error); + // TODO: Swap these nearing release + //return (null, UpdateCheckStatus.Error); + return (null, UpdateCheckStatus.NoUpdate); } Logger.Debug("Got Flathub data"); Logger.Debug($"Current Version: {Program.ApplicationVersion}"); @@ -46,7 +48,8 @@ public static string GetUpdatePackageName() { Version = app.CurrentReleaseVersion!, Title = app.Name, - Description = app.CurrentReleaseDescription + Description = app.CurrentReleaseDescription, + IsSingleExecutable = false }, UpdateCheckStatus.UpdateAvailable); } @@ -60,7 +63,9 @@ public static string GetUpdatePackageName() if (release == null) { Logger.Error("Failed to check for updates"); - return (null, UpdateCheckStatus.Error); + // TODO: Swap these nearing release + //return (null, UpdateCheckStatus.Error); + return (null, UpdateCheckStatus.NoUpdate); } Logger.Debug($"Current Version: {Program.ApplicationVersion}"); @@ -85,7 +90,8 @@ public static string GetUpdatePackageName() Version = release.TagName, Title = release.Name, Description = release.Body, - DownloadURI = asset.BrowserDownloadURL + DownloadURI = asset.BrowserDownloadURL, + IsSingleExecutable = OperatingSystem.IsWindows() }, UpdateCheckStatus.UpdateAvailable); } else @@ -104,7 +110,8 @@ public static async Task BeginUpdate(Update update, MainWindowViewModel? mainVie return; } string tempPath = Paths.GetTempPath(); - string packageFilePath = Path.Combine(tempPath, "update.zip"); + string packageFileName = update.IsSingleExecutable ? "update.exe" : "update.zip"; + string packageFilePath = Path.Combine(tempPath, packageFileName); await new Download(Localize("Download.Text.UpdateManager0"), true) .OnRun(async (d, c) => @@ -233,6 +240,7 @@ public class Update public string Title { get; set; } = "Update Title"; public string? Description { get; set; } public Uri? DownloadURI { get; set; } + public bool IsSingleExecutable { get; set; } } public class FlathubApp diff --git a/Source/HedgeModManager.UI/ViewModels/MainWindowViewModel.cs b/Source/HedgeModManager.UI/ViewModels/MainWindowViewModel.cs index e7bca78..d9d095c 100644 --- a/Source/HedgeModManager.UI/ViewModels/MainWindowViewModel.cs +++ b/Source/HedgeModManager.UI/ViewModels/MainWindowViewModel.cs @@ -121,7 +121,7 @@ public async Task CheckForManagerUpdates() } d.Destroy(); - string message = $"update found:\n{update.Title} - {update.Version}"; + string message = $"Update found:\n{update.Title} - {update.Version}"; Logger.Information(message); var messageBox = new MessageBoxModal("Modal.Title.UpdateManager", message); messageBox.AddButton("Common.Button.Cancel", (s, e) => messageBox.Close()); @@ -200,16 +200,20 @@ public async Task CheckForAllModUpdates() Logger.Debug($" Latest: {info.Version}"); if (promptUpdate) { - var messageBox = new MessageBoxModal("Modal.Title.UpdateMod", "Modal.Message.UpdateMod"); + var messageBox = new MessageBoxModal("Modal.Title.UpdateMod", Localize("Modal.Message.UpdateMod", mod.Title)); messageBox.AddButton("Common.Button.Cancel", (s, e) => messageBox.Close()); messageBox.AddButton("Common.Button.Update", async (s, e) => { - Modals.Where(x => x.Control is ModInfoModal).ToList().ForEach(x => x.Close()); - Logger.Information($"Update clicked for {mod.Title}"); - messageBox.Close(); - await mod.Updater.PerformUpdateAsync(c); - Modals.Where(x => x.Control is ModInfoModal).ToList().ForEach(x => x.Close()); - await Dispatcher.UIThread.InvokeAsync(RefreshGame); + // TODO: Look into threading issues + await Dispatcher.UIThread.InvokeAsync(async () => + { + Modals.Where(x => x.Control is ModInfoModal).ToList().ForEach(x => x.Close()); + Logger.Information($"Update clicked for {mod.Title}"); + messageBox.Close(); + await mod.Updater.PerformUpdateAsync(c); + Modals.Where(x => x.Control is ModInfoModal).ToList().ForEach(x => x.Close()); + RefreshGame(); + }); }); messageBox.Open(this); } @@ -247,7 +251,7 @@ public async Task LoadGame() if (game.ModDatabase is ModDatabaseGeneric modsDB) Profiles = new(await LoadProfiles(game) ?? []); SelectedProfile = Profiles.FirstOrDefault() ?? ModProfile.Default; - Logger.Debug($"Loaded {Profiles.Count} profiles"); + Logger.Debug($"Loaded {Profiles.Count} profile(s)"); Config.LastSelectedPath = Path.Combine(game.Root, game.Executable ?? ""); @@ -261,6 +265,34 @@ public async Task LoadGame() } } + public async Task InstallModLoader(bool? install = null) + { + IsBusy = true; + await CreateSimpleDownload("Download.Text.InstallModLoader", "Failed to install modloader", + async (d, p, c) => + { + if (SelectedGame == null) + return; + if (install != null) + { + var gameGeneric = GetModdableGameGeneric(); + if (gameGeneric == null || gameGeneric.ModLoader == null) + return; + + if (install == true) + _ = await gameGeneric.ModLoader.InstallAsync(); + else + _ = await gameGeneric.ModLoader.UninstallAsync(); + } + else + { + _ = await SelectedGame.Game.InstallModLoaderAsync(); + } + IsBusy = false; + Dispatcher.UIThread.Invoke(RefreshUI); + }).RunAsync(this); + } + // TODO: Implement mod config switching public Task LoadProfile() { @@ -300,6 +332,7 @@ public async Task Save(bool setBusy = true) { try { + // TODO: Resolve mod dependencies await SelectedGame.Game.ModDatabase.Save(); if (SelectedGame.Game.ModLoaderConfiguration is ModLoaderConfiguration config) await config.Save(Path.Combine(SelectedGame.Game.Root, "cpkredir.ini")); @@ -319,8 +352,7 @@ public async Task Save(bool setBusy = true) } catch (Exception e) { - OpenErrorMessage("Modal.Title.SaveError", "Modal.Message.UnknownSaveError", - "Failed to save", e); + OpenErrorMessage("Modal.Title.SaveError", "Modal.Message.UnknownSaveError", "Failed to save", e); } if (setBusy) IsBusy = false; @@ -333,7 +365,6 @@ public async Task RunGame() try { await SelectedGame.Game.Run(null, true); - // Add delay await Task.Delay(5000); } catch (Exception e) @@ -361,8 +392,7 @@ public void StartSetup() public async Task UpdateCodes(bool force, bool append) { - if (SelectedGame != null && - SelectedGame.Game is ModdableGameGeneric gameGeneric && + if (SelectedGame != null && SelectedGame.Game is ModdableGameGeneric gameGeneric && gameGeneric.ModLoaderConfiguration is ModLoaderConfiguration config) { string modsRoot = PathEx.GetDirectoryName(config.DatabasePath).ToString(); @@ -512,7 +542,6 @@ public async Task InstallMod(Visual visual, UIGame? game) InstallMod(file.Name, Utils.ConvertToPath(file.Path), game); } - public void InstallMod(string name, string path, UIGame? game) { game ??= SelectedGame; diff --git a/Source/HedgeModManager.UI/ViewModels/Settings/SettingsViewModel.cs b/Source/HedgeModManager.UI/ViewModels/Settings/SettingsViewModel.cs index 773d58b..30c8213 100644 --- a/Source/HedgeModManager.UI/ViewModels/Settings/SettingsViewModel.cs +++ b/Source/HedgeModManager.UI/ViewModels/Settings/SettingsViewModel.cs @@ -115,6 +115,8 @@ public Theme SelectedTheme } } + public bool SupportsProton => Game?.NativeOS == "Windows" && OperatingSystem.IsLinux() && !string.IsNullOrEmpty(Game.PrefixRoot); + public SettingsViewModel() { UpdateThemes(); @@ -130,6 +132,7 @@ public void Update() { OnPropertyChanged(nameof(InstallModLoaderText)); OnPropertyChanged(nameof(SelectedProfile)); + OnPropertyChanged(nameof(SupportsProton)); } protected override void OnPropertyChanged(PropertyChangedEventArgs e) @@ -142,6 +145,7 @@ protected override void OnPropertyChanged(PropertyChangedEventArgs e) OnPropertyChanged(nameof(EnableDebugConsole)); OnPropertyChanged(nameof(HasModLoader)); OnPropertyChanged(nameof(SupportsMultipleLaunchMethods)); + OnPropertyChanged(nameof(SupportsProton)); } if (e.PropertyName == nameof(MainViewModel)) OnPropertyChanged(nameof(SelectedLanguage)); diff --git a/Source/HedgeModManager/LinuxCompatibility.cs b/Source/HedgeModManager/LinuxCompatibility.cs index f82e813..1ddca69 100644 --- a/Source/HedgeModManager/LinuxCompatibility.cs +++ b/Source/HedgeModManager/LinuxCompatibility.cs @@ -4,6 +4,7 @@ using HedgeModManager.Steam; using System.IO; using System.IO.Compression; +using System.Text; public class LinuxCompatibility { @@ -55,8 +56,10 @@ await Task.Run(() => } Logger.Debug($"Extracted .NET runtime"); }); - await stream.DisposeAsync(); + + await AddDotnetRegPatch(path); + return true; } @@ -83,6 +86,30 @@ public static async Task AddDllOverride(string? path, string name) return true; } + public static async Task AddDotnetRegPatch(string? path) + { + Logger.Debug($"Adding .NET Framework registry patch to {path}"); + if (string.IsNullOrEmpty(path) || !Directory.Exists(path)) + { + Logger.Debug($"Prefix is missing!"); + return false; + } + + string reg = Path.Combine(path!, "system.reg"); + + if (!Path.Exists(reg)) + { + Logger.Debug($"Prefix is not initialised!"); + return false; + } + + string regPatch = Encoding.Unicode.GetString(Resources.dotnetReg); + + await File.AppendAllTextAsync(reg, regPatch); + Logger.Debug($"File written"); + return true; + } + public static bool IsPrefixValid(string? path) { if (path == null) diff --git a/Source/HedgeModManager/Properties/Resources.Designer.cs b/Source/HedgeModManager/Properties/Resources.Designer.cs index 2e9c432..9377d42 100644 --- a/Source/HedgeModManager/Properties/Resources.Designer.cs +++ b/Source/HedgeModManager/Properties/Resources.Designer.cs @@ -78,6 +78,16 @@ internal static string DotnetDownloadURL { } } + /// + /// Looks up a localized resource of type System.Byte[]. + /// + internal static byte[] dotnetReg { + get { + object obj = ResourceManager.GetObject("dotnetReg", resourceCulture); + return ((byte[])(obj)); + } + } + /// /// Looks up a localized string similar to https://github.com/thesupersonic16/HedgeModManager/raw/rewrite/HedgeModManager/Resources/ModLoader/HE1ML.dll. /// diff --git a/Source/HedgeModManager/Properties/Resources.resx b/Source/HedgeModManager/Properties/Resources.resx index 0d842e5..4bf17b9 100644 --- a/Source/HedgeModManager/Properties/Resources.resx +++ b/Source/HedgeModManager/Properties/Resources.resx @@ -135,4 +135,8 @@ https://raw.githubusercontent.com/hedge-dev/HMMCodes/refs/heads/build/ + + + ..\Resources\dotnet.reg;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + \ No newline at end of file diff --git a/Source/HedgeModManager/Resources/dotnet.reg b/Source/HedgeModManager/Resources/dotnet.reg new file mode 100644 index 0000000..d2fcff2 Binary files /dev/null and b/Source/HedgeModManager/Resources/dotnet.reg differ diff --git a/Source/HedgeModManager/Updates/UpdateSourceGMI.cs b/Source/HedgeModManager/Updates/UpdateSourceGMI.cs index 00c47c6..47be0a4 100644 --- a/Source/HedgeModManager/Updates/UpdateSourceGMI.cs +++ b/Source/HedgeModManager/Updates/UpdateSourceGMI.cs @@ -1,6 +1,7 @@ namespace HedgeModManager.Updates; using Foundation; using HedgeModManager.Text; +using System.Threading; public class UpdateSourceGMI : IUpdateSource where TMod : IMod { @@ -10,6 +11,7 @@ public class UpdateSourceGMI : IUpdateSource where TMod : IMod public string Host => Url.Host; public ModUpdateClient Client { get; set; } public TMod Mod { get; set; } + public SemaphoreSlim Semaphore = new(3); public UpdateSourceGMI(TMod mod, Uri url) { @@ -58,45 +60,84 @@ public async Task PerformUpdateAsync(CancellationToken cancellationToken) string type = line[..line.IndexOf(' ')]; string path = line[(line.IndexOf(' ') + 1)..]; - try + await Semaphore.WaitAsync(cancellationToken); + _ = Task.Run(async () => { - switch (type) + int attemptsLeft = 3; + while (attemptsLeft > 0) { - case "add": - var data = await Client.GetByteArrayAsync(path, cancellationToken); - string filePath = Path.Combine(Mod.Root, path); - Directory.CreateDirectory(Path.GetDirectoryName(filePath)!); - File.WriteAllBytes(filePath, data); + try + { + await ProcessCommand(type, path, cancellationToken); break; - case "mkdir": - Directory.CreateDirectory(Path.Combine(Mod.Root, path)); - break; - case "pause": - if (int.TryParse(line, out int timeout)) + } + catch (Exception ex) + { + attemptsLeft--; + Logger.Error(ex); + if (attemptsLeft == 0) + Logger.Error("Command process failed 3 times, skipping!"); + else { - Logger.Information($"Pausing for {timeout} miliseconds..."); - await Task.Delay(timeout, cancellationToken); + Logger.Error($"Command process failed, retrying ({attemptsLeft} attempts left)"); + await Task.Delay(1000, cancellationToken); } - break; - case "print": - Logger.Information($"[print] {path}"); - break; - case "delete": - if (File.Exists(path)) - File.Delete(path); - else if (Directory.Exists(path)) - Directory.Delete(path, true); - break; - default: - Logger.Error($"Unknown command type: {type}"); + } + if (cancellationToken.IsCancellationRequested) break; } - } - catch (Exception ex) - { - Logger.Error(ex); - Logger.Error($"Failed to process GMI command: {line}"); - } + Semaphore.Release(); + }, cancellationToken); + } + } + + public async Task ProcessCommand(string type, string path, CancellationToken? c) + { + CancellationToken cancellationToken = c ?? CancellationToken.None; + switch (type) + { + case "add": + Logger.Information($"Downloading {path}..."); + var data = await Client.GetByteArrayAsync(path, cancellationToken); + if (cancellationToken.IsCancellationRequested) + return; + string filePath = Path.Combine(Mod.Root, path); + Directory.CreateDirectory(Path.GetDirectoryName(filePath)!); + File.WriteAllBytes(filePath, data); + break; + case "mkdir": + Logger.Debug($"Creating directory {path}..."); + Directory.CreateDirectory(Path.Combine(Mod.Root, path)); + break; + case "pause": + if (int.TryParse(path, out int timeout)) + { + Logger.Information($"Pausing for {timeout} miliseconds..."); + await Task.Delay(timeout, cancellationToken); + } + else + { + Logger.Debug($"Could not parse timeout for \"{path}\""); + } + break; + case "print": + Logger.Information($"[{Mod.Title}] {path}"); + break; + case "delete": + if (File.Exists(path)) + { + Logger.Debug($"Deleting file \"{path}\""); + File.Delete(path); + } + else if (Directory.Exists(path)) + { + Logger.Debug($"Deleting directory \"{path}\""); + Directory.Delete(path, true); + } + break; + default: + Logger.Error($"Unknown command type: {type}"); + break; } } } \ No newline at end of file