Compare commits
11 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ab49284f2f | |||
| 864a07c75c | |||
| bac6bb9ccd | |||
| 21fc25741b | |||
| bbf905495c | |||
| 89c3dcb424 | |||
| 297a784956 | |||
| c6a0b39ded | |||
| 13a076ad79 | |||
| 3dc9c8c279 | |||
| c29658e7e4 |
21 changed files with 1238 additions and 312 deletions
7
MetaforceInstaller.Core/Defaults.cs
Normal file
7
MetaforceInstaller.Core/Defaults.cs
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
namespace MetaforceInstaller.Core;
|
||||||
|
|
||||||
|
public static class Defaults
|
||||||
|
{
|
||||||
|
public static readonly string StoragePath =
|
||||||
|
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + Path.DirectorySeparatorChar + "MetaforceInstaller";
|
||||||
|
}
|
||||||
9
MetaforceInstaller.Core/Intefaces/IStorageService.cs
Normal file
9
MetaforceInstaller.Core/Intefaces/IStorageService.cs
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
using MetaforceInstaller.Core.Models;
|
||||||
|
|
||||||
|
namespace MetaforceInstaller.Core.Intefaces;
|
||||||
|
|
||||||
|
public interface IStorageService
|
||||||
|
{
|
||||||
|
AppData Load();
|
||||||
|
void Save(AppData data);
|
||||||
|
}
|
||||||
|
|
@ -8,8 +8,14 @@
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<EmbeddedResource Include="adb\adb.exe"/>
|
<EmbeddedResource Include="adb\adb.exe"/>
|
||||||
<EmbeddedResource Include="adb\AdbWinApi.dll" />
|
<EmbeddedResource Include="adb\AdbWinApi.dll">
|
||||||
<EmbeddedResource Include="adb\AdbWinUsbApi.dll" />
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
<Link>AdbWinApi.dll</Link>
|
||||||
|
</EmbeddedResource>
|
||||||
|
<EmbeddedResource Include="adb\AdbWinUsbApi.dll">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
<Link>AdbWinUsbApi.dll</Link>
|
||||||
|
</EmbeddedResource>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
||||||
6
MetaforceInstaller.Core/Models/AppData.cs
Normal file
6
MetaforceInstaller.Core/Models/AppData.cs
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
namespace MetaforceInstaller.Core.Models;
|
||||||
|
|
||||||
|
public class AppData
|
||||||
|
{
|
||||||
|
public List<InstallationData> Installations { get; set; } = new();
|
||||||
|
}
|
||||||
9
MetaforceInstaller.Core/Models/InstallationData.cs
Normal file
9
MetaforceInstaller.Core/Models/InstallationData.cs
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
namespace MetaforceInstaller.Core.Models;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
12
MetaforceInstaller.Core/Models/InstallationParts.cs
Normal file
12
MetaforceInstaller.Core/Models/InstallationParts.cs
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
namespace MetaforceInstaller.Core.Models;
|
||||||
|
|
||||||
|
public class InstallationParts
|
||||||
|
{
|
||||||
|
public string? OculusClientPath { get; init; }
|
||||||
|
public string? PicoClientPath { get; init; }
|
||||||
|
public string? AndroidAdminPath { get; init; }
|
||||||
|
public string? AndroidContentPath { get; init; }
|
||||||
|
public string? WindowsContentPath { get; init; }
|
||||||
|
public string? WindowsAdminPath { get; init; }
|
||||||
|
public string? WindowsServerPath { get; init; }
|
||||||
|
}
|
||||||
|
|
@ -15,9 +15,15 @@ public class AdbService : IAdbService
|
||||||
private readonly ILogger<AdbService> _logger;
|
private readonly ILogger<AdbService> _logger;
|
||||||
private readonly AdbClient _adbClient;
|
private readonly AdbClient _adbClient;
|
||||||
private DeviceData _deviceData;
|
private DeviceData _deviceData;
|
||||||
|
private DeviceMonitor? _deviceMonitor;
|
||||||
|
|
||||||
public event EventHandler<ProgressInfo>? ProgressChanged;
|
public event EventHandler<ProgressInfo>? ProgressChanged;
|
||||||
public event EventHandler<string>? StatusChanged;
|
public event EventHandler<string>? StatusChanged;
|
||||||
|
public EventHandler<DeviceDataEventArgs>? DeviceConnected;
|
||||||
|
public EventHandler<DeviceDataEventArgs>? DeviceDisconnected;
|
||||||
|
public EventHandler<DeviceDataEventArgs>? DeviceChanged;
|
||||||
|
|
||||||
|
public bool IsDeviceConnected => _deviceData != null && _deviceData.State == DeviceState.Online;
|
||||||
|
|
||||||
public AdbService(ILogger<AdbService>? logger = null)
|
public AdbService(ILogger<AdbService>? logger = null)
|
||||||
{
|
{
|
||||||
|
|
@ -27,6 +33,46 @@ public class AdbService : IAdbService
|
||||||
var serverStatus = server.StartServer(adbPath, restartServerIfNewer: false);
|
var serverStatus = server.StartServer(adbPath, restartServerIfNewer: false);
|
||||||
_adbClient = new AdbClient();
|
_adbClient = new AdbClient();
|
||||||
RefreshDeviceData();
|
RefreshDeviceData();
|
||||||
|
StartDeviceMonitoring();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StartDeviceMonitoring()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_deviceMonitor = new DeviceMonitor(new AdbSocket(_adbClient.EndPoint));
|
||||||
|
_deviceMonitor.DeviceConnected += OnDeviceConnected;
|
||||||
|
_deviceMonitor.DeviceDisconnected += OnDeviceDisconnected;
|
||||||
|
_deviceMonitor.DeviceChanged += OnDeviceChanged;
|
||||||
|
_deviceMonitor.Start();
|
||||||
|
|
||||||
|
_logger.LogInformation("Device monitoring started");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError($"Failed to start device monitoring: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDeviceConnected(object? sender, DeviceDataEventArgs e)
|
||||||
|
{
|
||||||
|
_logger.LogInformation($"Device conn: {e.Device.Serial}");
|
||||||
|
RefreshDeviceData();
|
||||||
|
DeviceConnected?.Invoke(this, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDeviceDisconnected(object? sender, DeviceDataEventArgs e)
|
||||||
|
{
|
||||||
|
_logger.LogInformation($"Device disconnected: {e.Device.Serial}");
|
||||||
|
RefreshDeviceData();
|
||||||
|
DeviceDisconnected?.Invoke(this, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDeviceChanged(object? sender, DeviceDataEventArgs e)
|
||||||
|
{
|
||||||
|
_logger.LogInformation($"Device changed: {e.Device.Serial}");
|
||||||
|
RefreshDeviceData();
|
||||||
|
DeviceChanged?.Invoke(this, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RefreshDeviceData()
|
public void RefreshDeviceData()
|
||||||
|
|
@ -203,4 +249,15 @@ public class AdbService : IAdbService
|
||||||
{
|
{
|
||||||
return new DeviceInfo(_deviceData.Serial, _deviceData.State.ToString(), _deviceData.Model, _deviceData.Name);
|
return new DeviceInfo(_deviceData.Serial, _deviceData.State.ToString(), _deviceData.Model, _deviceData.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (_deviceMonitor != null)
|
||||||
|
{
|
||||||
|
_deviceMonitor.DeviceConnected -= OnDeviceConnected;
|
||||||
|
_deviceMonitor.DeviceDisconnected -= OnDeviceDisconnected;
|
||||||
|
_deviceMonitor.DeviceChanged -= OnDeviceChanged;
|
||||||
|
_deviceMonitor.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
35
MetaforceInstaller.Core/Services/StorageService.cs
Normal file
35
MetaforceInstaller.Core/Services/StorageService.cs
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
using System.Text.Json;
|
||||||
|
using MetaforceInstaller.Core.Intefaces;
|
||||||
|
using MetaforceInstaller.Core.Models;
|
||||||
|
|
||||||
|
namespace MetaforceInstaller.Core.Services;
|
||||||
|
|
||||||
|
public class StorageService : IStorageService
|
||||||
|
{
|
||||||
|
private readonly string _storagePath;
|
||||||
|
|
||||||
|
public StorageService()
|
||||||
|
{
|
||||||
|
var appDirectory = Defaults.StoragePath;
|
||||||
|
Directory.CreateDirectory(appDirectory);
|
||||||
|
_storagePath = Path.Combine(appDirectory, "installations.json");
|
||||||
|
}
|
||||||
|
|
||||||
|
public AppData Load()
|
||||||
|
{
|
||||||
|
if (!File.Exists(_storagePath))
|
||||||
|
return new AppData();
|
||||||
|
|
||||||
|
var json = File.ReadAllText(_storagePath);
|
||||||
|
return JsonSerializer.Deserialize<AppData>(json) ?? new AppData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Save(AppData data)
|
||||||
|
{
|
||||||
|
var json = JsonSerializer.Serialize(data, new JsonSerializerOptions
|
||||||
|
{
|
||||||
|
WriteIndented = true
|
||||||
|
});
|
||||||
|
File.WriteAllText(_storagePath, json);
|
||||||
|
}
|
||||||
|
}
|
||||||
160
MetaforceInstaller.Core/Services/ZipScrapper.cs
Normal file
160
MetaforceInstaller.Core/Services/ZipScrapper.cs
Normal file
|
|
@ -0,0 +1,160 @@
|
||||||
|
using System.IO.Compression;
|
||||||
|
using MetaforceInstaller.Core.Models;
|
||||||
|
|
||||||
|
namespace MetaforceInstaller.Core.Services;
|
||||||
|
|
||||||
|
public class ZipScrapper
|
||||||
|
{
|
||||||
|
public static InstallationParts PeekFiles(ZipArchive archive)
|
||||||
|
{
|
||||||
|
return new InstallationParts
|
||||||
|
{
|
||||||
|
AndroidContentPath = FindAndroidContent(archive),
|
||||||
|
OculusClientPath = FindOculusClient(archive),
|
||||||
|
PicoClientPath = FindPicoClient(archive),
|
||||||
|
AndroidAdminPath = FindAndroidAdmin(archive),
|
||||||
|
WindowsAdminPath = FindPcAdmin(archive),
|
||||||
|
WindowsContentPath = FindWindowsContent(archive),
|
||||||
|
WindowsServerPath = FindServer(archive),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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 totalEntries = entries.Count;
|
||||||
|
var processedEntries = 0;
|
||||||
|
|
||||||
|
foreach (var entry in entries)
|
||||||
|
{
|
||||||
|
var destinationPath = Path.Combine(installationFolder, entry.FullName);
|
||||||
|
var destinationDir = Path.GetDirectoryName(destinationPath);
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(destinationDir))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(destinationDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
entry.ExtractToFile(destinationPath, overwrite: true);
|
||||||
|
|
||||||
|
processedEntries++;
|
||||||
|
progress?.Report((double)processedEntries / totalEntries * 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
return installationFolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates InstallationParts paths to reflect the actual extracted location
|
||||||
|
/// </summary>
|
||||||
|
public static InstallationParts UpdatePathsAfterExtraction(
|
||||||
|
InstallationParts parts,
|
||||||
|
string extractedFolderPath)
|
||||||
|
{
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Finds an executable in archive, excluding crash handlers
|
||||||
|
/// </summary>
|
||||||
|
private static string? FindExecutable(ZipArchive archive, string containsName)
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
|
||||||
|
|
@ -13,6 +14,7 @@ public partial class App : Application
|
||||||
|
|
||||||
public override void OnFrameworkInitializationCompleted()
|
public override void OnFrameworkInitializationCompleted()
|
||||||
{
|
{
|
||||||
|
Lang.Resources.Culture = System.Globalization.CultureInfo.CurrentCulture;
|
||||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||||
{
|
{
|
||||||
desktop.MainWindow = new MainWindow();
|
desktop.MainWindow = new MainWindow();
|
||||||
|
|
|
||||||
251
MetaforceInstaller.UI/Lang/Resources.Designer.cs
generated
Normal file
251
MetaforceInstaller.UI/Lang/Resources.Designer.cs
generated
Normal file
|
|
@ -0,0 +1,251 @@
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// <auto-generated>
|
||||||
|
// This code was generated by a tool.
|
||||||
|
//
|
||||||
|
// Changes to this file may cause incorrect behavior and will be lost if
|
||||||
|
// the code is regenerated.
|
||||||
|
// </auto-generated>
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace MetaforceInstaller.UI.Lang {
|
||||||
|
using System;
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||||
|
/// </summary>
|
||||||
|
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||||
|
// class via a tool like ResGen or Visual Studio.
|
||||||
|
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||||
|
// with the /str option, or rebuild your VS project.
|
||||||
|
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
|
||||||
|
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||||
|
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||||
|
public class Resources {
|
||||||
|
|
||||||
|
private static global::System.Resources.ResourceManager resourceMan;
|
||||||
|
|
||||||
|
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||||
|
|
||||||
|
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||||
|
internal Resources() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the cached ResourceManager instance used by this class.
|
||||||
|
/// </summary>
|
||||||
|
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||||
|
public static global::System.Resources.ResourceManager ResourceManager {
|
||||||
|
get {
|
||||||
|
if (object.ReferenceEquals(resourceMan, null)) {
|
||||||
|
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("MetaforceInstaller.UI.Lang.Resources", typeof(Resources).Assembly);
|
||||||
|
resourceMan = temp;
|
||||||
|
}
|
||||||
|
return resourceMan;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Overrides the current thread's CurrentUICulture property for all
|
||||||
|
/// resource lookups using this strongly typed resource class.
|
||||||
|
/// </summary>
|
||||||
|
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||||
|
public static global::System.Globalization.CultureInfo Culture {
|
||||||
|
get {
|
||||||
|
return resourceCulture;
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
resourceCulture = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Add new installation.
|
||||||
|
/// </summary>
|
||||||
|
public static string AddInstallation {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("AddInstallation", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Cancel.
|
||||||
|
/// </summary>
|
||||||
|
public static string CancelButton {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("CancelButton", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Choose .zip.
|
||||||
|
/// </summary>
|
||||||
|
public static string ChooseZip {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("ChooseZip", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Connected to.
|
||||||
|
/// </summary>
|
||||||
|
public static string ConnectedTo {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("ConnectedTo", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Delete.
|
||||||
|
/// </summary>
|
||||||
|
public static string Delete {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Delete", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Install admin.
|
||||||
|
/// </summary>
|
||||||
|
public static string InstallAdminCheckbox {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("InstallAdminCheckbox", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Install Android admin.
|
||||||
|
/// </summary>
|
||||||
|
public static string InstallAndroidAdmin {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("InstallAndroidAdmin", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Install.
|
||||||
|
/// </summary>
|
||||||
|
public static string InstallButton {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("InstallButton", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Install server.
|
||||||
|
/// </summary>
|
||||||
|
public static string InstallServerCheckbox {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("InstallServerCheckbox", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Install VR client.
|
||||||
|
/// </summary>
|
||||||
|
public static string InstallVRClient {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("InstallVRClient", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Launch PC admin.
|
||||||
|
/// </summary>
|
||||||
|
public static string LaunchPCAdmin {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("LaunchPCAdmin", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Launch server.
|
||||||
|
/// </summary>
|
||||||
|
public static string LaunchServer {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("LaunchServer", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Name of new installation.
|
||||||
|
/// </summary>
|
||||||
|
public static string NameOfNewInstallation {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("NameOfNewInstallation", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Couldn't find Android content.
|
||||||
|
/// </summary>
|
||||||
|
public static string NoAndroidContentError {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("NoAndroidContentError", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Couldn't find directory with PC admin.
|
||||||
|
/// </summary>
|
||||||
|
public static string NoPCAdminError {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("NoPCAdminError", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Couldn't find directory with server.
|
||||||
|
/// </summary>
|
||||||
|
public static string NoServerError {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("NoServerError", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Not connected.
|
||||||
|
/// </summary>
|
||||||
|
public static string NotConnected {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("NotConnected", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Couldn't find any VR clients.
|
||||||
|
/// </summary>
|
||||||
|
public static string NoVRClientsError {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("NoVRClientsError", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Couldn't find Windows content.
|
||||||
|
/// </summary>
|
||||||
|
public static string NoWindowsContentError {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("NoWindowsContentError", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Save Android admin.
|
||||||
|
/// </summary>
|
||||||
|
public static string SaveAndroidAdminCheckbox {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("SaveAndroidAdminCheckbox", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Save VR client.
|
||||||
|
/// </summary>
|
||||||
|
public static string SaveVRClientCheckbox {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("SaveVRClientCheckbox", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
84
MetaforceInstaller.UI/Lang/Resources.resx
Normal file
84
MetaforceInstaller.UI/Lang/Resources.resx
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
<root>
|
||||||
|
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||||
|
<xsd:element name="root" msdata:IsDataSet="true">
|
||||||
|
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:schema>
|
||||||
|
<resheader name="resmimetype">
|
||||||
|
<value>text/microsoft-resx</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="version">
|
||||||
|
<value>1.3</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="reader">
|
||||||
|
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="writer">
|
||||||
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<data name="LaunchServer" xml:space="preserve">
|
||||||
|
<value>Launch server</value>
|
||||||
|
</data>
|
||||||
|
<data name="LaunchPCAdmin" xml:space="preserve">
|
||||||
|
<value>Launch PC admin</value>
|
||||||
|
</data>
|
||||||
|
<data name="InstallVRClient" xml:space="preserve">
|
||||||
|
<value>Install VR client</value>
|
||||||
|
</data>
|
||||||
|
<data name="InstallAndroidAdmin" xml:space="preserve">
|
||||||
|
<value>Install Android admin</value>
|
||||||
|
</data>
|
||||||
|
<data name="Delete" xml:space="preserve">
|
||||||
|
<value>Delete</value>
|
||||||
|
</data>
|
||||||
|
<data name="AddInstallation" xml:space="preserve">
|
||||||
|
<value>Add new installation</value>
|
||||||
|
</data>
|
||||||
|
<data name="NotConnected" xml:space="preserve">
|
||||||
|
<value>Not connected</value>
|
||||||
|
</data>
|
||||||
|
<data name="ConnectedTo" xml:space="preserve">
|
||||||
|
<value>Connected to</value>
|
||||||
|
</data>
|
||||||
|
<data name="NameOfNewInstallation" xml:space="preserve">
|
||||||
|
<value>Name of new installation</value>
|
||||||
|
</data>
|
||||||
|
<data name="ChooseZip" xml:space="preserve">
|
||||||
|
<value>Choose .zip</value>
|
||||||
|
</data>
|
||||||
|
<data name="InstallServerCheckbox" xml:space="preserve">
|
||||||
|
<value>Install server</value>
|
||||||
|
</data>
|
||||||
|
<data name="InstallAdminCheckbox" xml:space="preserve">
|
||||||
|
<value>Install admin</value>
|
||||||
|
</data>
|
||||||
|
<data name="SaveAndroidAdminCheckbox" xml:space="preserve">
|
||||||
|
<value>Save Android admin</value>
|
||||||
|
</data>
|
||||||
|
<data name="SaveVRClientCheckbox" xml:space="preserve">
|
||||||
|
<value>Save VR client</value>
|
||||||
|
</data>
|
||||||
|
<data name="InstallButton" xml:space="preserve">
|
||||||
|
<value>Install</value>
|
||||||
|
</data>
|
||||||
|
<data name="CancelButton" xml:space="preserve">
|
||||||
|
<value>Cancel</value>
|
||||||
|
</data>
|
||||||
|
<data name="NoServerError" xml:space="preserve">
|
||||||
|
<value>Couldn't find directory with server</value>
|
||||||
|
</data>
|
||||||
|
<data name="NoPCAdminError" xml:space="preserve">
|
||||||
|
<value>Couldn't find directory with PC admin</value>
|
||||||
|
</data>
|
||||||
|
<data name="NoWindowsContentError" xml:space="preserve">
|
||||||
|
<value>Couldn't find Windows content</value>
|
||||||
|
</data>
|
||||||
|
<data name="NoAndroidContentError" xml:space="preserve">
|
||||||
|
<value>Couldn't find Android content</value>
|
||||||
|
</data>
|
||||||
|
<data name="NoVRClientsError" xml:space="preserve">
|
||||||
|
<value>Couldn't find any VR clients</value>
|
||||||
|
</data>
|
||||||
|
</root>
|
||||||
77
MetaforceInstaller.UI/Lang/Resources.ru.resx
Normal file
77
MetaforceInstaller.UI/Lang/Resources.ru.resx
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
<root>
|
||||||
|
<resheader name="resmimetype">
|
||||||
|
<value>text/microsoft-resx</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="version">
|
||||||
|
<value>1.3</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="reader">
|
||||||
|
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="writer">
|
||||||
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<data name="LaunchServer" xml:space="preserve">
|
||||||
|
<value>Запустить сервер</value>
|
||||||
|
</data>
|
||||||
|
<data name="LaunchPCAdmin" xml:space="preserve">
|
||||||
|
<value>Запустить админку</value>
|
||||||
|
</data>
|
||||||
|
<data name="Delete" xml:space="preserve">
|
||||||
|
<value>Удалить</value>
|
||||||
|
</data>
|
||||||
|
<data name="InstallAndroidAdmin" xml:space="preserve">
|
||||||
|
<value>Установить андроид-админку</value>
|
||||||
|
</data>
|
||||||
|
<data name="InstallVRClient" xml:space="preserve">
|
||||||
|
<value>Установить на шлем</value>
|
||||||
|
</data>
|
||||||
|
<data name="AddInstallation" xml:space="preserve">
|
||||||
|
<value>Добавить версию</value>
|
||||||
|
</data>
|
||||||
|
<data name="ConnectedTo" xml:space="preserve">
|
||||||
|
<value>Подключено:</value>
|
||||||
|
</data>
|
||||||
|
<data name="NotConnected" xml:space="preserve">
|
||||||
|
<value>Не подключено</value>
|
||||||
|
</data>
|
||||||
|
<data name="ChooseZip" xml:space="preserve">
|
||||||
|
<value>Выбрать .zip</value>
|
||||||
|
</data>
|
||||||
|
<data name="CancelButton" xml:space="preserve">
|
||||||
|
<value>Отмена</value>
|
||||||
|
</data>
|
||||||
|
<data name="InstallAdminCheckbox" xml:space="preserve">
|
||||||
|
<value>Установить админку</value>
|
||||||
|
</data>
|
||||||
|
<data name="InstallButton" xml:space="preserve">
|
||||||
|
<value>Установить</value>
|
||||||
|
</data>
|
||||||
|
<data name="InstallServerCheckbox" xml:space="preserve">
|
||||||
|
<value>Установить сервер</value>
|
||||||
|
</data>
|
||||||
|
<data name="NameOfNewInstallation" xml:space="preserve">
|
||||||
|
<value>Название версии</value>
|
||||||
|
</data>
|
||||||
|
<data name="SaveAndroidAdminCheckbox" xml:space="preserve">
|
||||||
|
<value>Сохранить андроид-админку</value>
|
||||||
|
</data>
|
||||||
|
<data name="SaveVRClientCheckbox" xml:space="preserve">
|
||||||
|
<value>Сохранить VR-клиенты</value>
|
||||||
|
</data>
|
||||||
|
<data name="NoAndroidContentError" xml:space="preserve">
|
||||||
|
<value>Не удалось обнаружить Андроид контент</value>
|
||||||
|
</data>
|
||||||
|
<data name="NoPCAdminError" xml:space="preserve">
|
||||||
|
<value>Не удалось обнаружить ПК-админку</value>
|
||||||
|
</data>
|
||||||
|
<data name="NoServerError" xml:space="preserve">
|
||||||
|
<value>Не удалось обнаружить сервер</value>
|
||||||
|
</data>
|
||||||
|
<data name="NoVRClientsError" xml:space="preserve">
|
||||||
|
<value>Не удалось обнаружить VR-клиенты</value>
|
||||||
|
</data>
|
||||||
|
<data name="NoWindowsContentError" xml:space="preserve">
|
||||||
|
<value>Не удалось обнаружить Windows контент</value>
|
||||||
|
</data>
|
||||||
|
</root>
|
||||||
|
|
@ -1,70 +0,0 @@
|
||||||
<Window xmlns="https://github.com/avaloniaui"
|
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
|
||||||
x:Class="MetaforceInstaller.UI.MainWindow"
|
|
||||||
Title="MetaforceInstaller">
|
|
||||||
|
|
||||||
<Window.Resources>
|
|
||||||
<ResourceDictionary>
|
|
||||||
<ResourceDictionary.ThemeDictionaries>
|
|
||||||
<ResourceDictionary x:Key="Light">
|
|
||||||
<SvgImage x:Key="Logo" Source="/Images/logo_red.svg"/>
|
|
||||||
</ResourceDictionary>
|
|
||||||
|
|
||||||
<ResourceDictionary x:Key="Dark">
|
|
||||||
<SvgImage x:Key="Logo" Source="/Images/logo_red.svg"/>
|
|
||||||
</ResourceDictionary>
|
|
||||||
</ResourceDictionary.ThemeDictionaries>
|
|
||||||
</ResourceDictionary>
|
|
||||||
</Window.Resources>
|
|
||||||
|
|
||||||
<Grid>
|
|
||||||
<Grid.RowDefinitions>
|
|
||||||
<RowDefinition Height="*" />
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
</Grid.RowDefinitions>
|
|
||||||
|
|
||||||
<!-- Main Content Area -->
|
|
||||||
<Grid Grid.Row="0" Margin="8">
|
|
||||||
<Grid.ColumnDefinitions>
|
|
||||||
<ColumnDefinition Width="3*" /> <!-- 30% -->
|
|
||||||
<ColumnDefinition Width="7*" /> <!-- 70% -->
|
|
||||||
</Grid.ColumnDefinitions>
|
|
||||||
|
|
||||||
<!-- Left Panel - Buttons -->
|
|
||||||
<StackPanel Grid.Column="0" Margin="20" Spacing="15">
|
|
||||||
<Button Name="ChooseApkButton" Content="Choose .apk"
|
|
||||||
HorizontalAlignment="Stretch" />
|
|
||||||
|
|
||||||
<Button Name="ChooseContentButton" Content="Choose .zip"
|
|
||||||
HorizontalAlignment="Stretch" />
|
|
||||||
|
|
||||||
<Button Name="InstallButton" Content="Install"
|
|
||||||
HorizontalAlignment="Stretch" />
|
|
||||||
</StackPanel>
|
|
||||||
|
|
||||||
<Image Grid.Column="0"
|
|
||||||
Margin="20"
|
|
||||||
VerticalAlignment="Bottom"
|
|
||||||
Source="{DynamicResource Logo}"
|
|
||||||
HorizontalAlignment="Center">
|
|
||||||
</Image>
|
|
||||||
|
|
||||||
<!-- Right Panel - Logs -->
|
|
||||||
<TextBox Grid.Column="1" Name="LogsTextBox"
|
|
||||||
IsReadOnly="True"
|
|
||||||
AcceptsReturn="True"
|
|
||||||
TextWrapping="Wrap"
|
|
||||||
FontFamily="Consolas,Courier New,monospace"
|
|
||||||
Focusable="False"
|
|
||||||
Margin="4,0,0,0" />
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
<!-- Progress Bar at Bottom -->
|
|
||||||
<ProgressBar Grid.Row="1" Name="InstallProgressBar"
|
|
||||||
Height="20" Minimum="0" Maximum="100" Value="0"
|
|
||||||
Margin="8" />
|
|
||||||
</Grid>
|
|
||||||
</Window>
|
|
||||||
|
|
@ -1,230 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Avalonia.Controls;
|
|
||||||
using Avalonia.Interactivity;
|
|
||||||
using Avalonia.Platform.Storage;
|
|
||||||
using Avalonia.Threading;
|
|
||||||
using Avalonia.VisualTree;
|
|
||||||
using MetaforceInstaller.Core.Models;
|
|
||||||
using MetaforceInstaller.Core.Services;
|
|
||||||
|
|
||||||
namespace MetaforceInstaller.UI;
|
|
||||||
|
|
||||||
public partial class MainWindow : Window
|
|
||||||
{
|
|
||||||
private string? _apkPath;
|
|
||||||
private string? _zipPath;
|
|
||||||
private AdbService _adbService;
|
|
||||||
|
|
||||||
private const int PROGRESS_LOG_STEP = 10;
|
|
||||||
private const int PROGRESS_UPDATE_STEP = 1;
|
|
||||||
|
|
||||||
private int _lastLoggedProgress = -1;
|
|
||||||
private int _lastUpdatedProgress = -1;
|
|
||||||
|
|
||||||
public MainWindow()
|
|
||||||
{
|
|
||||||
InitializeComponent();
|
|
||||||
|
|
||||||
LogMessage("MetaforceInstaller by slavagm");
|
|
||||||
|
|
||||||
_adbService = new AdbService();
|
|
||||||
_adbService.ProgressChanged += OnAdbProgressChanged;
|
|
||||||
_adbService.StatusChanged += OnAdbStatusChanged;
|
|
||||||
|
|
||||||
CheckAndEnableInstallButton();
|
|
||||||
|
|
||||||
ChooseApkButton.Click += OnChooseApkClicked;
|
|
||||||
ChooseContentButton.Click += OnChooseContentClicked;
|
|
||||||
InstallButton.Click += OnInstallClicked;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnAdbProgressChanged(object? sender, ProgressInfo e)
|
|
||||||
{
|
|
||||||
Dispatcher.UIThread.InvokeAsync(() =>
|
|
||||||
{
|
|
||||||
if (e.PercentageComplete != _lastUpdatedProgress &&
|
|
||||||
e.PercentageComplete % PROGRESS_UPDATE_STEP == 0)
|
|
||||||
{
|
|
||||||
InstallProgressBar.Value = e.PercentageComplete;
|
|
||||||
_lastUpdatedProgress = e.PercentageComplete;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e.PercentageComplete != _lastLoggedProgress &&
|
|
||||||
e.PercentageComplete % PROGRESS_LOG_STEP == 0 || e.PercentageComplete == 100)
|
|
||||||
{
|
|
||||||
LogMessage(
|
|
||||||
e.TotalBytes > 0
|
|
||||||
? $"Прогресс: {e.PercentageComplete}% ({FormatBytes(e.BytesTransferred)} / {FormatBytes(e.TotalBytes)})"
|
|
||||||
: $"Прогресс: {e.PercentageComplete}%");
|
|
||||||
|
|
||||||
_lastLoggedProgress = e.PercentageComplete;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnProgressReport(ProgressInfo progressInfo)
|
|
||||||
{
|
|
||||||
Dispatcher.UIThread.InvokeAsync(() =>
|
|
||||||
{
|
|
||||||
if (progressInfo.PercentageComplete != _lastUpdatedProgress &&
|
|
||||||
progressInfo.PercentageComplete % PROGRESS_UPDATE_STEP == 0)
|
|
||||||
{
|
|
||||||
InstallProgressBar.Value = progressInfo.PercentageComplete;
|
|
||||||
_lastUpdatedProgress = progressInfo.PercentageComplete;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (progressInfo.PercentageComplete != _lastLoggedProgress &&
|
|
||||||
(progressInfo.PercentageComplete % PROGRESS_LOG_STEP == 0 || progressInfo.PercentageComplete == 100))
|
|
||||||
{
|
|
||||||
LogMessage(
|
|
||||||
progressInfo.TotalBytes > 0
|
|
||||||
? $"Прогресс: {progressInfo.PercentageComplete}% ({FormatBytes(progressInfo.BytesTransferred)} / {FormatBytes(progressInfo.TotalBytes)})"
|
|
||||||
: $"Прогресс: {progressInfo.PercentageComplete}%");
|
|
||||||
|
|
||||||
_lastLoggedProgress = progressInfo.PercentageComplete;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnAdbStatusChanged(object? sender, string e)
|
|
||||||
{
|
|
||||||
Dispatcher.UIThread.InvokeAsync(() =>
|
|
||||||
{
|
|
||||||
InstallProgressBar.Value = 0;
|
|
||||||
_lastLoggedProgress = -1;
|
|
||||||
_lastUpdatedProgress = -1;
|
|
||||||
LogMessage(e);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private string FormatBytes(long bytes)
|
|
||||||
{
|
|
||||||
string[] suffixes = ["B", "KB", "MB", "GB", "TB"];
|
|
||||||
var counter = 0;
|
|
||||||
double number = bytes;
|
|
||||||
|
|
||||||
while (Math.Round(number / 1024) >= 1)
|
|
||||||
{
|
|
||||||
number /= 1024;
|
|
||||||
counter++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $"{number:N1} {suffixes[counter]}";
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private async void CheckAndEnableInstallButton()
|
|
||||||
{
|
|
||||||
InstallButton.IsEnabled = !string.IsNullOrEmpty(_apkPath) && !string.IsNullOrEmpty(_zipPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async void OnChooseApkClicked(object? sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
var topLevel = GetTopLevel(this);
|
|
||||||
var files = await topLevel!.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
|
|
||||||
{
|
|
||||||
Title = "Выберите APK файл",
|
|
||||||
AllowMultiple = false,
|
|
||||||
FileTypeFilter = new[]
|
|
||||||
{
|
|
||||||
new FilePickerFileType("APK Files")
|
|
||||||
{
|
|
||||||
Patterns = new[] { "*.apk" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (files.Count >= 1)
|
|
||||||
{
|
|
||||||
_apkPath = files[0].Path.LocalPath;
|
|
||||||
LogMessage($"APK выбран: {Path.GetFileName(_apkPath)}");
|
|
||||||
}
|
|
||||||
|
|
||||||
CheckAndEnableInstallButton();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async void OnChooseContentClicked(object? sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
var topLevel = GetTopLevel(this);
|
|
||||||
var files = await topLevel!.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
|
|
||||||
{
|
|
||||||
Title = "Выберите архив с контентом",
|
|
||||||
AllowMultiple = false,
|
|
||||||
FileTypeFilter = new[]
|
|
||||||
{
|
|
||||||
new FilePickerFileType("ZIP Files")
|
|
||||||
{
|
|
||||||
Patterns = new[] { "*.zip" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (files.Count >= 1)
|
|
||||||
{
|
|
||||||
_zipPath = files[0].Path.LocalPath;
|
|
||||||
LogMessage($"Контент выбран: {Path.GetFileName(_zipPath)}");
|
|
||||||
}
|
|
||||||
|
|
||||||
CheckAndEnableInstallButton();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async void OnInstallClicked(object? sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(_apkPath) || string.IsNullOrEmpty(_zipPath))
|
|
||||||
{
|
|
||||||
LogMessage("Ошибка: Выберите APK файл и папку с контентом");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_adbService.RefreshDeviceData();
|
|
||||||
|
|
||||||
InstallButton.IsEnabled = false;
|
|
||||||
InstallProgressBar.Value = 0;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
LogMessage("Начинаем установку...");
|
|
||||||
|
|
||||||
var deviceInfo = _adbService.GetDeviceInfo();
|
|
||||||
LogMessage($"Найдено устройство: {deviceInfo.SerialNumber}");
|
|
||||||
LogMessage($"Состояние: {deviceInfo.State}");
|
|
||||||
LogMessage($"Модель: {deviceInfo.Model} - {deviceInfo.Name}");
|
|
||||||
|
|
||||||
var progress = new Progress<ProgressInfo>(OnProgressReport);
|
|
||||||
|
|
||||||
await _adbService.InstallApkAsync(_apkPath, progress);
|
|
||||||
|
|
||||||
var apkInfo = ApkScrapper.GetApkInfo(_apkPath);
|
|
||||||
LogMessage($"Ставим {apkInfo.PackageName} версии {apkInfo.VersionName}");
|
|
||||||
var zipName = Path.GetFileName(_zipPath);
|
|
||||||
var outputPath =
|
|
||||||
@$"/storage/emulated/0/Android/data/{apkInfo.PackageName}/files/{zipName}";
|
|
||||||
LogMessage($"Начинаем копирование контента в {outputPath}");
|
|
||||||
|
|
||||||
await _adbService.CopyFileAsync(_zipPath, outputPath, progress);
|
|
||||||
|
|
||||||
LogMessage("Установка завершена успешно!");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogMessage($"Ошибка установки: {ex.Message}");
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
InstallButton.IsEnabled = true;
|
|
||||||
InstallProgressBar.Value = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void LogMessage(string message)
|
|
||||||
{
|
|
||||||
var timestamp = DateTime.Now.ToString("HH:mm:ss");
|
|
||||||
LogsTextBox.Text += $"[{timestamp}] {message}\n";
|
|
||||||
|
|
||||||
var scrollViewer = LogsTextBox.FindAncestorOfType<ScrollViewer>();
|
|
||||||
scrollViewer?.ScrollToEnd();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
|
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
|
||||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||||
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
|
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
|
||||||
<FileVersion>1.2.1</FileVersion>
|
<FileVersion>2.0.0-b1</FileVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
@ -34,4 +34,19 @@
|
||||||
<AvaloniaResource Include="Images\logo_red.svg" />
|
<AvaloniaResource Include="Images\logo_red.svg" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<EmbeddedResource Update="Lang\Resources.resx">
|
||||||
|
<Generator>PublicResXFileCodeGenerator</Generator>
|
||||||
|
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||||
|
</EmbeddedResource>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Update="Lang\Resources.Designer.cs">
|
||||||
|
<DesignTime>True</DesignTime>
|
||||||
|
<AutoGen>True</AutoGen>
|
||||||
|
<DependentUpon>Resources.resx</DependentUpon>
|
||||||
|
</Compile>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
65
MetaforceInstaller.UI/ViewModels/MainWindowViewModel.cs
Normal file
65
MetaforceInstaller.UI/ViewModels/MainWindowViewModel.cs
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using Avalonia.Media;
|
||||||
|
using MetaforceInstaller.Core.Models;
|
||||||
|
|
||||||
|
namespace MetaforceInstaller.UI.ViewModels;
|
||||||
|
|
||||||
|
public class MainWindowViewModel : INotifyPropertyChanged
|
||||||
|
{
|
||||||
|
public event PropertyChangedEventHandler? PropertyChanged;
|
||||||
|
|
||||||
|
public ObservableCollection<InstallationData> Installations { get; set; } = new();
|
||||||
|
|
||||||
|
private bool _isDeviceConnected;
|
||||||
|
|
||||||
|
public bool IsDeviceConnected
|
||||||
|
{
|
||||||
|
get => _isDeviceConnected;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_isDeviceConnected != value)
|
||||||
|
{
|
||||||
|
_isDeviceConnected = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
OnPropertyChanged(nameof(StatusColor));
|
||||||
|
OnPropertyChanged(nameof(StatusText));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string _deviceSerial = string.Empty;
|
||||||
|
|
||||||
|
public string DeviceSerial
|
||||||
|
{
|
||||||
|
get => _deviceSerial;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_deviceSerial != value)
|
||||||
|
{
|
||||||
|
_deviceSerial = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
OnPropertyChanged(nameof(StatusText));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IBrush StatusColor => IsDeviceConnected ? Brushes.Green : Brushes.Red;
|
||||||
|
public string StatusText => IsDeviceConnected ? $"{Lang.Resources.ConnectedTo} {_deviceSerial}" : Lang.Resources.NotConnected;
|
||||||
|
|
||||||
|
public void LoadInstallations(IEnumerable<InstallationData> data)
|
||||||
|
{
|
||||||
|
Installations.Clear();
|
||||||
|
foreach (var installation in data)
|
||||||
|
{
|
||||||
|
Installations.Add(installation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
|
||||||
|
{
|
||||||
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||||
|
}
|
||||||
|
}
|
||||||
89
MetaforceInstaller.UI/Windows/MainWindow.axaml
Normal file
89
MetaforceInstaller.UI/Windows/MainWindow.axaml
Normal file
|
|
@ -0,0 +1,89 @@
|
||||||
|
<Window xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:vm="using:MetaforceInstaller.UI.ViewModels"
|
||||||
|
xmlns:lang="using:MetaforceInstaller.UI.Lang"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
|
x:CompileBindings="True"
|
||||||
|
x:DataType="vm:MainWindowViewModel"
|
||||||
|
x:Class="MetaforceInstaller.UI.Windows.MainWindow"
|
||||||
|
Title="MetaforceInstaller">
|
||||||
|
|
||||||
|
<Window.Resources>
|
||||||
|
<ResourceDictionary>
|
||||||
|
<ResourceDictionary.ThemeDictionaries>
|
||||||
|
<ResourceDictionary x:Key="Light">
|
||||||
|
<SvgImage x:Key="Logo" Source="/Images/logo_red.svg" />
|
||||||
|
</ResourceDictionary>
|
||||||
|
|
||||||
|
<ResourceDictionary x:Key="Dark">
|
||||||
|
<SvgImage x:Key="Logo" Source="/Images/logo_red.svg" />
|
||||||
|
</ResourceDictionary>
|
||||||
|
</ResourceDictionary.ThemeDictionaries>
|
||||||
|
</ResourceDictionary>
|
||||||
|
</Window.Resources>
|
||||||
|
|
||||||
|
<Design.DataContext>
|
||||||
|
<vm:MainWindowViewModel />
|
||||||
|
</Design.DataContext>
|
||||||
|
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="96" />
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
<RowDefinition Height="24" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<Image Grid.Row="0"
|
||||||
|
Margin="20"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Source="{DynamicResource Logo}"
|
||||||
|
HorizontalAlignment="Left">
|
||||||
|
</Image>
|
||||||
|
|
||||||
|
<Button Grid.Row="0" Name="NewInstallationButton" Margin="20" VerticalAlignment="Center"
|
||||||
|
HorizontalAlignment="Right">
|
||||||
|
<Button.Content>
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="4">
|
||||||
|
<TextBlock Text="+" />
|
||||||
|
<TextBlock Text="{x:Static lang:Resources.AddInstallation}" />
|
||||||
|
</StackPanel>
|
||||||
|
</Button.Content>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<ScrollViewer Grid.Row="1">
|
||||||
|
<ItemsControl ItemsSource="{Binding Installations}">
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<Expander Header="{Binding Title}" Margin="16, 8" HorizontalAlignment="Stretch">
|
||||||
|
<StackPanel Orientation="Vertical">
|
||||||
|
<Label Content="{Binding Id, StringFormat='ID: {0}'}" />
|
||||||
|
<Label Content="{Binding InstalledAt, StringFormat='Installed: {0}'}" />
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||||
|
<Button Name="LaunchServerButton" Click="OnLaunchServerClick"
|
||||||
|
Content="{x:Static lang:Resources.LaunchServer}" />
|
||||||
|
<Button Name="LaunchPcAdminButton" Click="OnLaunchAdminClick"
|
||||||
|
Content="{x:Static lang:Resources.LaunchPCAdmin}" />
|
||||||
|
<Button Name="InstallVrClientButton"
|
||||||
|
Content="{x:Static lang:Resources.InstallVRClient}" />
|
||||||
|
<Button Name="InstallAndroidAdminButton"
|
||||||
|
Content="{x:Static lang:Resources.InstallAndroidAdmin}" />
|
||||||
|
<Button Name="DeleteButton" Click="OnDeleteInstallationClick"
|
||||||
|
Content="{x:Static lang:Resources.Delete}" />
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</Expander>
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
</ScrollViewer>
|
||||||
|
|
||||||
|
<DockPanel Grid.Row="2">
|
||||||
|
<Border Name="StatusCircle" Margin="4" Width="16" Height="16" Background="{Binding StatusColor}"
|
||||||
|
CornerRadius="50" HorizontalAlignment="Left" />
|
||||||
|
<Label Name="SerialNumberLabel" Content="{Binding StatusText}" />
|
||||||
|
<Label Name="VersionLabel" HorizontalAlignment="Right" VerticalAlignment="Center" />
|
||||||
|
</DockPanel>
|
||||||
|
</Grid>
|
||||||
|
</Window>
|
||||||
150
MetaforceInstaller.UI/Windows/MainWindow.axaml.cs
Normal file
150
MetaforceInstaller.UI/Windows/MainWindow.axaml.cs
Normal file
|
|
@ -0,0 +1,150 @@
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Reflection;
|
||||||
|
using AdvancedSharpAdbClient.Models;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Interactivity;
|
||||||
|
using Avalonia.Threading;
|
||||||
|
using MetaforceInstaller.Core.Intefaces;
|
||||||
|
using MetaforceInstaller.Core.Models;
|
||||||
|
using MetaforceInstaller.Core.Services;
|
||||||
|
using MetaforceInstaller.UI.ViewModels;
|
||||||
|
|
||||||
|
namespace MetaforceInstaller.UI.Windows;
|
||||||
|
|
||||||
|
public partial class MainWindow : Window
|
||||||
|
{
|
||||||
|
private MainWindowViewModel _viewModel;
|
||||||
|
private IStorageService _storageService;
|
||||||
|
private AdbService _adbService;
|
||||||
|
|
||||||
|
public MainWindow()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
|
||||||
|
_viewModel = new MainWindowViewModel();
|
||||||
|
_storageService = new StorageService();
|
||||||
|
_adbService = new AdbService();
|
||||||
|
|
||||||
|
DataContext = _viewModel;
|
||||||
|
|
||||||
|
VersionLabel.Content = Assembly.GetExecutingAssembly()
|
||||||
|
.GetCustomAttribute<AssemblyFileVersionAttribute>()?.Version;
|
||||||
|
|
||||||
|
NewInstallationButton.Click += OnNewInstalltionClick;
|
||||||
|
|
||||||
|
_adbService.DeviceConnected += OnAdbDeviceConnected;
|
||||||
|
_adbService.DeviceDisconnected += OnAdbDeviceDisconnected;
|
||||||
|
_adbService.DeviceChanged += OnAdbDeviceChanged;
|
||||||
|
|
||||||
|
LoadInstallations();
|
||||||
|
UpdateDeviceStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateDeviceStatus()
|
||||||
|
{
|
||||||
|
var isConnected = _adbService.IsDeviceConnected;
|
||||||
|
_viewModel.IsDeviceConnected = isConnected;
|
||||||
|
|
||||||
|
if (isConnected)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var deviceInfo = _adbService.GetDeviceInfo();
|
||||||
|
_viewModel.DeviceSerial = deviceInfo.SerialNumber;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
_viewModel.DeviceSerial = "Unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_viewModel.DeviceSerial = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAdbDeviceConnected(object? sender, DeviceDataEventArgs e)
|
||||||
|
{
|
||||||
|
Dispatcher.UIThread.Post(() =>
|
||||||
|
{
|
||||||
|
UpdateDeviceStatus();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAdbDeviceDisconnected(object? sender, DeviceDataEventArgs e)
|
||||||
|
{
|
||||||
|
Dispatcher.UIThread.Post(() =>
|
||||||
|
{
|
||||||
|
UpdateDeviceStatus();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAdbDeviceChanged(object? sender, DeviceDataEventArgs e)
|
||||||
|
{
|
||||||
|
Dispatcher.UIThread.Post(() =>
|
||||||
|
{
|
||||||
|
UpdateDeviceStatus();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadInstallations()
|
||||||
|
{
|
||||||
|
var appData = _storageService.Load();
|
||||||
|
_viewModel.LoadInstallations(appData.Installations);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async void OnNewInstalltionClick(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
var newInstallationDialog = new NewInstallationDialog(_storageService);
|
||||||
|
await newInstallationDialog.ShowDialog<NewInstallationDialog>(this);
|
||||||
|
LoadInstallations();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async void OnDeleteInstallationClick(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is Button button && button.DataContext is InstallationData installationData)
|
||||||
|
{
|
||||||
|
var name = installationData.Title;
|
||||||
|
Console.WriteLine($"Delete {name}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async void OnLaunchServerClick(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is Button button && button.DataContext is InstallationData installationData)
|
||||||
|
{
|
||||||
|
var exePath = installationData.Parts.WindowsServerPath;
|
||||||
|
var processInfo = new ProcessStartInfo
|
||||||
|
{
|
||||||
|
FileName = exePath,
|
||||||
|
UseShellExecute = false,
|
||||||
|
};
|
||||||
|
Process.Start(processInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async void OnLaunchAdminClick(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is Button button && button.DataContext is InstallationData installationData)
|
||||||
|
{
|
||||||
|
var exePath = installationData.Parts.WindowsAdminPath;
|
||||||
|
var processInfo = new ProcessStartInfo
|
||||||
|
{
|
||||||
|
FileName = exePath,
|
||||||
|
UseShellExecute = false,
|
||||||
|
};
|
||||||
|
Process.Start(processInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnClosed(EventArgs e)
|
||||||
|
{
|
||||||
|
_adbService.DeviceConnected -= OnAdbDeviceConnected;
|
||||||
|
_adbService.DeviceDisconnected -= OnAdbDeviceDisconnected;
|
||||||
|
_adbService.DeviceChanged -= OnAdbDeviceChanged;
|
||||||
|
_adbService.Dispose();
|
||||||
|
|
||||||
|
base.OnClosed(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
28
MetaforceInstaller.UI/Windows/NewInstallationDialog.axaml
Normal file
28
MetaforceInstaller.UI/Windows/NewInstallationDialog.axaml
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
<Window xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:lang="clr-namespace:MetaforceInstaller.UI.Lang"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="200" d:DesignHeight="400"
|
||||||
|
x:Class="MetaforceInstaller.UI.Windows.NewInstallationDialog"
|
||||||
|
Title="MetaforceInstaller - Add new installation"
|
||||||
|
SizeToContent="WidthAndHeight"
|
||||||
|
CanResize="False">
|
||||||
|
|
||||||
|
<StackPanel Margin="24" Spacing="12">
|
||||||
|
<Label FontSize="36" Content="{x:Static lang:Resources.AddInstallation}" />
|
||||||
|
<TextBox Name="TitleTextBox" Watermark="{x:Static lang:Resources.NameOfNewInstallation}" />
|
||||||
|
<Button Name="ChooseZip" Content="{x:Static lang:Resources.ChooseZip}" />
|
||||||
|
<CheckBox Name="ServerCheckBox" Content="{x:Static lang:Resources.InstallServerCheckbox}" />
|
||||||
|
<CheckBox Name="PcAdminCheckBox" Content="{x:Static lang:Resources.InstallAdminCheckbox}" />
|
||||||
|
<CheckBox Name="AndroidAdminCheckbox" Content="{x:Static lang:Resources.SaveAndroidAdminCheckbox}" />
|
||||||
|
<CheckBox Name="VrClientCheckbox" Content="{x:Static lang:Resources.SaveVRClientCheckbox}" />
|
||||||
|
|
||||||
|
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Bottom" Spacing="8">
|
||||||
|
<Button Name="InstallButton" Content="{x:Static lang:Resources.InstallButton}" />
|
||||||
|
<Button Name="CancelButton" Content="{x:Static lang:Resources.CancelButton}" />
|
||||||
|
</StackPanel>
|
||||||
|
<ProgressBar Name="ProgressBar" Minimum="0" Maximum="100" Value="0" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
</Window>
|
||||||
164
MetaforceInstaller.UI/Windows/NewInstallationDialog.axaml.cs
Normal file
164
MetaforceInstaller.UI/Windows/NewInstallationDialog.axaml.cs
Normal file
|
|
@ -0,0 +1,164 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.IO.Compression;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
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.Windows;
|
||||||
|
|
||||||
|
public partial class NewInstallationDialog : Window
|
||||||
|
{
|
||||||
|
private string? _zipPath;
|
||||||
|
private InstallationParts? _installationParts;
|
||||||
|
private readonly IStorageService _storageService;
|
||||||
|
|
||||||
|
public NewInstallationDialog(IStorageService storageService)
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
|
||||||
|
_storageService = storageService;
|
||||||
|
|
||||||
|
RefreshCheckboxes();
|
||||||
|
CancelButton.Click += OnCancelClick;
|
||||||
|
ChooseZip.Click += OnChooseZipClick;
|
||||||
|
InstallButton.IsEnabled = false;
|
||||||
|
InstallButton.Click += OnInstallClick;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RefreshCheckboxes()
|
||||||
|
{
|
||||||
|
var serverCheckbox = ServerCheckBox;
|
||||||
|
var pcAdminCheckbox = PcAdminCheckBox;
|
||||||
|
var androidAdminCheckbox = AndroidAdminCheckbox;
|
||||||
|
var vrClientCheckbox = VrClientCheckbox;
|
||||||
|
serverCheckbox.Content = Lang.Resources.InstallServerCheckbox;
|
||||||
|
serverCheckbox.IsEnabled = true;
|
||||||
|
pcAdminCheckbox.Content = Lang.Resources.InstallAdminCheckbox;
|
||||||
|
pcAdminCheckbox.IsEnabled = true;
|
||||||
|
androidAdminCheckbox.Content = Lang.Resources.SaveAndroidAdminCheckbox;
|
||||||
|
androidAdminCheckbox.IsEnabled = true;
|
||||||
|
vrClientCheckbox.Content = Lang.Resources.SaveVRClientCheckbox;
|
||||||
|
vrClientCheckbox.IsEnabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void OnChooseZipClick(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
var topLevel = GetTopLevel(this);
|
||||||
|
var files = await topLevel!.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
|
||||||
|
{
|
||||||
|
Title = "Выберите архив с контентом",
|
||||||
|
AllowMultiple = false,
|
||||||
|
FileTypeFilter =
|
||||||
|
[
|
||||||
|
new FilePickerFileType("ZIP Files")
|
||||||
|
{
|
||||||
|
Patterns = ["*.zip"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
if (files.Count >= 1)
|
||||||
|
{
|
||||||
|
_zipPath = files[0].Path.LocalPath;
|
||||||
|
using var archive = ZipFile.OpenRead(_zipPath);
|
||||||
|
_installationParts = ZipScrapper.PeekFiles(archive);
|
||||||
|
UpdateCheckboxes();
|
||||||
|
archive.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void OnInstallClick(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
using var archive = ZipFile.OpenRead(_zipPath);
|
||||||
|
var title = TitleTextBox.Text ?? Path.GetFileNameWithoutExtension(_zipPath);
|
||||||
|
var installationGuid = Guid.NewGuid();
|
||||||
|
|
||||||
|
var progress = new Progress<double>(value => { ProgressBar.Value = value; });
|
||||||
|
|
||||||
|
string extractedPath = null;
|
||||||
|
await Task.Run(() =>
|
||||||
|
{
|
||||||
|
extractedPath = ZipScrapper.ExtractZip(
|
||||||
|
archive,
|
||||||
|
Defaults.StoragePath,
|
||||||
|
installationGuid,
|
||||||
|
progress);
|
||||||
|
});
|
||||||
|
|
||||||
|
InstallButton.IsEnabled = false;
|
||||||
|
|
||||||
|
var appData = _storageService.Load();
|
||||||
|
|
||||||
|
var updatedParts = ZipScrapper.UpdatePathsAfterExtraction(_installationParts, extractedPath);
|
||||||
|
|
||||||
|
var installationData = new InstallationData
|
||||||
|
{
|
||||||
|
Id = installationGuid,
|
||||||
|
Title = title,
|
||||||
|
Parts = updatedParts
|
||||||
|
};
|
||||||
|
|
||||||
|
appData.Installations.Add(installationData);
|
||||||
|
_storageService.Save(appData);
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateCheckboxes()
|
||||||
|
{
|
||||||
|
RefreshCheckboxes();
|
||||||
|
var serverCheckbox = ServerCheckBox;
|
||||||
|
var pcAdminCheckbox = PcAdminCheckBox;
|
||||||
|
var androidAdminCheckbox = AndroidAdminCheckbox;
|
||||||
|
var vrClientCheckbox = VrClientCheckbox;
|
||||||
|
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(_installationParts.WindowsServerPath))
|
||||||
|
{
|
||||||
|
serverCheckbox.IsEnabled = false;
|
||||||
|
serverCheckbox.Content += $"\n{Lang.Resources.NoServerError}";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(_installationParts.WindowsAdminPath))
|
||||||
|
{
|
||||||
|
pcAdminCheckbox.IsEnabled = false;
|
||||||
|
pcAdminCheckbox.Content += $"\n{Lang.Resources.NoPCAdminError}";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(_installationParts.WindowsContentPath))
|
||||||
|
{
|
||||||
|
pcAdminCheckbox.IsEnabled = false;
|
||||||
|
pcAdminCheckbox.Content += $"\n{Lang.Resources.NoWindowsContentError}";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(_installationParts.AndroidContentPath))
|
||||||
|
{
|
||||||
|
vrClientCheckbox.IsEnabled = false;
|
||||||
|
vrClientCheckbox.Content += $"\n{Lang.Resources.NoAndroidContentError}";
|
||||||
|
androidAdminCheckbox.IsEnabled = false;
|
||||||
|
androidAdminCheckbox.Content += $"\n{Lang.Resources.NoAndroidContentError}";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(_installationParts.PicoClientPath) &&
|
||||||
|
string.IsNullOrEmpty(_installationParts.OculusClientPath))
|
||||||
|
{
|
||||||
|
vrClientCheckbox.IsEnabled = false;
|
||||||
|
vrClientCheckbox.Content += $"\n{Lang.Resources.NoVRClientsError}";
|
||||||
|
}
|
||||||
|
|
||||||
|
InstallButton.IsEnabled = new List<CheckBox?>
|
||||||
|
{ serverCheckbox, pcAdminCheckbox, androidAdminCheckbox, vrClientCheckbox }.Any(x => x?.IsEnabled == true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnCancelClick(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue