Merge pull request 'move logic to Core project and create UI project with UI lol' (#1) from unstable into master
Reviewed-on: #1
This commit is contained in:
commit
fff5432e2a
23 changed files with 832 additions and 190 deletions
|
|
@ -7,14 +7,16 @@
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\MetaforceInstaller.Core\MetaforceInstaller.Core.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="AdvancedSharpAdbClient" Version="3.4.14" />
|
<PackageReference Include="AdvancedSharpAdbClient" Version="3.4.14" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<EmbeddedResource Include="adb\adb.exe" />
|
<ProjectReference Include="..\MetaforceInstaller.Core\MetaforceInstaller.Core.csproj" />
|
||||||
<EmbeddedResource Include="adb\AdbWinApi.dll" />
|
|
||||||
<EmbeddedResource Include="adb\AdbWinUsbApi.dll" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
|
|
@ -1,57 +1,54 @@
|
||||||
using System.Reflection;
|
using MetaforceInstaller.Cli.Utils;
|
||||||
using AdvancedSharpAdbClient;
|
using MetaforceInstaller.Core.Services;
|
||||||
using AdvancedSharpAdbClient.DeviceCommands;
|
|
||||||
using AdvancedSharpAdbClient.Models;
|
|
||||||
|
|
||||||
namespace MetaforceInstaller.Cli;
|
namespace MetaforceInstaller.Cli;
|
||||||
|
|
||||||
class Program
|
static class Program
|
||||||
{
|
{
|
||||||
// 1. Получить имя апк и зипки, если не предоставлены - забить дефолтными значениями
|
static async Task Main(string[] args)
|
||||||
// 2. Распаковать в временную директорию adb (готово)
|
|
||||||
// 3. Установить апк
|
|
||||||
// 4. Получить имя пакета
|
|
||||||
// 5. Сформировать строку пути для контента
|
|
||||||
// 6. Копировать зип по сформированному пути
|
|
||||||
|
|
||||||
static AdbClient adbClient;
|
|
||||||
static DeviceData deviceData;
|
|
||||||
|
|
||||||
static void Main(string[] args)
|
|
||||||
{
|
{
|
||||||
try
|
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))
|
||||||
{
|
{
|
||||||
ShowUsage();
|
ShowUsage();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var adbPath = ExtractAdbFiles();
|
var adbService = new AdbService();
|
||||||
|
|
||||||
var server = new AdbServer();
|
var apkInfo = ApkScrapper.GetApkInfo(installationRequest.ApkPath);
|
||||||
var result = server.StartServer(adbPath, restartServerIfNewer: false);
|
var zipName = Path.GetFileName(installationRequest.ZipPath);
|
||||||
Console.WriteLine($"ADB сервер запущен: {result}");
|
var outputPath =
|
||||||
|
@$"/storage/emulated/0/Android/data/{apkInfo.PackageName}/files/{zipName}";
|
||||||
|
|
||||||
adbClient = new AdbClient();
|
// Подписка на события прогресса
|
||||||
|
adbService.ProgressChanged += OnProgressChanged;
|
||||||
|
adbService.StatusChanged += OnStatusChanged;
|
||||||
|
|
||||||
var devices = adbClient.GetDevices();
|
// Получение информации об устройстве
|
||||||
|
var deviceInfo = adbService.GetDeviceInfo();
|
||||||
|
Console.WriteLine($"Найдено устройство: {deviceInfo.SerialNumber}");
|
||||||
|
Console.WriteLine($"Состояние: {deviceInfo.State}");
|
||||||
|
Console.WriteLine($"Модель: {deviceInfo.Model} - {deviceInfo.Name}");
|
||||||
|
Console.WriteLine();
|
||||||
|
|
||||||
if (!devices.Any())
|
// Создание объекта для отслеживания прогресса
|
||||||
{
|
var progress = new Progress<MetaforceInstaller.Core.Models.ProgressInfo>(OnProgressReport);
|
||||||
Console.WriteLine("Устройства не найдены. Подключите Android-устройство и включите отладку по USB.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
deviceData = devices.FirstOrDefault();
|
// Установка APK
|
||||||
Console.WriteLine($"Найдено устройство: {deviceData.Serial}");
|
await adbService.InstallApkAsync(installationRequest.ApkPath, progress);
|
||||||
Console.WriteLine($"Состояние: {deviceData.State}");
|
Console.WriteLine();
|
||||||
Console.WriteLine($"Имя устройства: {deviceData.Name} - {deviceData.Model}");
|
|
||||||
|
|
||||||
InstallApk(apkPath);
|
// Копирование файла
|
||||||
CopyFileToDevice(zipPath, outputPath);
|
await adbService.CopyFileAsync(installationRequest.ZipPath, outputPath, progress);
|
||||||
|
Console.WriteLine();
|
||||||
|
|
||||||
|
Console.WriteLine("Операция завершена успешно!");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
@ -59,6 +56,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()
|
static void ShowUsage()
|
||||||
{
|
{
|
||||||
Console.WriteLine("Использование:");
|
Console.WriteLine("Использование:");
|
||||||
|
|
@ -78,159 +98,27 @@ class Program
|
||||||
Console.WriteLine(" MetaforceInstaller.exe -a app.apk -c data.zip -o /sdcard/data.zip");
|
Console.WriteLine(" MetaforceInstaller.exe -a app.apk -c data.zip -o /sdcard/data.zip");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void DrawProgressBar(int progress, long receivedBytes, long totalBytes)
|
||||||
private static (string? apkPath, string? zipPath, string? outputPath) ParseArguments(string[] args)
|
|
||||||
{
|
{
|
||||||
string apkPath = null;
|
|
||||||
string zipPath = null;
|
|
||||||
string outputPath = null;
|
|
||||||
|
|
||||||
for (int i = 0; i < args.Length; i++)
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (apkPath, zipPath, outputPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ExtractResource(string resourceName, string outputPath)
|
|
||||||
{
|
|
||||||
using var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName);
|
|
||||||
using var fileStream = File.Create(outputPath);
|
|
||||||
stream.CopyTo(fileStream);
|
|
||||||
}
|
|
||||||
|
|
||||||
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))
|
|
||||||
{
|
|
||||||
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"));
|
|
||||||
}
|
|
||||||
|
|
||||||
return adbPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
Console.SetCursorPosition(0, Console.CursorTop);
|
||||||
|
|
||||||
var barLength = 40;
|
var barLength = 40;
|
||||||
var filledLength = (int)(barLength * progress / 100.0);
|
var filledLength = (int)(barLength * progress / 100.0);
|
||||||
|
|
||||||
var bar = "[" + new string('█', filledLength) + new string('░', barLength - filledLength) + "]";
|
var bar = "[" + new string('█', filledLength) + new string('░', barLength - filledLength) + "]";
|
||||||
var bytesText = $" {FormatBytes(receivedBytes)} / {FormatBytes(totalBytes)}";
|
|
||||||
|
string bytesText = "";
|
||||||
|
if (totalBytes > 0)
|
||||||
|
{
|
||||||
|
bytesText = $" {FormatBytes(receivedBytes)} / {FormatBytes(totalBytes)}";
|
||||||
|
}
|
||||||
|
|
||||||
Console.Write($"\r{bar} {progress}%{bytesText}");
|
Console.Write($"\r{bar} {progress}%{bytesText}");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string FormatBytes(long bytes)
|
private static string FormatBytes(long bytes)
|
||||||
{
|
{
|
||||||
string[] suffixes = { "B", "KB", "MB", "GB", "TB" };
|
string[] suffixes = ["B", "KB", "MB", "GB", "TB"];
|
||||||
var counter = 0;
|
var counter = 0;
|
||||||
double number = bytes;
|
double number = bytes;
|
||||||
|
|
||||||
|
|
@ -241,5 +129,5 @@ private static string FormatBytes(long bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
return $"{number:N1} {suffixes[counter]}";
|
return $"{number:N1} {suffixes[counter]}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
53
MetaforceInstaller.Cli/Utils/ArgumentParser.cs
Normal file
53
MetaforceInstaller.Cli/Utils/ArgumentParser.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
18
MetaforceInstaller.Core/Intefaces/IAdbService.cs
Normal file
18
MetaforceInstaller.Core/Intefaces/IAdbService.cs
Normal 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);
|
||||||
|
}
|
||||||
26
MetaforceInstaller.Core/MetaforceInstaller.Core.csproj
Normal file
26
MetaforceInstaller.Core/MetaforceInstaller.Core.csproj
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
<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="AlphaOmega.ApkReader" Version="2.0.5" />
|
||||||
|
<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>
|
||||||
3
MetaforceInstaller.Core/Models/ApkInfo.cs
Normal file
3
MetaforceInstaller.Core/Models/ApkInfo.cs
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
namespace MetaforceInstaller.Core.Models;
|
||||||
|
|
||||||
|
public record ApkInfo(string PackageName, string VersionName, string VersionCode);
|
||||||
8
MetaforceInstaller.Core/Models/DeviceInfo.cs
Normal file
8
MetaforceInstaller.Core/Models/DeviceInfo.cs
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
namespace MetaforceInstaller.Core.Models;
|
||||||
|
|
||||||
|
public record DeviceInfo(
|
||||||
|
string SerialNumber,
|
||||||
|
string State,
|
||||||
|
string Model,
|
||||||
|
string Name
|
||||||
|
);
|
||||||
8
MetaforceInstaller.Core/Models/InstallationRequest.cs
Normal file
8
MetaforceInstaller.Core/Models/InstallationRequest.cs
Normal 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; }
|
||||||
|
}
|
||||||
19
MetaforceInstaller.Core/Models/ProgressInfo.cs
Normal file
19
MetaforceInstaller.Core/Models/ProgressInfo.cs
Normal 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
|
||||||
|
}
|
||||||
194
MetaforceInstaller.Core/Services/AdbService.cs
Normal file
194
MetaforceInstaller.Core/Services/AdbService.cs
Normal file
|
|
@ -0,0 +1,194 @@
|
||||||
|
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();
|
||||||
|
RefreshDeviceData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RefreshDeviceData()
|
||||||
|
{
|
||||||
|
var devices = _adbClient.GetDevices();
|
||||||
|
_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);
|
||||||
|
}
|
||||||
|
}
|
||||||
19
MetaforceInstaller.Core/Services/ApkScrapper.cs
Normal file
19
MetaforceInstaller.Core/Services/ApkScrapper.cs
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
using AlphaOmega.Debug;
|
||||||
|
using MetaforceInstaller.Core.Models;
|
||||||
|
|
||||||
|
namespace MetaforceInstaller.Core.Services;
|
||||||
|
|
||||||
|
public static class ApkScrapper
|
||||||
|
{
|
||||||
|
public static ApkInfo GetApkInfo(string apkPath)
|
||||||
|
{
|
||||||
|
using var apk = new ApkFile(apkPath);
|
||||||
|
if (apk is { IsValid: true, AndroidManifest: not null })
|
||||||
|
{
|
||||||
|
return new ApkInfo(apk.AndroidManifest.Package, apk.AndroidManifest.VersionName,
|
||||||
|
apk.AndroidManifest.VersionCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Exception("Invalid APK file");
|
||||||
|
}
|
||||||
|
}
|
||||||
10
MetaforceInstaller.UI/App.axaml
Normal file
10
MetaforceInstaller.UI/App.axaml
Normal 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>
|
||||||
23
MetaforceInstaller.UI/App.axaml.cs
Normal file
23
MetaforceInstaller.UI/App.axaml.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
49
MetaforceInstaller.UI/MainWindow.axaml
Normal file
49
MetaforceInstaller.UI/MainWindow.axaml
Normal 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>
|
||||||
230
MetaforceInstaller.UI/MainWindow.axaml.cs
Normal file
230
MetaforceInstaller.UI/MainWindow.axaml.cs
Normal file
|
|
@ -0,0 +1,230 @@
|
||||||
|
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("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();
|
||||||
|
}
|
||||||
|
}
|
||||||
26
MetaforceInstaller.UI/MetaforceInstaller.UI.csproj
Normal file
26
MetaforceInstaller.UI/MetaforceInstaller.UI.csproj
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\MetaforceInstaller.Core\MetaforceInstaller.Core.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
21
MetaforceInstaller.UI/Program.cs
Normal file
21
MetaforceInstaller.UI/Program.cs
Normal 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();
|
||||||
|
}
|
||||||
18
MetaforceInstaller.UI/app.manifest
Normal file
18
MetaforceInstaller.UI/app.manifest
Normal 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>
|
||||||
|
|
@ -2,6 +2,10 @@
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
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}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MetaforceInstaller.Cli", "MetaforceInstaller.Cli\MetaforceInstaller.Cli.csproj", "{4928C2AC-6B63-4B18-9472-705807A15893}"
|
||||||
EndProject
|
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
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
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}.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.ActiveCfg = Release|Any CPU
|
||||||
{4928C2AC-6B63-4B18-9472-705807A15893}.Release|Any CPU.Build.0 = 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
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
|
|
||||||
15
README.md
Normal file
15
README.md
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
# MetaforceInstaller
|
||||||
|
|
||||||
|
Написал программку MetaforceInstaller для установки новых версий с контентом в отдельном файле, вместо перетаскивания зипки и установки апк из шлема просто нажать две кнопки
|
||||||
|
|
||||||
|
Инструкция:
|
||||||
|
1. Открываете консольку (Win + R, вписать powershell, нажать Enter)
|
||||||
|
2. Переходите в директорию с MetaforceInstaller
|
||||||
|
3. Пишете команду
|
||||||
|
```
|
||||||
|
MetaforceInstaller.exe -a <путь_к_apk> -c <путь_к_zip>
|
||||||
|
```
|
||||||
|
4. Жмете Enter
|
||||||
|
5. Ждете пару минут и радуетесь!!
|
||||||
|
|
||||||
|
Также работает и для планшетной админки, просто укажите в аргументе -a путь к файлу с админкой
|
||||||
Loading…
Add table
Add a link
Reference in a new issue