VaultSmpInstaller/Views/MainWindow.axaml.cs
CanadianBacon 4663bbca87 1.0.1
Refactor, make safer, add some logging
2024-02-21 07:34:48 +01:00

307 lines
13 KiB
C#

using System;
using System.IO;
using System.IO.Compression;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.ReactiveUI;
using Avalonia.Threading;
using ReactiveUI;
using VaultSmpInstaller.Data;
using VaultSmpInstaller.ViewModels;
namespace VaultSmpInstaller.Views;
public partial class MainWindow : ReactiveWindow<MainWindowViewModel>
{
public MainWindow()
{
InitializeComponent();
this.WhenActivated(action => action(ViewModel!.ShowProfileSelectionDialog.RegisterHandler(DoShowDialogAsync)));
this.WhenActivated(_ => StartDownload());
}
private async Task ProcessInstance(String instancePath)
{
await using FileStream fs = new FileStream(Path.Combine(instancePath, "instance.json"), FileMode.Open);
ViewModel!.LatestInstanceConfig = await JsonSerializer.DeserializeAsync(fs, JsonContext.Default.InstanceConfig);
if (ViewModel!.LatestInstanceConfig != null)
{
ViewModel!.LatestInstanceConfig.InstancePath = instancePath;
foreach (var modProfile in ViewModel!.LatestInstanceConfig.ModProfiles.Values)
if (modProfile.Required || modProfile.DefaultState)
modProfile.IsEnabled = true;
ViewModel!.LatestVersion = ViewModel!.LatestInstanceConfig.Version;
ViewModel!.RaisePropertyChanged(nameof(ViewModel.LatestInstanceConfig));
ViewModel!.RaisePropertyChanged(nameof(ViewModel.DisabledModProfiles));
ViewModel!.RaisePropertyChanged(nameof(ViewModel.EnabledModProfiles));
ViewModel!.RaisePropertyChanged(nameof(ViewModel.DisabledModProfileNames));
ViewModel!.RaisePropertyChanged(nameof(ViewModel.EnabledModProfileNames));
ViewModel!.SelectProfileCommand.Execute(null);
}
else
{
throw new Exception("Failed to download new version");
}
}
private async Task DoShowDialogAsync(InteractionContext<ProfileWindow1ViewModel, ProfileWindow2ViewModel.InstanceInfo?> interaction)
{
var dialog = new ProfileWindow1();
dialog.DataContext = interaction.Input;
var result = await dialog.ShowDialog<ProfileWindow2ViewModel.InstanceInfo?>(this);
interaction.SetOutput(result);
}
private readonly DownloadingWindow _downloadingWindow = new DownloadingWindow();
private void StartDownload()
{
var downloadThread = new Thread((arg) =>
{
if (arg is CancellationTokenSource tokenSource)
{
var archivePath = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".zip");
var extractPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
try
{
HttpClient httpClient = new HttpClient();
using var response = httpClient.GetAsync("https://holenode.cdnbcn.net/LatestProfile.zip", tokenSource.Token).Result;
using var fs = new FileStream(archivePath, FileMode.OpenOrCreate);
response.Content.CopyTo(fs, null, tokenSource.Token);
fs.Close();
ZipFile.ExtractToDirectory(archivePath, extractPath);
tokenSource.Token.ThrowIfCancellationRequested();
File.Delete(archivePath);
Dispatcher.UIThread.Invoke(() => ProcessInstance(extractPath));
}
catch (OperationCanceledException)
{
try
{
File.Delete(archivePath);
Directory.Delete(extractPath, true);
}
catch (Exception)
{
// ignored
}
Dispatcher.UIThread.Invoke(Close);
}
Dispatcher.UIThread.Invoke(() => _downloadingWindow.Close());
}
});
CancellationTokenSource source = new CancellationTokenSource();
downloadThread.Start(source);
_downloadingWindow.DataContext = new ThemeViewModel();
_downloadingWindow.Closed += (_, _) => source.Cancel();
_downloadingWindow.ShowDialog(this);
}
private string _disabledSelected = "";
private void DisabledSelectionChanged(object? sender, SelectionChangedEventArgs e)
{
if (e.AddedItems is [String])
_disabledSelected = (String)e.AddedItems[0]!;
}
private void EnableSelected(object? sender, RoutedEventArgs e)
{
if (ViewModel!.LatestInstanceConfig!.ModProfiles.ContainsKey(_disabledSelected))
{
ViewModel!.LatestInstanceConfig!.ModProfiles[_disabledSelected].IsEnabled = true;
ViewModel!.RaisePropertyChanged(nameof(ViewModel.EnabledModProfiles));
ViewModel!.RaisePropertyChanged(nameof(ViewModel.EnabledModProfileNames));
ViewModel!.RaisePropertyChanged(nameof(ViewModel.DisabledModProfiles));
ViewModel!.RaisePropertyChanged(nameof(ViewModel.DisabledModProfileNames));
_disabledSelected = "";
}
}
private string _enabledSelected = "";
private void EnabledSelectionChanged(object? sender, SelectionChangedEventArgs e)
{
if (e.AddedItems is [String])
_enabledSelected = (String)e.AddedItems[0]!;
}
private void DisableSelected(object? sender, RoutedEventArgs e)
{
if (ViewModel!.LatestInstanceConfig!.ModProfiles.ContainsKey(_enabledSelected) && !ViewModel!.LatestInstanceConfig!.ModProfiles[_enabledSelected].Required)
{
ViewModel!.LatestInstanceConfig!.ModProfiles[_enabledSelected].IsEnabled = false;
ViewModel!.RaisePropertyChanged(nameof(ViewModel.EnabledModProfiles));
ViewModel!.RaisePropertyChanged(nameof(ViewModel.EnabledModProfileNames));
ViewModel!.RaisePropertyChanged(nameof(ViewModel.DisabledModProfiles));
ViewModel!.RaisePropertyChanged(nameof(ViewModel.DisabledModProfileNames));
_enabledSelected = "";
}
}
public ManualResetEvent ContinueInstalling = new ManualResetEvent(false);
private void InstallProfile(object? sender, RoutedEventArgs e)
{
Thread installThread = new Thread(() =>
{
var md5 = MD5.Create();
var viewModel = Dispatcher.UIThread.Invoke(() => ViewModel);
if (viewModel!.InstalledInstanceConfig == null)
{
viewModel.InstalledInstanceConfig = viewModel.LatestInstanceConfig!;
viewModel.InstalledVersion = viewModel.LatestInstanceConfig!.Version;
File.WriteAllText(Path.Combine(viewModel.SelectedInstance!.InstancePath, "installedInstance.json"),
JsonSerializer.Serialize(viewModel.LatestInstanceConfig, JsonContext.Default.InstanceConfig));
foreach (var (path, md5Hash) in viewModel.LatestInstanceConfig.ReplaceFiles)
{
var stream = File.ReadAllBytes(Path.Combine(viewModel.SelectedInstance.MinecraftPath, path));
if (BitConverter.ToString(md5.ComputeHash(stream)).Replace("-","").ToLower() != md5Hash)
{
Dispatcher.UIThread.Invoke(() =>
{
var instanceNotIntactWindow = new InstanceNotIntactWindow(this);
instanceNotIntactWindow.DataContext = new ThemeViewModel();
instanceNotIntactWindow.ShowDialog(this);
});
ContinueInstalling.WaitOne();
break;
}
}
if(!Directory.Exists(Path.Combine(viewModel.SelectedInstance.InstancePath, "originalFiles")))
{
Directory.CreateDirectory(Path.Combine(viewModel.SelectedInstance.InstancePath, "originalFiles"));
}
foreach (var path in viewModel.LatestInstanceConfig.ReplaceFiles.Keys)
{
var pathParts = path.Split('/');
var currentPath = "";
foreach (var part in pathParts[0..^1])
{
currentPath = Path.Combine(currentPath, part);
if(!Directory.Exists(Path.Combine(viewModel.SelectedInstance.InstancePath, "originalFiles", currentPath)))
Directory.CreateDirectory(Path.Combine(viewModel.SelectedInstance.InstancePath, "originalFiles", currentPath));
}
File.Copy(Path.Combine(viewModel.SelectedInstance.MinecraftPath, path), Path.Combine(viewModel.SelectedInstance.InstancePath, "originalFiles", path));
}
}
CopyAll(Path.Combine(viewModel.LatestInstanceConfig!.InstancePath, "config"), viewModel.SelectedInstance!.ConfigPath);
CopyAll(Path.Combine(viewModel.LatestInstanceConfig.InstancePath, "scripts"), viewModel.SelectedInstance.ScriptsPath);
if(!Directory.Exists(Path.Combine(viewModel.SelectedInstance.InstancePath, "originalFiles")))
{
Directory.CreateDirectory(Path.Combine(viewModel.SelectedInstance.InstancePath, "originalFiles"));
}
if(!Directory.Exists(Path.Combine(viewModel.SelectedInstance.InstancePath, "originalFiles", "mods")))
{
Directory.CreateDirectory(Path.Combine(viewModel.SelectedInstance.InstancePath, "originalFiles", "mods"));
}
foreach (var (profileName, modProfile) in viewModel.LatestInstanceConfig.ModProfiles)
{
if (modProfile.IsEnabled)
{
foreach (var (modName, md5Hash) in modProfile.Replaces)
{
if (File.Exists(Path.Combine(viewModel.SelectedInstance.ModsPath, modName)))
{
var stream = File.ReadAllBytes(Path.Combine(viewModel.SelectedInstance.ModsPath, modName));
if (BitConverter.ToString(md5.ComputeHash(stream)).Replace("-","").ToLower() == md5Hash)
{
File.Copy(Path.Combine(viewModel.SelectedInstance.ModsPath, modName), Path.Combine(viewModel.SelectedInstance.InstancePath, "originalFiles", "mods", modName));
File.Delete(Path.Combine(viewModel.SelectedInstance.ModsPath, modName));
}
}
}
foreach (var modName in modProfile.Mods.Keys)
{
File.Copy(Path.Combine(viewModel.LatestInstanceConfig.InstancePath, "mods", modName), Path.Combine(viewModel.SelectedInstance.ModsPath, modName), true);
}
} else if (viewModel.InstalledInstanceConfig.ModProfiles[profileName].IsEnabled)
{
foreach (var modName in modProfile.Mods.Keys)
{
if(File.Exists(Path.Combine(viewModel.SelectedInstance.ModsPath, modName)))
File.Delete(Path.Combine(viewModel.SelectedInstance.ModsPath, modName));
}
}
}
Dispatcher.UIThread.Invoke(() =>
{
var successWindow = new SuccessWindow(this);
successWindow.DataContext = new ThemeViewModel();
successWindow.ShowDialog(this);
});
viewModel.InstalledInstanceConfig = viewModel.LatestInstanceConfig!;
viewModel.InstalledVersion = viewModel.LatestInstanceConfig!.Version;
viewModel.RaisePropertyChanged(nameof(viewModel.InstalledInstanceConfig));
viewModel.RaisePropertyChanged(nameof(viewModel.InstalledModProfiles));
viewModel.RaisePropertyChanged(nameof(viewModel.InstalledModProfileNames));
ContinueInstalling.Reset();
ContinueInstalling.WaitOne();
});
installThread.Start();
}
public static void CopyAll(string source, string target)
{
CopyAll(new DirectoryInfo(source), new DirectoryInfo(target));
}
public static void CopyAll(DirectoryInfo source, DirectoryInfo target)
{
if (source.FullName.ToLower() == target.FullName.ToLower())
{
return;
}
// Check if the target directory exists, if not, create it.
if (Directory.Exists(target.FullName) == false)
{
Directory.CreateDirectory(target.FullName);
}
// Copy each file into it's new directory.
foreach (FileInfo fi in source.GetFiles())
{
fi.CopyTo(Path.Combine(target.ToString(), fi.Name), true);
}
// Copy each subdirectory using recursion.
foreach (DirectoryInfo diSourceSubDir in source.GetDirectories())
{
DirectoryInfo nextTargetSubDir =
target.CreateSubdirectory(diSourceSubDir.Name);
CopyAll(diSourceSubDir, nextTargetSubDir);
}
}
}