some refactoring

This commit is contained in:
Вячеслав 2026-01-01 22:58:33 +05:00
parent 89c3dcb424
commit bbf905495c
8 changed files with 160 additions and 106 deletions

View file

@ -5,4 +5,5 @@ public class InstallationData
public Guid Id { get; set; } = Guid.NewGuid(); public Guid Id { get; set; } = Guid.NewGuid();
public required string Title { get; set; } public required string Title { get; set; }
public required InstallationParts Parts { get; set; } public required InstallationParts Parts { get; set; }
public DateTime InstalledAt { get; set; } = DateTime.Now;
} }

View file

@ -9,18 +9,4 @@ public class InstallationParts
public string? WindowsContentPath { get; init; } public string? WindowsContentPath { get; init; }
public string? WindowsAdminPath { get; init; } public string? WindowsAdminPath { get; init; }
public string? WindowsServerPath { 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)
};
}
} }

View file

@ -9,42 +9,46 @@ public class ZipScrapper
{ {
return new InstallationParts return new InstallationParts
{ {
AndroidContentPath = DoesAndroidContentExists(archive), AndroidContentPath = FindAndroidContent(archive),
OculusClientPath = DoesOculusClientExists(archive), OculusClientPath = FindOculusClient(archive),
PicoClientPath = DoesPicoClientExists(archive), PicoClientPath = FindPicoClient(archive),
AndroidAdminPath = DoesAndroidAdminExists(archive), AndroidAdminPath = FindAndroidAdmin(archive),
WindowsAdminPath = DoesPcAdminExists(archive), WindowsAdminPath = FindPcAdmin(archive),
WindowsContentPath = DoesWindowsContentExists(archive), WindowsContentPath = FindWindowsContent(archive),
WindowsServerPath = DoesServerExists(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<double>? progress = null) /// <summary>
/// Extracts ZIP archive to a unique folder based on installation GUID
/// </summary>
/// <param name="archive">ZIP archive to extract</param>
/// <param name="baseOutputPath">Base storage path</param>
/// <param name="installationGuid">Unique GUID for this installation</param>
/// <param name="progress">Progress reporter</param>
/// <returns>Full path to the extracted folder</returns>
public static string ExtractZip(
ZipArchive archive,
string baseOutputPath,
Guid installationGuid,
IProgress<double>? 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 entries = archive.Entries.Where(e => !string.IsNullOrEmpty(e.Name)).ToList();
var totalEntries = entries.Count; var totalEntries = entries.Count;
var processedEntries = 0; var processedEntries = 0;
foreach (var entry in entries) foreach (var entry in entries)
{ {
var destinationPath = Path.Combine(outputPath, entry.FullName); var destinationPath = Path.Combine(installationFolder, entry.FullName);
var destinationDir = Path.GetDirectoryName(destinationPath); var destinationDir = Path.GetDirectoryName(destinationPath);
if (!string.IsNullOrEmpty(destinationDir)) if (!string.IsNullOrEmpty(destinationDir))
{ {
Directory.CreateDirectory(destinationDir); Directory.CreateDirectory(destinationDir);
Console.WriteLine($"Extracting {entry.FullName} to {destinationPath}");
} }
entry.ExtractToFile(destinationPath, overwrite: true); entry.ExtractToFile(destinationPath, overwrite: true);
@ -53,66 +57,104 @@ public class ZipScrapper
progress?.Report((double)processedEntries / totalEntries * 100); progress?.Report((double)processedEntries / totalEntries * 100);
} }
return outputPath; return installationFolder;
} }
private static string? DoesPicoClientExists(ZipArchive archive) /// <summary>
/// Updates InstallationParts paths to reflect the actual extracted location
/// </summary>
public static InstallationParts UpdatePathsAfterExtraction(
InstallationParts parts,
string extractedFolderPath)
{ {
var entry = archive.Entries.FirstOrDefault(entry => return new InstallationParts
entry.Name.Contains("MetaforcePico") {
&& entry.Name.EndsWith(".apk")); 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");
}
/// <summary>
/// Finds an entry in archive by name and extension
/// </summary>
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; return entry?.FullName;
} }
private static string? DoesOculusClientExists(ZipArchive archive) /// <summary>
/// Finds an executable in archive, excluding crash handlers
/// </summary>
private static string? FindExecutable(ZipArchive archive, string containsName)
{ {
var entry = archive.Entries.FirstOrDefault(entry => var entry = archive.Entries.FirstOrDefault(e =>
entry.Name.Contains("MetaforceOculus") e.FullName.Contains(containsName, StringComparison.OrdinalIgnoreCase) &&
&& entry.Name.EndsWith(".apk")); e.Name.EndsWith(".exe", StringComparison.OrdinalIgnoreCase) &&
return entry?.FullName; !e.Name.Contains("UnityCrashHandler", StringComparison.OrdinalIgnoreCase) &&
} !e.Name.Contains("crashpad_handler", StringComparison.OrdinalIgnoreCase));
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"));
return entry?.FullName; return entry?.FullName;
} }
} }

View file

@ -1,6 +1,7 @@
using Avalonia; using Avalonia;
using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using MetaforceInstaller.UI.Windows;
namespace MetaforceInstaller.UI; namespace MetaforceInstaller.UI;

View file

@ -6,7 +6,7 @@
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:CompileBindings="True" x:CompileBindings="True"
x:DataType="vm:MainWindowViewModel" x:DataType="vm:MainWindowViewModel"
x:Class="MetaforceInstaller.UI.MainWindow" x:Class="MetaforceInstaller.UI.Windows.MainWindow"
Title="MetaforceInstaller"> Title="MetaforceInstaller">
<Window.Resources> <Window.Resources>
@ -49,9 +49,13 @@
<ItemsControl ItemsSource="{Binding Installations}"> <ItemsControl ItemsSource="{Binding Installations}">
<ItemsControl.ItemTemplate> <ItemsControl.ItemTemplate>
<DataTemplate> <DataTemplate>
<Expander Header="{Binding Title}" Margin="20, 0" HorizontalAlignment="Stretch"> <Expander Header="{Binding Title}" Margin="16, 8" HorizontalAlignment="Stretch">
<StackPanel Margin="10"> <StackPanel Orientation="Horizontal">
<Button Name="LaunchServerButton">Launch server</Button>
<Button Name="LaunchPcAdminButton">Launch PC admin</Button>
<Button Name="InstallVrClientButton">Install VR client</Button>
<Button Name="InstallAndroidAdminButton">Install Android admin</Button>
<Button Name="DeleteButton">Delete</Button>
</StackPanel> </StackPanel>
</Expander> </Expander>
</DataTemplate> </DataTemplate>

View file

@ -4,7 +4,7 @@ using MetaforceInstaller.Core.Intefaces;
using MetaforceInstaller.Core.Services; using MetaforceInstaller.Core.Services;
using MetaforceInstaller.UI.ViewModels; using MetaforceInstaller.UI.ViewModels;
namespace MetaforceInstaller.UI; namespace MetaforceInstaller.UI.Windows;
public partial class MainWindow : Window public partial class MainWindow : Window
{ {
@ -16,9 +16,8 @@ public partial class MainWindow : Window
InitializeComponent(); InitializeComponent();
_viewModel = new MainWindowViewModel(); _viewModel = new MainWindowViewModel();
DataContext = _viewModel;
_storageService = new StorageService(); _storageService = new StorageService();
DataContext = _viewModel;
NewInstallationButton.Click += OnNewInstalltionClick; NewInstallationButton.Click += OnNewInstalltionClick;
@ -33,7 +32,7 @@ public partial class MainWindow : Window
public async void OnNewInstalltionClick(object? sender, RoutedEventArgs e) public async void OnNewInstalltionClick(object? sender, RoutedEventArgs e)
{ {
var newInstallationDialog = new NewInstallationDialog(); var newInstallationDialog = new NewInstallationDialog(_storageService);
await newInstallationDialog.ShowDialog<NewInstallationDialog>(this); await newInstallationDialog.ShowDialog<NewInstallationDialog>(this);
LoadInstallations(); LoadInstallations();
} }

View file

@ -3,7 +3,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="200" d:DesignHeight="400" 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" Title="MetaforceInstaller - Add new installation"
SizeToContent="WidthAndHeight" SizeToContent="WidthAndHeight"
CanResize="False"> CanResize="False">

View file

@ -8,19 +8,24 @@ using Avalonia.Controls;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Platform.Storage; using Avalonia.Platform.Storage;
using MetaforceInstaller.Core; using MetaforceInstaller.Core;
using MetaforceInstaller.Core.Intefaces;
using MetaforceInstaller.Core.Models; using MetaforceInstaller.Core.Models;
using MetaforceInstaller.Core.Services; using MetaforceInstaller.Core.Services;
namespace MetaforceInstaller.UI; namespace MetaforceInstaller.UI.Windows;
public partial class NewInstallationDialog : Window public partial class NewInstallationDialog : Window
{ {
private string? _zipPath; private string? _zipPath;
private InstallationParts? _installationParts; private InstallationParts? _installationParts;
private readonly IStorageService _storageService;
public NewInstallationDialog() public NewInstallationDialog(IStorageService storageService)
{ {
InitializeComponent(); InitializeComponent();
_storageService = storageService;
RefreshCheckboxes(); RefreshCheckboxes();
CancelButton.Click += OnCancelClick; CancelButton.Click += OnCancelClick;
ChooseZip.Click += OnChooseZipClick; ChooseZip.Click += OnChooseZipClick;
@ -74,19 +79,35 @@ public partial class NewInstallationDialog : Window
{ {
using var archive = ZipFile.OpenRead(_zipPath); using var archive = ZipFile.OpenRead(_zipPath);
var title = TitleTextBox.Text ?? Path.GetFileNameWithoutExtension(_zipPath); var title = TitleTextBox.Text ?? Path.GetFileNameWithoutExtension(_zipPath);
var installationGuid = Guid.NewGuid();
var progress = new Progress<double>(value => { ProgressBar.Value = value; }); var progress = new Progress<double>(value => { ProgressBar.Value = value; });
string extractedPath = null;
await Task.Run(() => await Task.Run(() =>
ZipScrapper.ExtractZip(archive, Defaults.StoragePath, progress)); {
extractedPath = ZipScrapper.ExtractZip(
archive,
Defaults.StoragePath,
installationGuid,
progress);
});
InstallButton.IsEnabled = false; 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 var installationData = new InstallationData
{ {
Id = installationGuid,
Title = title, Title = title,
Parts = _installationParts.ExtendPaths(Defaults.StoragePath + Path.DirectorySeparatorChar) Parts = updatedParts
}; };
appData.Installations.Add(installationData); appData.Installations.Add(installationData);
storageService.Save(appData); _storageService.Save(appData);
Close(); Close();
} }