Compare commits
No commits in common. "master" and "v1.0" have entirely different histories.
26 changed files with 190 additions and 948 deletions
|
|
@ -4,21 +4,17 @@
|
|||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<PublishSingleFile>true</PublishSingleFile>
|
||||
<SelfContained>true</SelfContained>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MetaforceInstaller.Core\MetaforceInstaller.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AdvancedSharpAdbClient" Version="3.4.14" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MetaforceInstaller.Core\MetaforceInstaller.Core.csproj" />
|
||||
<EmbeddedResource Include="adb\adb.exe" />
|
||||
<EmbeddedResource Include="adb\AdbWinApi.dll" />
|
||||
<EmbeddedResource Include="adb\AdbWinUsbApi.dll" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -1,54 +1,57 @@
|
|||
using MetaforceInstaller.Cli.Utils;
|
||||
using MetaforceInstaller.Core.Services;
|
||||
using System.Reflection;
|
||||
using AdvancedSharpAdbClient;
|
||||
using AdvancedSharpAdbClient.DeviceCommands;
|
||||
using AdvancedSharpAdbClient.Models;
|
||||
|
||||
namespace MetaforceInstaller.Cli;
|
||||
|
||||
static class Program
|
||||
class Program
|
||||
{
|
||||
static async Task Main(string[] args)
|
||||
// 1. Получить имя апк и зипки, если не предоставлены - забить дефолтными значениями
|
||||
// 2. Распаковать в временную директорию adb (готово)
|
||||
// 3. Установить апк
|
||||
// 4. Получить имя пакета
|
||||
// 5. Сформировать строку пути для контента
|
||||
// 6. Копировать зип по сформированному пути
|
||||
|
||||
static AdbClient adbClient;
|
||||
static DeviceData deviceData;
|
||||
|
||||
static void Main(string[] args)
|
||||
{
|
||||
try
|
||||
{
|
||||
var installationRequest = ArgumentParser.ParseArguments(args);
|
||||
var (apkPath, zipPath, outputPath) = ParseArguments(args);
|
||||
|
||||
if (installationRequest is null ||
|
||||
string.IsNullOrEmpty(installationRequest.ApkPath) ||
|
||||
string.IsNullOrEmpty(installationRequest.ZipPath))
|
||||
if (string.IsNullOrEmpty(apkPath) || string.IsNullOrEmpty(zipPath) || string.IsNullOrEmpty(outputPath))
|
||||
{
|
||||
ShowUsage();
|
||||
return;
|
||||
}
|
||||
|
||||
var adbService = new AdbService();
|
||||
var adbPath = ExtractAdbFiles();
|
||||
|
||||
var apkInfo = ApkScrapper.GetApkInfo(installationRequest.ApkPath);
|
||||
var zipName = Path.GetFileName(installationRequest.ZipPath);
|
||||
var outputPath =
|
||||
@$"/storage/emulated/0/Android/data/{apkInfo.PackageName}/files/{zipName}";
|
||||
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, outputPath, progress);
|
||||
Console.WriteLine();
|
||||
|
||||
Console.WriteLine("Операция завершена успешно!");
|
||||
InstallApk(apkPath);
|
||||
CopyFileToDevice(zipPath, outputPath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
@ -56,29 +59,6 @@ static 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("Использование:");
|
||||
|
|
@ -98,27 +78,159 @@ static class Program
|
|||
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);
|
||||
|
||||
var barLength = 40;
|
||||
var filledLength = (int)(barLength * progress / 100.0);
|
||||
|
||||
var bar = "[" + new string('█', filledLength) + new string('░', barLength - filledLength) + "]";
|
||||
|
||||
string bytesText = "";
|
||||
if (totalBytes > 0)
|
||||
{
|
||||
bytesText = $" {FormatBytes(receivedBytes)} / {FormatBytes(totalBytes)}";
|
||||
}
|
||||
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"];
|
||||
private static string FormatBytes(long bytes)
|
||||
{
|
||||
string[] suffixes = { "B", "KB", "MB", "GB", "TB" };
|
||||
var counter = 0;
|
||||
double number = bytes;
|
||||
|
||||
|
|
@ -129,5 +241,5 @@ static class Program
|
|||
}
|
||||
|
||||
return $"{number:N1} {suffixes[counter]}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
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);
|
||||
}
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
<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>
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
namespace MetaforceInstaller.Core.Models;
|
||||
|
||||
public record ApkInfo(string PackageName, string VersionName, string VersionCode);
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
namespace MetaforceInstaller.Core.Models;
|
||||
|
||||
public record DeviceInfo(
|
||||
string SerialNumber,
|
||||
string State,
|
||||
string Model,
|
||||
string Name
|
||||
);
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
namespace MetaforceInstaller.Core.Models;
|
||||
|
||||
public class InstallationRequest
|
||||
{
|
||||
public string ApkPath { get; set; }
|
||||
public string ZipPath { get; set; }
|
||||
public string OutputPath { get; set; }
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
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
|
||||
}
|
||||
|
|
@ -1,206 +0,0 @@
|
|||
using System.Reflection;
|
||||
using AdvancedSharpAdbClient;
|
||||
using AdvancedSharpAdbClient.DeviceCommands;
|
||||
using AdvancedSharpAdbClient.Models;
|
||||
using AdvancedSharpAdbClient.Receivers;
|
||||
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
|
||||
});
|
||||
|
||||
var remoteDir = Path.GetDirectoryName(remotePath)?.Replace('\\', '/');
|
||||
if (!string.IsNullOrEmpty(remoteDir))
|
||||
{
|
||||
var reciever = new ConsoleOutputReceiver();
|
||||
await Task.Run(
|
||||
() => { _adbClient.ExecuteRemoteCommand($"mkdir -p \"{remoteDir}\"", _deviceData, reciever); },
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
_logger.LogInformation($"Ensured remote directory: {remoteDir}");
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
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");
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
<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>
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
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();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="_Слой_2" data-name="Слой 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 678.69 65.86">
|
||||
<g id="_Слой_1-2" data-name="Слой 1">
|
||||
<g>
|
||||
<polygon points="43.16 44.66 42.89 44.66 20.1 1.24 0 1.24 0 64.58 13.87 64.58 13.87 16.65 14.22 16.65 38.65 64.58 47.49 64.58 71.83 16.65 72.31 16.65 72.31 64.58 86.05 64.58 86.05 1.24 65.95 1.24 43.16 44.66"/>
|
||||
<polygon points="97.93 64.58 159.2 64.58 159.2 51.99 111.8 51.99 111.8 39.45 129.1 39.45 129.1 36.26 155.85 36.26 155.85 24.95 129.1 24.95 129.1 28.13 111.8 28.13 111.8 13.82 159.2 13.82 159.2 1.24 97.93 1.24 97.93 64.58"/>
|
||||
<polygon points="232.77 1.24 168.9 1.24 168.9 13.83 193.94 13.83 193.94 64.58 207.68 64.58 207.68 13.83 232.77 13.83 232.77 1.24"/>
|
||||
<polygon points="254.69 1.24 223.77 64.58 239.15 64.58 263.04 14 263.4 14 287.47 64.58 302.75 64.58 271.79 1.24 254.69 1.24"/>
|
||||
<path d="m445.01,9.28c-7.36-6.18-16.83-9.28-28.4-9.28s-21.06,3.09-28.45,9.28c-7.39,6.18-11.09,14.06-11.09,23.63s3.7,17.49,11.09,23.68c7.39,6.18,16.87,9.28,28.45,9.28s21.04-3.09,28.4-9.28c7.36-6.18,11.04-14.08,11.04-23.68s-3.68-17.45-11.04-23.63Zm-9.89,38.25c-4.8,3.83-10.97,5.74-18.51,5.74s-13.75-1.91-18.55-5.74c-4.83-3.86-7.24-8.73-7.24-14.62s2.41-10.78,7.24-14.58c4.8-3.8,10.98-5.7,18.55-5.7s13.68,1.9,18.51,5.7c4.8,3.8,7.2,8.66,7.2,14.58s-2.4,10.79-7.2,14.62Z"/>
|
||||
<path d="m593.41,51.9c-4.39.91-8.58,1.37-12.59,1.37-8.54,0-15.45-1.78-20.72-5.34-5.33-3.56-8-8.39-8-14.49s2.59-10.76,7.77-14.8c5.15-4,12.09-6.01,20.81-6.01,8.13.03,14.83,1.4,20.1,4.11.03-.03.6-2.09,1.72-6.18,1.09-3.98,1.63-6.05,1.63-6.23-7.16-2.88-14.84-4.33-23.06-4.33-12.99,0-23.37,3.14-31.14,9.41-7.77,6.27-11.66,14.28-11.66,24.03s3.9,17.63,11.71,23.54c7.77,5.92,18.14,8.88,31.1,8.88,9.81,0,18.13-1.72,24.96-5.17l-2.25-12.1c-2.47,1.33-5.93,2.43-10.38,3.31Z"/>
|
||||
<polygon points="307.02 31 311.21 31 311.21 64.58 325.08 64.58 325.08 42.4 367.23 42.4 367.23 31 320.89 31 320.89 13.82 367.23 13.82 367.23 1.24 307.02 1.24 307.02 31"/>
|
||||
<path d="m530.77,21.99h0c.05-.53.08-1.07.08-1.62,0-5.86-2.48-10.52-7.43-13.96-5.01-3.44-11.5-5.16-19.48-5.16h-42.14v20.75h3.88v42.59h13.87V21.98h-3.88v-8.06l29.82-.11c3.27,0,5.98.77,8.12,2.31,2.09,1.52,3.15,3.49,3.19,5.86v.14c0,2.47-1.06,4.5-3.19,6.09-2.14,1.62-4.86,2.43-8.12,2.43h-20.14v11.23h12.62l17.95,22.7h16.18l-19.2-24.29c18.08-3.97,17.63-15.7,17.88-18.3Z"/>
|
||||
<polygon points="678.69 13.82 678.69 1.24 616.36 1.24 616.36 28.21 621.5 28.21 621.5 39.45 616.36 39.45 616.36 64.58 678.69 64.58 678.69 51.99 630.23 51.99 630.23 39.45 674.27 39.45 674.27 28.13 630.23 28.13 630.23 13.82 678.69 13.82"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.6 KiB |
|
|
@ -1,31 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 25.3.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 796.5 168.5" style="enable-background:new 0 0 796.5 168.5;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
.st1{fill:#E62027;}
|
||||
</style>
|
||||
<g id="Guides_For_Artboard">
|
||||
</g>
|
||||
<g id="Layer_1">
|
||||
<rect class="st0" width="796.5" height="168.5"/>
|
||||
<g>
|
||||
<path class="st1" d="M478.5,42.2c-3,0-8.1,0.6-8.1,7.2v68.7c0,6.6,5.1,8,8.1,8c4.4,0,8.1-2.7,8.1-8V49.4
|
||||
C486.6,44.1,482.9,42.2,478.5,42.2z"/>
|
||||
<path class="st1" d="M332.6,39.4c1.2-2.2-0.4-4.8-2.9-4.8c-2.5,0-4.1,2.6-2.9,4.8l1.1,2.1l-6.1,63.1h14.6l-4.8-63.1L332.6,39.4z"
|
||||
/>
|
||||
<path class="st1" d="M0,0v168.5h796.5V0H0z M139.3,141.2h-14.4V76.5l-17.1,64.7H93.4L76.6,76.5v64.7H62.2V28.1h0.2h14.3h0.1
|
||||
l23.9,86.5l24.1-86.5h0.1h14.3h0.2V141.2z M206.6,76.5l-4.2,13.7h-17.8v37.1h18.1l4.2,14h-36.9V28.1l36.9,0.3l-4.2,13.9h-18.1
|
||||
v34.2H206.6z M278.2,42.3H264v98.9h-16.8V42.3h-14.1V28.4h45.1V42.3z M339.3,141.2l-1.6-22.8h-17.3l-2.4,22.8h-13.8l10.5-112h30.1
|
||||
l10.4,112H339.3z M425.4,77.2l-4.2,13.7h-18v50.3h-14.7V28.4h36.9l-4.2,13.9h-18v34.9H425.4z M502.3,49.3v69v0.1
|
||||
c0,14.9-7.8,21.8-23.6,21.8c-15.9,0-24-6.9-24-21.8v-0.1v-69v-0.1c0-14.9,8.2-21.8,24-21.8c15.9,0,23.6,6.9,23.6,21.8V49.3z
|
||||
M566.8,141.2l-7.9-48.1c4.8-0.3,7.5-2.3,7.5-8.1V50.2c0-6.2-3.1-8-8.6-8h-8.1v99h-14.5V28.4h27c16.4,0,19.2,6.7,19.2,21.8V84
|
||||
c0,7.5-2.2,12.9-6.7,16.4l-0.3,0.2l7.3,40.6H566.8z M660.8,75.8L645.5,71h-0.1V49.3c0-3.3-1.4-5.6-3.5-6.9
|
||||
c-1.5-0.8-3.1-1.1-4.4-1.1c-2.5,0-8.2,1-8.2,8v69.1c0,2.8,0.9,4.6,2.2,5.9c1.9,1.7,4.4,2.1,5.9,2.1c3,0,8-1.4,8-8V96.4l15.3-4.5
|
||||
v0.4l0.1,26.3c0,14.9-7.4,21.8-23.3,21.8c-11.4,0-18.4-3.6-21.5-11.1c-1.3-3-1.9-6.6-1.9-10.8V49.1c0-14.9,7.4-21.8,23.3-21.8
|
||||
c10,0,16.7,2.8,20.2,8.6c2.1,3.4,3.2,7.8,3.2,13.3V75.8z M733.9,76.5l-4.2,13.7h-17.8v37.1h18.1l4.2,14h-36.9V28.4h36.9l-4.2,13.9
|
||||
h-18.1v34.2H733.9z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2 KiB |
|
|
@ -1,23 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="_Слой_2" data-name="Слой 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 678.69 65.86">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: #fff;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<g id="_Слой_1-2" data-name="Слой 1">
|
||||
<g>
|
||||
<polygon class="cls-1" points="43.16 44.66 42.89 44.66 20.1 1.24 0 1.24 0 64.58 13.87 64.58 13.87 16.65 14.22 16.65 38.65 64.58 47.49 64.58 71.83 16.65 72.31 16.65 72.31 64.58 86.05 64.58 86.05 1.24 65.95 1.24 43.16 44.66"/>
|
||||
<polygon class="cls-1" points="97.93 64.58 159.2 64.58 159.2 51.99 111.8 51.99 111.8 39.45 129.1 39.45 129.1 36.26 155.85 36.26 155.85 24.95 129.1 24.95 129.1 28.13 111.8 28.13 111.8 13.82 159.2 13.82 159.2 1.24 97.93 1.24 97.93 64.58"/>
|
||||
<polygon class="cls-1" points="232.77 1.24 168.9 1.24 168.9 13.83 193.94 13.83 193.94 64.58 207.68 64.58 207.68 13.83 232.77 13.83 232.77 1.24"/>
|
||||
<polygon class="cls-1" points="254.69 1.24 223.77 64.58 239.15 64.58 263.04 14 263.4 14 287.47 64.58 302.75 64.58 271.79 1.24 254.69 1.24"/>
|
||||
<path class="cls-1" d="m445.01,9.28c-7.36-6.18-16.83-9.28-28.4-9.28s-21.06,3.09-28.45,9.28c-7.39,6.18-11.09,14.06-11.09,23.63s3.7,17.49,11.09,23.68c7.39,6.18,16.87,9.28,28.45,9.28s21.04-3.09,28.4-9.28c7.36-6.18,11.04-14.08,11.04-23.68s-3.68-17.45-11.04-23.63Zm-9.89,38.25c-4.8,3.83-10.97,5.74-18.51,5.74s-13.75-1.91-18.55-5.74c-4.83-3.86-7.24-8.73-7.24-14.62s2.41-10.78,7.24-14.58c4.8-3.8,10.98-5.7,18.55-5.7s13.68,1.9,18.51,5.7c4.8,3.8,7.2,8.66,7.2,14.58s-2.4,10.79-7.2,14.62Z"/>
|
||||
<path class="cls-1" d="m593.41,51.9c-4.39.91-8.58,1.37-12.59,1.37-8.54,0-15.45-1.78-20.72-5.34-5.33-3.56-8-8.39-8-14.49s2.59-10.76,7.77-14.8c5.15-4,12.09-6.01,20.81-6.01,8.13.03,14.83,1.4,20.1,4.11.03-.03.6-2.09,1.72-6.18,1.09-3.98,1.63-6.05,1.63-6.23-7.16-2.88-14.84-4.33-23.06-4.33-12.99,0-23.37,3.14-31.14,9.41-7.77,6.27-11.66,14.28-11.66,24.03s3.9,17.63,11.71,23.54c7.77,5.92,18.14,8.88,31.1,8.88,9.81,0,18.13-1.72,24.96-5.17l-2.25-12.1c-2.47,1.33-5.93,2.43-10.38,3.31Z"/>
|
||||
<polygon class="cls-1" points="307.02 31 311.21 31 311.21 64.58 325.08 64.58 325.08 42.4 367.23 42.4 367.23 31 320.89 31 320.89 13.82 367.23 13.82 367.23 1.24 307.02 1.24 307.02 31"/>
|
||||
<path class="cls-1" d="m530.77,21.99h0c.05-.53.08-1.07.08-1.62,0-5.86-2.48-10.52-7.43-13.96-5.01-3.44-11.5-5.16-19.48-5.16h-42.14v20.75h3.88v42.59h13.87V21.98h-3.88v-8.06l29.82-.11c3.27,0,5.98.77,8.12,2.31,2.09,1.52,3.15,3.49,3.19,5.86v.14c0,2.47-1.06,4.5-3.19,6.09-2.14,1.62-4.86,2.43-8.12,2.43h-20.14v11.23h12.62l17.95,22.7h16.18l-19.2-24.29c18.08-3.97,17.63-15.7,17.88-18.3Z"/>
|
||||
<polygon class="cls-1" points="678.69 13.82 678.69 1.24 616.36 1.24 616.36 28.21 621.5 28.21 621.5 39.45 616.36 39.45 616.36 64.58 678.69 64.58 678.69 51.99 630.23 51.99 630.23 39.45 674.27 39.45 674.27 28.13 630.23 28.13 630.23 13.82 678.69 13.82"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.8 KiB |
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<SelfContained>true</SelfContained>
|
||||
<PublishSingleFile>true</PublishSingleFile>
|
||||
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
|
||||
<FileVersion>1.2.1</FileVersion>
|
||||
</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>
|
||||
<PackageReference Include="Svg.Controls.Skia.Avalonia" Version="11.3.0.4" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MetaforceInstaller.Core\MetaforceInstaller.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AvaloniaResource Include="Images\logo_black.svg" />
|
||||
<AvaloniaResource Include="Images\logo_white.svg" />
|
||||
<AvaloniaResource Include="Images\logo_red.svg" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
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();
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
<?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,10 +2,6 @@
|
|||
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
|
||||
|
|
@ -16,13 +12,5 @@ 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
|
||||
|
|
|
|||
15
README.md
15
README.md
|
|
@ -1,15 +0,0 @@
|
|||
# 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