From bbf905495c608f3f1bd798b4b4168c807ed9a609 Mon Sep 17 00:00:00 2001 From: shept Date: Thu, 1 Jan 2026 22:58:33 +0500 Subject: [PATCH] some refactoring --- .../Models/InstallationData.cs | 1 + .../Models/InstallationParts.cs | 14 -- .../Services/ZipScrapper.cs | 190 +++++++++++------- MetaforceInstaller.UI/App.axaml.cs | 1 + .../{ => Windows}/MainWindow.axaml | 16 +- .../{ => Windows}/MainWindow.axaml.cs | 7 +- .../{ => Windows}/NewInstallationDialog.axaml | 2 +- .../NewInstallationDialog.axaml.cs | 35 +++- 8 files changed, 160 insertions(+), 106 deletions(-) rename MetaforceInstaller.UI/{ => Windows}/MainWindow.axaml (75%) rename MetaforceInstaller.UI/{ => Windows}/MainWindow.axaml.cs (88%) rename MetaforceInstaller.UI/{ => Windows}/NewInstallationDialog.axaml (94%) rename MetaforceInstaller.UI/{ => Windows}/NewInstallationDialog.axaml.cs (85%) diff --git a/MetaforceInstaller.Core/Models/InstallationData.cs b/MetaforceInstaller.Core/Models/InstallationData.cs index 9eadeec..6f53baa 100644 --- a/MetaforceInstaller.Core/Models/InstallationData.cs +++ b/MetaforceInstaller.Core/Models/InstallationData.cs @@ -5,4 +5,5 @@ public class InstallationData public Guid Id { get; set; } = Guid.NewGuid(); public required string Title { get; set; } public required InstallationParts Parts { get; set; } + public DateTime InstalledAt { get; set; } = DateTime.Now; } \ No newline at end of file diff --git a/MetaforceInstaller.Core/Models/InstallationParts.cs b/MetaforceInstaller.Core/Models/InstallationParts.cs index 807c902..62acb62 100644 --- a/MetaforceInstaller.Core/Models/InstallationParts.cs +++ b/MetaforceInstaller.Core/Models/InstallationParts.cs @@ -9,18 +9,4 @@ public class InstallationParts public string? WindowsContentPath { get; init; } public string? WindowsAdminPath { get; init; } public string? WindowsServerPath { get; init; } - - public InstallationParts ExtendPaths(string prefixPath) - { - return new InstallationParts - { - OculusClientPath = OculusClientPath is null ? null : Path.Combine(prefixPath, OculusClientPath), - PicoClientPath = PicoClientPath is null ? null : Path.Combine(prefixPath, PicoClientPath), - AndroidAdminPath = AndroidAdminPath is null ? null : Path.Combine(prefixPath, AndroidAdminPath), - AndroidContentPath = AndroidContentPath is null ? null : Path.Combine(prefixPath, AndroidContentPath), - WindowsContentPath = WindowsContentPath is null ? null : Path.Combine(prefixPath, WindowsContentPath), - WindowsAdminPath = WindowsAdminPath is null ? null : Path.Combine(prefixPath, WindowsAdminPath), - WindowsServerPath = WindowsServerPath is null ? null : Path.Combine(prefixPath, WindowsServerPath) - }; - } } \ No newline at end of file diff --git a/MetaforceInstaller.Core/Services/ZipScrapper.cs b/MetaforceInstaller.Core/Services/ZipScrapper.cs index 7a4496a..b5c4122 100644 --- a/MetaforceInstaller.Core/Services/ZipScrapper.cs +++ b/MetaforceInstaller.Core/Services/ZipScrapper.cs @@ -9,42 +9,46 @@ public class ZipScrapper { return new InstallationParts { - AndroidContentPath = DoesAndroidContentExists(archive), - OculusClientPath = DoesOculusClientExists(archive), - PicoClientPath = DoesPicoClientExists(archive), - AndroidAdminPath = DoesAndroidAdminExists(archive), - WindowsAdminPath = DoesPcAdminExists(archive), - WindowsContentPath = DoesWindowsContentExists(archive), - WindowsServerPath = DoesServerExists(archive), + AndroidContentPath = FindAndroidContent(archive), + OculusClientPath = FindOculusClient(archive), + PicoClientPath = FindPicoClient(archive), + AndroidAdminPath = FindAndroidAdmin(archive), + WindowsAdminPath = FindPcAdmin(archive), + WindowsContentPath = FindWindowsContent(archive), + WindowsServerPath = FindServer(archive), }; - // Console.WriteLine($"Contents of {archive}:"); - // Console.WriteLine("----------------------------------"); - // - // foreach (ZipArchiveEntry entry in archive.Entries) - // { - // // You can access properties like entry.Name, entry.FullName, entry.Length (uncompressed size), entry.CompressedLength - // Console.WriteLine($"Entry: {entry.FullName}"); - // Console.WriteLine($" Uncompressed Size: {entry.Length} bytes"); - // Console.WriteLine($" Compressed Size: {entry.CompressedLength} bytes"); - // Console.WriteLine("----------------------------------"); - // } } - public static string ExtractZip(ZipArchive archive, string outputPath, IProgress? progress = null) + /// + /// Extracts ZIP archive to a unique folder based on installation GUID + /// + /// ZIP archive to extract + /// Base storage path + /// Unique GUID for this installation + /// Progress reporter + /// Full path to the extracted folder + public static string ExtractZip( + ZipArchive archive, + string baseOutputPath, + Guid installationGuid, + IProgress? progress = null) { + // Create unique folder for this installation + var installationFolder = Path.Combine(baseOutputPath, installationGuid.ToString()); + Directory.CreateDirectory(installationFolder); + var entries = archive.Entries.Where(e => !string.IsNullOrEmpty(e.Name)).ToList(); var totalEntries = entries.Count; var processedEntries = 0; foreach (var entry in entries) { - var destinationPath = Path.Combine(outputPath, entry.FullName); + var destinationPath = Path.Combine(installationFolder, entry.FullName); var destinationDir = Path.GetDirectoryName(destinationPath); if (!string.IsNullOrEmpty(destinationDir)) { Directory.CreateDirectory(destinationDir); - Console.WriteLine($"Extracting {entry.FullName} to {destinationPath}"); } entry.ExtractToFile(destinationPath, overwrite: true); @@ -53,66 +57,104 @@ public class ZipScrapper progress?.Report((double)processedEntries / totalEntries * 100); } - return outputPath; + return installationFolder; } - private static string? DoesPicoClientExists(ZipArchive archive) + /// + /// Updates InstallationParts paths to reflect the actual extracted location + /// + public static InstallationParts UpdatePathsAfterExtraction( + InstallationParts parts, + string extractedFolderPath) { - var entry = archive.Entries.FirstOrDefault(entry => - entry.Name.Contains("MetaforcePico") - && entry.Name.EndsWith(".apk")); + return new InstallationParts + { + AndroidContentPath = UpdatePath(parts.AndroidContentPath, extractedFolderPath), + OculusClientPath = UpdatePath(parts.OculusClientPath, extractedFolderPath), + PicoClientPath = UpdatePath(parts.PicoClientPath, extractedFolderPath), + AndroidAdminPath = UpdatePath(parts.AndroidAdminPath, extractedFolderPath), + WindowsAdminPath = UpdatePath(parts.WindowsAdminPath, extractedFolderPath), + WindowsContentPath = UpdatePath(parts.WindowsContentPath, extractedFolderPath), + WindowsServerPath = UpdatePath(parts.WindowsServerPath, extractedFolderPath), + }; + } + + private static string? UpdatePath(string? relativePath, string basePath) + { + if (string.IsNullOrEmpty(relativePath)) + return null; + + return Path.Combine(basePath, relativePath); + } + + private static string? FindPicoClient(ZipArchive archive) + { + return FindEntry(archive, + name: "MetaforcePico", + extension: ".apk"); + } + + private static string? FindOculusClient(ZipArchive archive) + { + return FindEntry(archive, + name: "MetaforceOculus", + extension: ".apk"); + } + + private static string? FindAndroidAdmin(ZipArchive archive) + { + return FindEntry(archive, + name: "MetaforceAdmin", + extension: ".apk"); + } + + private static string? FindAndroidContent(ZipArchive archive) + { + return FindEntry(archive, + name: "Content_Android", + extension: ".zip"); + } + + private static string? FindWindowsContent(ZipArchive archive) + { + return FindEntry(archive, + name: "Content_StandaloneWindows", + extension: ".zip"); + } + + private static string? FindPcAdmin(ZipArchive archive) + { + return FindExecutable(archive, "MetaforceAdminPC"); + } + + private static string? FindServer(ZipArchive archive) + { + return FindExecutable(archive, "MetaforceServer"); + } + + /// + /// Finds an entry in archive by name and extension + /// + private static string? FindEntry(ZipArchive archive, string name, string extension) + { + var entry = archive.Entries.FirstOrDefault(e => + e.Name.Contains(name, StringComparison.OrdinalIgnoreCase) && + e.Name.EndsWith(extension, StringComparison.OrdinalIgnoreCase)); + return entry?.FullName; } - private static string? DoesOculusClientExists(ZipArchive archive) + /// + /// Finds an executable in archive, excluding crash handlers + /// + private static string? FindExecutable(ZipArchive archive, string containsName) { - var entry = archive.Entries.FirstOrDefault(entry => - entry.Name.Contains("MetaforceOculus") - && entry.Name.EndsWith(".apk")); - return entry?.FullName; - } - - private static string? DoesAndroidAdminExists(ZipArchive archive) - { - var entry = archive.Entries.FirstOrDefault(entry => - entry.Name.Contains("MetaforceAdmin") - && entry.Name.EndsWith(".apk")); - return entry?.FullName; - } - - private static string? DoesAndroidContentExists(ZipArchive archive) - { - var entry = archive.Entries.FirstOrDefault(entry => - entry.Name.Contains("Content_Android") - && entry.Name.EndsWith(".zip")); - return entry?.FullName; - } - - private static string? DoesWindowsContentExists(ZipArchive archive) - { - var entry = archive.Entries.FirstOrDefault(entry => - entry.Name.Contains("Content_StandaloneWindows") - && entry.Name.EndsWith(".zip")); - return entry?.FullName; - } - - private static string? DoesPcAdminExists(ZipArchive archive) - { - var entry = archive.Entries.FirstOrDefault(entry => - entry.FullName.Contains("MetaforceAdminPC") && - entry.Name.EndsWith(".exe") - && !entry.Name.Contains("UnityCrashHandler") && - !entry.Name.Contains("crashpad_handler")); - return entry?.FullName; - } - - private static string? DoesServerExists(ZipArchive archive) - { - var entry = archive.Entries.FirstOrDefault(entry => - entry.FullName.Contains("MetaforceServer") && - entry.Name.EndsWith(".exe") - && !entry.Name.Contains("UnityCrashHandler") && - !entry.Name.Contains("crashpad_handler")); + var entry = archive.Entries.FirstOrDefault(e => + e.FullName.Contains(containsName, StringComparison.OrdinalIgnoreCase) && + e.Name.EndsWith(".exe", StringComparison.OrdinalIgnoreCase) && + !e.Name.Contains("UnityCrashHandler", StringComparison.OrdinalIgnoreCase) && + !e.Name.Contains("crashpad_handler", StringComparison.OrdinalIgnoreCase)); + return entry?.FullName; } } \ No newline at end of file diff --git a/MetaforceInstaller.UI/App.axaml.cs b/MetaforceInstaller.UI/App.axaml.cs index 16212ac..fc9da21 100644 --- a/MetaforceInstaller.UI/App.axaml.cs +++ b/MetaforceInstaller.UI/App.axaml.cs @@ -1,6 +1,7 @@ using Avalonia; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Markup.Xaml; +using MetaforceInstaller.UI.Windows; namespace MetaforceInstaller.UI; diff --git a/MetaforceInstaller.UI/MainWindow.axaml b/MetaforceInstaller.UI/Windows/MainWindow.axaml similarity index 75% rename from MetaforceInstaller.UI/MainWindow.axaml rename to MetaforceInstaller.UI/Windows/MainWindow.axaml index 8b8276a..fa144a4 100644 --- a/MetaforceInstaller.UI/MainWindow.axaml +++ b/MetaforceInstaller.UI/Windows/MainWindow.axaml @@ -6,7 +6,7 @@ mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:CompileBindings="True" x:DataType="vm:MainWindowViewModel" - x:Class="MetaforceInstaller.UI.MainWindow" + x:Class="MetaforceInstaller.UI.Windows.MainWindow" Title="MetaforceInstaller"> @@ -22,9 +22,9 @@ - + - + @@ -49,9 +49,13 @@ - - - + + + + + + + diff --git a/MetaforceInstaller.UI/MainWindow.axaml.cs b/MetaforceInstaller.UI/Windows/MainWindow.axaml.cs similarity index 88% rename from MetaforceInstaller.UI/MainWindow.axaml.cs rename to MetaforceInstaller.UI/Windows/MainWindow.axaml.cs index 9e5ac6b..d628bdd 100644 --- a/MetaforceInstaller.UI/MainWindow.axaml.cs +++ b/MetaforceInstaller.UI/Windows/MainWindow.axaml.cs @@ -4,7 +4,7 @@ using MetaforceInstaller.Core.Intefaces; using MetaforceInstaller.Core.Services; using MetaforceInstaller.UI.ViewModels; -namespace MetaforceInstaller.UI; +namespace MetaforceInstaller.UI.Windows; public partial class MainWindow : Window { @@ -16,9 +16,8 @@ public partial class MainWindow : Window InitializeComponent(); _viewModel = new MainWindowViewModel(); - DataContext = _viewModel; - _storageService = new StorageService(); + DataContext = _viewModel; NewInstallationButton.Click += OnNewInstalltionClick; @@ -33,7 +32,7 @@ public partial class MainWindow : Window public async void OnNewInstalltionClick(object? sender, RoutedEventArgs e) { - var newInstallationDialog = new NewInstallationDialog(); + var newInstallationDialog = new NewInstallationDialog(_storageService); await newInstallationDialog.ShowDialog(this); LoadInstallations(); } diff --git a/MetaforceInstaller.UI/NewInstallationDialog.axaml b/MetaforceInstaller.UI/Windows/NewInstallationDialog.axaml similarity index 94% rename from MetaforceInstaller.UI/NewInstallationDialog.axaml rename to MetaforceInstaller.UI/Windows/NewInstallationDialog.axaml index 1208fdd..603e651 100644 --- a/MetaforceInstaller.UI/NewInstallationDialog.axaml +++ b/MetaforceInstaller.UI/Windows/NewInstallationDialog.axaml @@ -3,7 +3,7 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" d:DesignWidth="200" d:DesignHeight="400" - x:Class="MetaforceInstaller.UI.NewInstallationDialog" + x:Class="MetaforceInstaller.UI.Windows.NewInstallationDialog" Title="MetaforceInstaller - Add new installation" SizeToContent="WidthAndHeight" CanResize="False"> diff --git a/MetaforceInstaller.UI/NewInstallationDialog.axaml.cs b/MetaforceInstaller.UI/Windows/NewInstallationDialog.axaml.cs similarity index 85% rename from MetaforceInstaller.UI/NewInstallationDialog.axaml.cs rename to MetaforceInstaller.UI/Windows/NewInstallationDialog.axaml.cs index e592b8c..f3f39a8 100644 --- a/MetaforceInstaller.UI/NewInstallationDialog.axaml.cs +++ b/MetaforceInstaller.UI/Windows/NewInstallationDialog.axaml.cs @@ -8,19 +8,24 @@ using Avalonia.Controls; using Avalonia.Interactivity; using Avalonia.Platform.Storage; using MetaforceInstaller.Core; +using MetaforceInstaller.Core.Intefaces; using MetaforceInstaller.Core.Models; using MetaforceInstaller.Core.Services; -namespace MetaforceInstaller.UI; +namespace MetaforceInstaller.UI.Windows; public partial class NewInstallationDialog : Window { private string? _zipPath; private InstallationParts? _installationParts; + private readonly IStorageService _storageService; - public NewInstallationDialog() + public NewInstallationDialog(IStorageService storageService) { InitializeComponent(); + + _storageService = storageService; + RefreshCheckboxes(); CancelButton.Click += OnCancelClick; ChooseZip.Click += OnChooseZipClick; @@ -74,19 +79,35 @@ public partial class NewInstallationDialog : Window { using var archive = ZipFile.OpenRead(_zipPath); var title = TitleTextBox.Text ?? Path.GetFileNameWithoutExtension(_zipPath); + var installationGuid = Guid.NewGuid(); + var progress = new Progress(value => { ProgressBar.Value = value; }); + + string extractedPath = null; await Task.Run(() => - ZipScrapper.ExtractZip(archive, Defaults.StoragePath, progress)); + { + extractedPath = ZipScrapper.ExtractZip( + archive, + Defaults.StoragePath, + installationGuid, + progress); + }); + InstallButton.IsEnabled = false; - var storageService = new StorageService(); - var appData = storageService.Load(); + + var appData = _storageService.Load(); + + var updatedParts = ZipScrapper.UpdatePathsAfterExtraction(_installationParts, extractedPath); + var installationData = new InstallationData { + Id = installationGuid, Title = title, - Parts = _installationParts.ExtendPaths(Defaults.StoragePath + Path.DirectorySeparatorChar) + Parts = updatedParts }; + appData.Installations.Add(installationData); - storageService.Save(appData); + _storageService.Save(appData); Close(); }