Files
minecraft-modpack-updater/ModpackUpdater.Apps.Client.Gui/MainForm.axaml.cs
2025-11-09 13:58:43 +01:00

381 lines
12 KiB
C#

using System.Diagnostics;
using System.Reflection;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Media;
using Avalonia.Platform.Storage;
using Avalonia.Threading;
using ModpackUpdater.Apps.Client.Gui.LangRes;
using ModpackUpdater.Manager;
using MsBox.Avalonia;
using MsBox.Avalonia.Enums;
using Pilz;
using Pilz.Extensions;
using Pilz.Runtime;
using Pilz.SymbolPacks.Sets;
using Pilz.UI.AvaloniaUI.Symbols;
using Pilz.UI.Symbols;
using Pilz.Updating.Client;
namespace ModpackUpdater.Apps.Client.Gui;
public partial class MainForm : Window
{
private readonly UpdateCheckOptions updateOptions = new();
private ModpackInfo modpackInfo = new();
private ModpackConfig updateConfig = new();
private ModpackFeatures? features;
private UpdateCheckResult? lastUpdateCheckResult;
private bool currentUpdating;
private bool loadingData;
private int curOptionsRow = 3;
public MainForm()
{
InitializeComponent();
Title = $"{Title} (v{Assembly.GetExecutingAssembly().GetAppVersion().ToShortString()})";
Closing += MainForm_Closing;
Loaded += MainForm_Loaded;
ButtonSearchProfileFolder.ImageSource = Symbols.Fluent.GetImageSource(SymbolsFluent.opened_folder);
ButtonCheckForUpdates.ImageSource = Symbols.Fluent.GetImageSource(SymbolsFluent.update);
ButtonInstall.ImageSource = Symbols.Fluent.GetImageSource(SymbolsFluent.software_installer);
MenuItemRepair.Icon = Symbols.Fluent.GetImage(SymbolsFluent.wrench, SymbolSize.Small);
ClearStatus();
LoadProfileToUi();
}
#region Features
private void SetStatus(string statusText, IImage? image)
{
TextStatus.Text = statusText;
ImageStatus.Source = image;
}
private void ClearStatus()
{
TextStatus.Text = "-";
ImageStatus.Source = null;
}
private void LoadProfileToUi()
{
loadingData = true;
TextBoxMinecraftProfileFolder.Text = modpackInfo.LocaLPath ?? AppConfig.Instance.LastMinecraftProfilePath ?? TextBoxMinecraftProfileFolder.Text;
TextBoxModpackConfig.Text = modpackInfo.ConfigUrl ?? TextBoxModpackConfig.Text;
TextBoxInstallKey.Text = modpackInfo.ExtrasKey ?? TextBoxInstallKey.Text;
Dispatcher.UIThread.Post(() => loadingData = false, DispatcherPriority.Background);
}
private void LoadOptionsToUi()
{
//foreach (var set in )
//{
// // ...
//}
}
private async void CheckStatusAndUpdate(bool loadProfileToUi)
{
if (CheckStatus(loadProfileToUi))
await ExecuteUpdate(false, false);
}
private bool CheckStatus(bool loadProfileToUi)
{
try
{
modpackInfo = ModpackInfo.TryLoad(TextBoxMinecraftProfileFolder.Text?.Trim());
}
catch
{
// Ignore
}
if (loadProfileToUi)
LoadProfileToUi();
try
{
updateConfig = ModpackConfig.LoadFromUrl(TextBoxModpackConfig.Text);
}
catch
{
// Ignore
}
if (modpackInfo != null)
{
features = new(updateConfig);
modpackInfo.ExtrasKey = TextBoxInstallKey.Text?.Trim();
if (!features.IsInvalid() && !string.IsNullOrWhiteSpace(TextBoxInstallKey.Text) && !AllowExtras())
{
SetStatus(GeneralLangRes.InstallationKeyNotValid, Symbols.Fluent.GetImageSource(SymbolsFluent.warning_shield));
return false;
}
}
LabelInstallKey.IsVisible = TextBoxInstallKey.IsVisible = !string.IsNullOrWhiteSpace(updateConfig.UnleashApiUrl);
if (modpackInfo == null || string.IsNullOrWhiteSpace(TextBoxMinecraftProfileFolder.Text) /*|| modpackInfo.Valid*/)
{
SetStatus(GeneralLangRes.MinecraftProfileFolderSeemsInvalid, Symbols.Fluent.GetImageSource(SymbolsFluent.warning_shield));
ButtonCheckForUpdates.IsEnabled = false;
ButtonInstall.IsEnabled = false;
return false;
}
else if (string.IsNullOrWhiteSpace(TextBoxModpackConfig.Text))
{
SetStatus(GeneralLangRes.ConfigIncompleteOrNotLoaded, Symbols.Fluent.GetImageSource(SymbolsFluent.warning_shield));
ButtonCheckForUpdates.IsEnabled = false;
ButtonInstall.IsEnabled = false;
return false;
}
else if (updateConfig.Maintenance && !updateOptions.IgnoreMaintenance)
{
SetStatus(GeneralLangRes.UpdateServerInMaintenance, Symbols.Fluent.GetImageSource(SymbolsFluent.services));
ButtonCheckForUpdates.IsEnabled = false;
ButtonInstall.IsEnabled = false;
return false;
}
LoadOptionsToUi();
ButtonCheckForUpdates.IsEnabled = true;
ButtonInstall.IsEnabled = true;
return true;
}
private async Task ExecuteUpdate(bool doInstall, bool repair)
{
// Ensure set extras key
modpackInfo.ExtrasKey = TextBoxInstallKey.Text?.Trim();
var updater = new ModpackInstaller(updateConfig, modpackInfo);
updater.InstallProgessUpdated += Update_InstallProgessUpdated;
updater.CheckingProgressUpdated += Updated_CheckingProgresssUpdated;
void error()
{
SetStatus(GeneralLangRes.ErrorOnUpdateCheckOrUpdating, Symbols.Fluent.GetImageSource(SymbolsFluent.close));
currentUpdating = false;
}
void installing()
{
SetStatus(GeneralLangRes.Installing, Symbols.Fluent.GetImageSource(SymbolsFluent.software_installer));
currentUpdating = true;
}
void updatesAvailable()
{
SetStatus(GeneralLangRes.AnUpdateIsAvailable, Symbols.Fluent.GetImageSource(SymbolsFluent.software_installer));
}
void everythingOk()
{
SetStatus(GeneralLangRes.EverythingIsRightAndUpToDate, Symbols.Fluent.GetImageSource(SymbolsFluent.done));
currentUpdating = false;
}
// Check only if not pressed "install", not really needed otherwise.
if (lastUpdateCheckResult is null || !doInstall || repair)
{
SetStatus(GeneralLangRes.CheckingForUpdates, Symbols.Fluent.GetImageSource(SymbolsFluent.update));
// Check for extras once again
updateOptions.IncludeExtras = AllowExtras();
// Force re-install on repair
updateOptions.IgnoreInstalledVersion = repair;
try
{
lastUpdateCheckResult = await updater.Check(updateOptions);
}
catch (Exception)
{
error();
if (Debugger.IsAttached)
throw;
}
}
// Error while update check
if (lastUpdateCheckResult is null || lastUpdateCheckResult.HasError)
{
error();
return;
}
// Load options
// lastUpdateCheckResult.OptionsAvailable...
// lastUpdateCheckResult.OptionsEnabled...
// 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 bool AllowExtras()
{
return features != null && features.IsEnabled(ModpackFeatures.FeatureAllowExtas, new AllowExtrasFeatureContext(modpackInfo));
}
private void Updated_CheckingProgresssUpdated(int toCheck, int processed)
{
SetStatus(Math.Round(processed / (double)toCheck * 100d, 1) + "%", Symbols.Fluent.GetImageSource(SymbolsFluent.update));
}
private void Update_InstallProgessUpdated(UpdateCheckResult result, int processedSyncs)
{
var actionCount = result.Actions.Count;
SetStatus(Math.Round(processedSyncs / (double)actionCount * 100d, 1) + "%", Symbols.Fluent.GetImageSource(SymbolsFluent.software_installer));
}
#endregion
#region Gui
private void MainForm_Closing(object? sender, WindowClosingEventArgs e)
{
AppConfig.Instance.LastMinecraftProfilePath = TextBoxMinecraftProfileFolder.Text?.Trim();
}
private async Task UpdateApp()
{
if (Debugger.IsAttached)
return;
var myAppPath = EnvironmentEx.ProcessPath!;
var updater = new UpdateClient(Program.UpdateUrl, Assembly.GetEntryAssembly()!.GetAppVersion(), AppChannel.Stable)
{
Distro = RuntimeInformationsEx.GetRuntimeIdentifier(),
};
if (await updater.CheckForUpdate() is {} packageToInstall
&& await MessageBoxManager.GetMessageBoxStandard(MsgBoxLangRes.UpdateAvailable_Title, MsgBoxLangRes.UpdateAvailable, ButtonEnum.YesNo, MsBox.Avalonia.Enums.Icon.Info).ShowWindowDialogAsync(this) == ButtonResult.Yes)
{
SetStatus(GeneralLangRes.DownloadProgramUpdate, Symbols.Fluent.GetImageSource(SymbolsFluent.software_installer));
IsEnabled = false;
if (await updater.DownloadPackageAsync(packageToInstall)
&& await updater.InstallPackageAsync(packageToInstall, myAppPath))
{
IsVisible = false;
await Process.Start(myAppPath).WaitForExitAsync();
Environment.Exit(0);
return;
}
IsEnabled = true;
}
}
private async void MainForm_Loaded(object? sender, RoutedEventArgs e)
{
#if !DISABLE_UPDATE
try
{
await UpdateApp();
}
catch (Exception ex)
{
await MessageBoxManager.GetMessageBoxStandard(MsgBoxLangRes.UpdateAvailable_Title, string.Format(MsgBoxLangRes.UpdateAvailable, ex.Message), ButtonEnum.YesNo, MsBox.Avalonia.Enums.Icon.Info).ShowAsync();
IsEnabled = true;
}
#endif
CheckStatusAndUpdate(true);
}
private void TextBoxMinecraftProfileFolder_TextChanged(object? o, TextChangedEventArgs args)
{
if (!loadingData)
CheckStatusAndUpdate(true);
}
private void TextBoxModpackConfig_TextChanged(object? o, RoutedEventArgs args)
{
if (!loadingData)
CheckStatusAndUpdate(false);
}
private void TextBoxInstallKey_TextChanged(object? o, RoutedEventArgs args)
{
if (!loadingData)
CheckStatusAndUpdate(false);
}
private async void ButtonSearchProfileFolder_Click(object? sender, RoutedEventArgs e)
{
var filePaths = await StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
{
Title = GeneralLangRes.SelectMinecraftProfileFolder,
SuggestedStartLocation = await StorageProvider.TryGetFolderFromPathAsync(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)),
AllowMultiple = false,
});
if (filePaths.Count >= 1)
TextBoxMinecraftProfileFolder.Text = filePaths[0].Path.AbsolutePath;
}
private async void ButtonCheckForUpdates_Click(object? sender, RoutedEventArgs e)
{
ClearStatus();
await ExecuteUpdate(false, false);
}
private async void ButtonInstall_Click(object? sender, RoutedEventArgs e)
{
if (currentUpdating)
return;
ClearStatus();
await ExecuteUpdate(true, false);
}
private async void MenuItemRepair_Click(object? sender, RoutedEventArgs e)
{
if (currentUpdating)
return;
ClearStatus();
await ExecuteUpdate(true, true);
}
#endregion
}