using System.Collections.ObjectModel; using System.Diagnostics; using System.Windows.Input; using Avalonia.Controls; using Avalonia.Media; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using ModpackUpdater.Apps.Client.Gui.LangRes; using ModpackUpdater.Manager; using Pilz.Extensions.Collections; namespace ModpackUpdater.Apps.Client.Gui; public partial class MainViewModel : ObservableObject { public class EmptyRecentFilesItem; public class RecentFilesItem(string path) { public string Path => path; public string Display => path.Length > 50 ? $"...{path[^50..]}" : path; } private readonly UpdateCheckOptions updateOptions = new(); private ModpackInfo modpackInfo = new(); private ModpackConfig updateConfig = new(); private UpdateCheckResult? lastUpdateCheckResult; [ObservableProperty] private string? minecraftProfileFolder; [ObservableProperty] private string? modpackConfigUrl; [ObservableProperty] private string? installKey; [ObservableProperty] private string statusText = "-"; [ObservableProperty] private IImage? statusImage; [ObservableProperty] private bool hasInitialized; [ObservableProperty] private bool isUpdating; [ObservableProperty] private bool loadingData; [ObservableProperty] private bool canUpdate; [ObservableProperty] private bool canUseExtrasKey; public ObservableCollection RecentMinecraftProfilePathItems { get; } = []; public ICommand CheckForUpdatesCommand { get; } public ICommand InstallCommand { get; } public ICommand RepairCommand { get; } public MainViewModel() { CheckForUpdatesCommand = new AsyncRelayCommand(async () => await CheckStatusAndUpdate(false)); InstallCommand = new AsyncRelayCommand(async () => await ExecuteUpdate(true, false)); RepairCommand = new AsyncRelayCommand(async () => await ExecuteUpdate(true, true)); } public async Task CheckForUpdates(Window parent) { var updates = new AppUpdates("client", parent); updates.OnDownloadProgramUpdate += (_, _) => SetStatus(GeneralLangRes.DownloadProgramUpdate, AppGlobals.Symbols.GetImageSource(AppSymbols.software_installer)); await updates.UpdateApp(); } public async Task Initialize() { ClearStatus(); LoadProfileToUi(); LoadRecentFilesToUi(); HasInitialized = true; await CheckStatusAndUpdate(true); } partial void OnMinecraftProfileFolderChanged(string? value) { if (!LoadingData) _ = CheckStatusAndUpdate(true); } partial void OnModpackConfigUrlChanged(string? value) { if (!LoadingData) _ = CheckStatusAndUpdate(false); } partial void OnInstallKeyChanged(string? value) { if (!LoadingData) _ = CheckStatusAndUpdate(false); } public void SetStatus(string statusText, IImage? image) { StatusText = statusText; StatusImage = image; } public void ClearStatus() { StatusText = "-"; StatusImage = null; } private bool AllowExtras() { return !string.IsNullOrWhiteSpace(modpackInfo.ExtrasKey) && updateConfig.ExtrasKeys.Contains(modpackInfo.ExtrasKey); } public void LoadProfileToUi() { LoadingData = true; MinecraftProfileFolder = modpackInfo.LocalPath ?? AppConfig.Instance.RecentMinecraftProfilePaths.FirstOrDefault() ?? MinecraftProfileFolder; ModpackConfigUrl = modpackInfo.ConfigUrl ?? ModpackConfigUrl; InstallKey = modpackInfo.ExtrasKey ?? InstallKey; LoadingData = false; } private void LoadOptionsToUi() { //foreach (var set in ) //{ // // ... //} } public void LoadRecentFilesToUi() { RecentMinecraftProfilePathItems.Clear(); if (AppConfig.Instance.RecentMinecraftProfilePaths.Count == 0) { RecentMinecraftProfilePathItems.Add(new EmptyRecentFilesItem()); return; } AppConfig.Instance.RecentMinecraftProfilePaths.ForEach(path => { if (Directory.Exists(path)) RecentMinecraftProfilePathItems.Add(new RecentFilesItem(path)); }); } private void StoreRecentMinecraftProfilePath(string? path) { if (string.IsNullOrWhiteSpace(path)) return; AppConfig.Instance.RecentMinecraftProfilePaths.RemoveAll(n => n == path); AppConfig.Instance.RecentMinecraftProfilePaths.Insert(0, path); AppConfig.Instance.RecentMinecraftProfilePaths.Skip(10).ForEach(n => AppConfig.Instance.RecentMinecraftProfilePaths.Remove(n)); } [RelayCommand] private void OpenRecentPath(string path) { if (!string.IsNullOrWhiteSpace(path)) MinecraftProfileFolder = path; } public async Task CheckStatusAndUpdate(bool loadProfileToUi) { if (!CheckStatus(loadProfileToUi)) return; await ExecuteUpdate(false, false); StoreRecentMinecraftProfilePath(modpackInfo.LocalPath); LoadRecentFilesToUi(); } private bool CheckStatus(bool loadProfileToUi) { ClearStatus(); try { modpackInfo = ModpackInfo.TryLoad(MinecraftProfileFolder?.Trim()); } catch { // Ignore } if (loadProfileToUi) LoadProfileToUi(); try { updateConfig = ModpackConfig.LoadFromUrl(ModpackConfigUrl); } catch { // Ignore } modpackInfo.ExtrasKey = InstallKey?.Trim(); if (!string.IsNullOrWhiteSpace(modpackInfo.ExtrasKey) && !AllowExtras()) { SetStatus(GeneralLangRes.InstallationKeyNotValid, AppGlobals.Symbols.GetImageSource(AppSymbols.general_warning_sign)); return false; } CanUseExtrasKey = updateConfig.ExtrasKeys.Count > 0; if (string.IsNullOrWhiteSpace(MinecraftProfileFolder) /*|| modpackInfo.Valid*/) { SetStatus(GeneralLangRes.MinecraftProfileFolderSeemsInvalid, AppGlobals.Symbols.GetImageSource(AppSymbols.general_warning_sign)); CanUpdate = false; return false; } else if (string.IsNullOrWhiteSpace(ModpackConfigUrl)) { SetStatus(GeneralLangRes.ConfigIncompleteOrNotLoaded, AppGlobals.Symbols.GetImageSource(AppSymbols.general_warning_sign)); CanUpdate = false; return false; } else if (updateConfig.Maintenance && !updateOptions.IgnoreMaintenance) { SetStatus(GeneralLangRes.UpdateServerInMaintenance, AppGlobals.Symbols.GetImageSource(AppSymbols.services)); CanUpdate = false; return false; } LoadOptionsToUi(); CanUpdate = true; return true; } private async Task ExecuteUpdate(bool doInstall, bool repair) { ClearStatus(); // Ensure set extras key modpackInfo.ExtrasKey = InstallKey?.Trim(); var updater = new ModpackInstaller(updateConfig, modpackInfo); updater.InstallProgessUpdated += Updater_InstallProgressUpdated; updater.CheckingProgressUpdated += Updater_CheckingProgressUpdated; void error() { SetStatus(GeneralLangRes.ErrorOnUpdateCheckOrUpdating, AppGlobals.Symbols.GetImageSource(AppSymbols.close)); IsUpdating = false; } void installing() { SetStatus(GeneralLangRes.Installing, AppGlobals.Symbols.GetImageSource(AppSymbols.software_installer)); IsUpdating = true; } void updatesAvailable() { SetStatus(GeneralLangRes.AnUpdateIsAvailable, AppGlobals.Symbols.GetImageSource(AppSymbols.software_installer)); IsUpdating = false; } void everythingOk() { SetStatus(GeneralLangRes.EverythingIsRightAndUpToDate, AppGlobals.Symbols.GetImageSource(AppSymbols.done)); IsUpdating = false; } // Check only if not pressed "install", not really needed otherwise. if (lastUpdateCheckResult is null || !doInstall || repair) { SetStatus(GeneralLangRes.CheckingForUpdates, AppGlobals.Symbols.GetImageSource(AppSymbols.update_done)); // Check for extras once again updateOptions.IncludeExtras = AllowExtras(); // Force re-install on repair updateOptions.IgnoreInstalledVersion = repair; try { lastUpdateCheckResult = await updater.Check(updateOptions); } catch { 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(); IsUpdating = 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 { // Error error(); if (Debugger.IsAttached) throw; } } private void Updater_CheckingProgressUpdated(int toCheck, int processed) { SetStatus(Math.Round(processed / (double)toCheck * 100d, 1) + "%", AppGlobals.Symbols.GetImageSource(AppSymbols.update_done)); } private void Updater_InstallProgressUpdated(UpdateCheckResult result, int processedSyncs) { var actionCount = result.Actions.Count; SetStatus(Math.Round(processedSyncs / (double)actionCount * 100d, 1) + "%", AppGlobals.Symbols.GetImageSource(AppSymbols.software_installer)); } }