WIP: move logic to Core project

This commit is contained in:
Вячеслав 2025-09-15 00:12:36 +05:00
parent 54e04da524
commit 7a7cefe7b5
12 changed files with 267 additions and 200 deletions

View file

@ -12,9 +12,7 @@
</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>

View file

@ -1,57 +1,53 @@
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. Получить имя апк и зипки, если не предоставлены - забить дефолтными значениями
// 2. Распаковать в временную директорию adb (готово)
// 3. Установить апк
// 4. Получить имя пакета
// 5. Сформировать строку пути для контента
// 6. Копировать зип по сформированному пути
static AdbClient adbClient;
static DeviceData deviceData;
static void Main(string[] args) 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) ||
string.IsNullOrEmpty(installationRequest.OutputPath))
{ {
ShowUsage(); ShowUsage();
return; return;
} }
var adbPath = ExtractAdbFiles(); var adbService = new AdbService();
var server = new AdbServer(); adbService.InstallApk(installationRequest.ApkPath);
var result = server.StartServer(adbPath, restartServerIfNewer: false); adbService.CopyFile(installationRequest.ZipPath, installationRequest.OutputPath);
Console.WriteLine($"ADB сервер запущен: {result}");
adbClient = new AdbClient(); // var adbPath = ExtractAdbFiles();
//
var devices = adbClient.GetDevices(); // var server = new AdbServer();
// var result = server.StartServer(adbPath, restartServerIfNewer: false);
if (!devices.Any()) // Console.WriteLine($"ADB сервер запущен: {result}");
{ //
Console.WriteLine("Устройства не найдены. Подключите Android-устройство и включите отладку по USB."); // adbClient = new AdbClient();
return; //
} // var devices = adbClient.GetDevices();
//
deviceData = devices.FirstOrDefault(); // if (!devices.Any())
Console.WriteLine($"Найдено устройство: {deviceData.Serial}"); // {
Console.WriteLine($"Состояние: {deviceData.State}"); // Console.WriteLine("Устройства не найдены. Подключите Android-устройство и включите отладку по USB.");
Console.WriteLine($"Имя устройства: {deviceData.Name} - {deviceData.Model}"); // return;
// }
InstallApk(apkPath); //
CopyFileToDevice(zipPath, outputPath); // deviceData = devices.FirstOrDefault();
// Console.WriteLine($"Найдено устройство: {deviceData.Serial}");
// Console.WriteLine($"Состояние: {deviceData.State}");
// Console.WriteLine($"Имя устройства: {deviceData.Name} - {deviceData.Model}");
//
// InstallApk(installationRequest.ApkPath);
// CopyFileToDevice(installationRequest.ZipPath, installationRequest.OutputPath);
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -78,168 +74,31 @@ 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; Console.SetCursorPosition(0, Console.CursorTop);
string zipPath = null;
string outputPath = null;
for (int i = 0; i < args.Length; i++) var barLength = 40;
{ var filledLength = (int)(barLength * progress / 100.0);
switch (args[i].ToLower())
{
case "--apk":
case "-a":
if (i + 1 < args.Length)
{
apkPath = args[i + 1];
i++;
}
break; var bar = "[" + new string('█', filledLength) + new string('░', barLength - filledLength) + "]";
var bytesText = $" {FormatBytes(receivedBytes)} / {FormatBytes(totalBytes)}";
case "--content": Console.Write($"\r{bar} {progress}%{bytesText}");
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) private static string FormatBytes(long bytes)
{ {
using var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName); string[] suffixes = ["B", "KB", "MB", "GB", "TB"];
using var fileStream = File.Create(outputPath); var counter = 0;
stream.CopyTo(fileStream); double number = bytes;
}
private static string ExtractAdbFiles() while (Math.Round(number / 1024) >= 1)
{
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); number /= 1024;
ExtractResource("MetaforceInstaller.Cli.adb.AdbWinApi.dll", Path.Combine(tempDir, "AdbWinApi.dll")); counter++;
ExtractResource("MetaforceInstaller.Cli.adb.AdbWinUsbApi.dll", Path.Combine(tempDir, "AdbWinUsbApi.dll"));
} }
return adbPath; return $"{number:N1} {suffixes[counter]}";
}
private static void InstallApk(string apkPath)
{
try
{
if (!File.Exists(apkPath))
{
Console.WriteLine($"APK файл не найден: {apkPath}");
return;
}
Console.WriteLine($"Установка APK: {apkPath}");
var packageManager = new PackageManager(adbClient, deviceData);
packageManager.InstallPackage(apkPath, new Action<InstallProgressEventArgs>(o => { }));
Console.WriteLine("APK успешно установлен!");
}
catch (Exception ex)
{
Console.WriteLine($"Ошибка установки APK: {ex.Message}");
}
}
private static void CopyFileToDevice(string localPath, string remotePath)
{
try
{
if (!File.Exists(localPath))
{
Console.WriteLine($"Локальный файл не найден: {localPath}");
return;
}
Console.WriteLine($"Копирование файла {localPath} в {remotePath}");
var lastProgress = -1;
using var fileStream = File.OpenRead(localPath);
var syncService = new SyncService(adbClient, deviceData);
syncService.Push(fileStream, remotePath, UnixFileStatus.DefaultFileMode, DateTime.Now,
new Action<SyncProgressChangedEventArgs>(progress =>
{
var currentProgress = progress.ProgressPercentage;
if (currentProgress != lastProgress)
{
lastProgress = (int)currentProgress;
DrawProgressBar(lastProgress, progress.ReceivedBytesSize, progress.TotalBytesToReceive);
}
}));
Console.WriteLine();
Console.WriteLine("Файл успешно скопирован!");
}
catch (Exception ex)
{
Console.WriteLine($"Ошибка копирования файла: {ex.Message}");
} }
} }
private static void DrawProgressBar(int progress, long receivedBytes, long totalBytes)
{
Console.SetCursorPosition(0, Console.CursorTop);
var barLength = 40;
var filledLength = (int)(barLength * progress / 100.0);
var bar = "[" + new string('█', filledLength) + new string('░', barLength - filledLength) + "]";
var bytesText = $" {FormatBytes(receivedBytes)} / {FormatBytes(totalBytes)}";
Console.Write($"\r{bar} {progress}%{bytesText}");
}
private static string FormatBytes(long bytes)
{
string[] suffixes = { "B", "KB", "MB", "GB", "TB" };
var counter = 0;
double number = bytes;
while (Math.Round(number / 1024) >= 1)
{
number /= 1024;
counter++;
}
return $"{number:N1} {suffixes[counter]}";
}
}

View file

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

View file

@ -0,0 +1,10 @@
using MetaforceInstaller.Core.Models;
namespace MetaforceInstaller.Core.Intefaces;
public interface IAdbService
{
public void InstallApk(string apkPath);
public void CopyFile(string localPath, string remotePath);
public DeviceInfo GetDeviceInfo();
}

View file

@ -0,0 +1,19 @@
<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="AdvancedSharpAdbClient" Version="3.4.14" />
</ItemGroup>
</Project>

View file

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

View file

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

View file

@ -0,0 +1,106 @@
using System.Reflection;
using AdvancedSharpAdbClient;
using AdvancedSharpAdbClient.DeviceCommands;
using AdvancedSharpAdbClient.Logs;
using AdvancedSharpAdbClient.Models;
using MetaforceInstaller.Core.Intefaces;
using MetaforceInstaller.Core.Models;
namespace MetaforceInstaller.Core.Services;
public class AdbService : IAdbService
{
private ILogger<AdbService> _logger;
private readonly AdbClient _adbClient;
private DeviceData _deviceData;
public AdbService()
{
var adbPath = GetAdbPath();
var server = new AdbServer();
var serverStatus = server.StartServer(adbPath, restartServerIfNewer: false);
_adbClient = new AdbClient();
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.GetExecutingAssembly().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.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;
}
public void InstallApk(string apkPath)
{
try
{
if (!File.Exists(apkPath))
{
_logger.LogCritical("Error: Could not find APK file.");
return;
}
_logger.LogInformation($"Installing APK: {apkPath}");
var packageManager = new PackageManager(_adbClient, _deviceData);
packageManager.InstallPackage(apkPath, o => { });
_logger.LogInformation("APK successfully installed!");
}
catch (Exception ex)
{
_logger.LogCritical($"Error: {ex.Message}");
throw;
}
}
public void CopyFile(string localPath, string remotePath)
{
try
{
if (!File.Exists(localPath))
{
_logger.LogCritical($"Error: Could not find file: {localPath}");
return;
}
_logger.LogInformation($"Copying file: {localPath} to {remotePath}");
using var fileStream = File.OpenRead(localPath);
var syncService = new SyncService(_adbClient, _deviceData);
syncService.Push(fileStream, remotePath, UnixFileStatus.DefaultFileMode, DateTime.Now,
new Action<SyncProgressChangedEventArgs>(progress => { }));
_logger.LogInformation("File successfully copied!");
}
catch (Exception ex)
{
_logger.LogCritical($"Error: {ex.Message}");
throw;
}
}
public DeviceInfo GetDeviceInfo()
{
return new DeviceInfo(_deviceData.Serial, _deviceData.State.ToString(), _deviceData.Model, _deviceData.Name);
}
}

View file

@ -2,6 +2,8 @@
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
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -12,5 +14,9 @@ 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
EndGlobalSection EndGlobalSection
EndGlobal EndGlobal