Compare commits

..

3 commits

Author SHA1 Message Date
4d5371be5e dummy ui 2025-09-15 02:08:19 +05:00
243224a720 now with animations lol 2025-09-15 00:38:30 +05:00
7a7cefe7b5 WIP: move logic to Core project 2025-09-15 00:12:36 +05:00
20 changed files with 686 additions and 190 deletions

View file

@ -7,14 +7,16 @@
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\MetaforceInstaller.Core\MetaforceInstaller.Core.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="AdvancedSharpAdbClient" Version="3.4.14" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="adb\adb.exe" />
<EmbeddedResource Include="adb\AdbWinApi.dll" />
<EmbeddedResource Include="adb\AdbWinUsbApi.dll" />
<ProjectReference Include="..\MetaforceInstaller.Core\MetaforceInstaller.Core.csproj" />
</ItemGroup>
</Project>

View file

@ -1,57 +1,50 @@
using System.Reflection;
using AdvancedSharpAdbClient;
using AdvancedSharpAdbClient.DeviceCommands;
using AdvancedSharpAdbClient.Models;
using MetaforceInstaller.Cli.Utils;
using MetaforceInstaller.Core.Services;
namespace MetaforceInstaller.Cli;
class Program
static class Program
{
// 1. Получить имя апк и зипки, если не предоставлены - забить дефолтными значениями
// 2. Распаковать в временную директорию adb (готово)
// 3. Установить апк
// 4. Получить имя пакета
// 5. Сформировать строку пути для контента
// 6. Копировать зип по сформированному пути
static AdbClient adbClient;
static DeviceData deviceData;
static void Main(string[] args)
static async Task Main(string[] args)
{
try
{
var (apkPath, zipPath, outputPath) = ParseArguments(args);
var installationRequest = ArgumentParser.ParseArguments(args);
if (string.IsNullOrEmpty(apkPath) || string.IsNullOrEmpty(zipPath) || string.IsNullOrEmpty(outputPath))
if (installationRequest is null ||
string.IsNullOrEmpty(installationRequest.ApkPath) ||
string.IsNullOrEmpty(installationRequest.ZipPath) ||
string.IsNullOrEmpty(installationRequest.OutputPath))
{
ShowUsage();
return;
}
var adbPath = ExtractAdbFiles();
var adbService = new AdbService();
var server = new AdbServer();
var result = server.StartServer(adbPath, restartServerIfNewer: false);
Console.WriteLine($"ADB сервер запущен: {result}");
// Подписка на события прогресса
adbService.ProgressChanged += OnProgressChanged;
adbService.StatusChanged += OnStatusChanged;
adbClient = new AdbClient();
// Получение информации об устройстве
var deviceInfo = adbService.GetDeviceInfo();
Console.WriteLine($"Найдено устройство: {deviceInfo.SerialNumber}");
Console.WriteLine($"Состояние: {deviceInfo.State}");
Console.WriteLine($"Модель: {deviceInfo.Model} - {deviceInfo.Name}");
Console.WriteLine();
var devices = adbClient.GetDevices();
// Создание объекта для отслеживания прогресса
var progress = new Progress<MetaforceInstaller.Core.Models.ProgressInfo>(OnProgressReport);
if (!devices.Any())
{
Console.WriteLine("Устройства не найдены. Подключите Android-устройство и включите отладку по USB.");
return;
}
// Установка APK
await adbService.InstallApkAsync(installationRequest.ApkPath, progress);
Console.WriteLine();
deviceData = devices.FirstOrDefault();
Console.WriteLine($"Найдено устройство: {deviceData.Serial}");
Console.WriteLine($"Состояние: {deviceData.State}");
Console.WriteLine($"Имя устройства: {deviceData.Name} - {deviceData.Model}");
// Копирование файла
await adbService.CopyFileAsync(installationRequest.ZipPath, installationRequest.OutputPath, progress);
Console.WriteLine();
InstallApk(apkPath);
CopyFileToDevice(zipPath, outputPath);
Console.WriteLine("Операция завершена успешно!");
}
catch (Exception ex)
{
@ -59,6 +52,29 @@ class Program
}
}
private static void OnProgressChanged(object? sender, MetaforceInstaller.Core.Models.ProgressInfo e)
{
DrawProgressBar(e.PercentageComplete, e.BytesTransferred, e.TotalBytes);
}
private static void OnStatusChanged(object? sender, string e)
{
Console.WriteLine(e);
}
private static void OnProgressReport(MetaforceInstaller.Core.Models.ProgressInfo progressInfo)
{
if (progressInfo.TotalBytes > 0)
{
DrawProgressBar(progressInfo.PercentageComplete, progressInfo.BytesTransferred, progressInfo.TotalBytes);
}
else
{
// Для случаев без информации о байтах (например, установка APK)
DrawProgressBar(progressInfo.PercentageComplete, 0, 100);
}
}
static void ShowUsage()
{
Console.WriteLine("Использование:");
@ -78,168 +94,36 @@ class Program
Console.WriteLine(" MetaforceInstaller.exe -a app.apk -c data.zip -o /sdcard/data.zip");
}
private static (string? apkPath, string? zipPath, string? outputPath) ParseArguments(string[] args)
private static void DrawProgressBar(int progress, long receivedBytes, long totalBytes)
{
string apkPath = null;
string zipPath = null;
string outputPath = null;
Console.SetCursorPosition(0, Console.CursorTop);
for (int i = 0; i < args.Length; i++)
var barLength = 40;
var filledLength = (int)(barLength * progress / 100.0);
var bar = "[" + new string('█', filledLength) + new string('░', barLength - filledLength) + "]";
string bytesText = "";
if (totalBytes > 0)
{
switch (args[i].ToLower())
{
case "--apk":
case "-a":
if (i + 1 < args.Length)
{
apkPath = args[i + 1];
i++;
}
break;
case "--content":
case "-c":
if (i + 1 < args.Length)
{
zipPath = args[i + 1];
i++;
}
break;
case "--output":
case "-o":
if (i + 1 < args.Length)
{
outputPath = args[i + 1];
i++;
}
break;
case "--help":
case "-h":
ShowUsage();
Environment.Exit(0);
break;
}
bytesText = $" {FormatBytes(receivedBytes)} / {FormatBytes(totalBytes)}";
}
return (apkPath, zipPath, outputPath);
Console.Write($"\r{bar} {progress}%{bytesText}");
}
private static void ExtractResource(string resourceName, string outputPath)
private static string FormatBytes(long bytes)
{
using var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName);
using var fileStream = File.Create(outputPath);
stream.CopyTo(fileStream);
}
string[] suffixes = ["B", "KB", "MB", "GB", "TB"];
var counter = 0;
double number = bytes;
private static string ExtractAdbFiles()
{
var tempDir = Path.Combine(Path.GetTempPath(), "MetaforceInstaller", "adb");
Directory.CreateDirectory(tempDir);
var adbPath = Path.Combine(tempDir, "adb.exe");
if (!File.Exists(adbPath))
while (Math.Round(number / 1024) >= 1)
{
ExtractResource("MetaforceInstaller.Cli.adb.adb.exe", adbPath);
ExtractResource("MetaforceInstaller.Cli.adb.AdbWinApi.dll", Path.Combine(tempDir, "AdbWinApi.dll"));
ExtractResource("MetaforceInstaller.Cli.adb.AdbWinUsbApi.dll", Path.Combine(tempDir, "AdbWinUsbApi.dll"));
number /= 1024;
counter++;
}
return adbPath;
return $"{number:N1} {suffixes[counter]}";
}
private static void InstallApk(string apkPath)
{
try
{
if (!File.Exists(apkPath))
{
Console.WriteLine($"APK файл не найден: {apkPath}");
return;
}
Console.WriteLine($"Установка APK: {apkPath}");
var packageManager = new PackageManager(adbClient, deviceData);
packageManager.InstallPackage(apkPath, new Action<InstallProgressEventArgs>(o => { }));
Console.WriteLine("APK успешно установлен!");
}
catch (Exception ex)
{
Console.WriteLine($"Ошибка установки APK: {ex.Message}");
}
}
private static void CopyFileToDevice(string localPath, string remotePath)
{
try
{
if (!File.Exists(localPath))
{
Console.WriteLine($"Локальный файл не найден: {localPath}");
return;
}
Console.WriteLine($"Копирование файла {localPath} в {remotePath}");
var lastProgress = -1;
using var fileStream = File.OpenRead(localPath);
var syncService = new SyncService(adbClient, deviceData);
syncService.Push(fileStream, remotePath, UnixFileStatus.DefaultFileMode, DateTime.Now,
new Action<SyncProgressChangedEventArgs>(progress =>
{
var currentProgress = progress.ProgressPercentage;
if (currentProgress != lastProgress)
{
lastProgress = (int)currentProgress;
DrawProgressBar(lastProgress, progress.ReceivedBytesSize, progress.TotalBytesToReceive);
}
}));
Console.WriteLine();
Console.WriteLine("Файл успешно скопирован!");
}
catch (Exception ex)
{
Console.WriteLine($"Ошибка копирования файла: {ex.Message}");
}
}
private static void DrawProgressBar(int progress, long receivedBytes, long totalBytes)
{
Console.SetCursorPosition(0, Console.CursorTop);
var barLength = 40;
var filledLength = (int)(barLength * progress / 100.0);
var bar = "[" + new string('█', filledLength) + new string('░', barLength - filledLength) + "]";
var bytesText = $" {FormatBytes(receivedBytes)} / {FormatBytes(totalBytes)}";
Console.Write($"\r{bar} {progress}%{bytesText}");
}
private static 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]}";
}
}

View file

@ -0,0 +1,53 @@
using MetaforceInstaller.Core.Models;
namespace MetaforceInstaller.Cli.Utils;
public static class ArgumentParser
{
public static InstallationRequest? ParseArguments(string[] args)
{
var result = new InstallationRequest();
for (var i = 0; i < args.Length; i++)
{
switch (args[i].ToLower())
{
case "--apk":
case "-a":
if (i + 1 < args.Length)
{
result.ApkPath = args[i + 1];
i++;
}
break;
case "--content":
case "-c":
if (i + 1 < args.Length)
{
result.ZipPath = args[i + 1];
i++;
}
break;
case "--output":
case "-o":
if (i + 1 < args.Length)
{
result.OutputPath = args[i + 1];
i++;
}
break;
case "--help":
case "-h":
return null;
}
}
return result;
}
}

View file

@ -0,0 +1,18 @@
using MetaforceInstaller.Core.Models;
using MetaforceInstaller.Core.Models;
namespace MetaforceInstaller.Core.Intefaces;
public interface IAdbService
{
event EventHandler<ProgressInfo>? ProgressChanged;
event EventHandler<string>? StatusChanged;
Task InstallApkAsync(string apkPath, IProgress<ProgressInfo>? progress = null, CancellationToken cancellationToken = default);
Task CopyFileAsync(string localPath, string remotePath, IProgress<ProgressInfo>? progress = null, CancellationToken cancellationToken = default);
DeviceInfo GetDeviceInfo();
// Синхронные версии для обратной совместимости
void InstallApk(string apkPath);
void CopyFile(string localPath, string remotePath);
}

View file

@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<EmbeddedResource Include="adb\adb.exe" />
<EmbeddedResource Include="adb\AdbWinApi.dll" />
<EmbeddedResource Include="adb\AdbWinUsbApi.dll" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" />
<PackageReference Include="AdvancedSharpAdbClient" Version="3.4.14" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="AdvancedSharpAdbClient" Version="3.4.14" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.3" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,8 @@
namespace MetaforceInstaller.Core.Models;
public record DeviceInfo(
string SerialNumber,
string State,
string Model,
string Name
);

View file

@ -0,0 +1,8 @@
namespace MetaforceInstaller.Core.Models;
public class InstallationRequest
{
public string ApkPath { get; set; }
public string ZipPath { get; set; }
public string OutputPath { get; set; }
}

View file

@ -0,0 +1,19 @@
namespace MetaforceInstaller.Core.Models;
public class ProgressInfo
{
public int PercentageComplete { get; set; }
public long BytesTransferred { get; set; }
public long TotalBytes { get; set; }
public string? Message { get; set; }
public string? CurrentFile { get; set; }
public ProgressType Type { get; set; }
}
public enum ProgressType
{
Installation,
FileCopy,
Extraction,
General
}

View file

@ -0,0 +1,193 @@
using System.Reflection;
using AdvancedSharpAdbClient;
using AdvancedSharpAdbClient.DeviceCommands;
using AdvancedSharpAdbClient.Models;
using MetaforceInstaller.Core.Intefaces;
using MetaforceInstaller.Core.Models;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
namespace MetaforceInstaller.Core.Services;
public class AdbService : IAdbService
{
private readonly ILogger<AdbService> _logger;
private readonly AdbClient _adbClient;
private DeviceData _deviceData;
public event EventHandler<ProgressInfo>? ProgressChanged;
public event EventHandler<string>? StatusChanged;
public AdbService(ILogger<AdbService>? logger = null)
{
_logger = logger ?? new NullLogger<AdbService>();
var adbPath = GetAdbPath();
var server = new AdbServer();
var serverStatus = server.StartServer(adbPath, restartServerIfNewer: false);
_adbClient = new AdbClient();
var devices = _adbClient.GetDevices();
foreach (var device in devices)
{
Console.WriteLine(device.Model);
}
_deviceData = devices.FirstOrDefault();
}
private void ExtractResource(string resourceName, string outputPath)
{
_logger.LogInformation($"Extracting resource: {resourceName} to {outputPath}");
using var stream = Assembly.GetAssembly(typeof(AdbService)).GetManifestResourceStream(resourceName);
using var fileStream = File.Create(outputPath);
stream.CopyTo(fileStream);
_logger.LogInformation($"Resource extracted: {resourceName} to {outputPath}");
}
private string GetAdbPath()
{
var tempDir = Path.Combine(Path.GetTempPath(), "MetaforceInstaller", "adb");
Directory.CreateDirectory(tempDir);
var adbPath = Path.Combine(tempDir, "adb.exe");
if (File.Exists(adbPath)) return adbPath;
ExtractResource("MetaforceInstaller.Core.adb.adb.exe", adbPath);
ExtractResource("MetaforceInstaller.Core.adb.AdbWinApi.dll", Path.Combine(tempDir, "AdbWinApi.dll"));
ExtractResource("MetaforceInstaller.Core.adb.AdbWinUsbApi.dll", Path.Combine(tempDir, "AdbWinUsbApi.dll"));
return adbPath;
}
private void OnProgressChanged(ProgressInfo progressInfo)
{
ProgressChanged?.Invoke(this, progressInfo);
}
private void OnStatusChanged(string status)
{
StatusChanged?.Invoke(this, status);
}
public void InstallApk(string apkPath)
{
InstallApkAsync(apkPath).Wait();
}
public async Task InstallApkAsync(string apkPath, IProgress<ProgressInfo>? progress = null,
CancellationToken cancellationToken = default)
{
try
{
if (!File.Exists(apkPath))
{
_logger.LogCritical("Error: Could not find APK file.");
return;
}
OnStatusChanged("Начинаем установку APK...");
_logger.LogInformation($"Installing APK: {apkPath}");
progress?.Report(new ProgressInfo
{
PercentageComplete = 0,
Message = "Подготовка к установке APK...",
Type = ProgressType.Installation,
CurrentFile = Path.GetFileName(apkPath)
});
var packageManager = new PackageManager(_adbClient, _deviceData);
await Task.Run(() =>
{
packageManager.InstallPackage(apkPath, installProgress =>
{
var progressInfo = new ProgressInfo
{
PercentageComplete = (int)installProgress.UploadProgress,
Message = $"Установка APK: {installProgress.UploadProgress:F1}%",
Type = ProgressType.Installation,
CurrentFile = Path.GetFileName(apkPath)
};
progress?.Report(progressInfo);
OnProgressChanged(progressInfo);
});
}, cancellationToken);
OnStatusChanged("APK успешно установлен!");
_logger.LogInformation("APK successfully installed!");
}
catch (Exception ex)
{
_logger.LogCritical($"Error: {ex.Message}");
throw;
}
}
public void CopyFile(string localPath, string remotePath)
{
CopyFileAsync(localPath, remotePath).Wait();
}
public async Task CopyFileAsync(string localPath, string remotePath, IProgress<ProgressInfo>? progress = null,
CancellationToken cancellationToken = default)
{
try
{
if (!File.Exists(localPath))
{
_logger.LogCritical($"Error: Could not find file: {localPath}");
return;
}
OnStatusChanged("Начинаем копирование файла...");
_logger.LogInformation($"Copying file: {localPath} to {remotePath}");
var fileInfo = new FileInfo(localPath);
progress?.Report(new ProgressInfo
{
PercentageComplete = 0,
Message = "Подготовка к копированию файла...",
Type = ProgressType.FileCopy,
CurrentFile = Path.GetFileName(localPath),
TotalBytes = fileInfo.Length
});
await Task.Run(() =>
{
using var fileStream = File.OpenRead(localPath);
var syncService = new SyncService(_adbClient, _deviceData);
syncService.Push(fileStream, remotePath, UnixFileStatus.DefaultFileMode, DateTime.Now,
copyProgress =>
{
var progressInfo = new ProgressInfo
{
PercentageComplete = (int)copyProgress.ProgressPercentage,
BytesTransferred = copyProgress.ReceivedBytesSize,
TotalBytes = copyProgress.TotalBytesToReceive,
Message = $"Копирование: {copyProgress.ProgressPercentage:F1}%",
Type = ProgressType.FileCopy,
CurrentFile = Path.GetFileName(localPath)
};
progress?.Report(progressInfo);
OnProgressChanged(progressInfo);
});
}, cancellationToken);
OnStatusChanged("Файл успешно скопирован!");
_logger.LogInformation("File successfully copied!");
}
catch (Exception ex)
{
_logger.LogCritical($"Error: {ex.Message}");
throw;
}
}
public DeviceInfo GetDeviceInfo()
{
return new DeviceInfo(_deviceData.Serial, _deviceData.State.ToString(), _deviceData.Model, _deviceData.Name);
}
}

View file

@ -0,0 +1,10 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="MetaforceInstaller.UI.App"
RequestedThemeVariant="Default">
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
<Application.Styles>
<FluentTheme />
</Application.Styles>
</Application>

View file

@ -0,0 +1,23 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
namespace MetaforceInstaller.UI;
public partial class App : Application
{
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
}
public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
desktop.MainWindow = new MainWindow();
}
base.OnFrameworkInitializationCompleted();
}
}

View file

@ -0,0 +1,49 @@
<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">
<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>
<!-- 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>

View file

@ -0,0 +1,131 @@
using System;
using System.IO;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Platform.Storage;
using Avalonia.VisualTree;
namespace MetaforceInstaller.UI;
public partial class MainWindow : Window
{
private string? _apkPath;
private string? _zipPath;
public MainWindow()
{
InitializeComponent();
CheckAndEnableInstallButton();
ChooseApkButton.Click += OnChooseApkClicked;
ChooseContentButton.Click += OnChooseContentClicked;
InstallButton.Click += OnInstallClicked;
}
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;
}
InstallButton.IsEnabled = false;
InstallProgressBar.Value = 0;
try
{
LogMessage("Начинаем установку...");
// Здесь будет ваша логика установки
await SimulateInstallation();
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();
}
private async Task SimulateInstallation()
{
for (int i = 0; i <= 100; i += 10)
{
InstallProgressBar.Value = i;
LogMessage($"Прогресс: {i}%");
await Task.Delay(500); // Симуляция работы
}
}
}

View file

@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<ApplicationManifest>app.manifest</ApplicationManifest>
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="11.3.6"/>
<PackageReference Include="Avalonia.Desktop" Version="11.3.6"/>
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.3.6"/>
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.3.6"/>
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Include="Avalonia.Diagnostics" Version="11.3.6">
<IncludeAssets Condition="'$(Configuration)' != 'Debug'">None</IncludeAssets>
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
</PackageReference>
</ItemGroup>
</Project>

View file

@ -0,0 +1,21 @@
using Avalonia;
using System;
namespace MetaforceInstaller.UI;
class Program
{
// Initialization code. Don't use any Avalonia, third-party APIs or any
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
// yet and stuff might break.
[STAThread]
public static void Main(string[] args) => BuildAvaloniaApp()
.StartWithClassicDesktopLifetime(args);
// Avalonia configuration, don't remove; also used by visual designer.
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>()
.UsePlatformDetect()
.WithInterFont()
.LogToTrace();
}

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<!-- This manifest is used on Windows only.
Don't remove it as it might cause problems with window transparency and embedded controls.
For more details visit https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests -->
<assemblyIdentity version="1.0.0.0" name="MetaforceInstaller.UI.Desktop"/>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- A list of the Windows versions that this application has been tested on
and is designed to work with. Uncomment the appropriate elements
and Windows will automatically select the most compatible environment. -->
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
</assembly>

View file

@ -2,6 +2,10 @@
Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MetaforceInstaller.Cli", "MetaforceInstaller.Cli\MetaforceInstaller.Cli.csproj", "{4928C2AC-6B63-4B18-9472-705807A15893}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MetaforceInstaller.Core", "MetaforceInstaller.Core\MetaforceInstaller.Core.csproj", "{83961B6E-21E7-4B7A-B4FA-6128A44BCE1F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MetaforceInstaller.UI", "MetaforceInstaller.UI\MetaforceInstaller.UI.csproj", "{546DDE53-4607-4852-B951-434061C46FB2}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -12,5 +16,13 @@ Global
{4928C2AC-6B63-4B18-9472-705807A15893}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4928C2AC-6B63-4B18-9472-705807A15893}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4928C2AC-6B63-4B18-9472-705807A15893}.Release|Any CPU.Build.0 = Release|Any CPU
{83961B6E-21E7-4B7A-B4FA-6128A44BCE1F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{83961B6E-21E7-4B7A-B4FA-6128A44BCE1F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{83961B6E-21E7-4B7A-B4FA-6128A44BCE1F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{83961B6E-21E7-4B7A-B4FA-6128A44BCE1F}.Release|Any CPU.Build.0 = Release|Any CPU
{546DDE53-4607-4852-B951-434061C46FB2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{546DDE53-4607-4852-B951-434061C46FB2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{546DDE53-4607-4852-B951-434061C46FB2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{546DDE53-4607-4852-B951-434061C46FB2}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal