using Gtk; using ModpackUpdater.Manager; using ModpackUpdater.My.Resources; using Pilz.Extensions; using Pilz.UI.Gtk.Dialogs; using Pilz.UI.Symbols; using System.Diagnostics; using System.Reflection; using System.Security.Cryptography; using UI = Gtk.Builder.ObjectAttribute; namespace ModpackUpdater.Apps.Client; internal class MainWindow : Window { [UI] private readonly Entry textBox_MinecraftFolder; [UI] private readonly Entry textBox_ModpackUrl; [UI] private readonly Entry textBox_InstallationKey; [UI] private readonly Button button_SearchMinecraftFolder; [UI] private readonly Button button_Install; [UI] private readonly Button button_CheckForUpdates; [UI] private readonly ImageMenuItem menuItem_Repair; [UI] private readonly Label label_Status; [UI] private readonly Image image_Status; [UI] private readonly Image image_Repair; private readonly UpdateCheckOptionsAdv updateOptions; private ModpackInfo modpackInfo = new(); private ModpackConfig updateConfig = new(); private ModpackFeatures features; private UpdateCheckResult lastUpdateCheckResult; private bool currentUpdating; private bool loadingData; public MainWindow(UpdateCheckOptionsAdv updateOptions) : this(new Builder("MainWindow.glade"), updateOptions) { } private MainWindow(Builder builder, UpdateCheckOptionsAdv updateOptions) : base(builder.GetRawOwnedObject("MainWindow")) { this.updateOptions = updateOptions; builder.Autoconnect(this); Title = $"{Title} (v{Assembly.GetExecutingAssembly().GetAppVersion()})"; button_CheckForUpdates.Image = AppGlobals.Symbols.GetImage(AppSymbols.update_done, SymbolSize.Small); button_SearchMinecraftFolder.Image = AppGlobals.Symbols.GetImage(AppSymbols.opened_folder, SymbolSize.Small); button_Install.Image = AppGlobals.Symbols.GetImage(AppSymbols.software_installer, SymbolSize.Small); image_Repair.Pixbuf = AppGlobals.Symbols.GetPixbuf(AppSymbols.wrench, SymbolSize.Small); LoadProfileToUi(); DeleteEvent += MainWindow_DeleteEvent; Shown += MainWindow_Shown; textBox_MinecraftFolder.Changed += TextBox_MinecraftFolder_TextInserted; textBox_ModpackUrl.Changed += TextBox_ModpackUrl_TextInserted; textBox_InstallationKey.Changed += TextBox_InstallationKey_TextInserted; button_SearchMinecraftFolder.Clicked += Button_SearchMinecraftFolder_Clicked; button_Install.Clicked += Button_Install_Clicked; button_CheckForUpdates.Clicked += Button_CheckForUpdates_Clicked; menuItem_Repair.Activated += MenuItem_Repair_Activated; } #region Gui private void MainWindow_DeleteEvent(object o, DeleteEventArgs args) { AppConfig.Instance.LastMinecraftProfilePath = textBox_MinecraftFolder.Text.Trim(); Application.Quit(); } private async void MainWindow_Shown(object sender, EventArgs e) { var updater = new AppUpdater(); if (!updateOptions.NoUpdate && await updater.Check() && MessageBox.Show(this, ButtonsType.YesNo, MessageType.Info, LangRes.MsgBox_UpdateAvailable) == ResponseType.Yes) { SetStatus(LangRes.StatusText_InstallingAppUpdate, AppGlobals.Symbols.GetPixbuf(AppSymbols.software_installer, SymbolSize.Small)); Sensitive = false; await updater.Install(); Program.Restart(); return; } CheckStatusAndUpdate(true); LoadProfileToUi(); } private void TextBox_MinecraftFolder_TextInserted(object o, EventArgs args) { if (!loadingData) CheckStatusAndUpdate(false); } private void TextBox_ModpackUrl_TextInserted(object o, EventArgs args) { if (!loadingData) CheckStatusAndUpdate(false); } private void TextBox_InstallationKey_TextInserted(object o, EventArgs args) { if (!loadingData) CheckStatusAndUpdate(false); } private void Button_SearchMinecraftFolder_Clicked(object sender, EventArgs e) { var ofd = new FileChooserDialog("Select Minecraft profile folder", this, FileChooserAction.SelectFolder, "Accept", ResponseType.Ok, "Cancel", ResponseType.Cancel); ofd.Response += SearchMinecraftFolder_Response; ofd.Present(); } private void SearchMinecraftFolder_Response(object o, ResponseArgs args) { if (o is not FileChooserDialog ofd) return; if (args.ResponseId == ResponseType.Ok) textBox_MinecraftFolder.Text = ofd.Filename; ofd.Destroy(); } private async void Button_CheckForUpdates_Clicked(object sender, EventArgs e) { ClearStatus(); await ExecuteUpdate(false, false); } private async void Button_Install_Clicked(object sender, EventArgs e) { if (!currentUpdating) { ClearStatus(); await ExecuteUpdate(true, false); } } private async void MenuItem_Repair_Activated(object sender, EventArgs e) { if (!currentUpdating) { ClearStatus(); await ExecuteUpdate(true, true); } } #endregion #region Features private void SetStatus(string statusText, Gdk.Pixbuf image) { label_Status.Text = statusText; image_Status.Pixbuf = image; } private void ClearStatus() { label_Status.Text = "-"; image_Status.Pixbuf = null; } private void LoadProfileToUi() { loadingData = true; textBox_MinecraftFolder.Text = modpackInfo?.LocaLPath ?? updateOptions.ProfileFolder ?? AppConfig.Instance.LastMinecraftProfilePath ?? textBox_MinecraftFolder.Text; textBox_ModpackUrl.Text = modpackInfo?.ConfigUrl ?? updateOptions.ModpackConfig ?? textBox_ModpackUrl.Text; textBox_InstallationKey.Text = modpackInfo?.ExtrasKey ?? updateOptions.ExtrasKey ?? textBox_InstallationKey.Text; loadingData = false; } private async void CheckStatusAndUpdate(bool loadProfileToUi) { if (CheckStatus(loadProfileToUi)) await ExecuteUpdate(false, false); } private bool CheckStatus(bool loadProfileToUi) { try { modpackInfo = ModpackInfo.TryLoad(textBox_MinecraftFolder.Text.Trim()); } catch { } if (loadProfileToUi) LoadProfileToUi(); try { updateConfig = ModpackConfig.LoadFromUrl(textBox_ModpackUrl.Text); } catch (Exception) { } if (modpackInfo != null) features = new(updateConfig); if (modpackInfo == null || string.IsNullOrWhiteSpace(textBox_MinecraftFolder.Text) /*|| modpackInfo.Valid*/) { SetStatus(LangRes.StatusText_MinecraftProfileWarning, AppGlobals.Symbols.GetPixbuf(AppSymbols.general_warning_sign, SymbolSize.Small)); button_CheckForUpdates.Sensitive = false; button_Install.Sensitive = false; return false; } else if (updateConfig == null || string.IsNullOrWhiteSpace(textBox_ModpackUrl.Text)) { SetStatus(LangRes.StatusText_ConfigIncompleteOrNotLoaded, AppGlobals.Symbols.GetPixbuf(AppSymbols.general_warning_sign, SymbolSize.Small)); button_CheckForUpdates.Sensitive = false; button_Install.Sensitive = false; return false; } else if (updateConfig.Maintenance && !updateOptions.IgnoreMaintenance) { SetStatus(LangRes.StatusText_Maintenance, AppGlobals.Symbols.GetPixbuf(AppSymbols.services, SymbolSize.Small)); button_CheckForUpdates.Sensitive = false; button_Install.Sensitive = false; return false; } button_CheckForUpdates.Sensitive = true; button_Install.Sensitive = true; return true; } private async Task ExecuteUpdate(bool doInstall, bool repair) { // Ensure set extras key modpackInfo.ExtrasKey = textBox_InstallationKey.Text.Trim(); var updater = new ModpackInstaller(updateConfig, modpackInfo) { OverwriteRefTag = Program.Options.RefTag, OverwriteVersion = Program.Options.Version, }; updater.InstallProgessUpdated += Update_InstallProgessUpdated; updater.CheckingProgressUpdated += Updated_CheckingProgresssUpdated; void error() { SetStatus(LangRes.StatusText_ErrorWhileUpdateCheckOrUpdate, AppGlobals.Symbols.GetPixbuf(AppSymbols.close, SymbolSize.Small)); currentUpdating = false; } void installing() { SetStatus(LangRes.StatusText_Installing, AppGlobals.Symbols.GetPixbuf(AppSymbols.software_installer, SymbolSize.Small)); currentUpdating = true; } void updatesAvailable() { SetStatus(LangRes.StatusText_UpdateAvailable, AppGlobals.Symbols.GetPixbuf(AppSymbols.software_installer, SymbolSize.Small)); } void everythingOk() { SetStatus(LangRes.StatusTest_EverythingOk, AppGlobals.Symbols.GetPixbuf(AppSymbols.done, SymbolSize.Small)); currentUpdating = false; } // Check only if not pressed "install", not really needed otherwise. if (lastUpdateCheckResult is null || !doInstall || repair) { SetStatus(LangRes.StatusText_CheckingForUpdates, AppGlobals.Symbols.GetPixbuf(AppSymbols.update_done, SymbolSize.Small)); // Check for extras once again updateOptions.IncludeExtras = features.IsEnabled(ModpackFeatures.FeatureAllowExtas, new AllowExtrasFeatureContext(modpackInfo)); // Force re-install on repair updateOptions.IgnoreInstalledVersion = repair; try { lastUpdateCheckResult = await updater.Check(updateOptions); } catch (Exception) { error(); if (Debugger.IsAttached) throw; } finally { } } // Error while update check if (lastUpdateCheckResult is null || lastUpdateCheckResult.HasError) { error(); return; } // No updates available if (!lastUpdateCheckResult.HasUpdates) { everythingOk(); return; } // Updates available (but don't install) if (!doInstall) { updatesAvailable(); return; } // Install updates installing(); currentUpdating = true; try { // Install if (await updater.Install(lastUpdateCheckResult) == false) { error(); return; } // Success lastUpdateCheckResult = null; // Reset last update check, a new one would be needed now. everythingOk(); } catch (Exception) { // Error error(); if (Debugger.IsAttached) throw; } } private void Updated_CheckingProgresssUpdated(int toCheck, int processed) { SetStatus(Math.Round(processed / (double)toCheck * 100d, 1) + "%", AppGlobals.Symbols.GetPixbuf(AppSymbols.update_done, SymbolSize.Small)); } private void Update_InstallProgessUpdated(UpdateCheckResult result, int processedSyncs) { int actionCount = result.Actions.Count; SetStatus(Math.Round(processedSyncs / (double)actionCount * 100d, 1) + "%", AppGlobals.Symbols.GetPixbuf(AppSymbols.software_installer, SymbolSize.Small)); } #endregion }