diff --git a/MetaforceInstaller.Cli/MetaforceInstaller.Cli.csproj b/MetaforceInstaller.Cli/MetaforceInstaller.Cli.csproj
deleted file mode 100644
index a267acc..0000000
--- a/MetaforceInstaller.Cli/MetaforceInstaller.Cli.csproj
+++ /dev/null
@@ -1,24 +0,0 @@
-
-
-
- Exe
- net8.0
- enable
- true
- true
- enable
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/MetaforceInstaller.Cli/Program.cs b/MetaforceInstaller.Cli/Program.cs
deleted file mode 100644
index bf8bf51..0000000
--- a/MetaforceInstaller.Cli/Program.cs
+++ /dev/null
@@ -1,133 +0,0 @@
-using MetaforceInstaller.Cli.Utils;
-using MetaforceInstaller.Core.Services;
-
-namespace MetaforceInstaller.Cli;
-
-static class Program
-{
- static async Task Main(string[] args)
- {
- try
- {
- var installationRequest = ArgumentParser.ParseArguments(args);
-
- if (installationRequest is null ||
- string.IsNullOrEmpty(installationRequest.ApkPath) ||
- string.IsNullOrEmpty(installationRequest.ZipPath))
- {
- ShowUsage();
- return;
- }
-
- var adbService = new AdbService();
-
- var apkInfo = ApkScrapper.GetApkInfo(installationRequest.ApkPath);
- var zipName = Path.GetFileName(installationRequest.ZipPath);
- var outputPath =
- @$"/storage/emulated/0/Android/data/{apkInfo.PackageName}/files/{zipName}";
-
- // Подписка на события прогресса
- adbService.ProgressChanged += OnProgressChanged;
- adbService.StatusChanged += OnStatusChanged;
-
- // Получение информации об устройстве
- var deviceInfo = adbService.GetDeviceInfo();
- Console.WriteLine($"Найдено устройство: {deviceInfo.SerialNumber}");
- Console.WriteLine($"Состояние: {deviceInfo.State}");
- Console.WriteLine($"Модель: {deviceInfo.Model} - {deviceInfo.Name}");
- Console.WriteLine();
-
- // Создание объекта для отслеживания прогресса
- var progress = new Progress(OnProgressReport);
-
- // Установка APK
- await adbService.InstallApkAsync(installationRequest.ApkPath, progress);
- Console.WriteLine();
-
- // Копирование файла
- await adbService.CopyFileAsync(installationRequest.ZipPath, outputPath, progress);
- Console.WriteLine();
-
- Console.WriteLine("Операция завершена успешно!");
- }
- catch (Exception ex)
- {
- Console.WriteLine($"Ошибка: {ex.Message}");
- }
- }
-
- 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("Использование:");
- Console.WriteLine(
- " MetaforceInstaller.exe --apk <путь_к_apk> --content <путь_к_zip> --output <путь_для контента>");
- Console.WriteLine(" MetaforceInstaller.exe -a <путь_к_apk> -c <путь_к_zip> -o <путь_для_контента>");
- Console.WriteLine();
- Console.WriteLine("Параметры:");
- Console.WriteLine(" --apk, -a Путь к APK файлу");
- Console.WriteLine(" --content, -c Путь к ZIP файлу с контентом");
- Console.WriteLine(" --output, -o Путь для копирования контента");
- Console.WriteLine(" --help, -h Показать эту справку");
- Console.WriteLine();
- Console.WriteLine("Пример:");
- Console.WriteLine(
- " MetaforceInstaller.exe --apk \"C:\\app.apk\" --content \"C:\\data.zip\" --output \"/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)
- {
- 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)}";
- }
-
- 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]}";
- }
-}
\ No newline at end of file
diff --git a/MetaforceInstaller.Cli/Utils/ArgumentParser.cs b/MetaforceInstaller.Cli/Utils/ArgumentParser.cs
deleted file mode 100644
index ffdb561..0000000
--- a/MetaforceInstaller.Cli/Utils/ArgumentParser.cs
+++ /dev/null
@@ -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;
- }
-}
\ No newline at end of file
diff --git a/MetaforceInstaller.Cloud/ICloudService.cs b/MetaforceInstaller.Cloud/ICloudService.cs
new file mode 100644
index 0000000..a179bba
--- /dev/null
+++ b/MetaforceInstaller.Cloud/ICloudService.cs
@@ -0,0 +1,8 @@
+using MetaforceInstaller.Cloud.Models;
+
+namespace MetaforceInstaller.Cloud;
+
+public interface ICloudService
+{
+ Task> GetObjects(string webLink);
+}
\ No newline at end of file
diff --git a/MetaforceInstaller.Cloud/Implementations/MailRu/DTO/CloudObjectResponse.cs b/MetaforceInstaller.Cloud/Implementations/MailRu/DTO/CloudObjectResponse.cs
new file mode 100644
index 0000000..7f2bdb3
--- /dev/null
+++ b/MetaforceInstaller.Cloud/Implementations/MailRu/DTO/CloudObjectResponse.cs
@@ -0,0 +1,181 @@
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace MetaforceInstaller.Cloud.Implementations.MailRu.DTO;
+
+public sealed class CloudObjectsResponse
+{
+ [JsonPropertyName("count")]
+ public CountInfo? Count { get; init; }
+
+ [JsonPropertyName("name")]
+ public string? Name { get; init; }
+
+ [JsonPropertyName("weblink")]
+ public string? WebLink { get; init; }
+
+ [JsonPropertyName("size")]
+ public long? Size { get; init; }
+
+ [JsonPropertyName("rev")]
+ public long? Rev { get; init; }
+
+ [JsonPropertyName("kind")]
+ public string? Kind { get; init; }
+
+ [JsonPropertyName("type")]
+ public string? Type { get; init; }
+
+ [JsonPropertyName("public")]
+ public PublicInfo? Public { get; init; }
+
+ [JsonPropertyName("list")]
+ public List? List { get; init; }
+
+ [JsonPropertyName("owner")]
+ public OwnerInfo? Owner { get; init; }
+}
+
+public sealed class CountInfo
+{
+ [JsonPropertyName("folders")]
+ public int? Folders { get; init; }
+
+ [JsonPropertyName("files")]
+ public int? Files { get; init; }
+}
+
+public sealed class PublicInfo
+{
+ [JsonPropertyName("type")]
+ public string? Type { get; init; }
+
+ [JsonPropertyName("name")]
+ public string? Name { get; init; }
+
+ [JsonPropertyName("id")]
+ public string? Id { get; init; }
+
+ [JsonPropertyName("ctime")]
+ public long? CTime { get; init; }
+
+ [JsonPropertyName("views")]
+ public long? Views { get; init; }
+
+ [JsonPropertyName("downloads")]
+ public long? Downloads { get; init; }
+}
+
+public sealed class OwnerInfo
+{
+ [JsonPropertyName("email")]
+ public string? Email { get; init; }
+
+ [JsonPropertyName("user_flags")]
+ public UserFlags? UserFlags { get; init; }
+}
+
+public sealed class UserFlags
+{
+ [JsonPropertyName("PAID_ACCOUNT")]
+ public bool? PaidAccount { get; init; }
+}
+
+///
+/// Базовый тип элемента из "list". Конкретный тип выбирается по полю "type".
+///
+[JsonConverter(typeof(CloudObjectJsonConverter))]
+public abstract class CloudObjectBase
+{
+ [JsonPropertyName("name")]
+ public string? Name { get; init; }
+
+ [JsonPropertyName("weblink")]
+ public string? WebLink { get; init; }
+
+ [JsonPropertyName("size")]
+ public long? Size { get; init; }
+
+ [JsonPropertyName("kind")]
+ public string? Kind { get; init; }
+
+ [JsonPropertyName("type")]
+ public string? Type { get; init; }
+}
+
+public sealed class CloudFolderObject : CloudObjectBase
+{
+ [JsonPropertyName("count")]
+ public CountInfo? Count { get; init; }
+
+ [JsonPropertyName("rev")]
+ public long? Rev { get; init; }
+}
+
+public sealed class CloudFileObject : CloudObjectBase
+{
+ [JsonPropertyName("mtime")]
+ public long? MTime { get; init; }
+
+ [JsonPropertyName("hash")]
+ public string? Hash { get; init; }
+}
+
+public sealed class ZipWebLinkRequest
+{
+ [JsonPropertyName("x-email")]
+ public string XEmail { get; init; }
+
+ [JsonPropertyName("weblink_list")]
+ public IReadOnlyList WeblinkList { get; init; }
+
+ [JsonPropertyName("name")]
+ public string Name { get; init; }
+}
+
+public sealed class ZipWebLinkResponse
+{
+ [JsonPropertyName("key")]
+ public string Key { get; init; }
+}
+
+///
+/// Конвертер, который смотрит на поле "type" и десериализует в CloudFolderObject или CloudFileObject.
+///
+public sealed class CloudObjectJsonConverter : JsonConverter
+{
+ public override CloudObjectBase Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ using var doc = JsonDocument.ParseValue(ref reader);
+
+ var root = doc.RootElement;
+ if (!root.TryGetProperty("type", out var typeProp))
+ throw new JsonException("Missing required property 'type' for cloud object.");
+
+ var type = typeProp.GetString();
+
+ return type switch
+ {
+ "folder" => root.Deserialize(options)
+ ?? throw new JsonException("Failed to deserialize folder object."),
+ "file" => root.Deserialize(options)
+ ?? throw new JsonException("Failed to deserialize file object."),
+ _ => throw new JsonException($"Unknown cloud object type '{type}'.")
+ };
+ }
+
+ public override void Write(Utf8JsonWriter writer, CloudObjectBase value, JsonSerializerOptions options)
+ {
+ switch (value)
+ {
+ case CloudFolderObject folder:
+ JsonSerializer.Serialize(writer, folder, options);
+ break;
+ case CloudFileObject file:
+ JsonSerializer.Serialize(writer, file, options);
+ break;
+ default:
+ throw new JsonException($"Unknown runtime type '{value.GetType().Name}'.");
+ }
+ }
+}
\ No newline at end of file
diff --git a/MetaforceInstaller.Cloud/Implementations/MailRu/MailRuCloudService.cs b/MetaforceInstaller.Cloud/Implementations/MailRu/MailRuCloudService.cs
new file mode 100644
index 0000000..d7ae0ca
--- /dev/null
+++ b/MetaforceInstaller.Cloud/Implementations/MailRu/MailRuCloudService.cs
@@ -0,0 +1,116 @@
+using System.Text;
+using System.Text.Json;
+using System.Web;
+using MetaforceInstaller.Cloud.Implementations.MailRu.DTO;
+using MetaforceInstaller.Cloud.Models;
+
+namespace MetaforceInstaller.Cloud.Implementations.MailRu;
+
+public class MailRuCloudService : ICloudService
+{
+ private const string BaseUrl = "https://cloud.mail.ru";
+
+ private static readonly JsonSerializerOptions JsonOptions = new()
+ {
+ PropertyNameCaseInsensitive = true
+ };
+
+ private readonly HttpClient _httpClient;
+
+ public MailRuCloudService(HttpClient httpClient)
+ {
+ _httpClient = httpClient;
+ }
+
+ public async Task> GetObjects(string webLink)
+ {
+ var dto = await GetFromPublicApi(
+ path: "/api/v4/public/list",
+ query: new Dictionary
+ {
+ ["weblink"] = webLink,
+ ["sort"] = "name",
+ ["order"] = "asc",
+ ["offset"] = "0",
+ ["limit"] = "500",
+ ["version"] = "4"
+ }).ConfigureAwait(false);
+
+ if (dto?.List is null || dto.List.Count == 0)
+ return [];
+
+ var result = new List(dto.List.Count);
+ foreach (var entry in dto.List)
+ result.Add(Map(entry));
+
+ return result;
+ }
+
+ public async Task GetDownloadLink(IEnumerable webLinks, string outputFileName = "archive")
+ {
+ var result = await PostToPublicApi(
+ path: "/api/v3/zip/weblink",
+ query: new Dictionary(),
+ new ZipWebLinkRequest
+ {
+ XEmail = "anonym",
+ WeblinkList = webLinks.ToList(),
+ Name = outputFileName
+ }
+ );
+
+ return result?.Key;
+ }
+
+ private async Task GetFromPublicApi(string path, IReadOnlyDictionary query)
+ {
+ var uri = BuildUri(path, query);
+
+ using var response = await _httpClient.GetAsync(uri).ConfigureAwait(false);
+ response.EnsureSuccessStatusCode();
+
+ var json = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
+ return JsonSerializer.Deserialize(json, JsonOptions);
+ }
+
+ private async Task PostToPublicApi(string path, IReadOnlyDictionary query,
+ object body)
+ {
+ var uri = BuildUri(path, query);
+
+ var jsonBody = JsonSerializer.Serialize(body, JsonOptions);
+ using var content = new StringContent(jsonBody, Encoding.UTF8, "application/json");
+
+ using var response = await _httpClient.PostAsync(uri, content).ConfigureAwait(false);
+ response.EnsureSuccessStatusCode();
+
+ var json = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
+ return JsonSerializer.Deserialize(json, JsonOptions);
+ }
+
+ private static Uri BuildUri(string path, IReadOnlyDictionary query)
+ {
+ var builder = new UriBuilder(BaseUrl)
+ {
+ Port = -1,
+ Path = path
+ };
+
+ var qs = HttpUtility.ParseQueryString(builder.Query);
+ foreach (var (key, value) in query)
+ qs[key] = value;
+
+ builder.Query = qs.ToString();
+ return builder.Uri;
+ }
+
+ private static CloudObject Map(CloudObjectBase entry)
+ {
+ return new CloudObject
+ {
+ Name = entry.Name,
+ WebLink = entry.WebLink,
+ Type = entry.Type == "folder" ? CloudObjectType.Folder : CloudObjectType.File
+ };
+ }
+}
\ No newline at end of file
diff --git a/MetaforceInstaller.Cloud/MetaforceInstaller.Cloud.csproj b/MetaforceInstaller.Cloud/MetaforceInstaller.Cloud.csproj
new file mode 100644
index 0000000..17b910f
--- /dev/null
+++ b/MetaforceInstaller.Cloud/MetaforceInstaller.Cloud.csproj
@@ -0,0 +1,9 @@
+
+
+
+ net9.0
+ enable
+ enable
+
+
+
diff --git a/MetaforceInstaller.Cloud/Models/CloudObject.cs b/MetaforceInstaller.Cloud/Models/CloudObject.cs
new file mode 100644
index 0000000..643a9d5
--- /dev/null
+++ b/MetaforceInstaller.Cloud/Models/CloudObject.cs
@@ -0,0 +1,14 @@
+namespace MetaforceInstaller.Cloud.Models;
+
+public class CloudObject
+{
+ public string Name { get; set; }
+ public string WebLink { get; set; }
+ public CloudObjectType Type { get; set; }
+}
+
+public enum CloudObjectType
+{
+ Folder,
+ File
+}
\ No newline at end of file
diff --git a/MetaforceInstaller.Core/Intefaces/IAdbService.cs b/MetaforceInstaller.Core/Intefaces/IAdbService.cs
deleted file mode 100644
index 444c237..0000000
--- a/MetaforceInstaller.Core/Intefaces/IAdbService.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-using MetaforceInstaller.Core.Models;
-using MetaforceInstaller.Core.Models;
-
-namespace MetaforceInstaller.Core.Intefaces;
-
-public interface IAdbService
-{
- event EventHandler? ProgressChanged;
- event EventHandler? StatusChanged;
-
- Task InstallApkAsync(string apkPath, IProgress? progress = null, CancellationToken cancellationToken = default);
- Task CopyFileAsync(string localPath, string remotePath, IProgress? progress = null, CancellationToken cancellationToken = default);
- DeviceInfo GetDeviceInfo();
-
- // Синхронные версии для обратной совместимости
- void InstallApk(string apkPath);
- void CopyFile(string localPath, string remotePath);
-}
\ No newline at end of file
diff --git a/MetaforceInstaller.Core/MetaforceInstaller.Core.csproj b/MetaforceInstaller.Core/MetaforceInstaller.Core.csproj
deleted file mode 100644
index 0a85bfc..0000000
--- a/MetaforceInstaller.Core/MetaforceInstaller.Core.csproj
+++ /dev/null
@@ -1,26 +0,0 @@
-
-
-
- net8.0
- enable
- enable
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/MetaforceInstaller.Core/Models/ApkInfo.cs b/MetaforceInstaller.Core/Models/ApkInfo.cs
deleted file mode 100644
index 9f042bf..0000000
--- a/MetaforceInstaller.Core/Models/ApkInfo.cs
+++ /dev/null
@@ -1,3 +0,0 @@
-namespace MetaforceInstaller.Core.Models;
-
-public record ApkInfo(string PackageName, string VersionName, string VersionCode);
\ No newline at end of file
diff --git a/MetaforceInstaller.Core/Models/DeviceInfo.cs b/MetaforceInstaller.Core/Models/DeviceInfo.cs
deleted file mode 100644
index 802cd3c..0000000
--- a/MetaforceInstaller.Core/Models/DeviceInfo.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-namespace MetaforceInstaller.Core.Models;
-
-public record DeviceInfo(
- string SerialNumber,
- string State,
- string Model,
- string Name
-);
\ No newline at end of file
diff --git a/MetaforceInstaller.Core/Models/InstallationRequest.cs b/MetaforceInstaller.Core/Models/InstallationRequest.cs
deleted file mode 100644
index c64b271..0000000
--- a/MetaforceInstaller.Core/Models/InstallationRequest.cs
+++ /dev/null
@@ -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; }
-}
\ No newline at end of file
diff --git a/MetaforceInstaller.Core/Models/ProgressInfo.cs b/MetaforceInstaller.Core/Models/ProgressInfo.cs
deleted file mode 100644
index cc09a74..0000000
--- a/MetaforceInstaller.Core/Models/ProgressInfo.cs
+++ /dev/null
@@ -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
-}
diff --git a/MetaforceInstaller.Core/Services/AdbService.cs b/MetaforceInstaller.Core/Services/AdbService.cs
deleted file mode 100644
index ddf790f..0000000
--- a/MetaforceInstaller.Core/Services/AdbService.cs
+++ /dev/null
@@ -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 _logger;
- private readonly AdbClient _adbClient;
- private DeviceData _deviceData;
-
- public event EventHandler? ProgressChanged;
- public event EventHandler? StatusChanged;
-
- public AdbService(ILogger? logger = null)
- {
- _logger = logger ?? new NullLogger();
- 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? 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? 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);
- }
-}
\ No newline at end of file
diff --git a/MetaforceInstaller.Core/Services/ApkScrapper.cs b/MetaforceInstaller.Core/Services/ApkScrapper.cs
deleted file mode 100644
index c06c543..0000000
--- a/MetaforceInstaller.Core/Services/ApkScrapper.cs
+++ /dev/null
@@ -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");
- }
-}
\ No newline at end of file
diff --git a/MetaforceInstaller.Core/adb/AdbWinApi.dll b/MetaforceInstaller.Core/adb/AdbWinApi.dll
deleted file mode 100644
index 7abe26c..0000000
Binary files a/MetaforceInstaller.Core/adb/AdbWinApi.dll and /dev/null differ
diff --git a/MetaforceInstaller.Core/adb/AdbWinUsbApi.dll b/MetaforceInstaller.Core/adb/AdbWinUsbApi.dll
deleted file mode 100644
index e7a6de1..0000000
Binary files a/MetaforceInstaller.Core/adb/AdbWinUsbApi.dll and /dev/null differ
diff --git a/MetaforceInstaller.Core/adb/adb.exe b/MetaforceInstaller.Core/adb/adb.exe
deleted file mode 100644
index 85bfeaa..0000000
Binary files a/MetaforceInstaller.Core/adb/adb.exe and /dev/null differ
diff --git a/MetaforceInstaller.UI/App.axaml b/MetaforceInstaller.UI/App.axaml
deleted file mode 100644
index d947dd9..0000000
--- a/MetaforceInstaller.UI/App.axaml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/MetaforceInstaller.UI/App.axaml.cs b/MetaforceInstaller.UI/App.axaml.cs
deleted file mode 100644
index 16212ac..0000000
--- a/MetaforceInstaller.UI/App.axaml.cs
+++ /dev/null
@@ -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();
- }
-}
\ No newline at end of file
diff --git a/MetaforceInstaller.UI/Images/logo_black.svg b/MetaforceInstaller.UI/Images/logo_black.svg
deleted file mode 100644
index d41fcc9..0000000
--- a/MetaforceInstaller.UI/Images/logo_black.svg
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
\ No newline at end of file
diff --git a/MetaforceInstaller.UI/Images/logo_red.svg b/MetaforceInstaller.UI/Images/logo_red.svg
deleted file mode 100644
index 1b73fdc..0000000
--- a/MetaforceInstaller.UI/Images/logo_red.svg
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
-
diff --git a/MetaforceInstaller.UI/Images/logo_white.svg b/MetaforceInstaller.UI/Images/logo_white.svg
deleted file mode 100644
index 12ba8d1..0000000
--- a/MetaforceInstaller.UI/Images/logo_white.svg
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
\ No newline at end of file
diff --git a/MetaforceInstaller.UI/MainWindow.axaml b/MetaforceInstaller.UI/MainWindow.axaml
deleted file mode 100644
index 61a426d..0000000
--- a/MetaforceInstaller.UI/MainWindow.axaml
+++ /dev/null
@@ -1,70 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/MetaforceInstaller.UI/MainWindow.axaml.cs b/MetaforceInstaller.UI/MainWindow.axaml.cs
deleted file mode 100644
index d6a49e1..0000000
--- a/MetaforceInstaller.UI/MainWindow.axaml.cs
+++ /dev/null
@@ -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(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?.ScrollToEnd();
- }
-}
\ No newline at end of file
diff --git a/MetaforceInstaller.UI/MetaforceInstaller.UI.csproj b/MetaforceInstaller.UI/MetaforceInstaller.UI.csproj
deleted file mode 100644
index 26ffaf3..0000000
--- a/MetaforceInstaller.UI/MetaforceInstaller.UI.csproj
+++ /dev/null
@@ -1,37 +0,0 @@
-
-
- WinExe
- net8.0
- enable
- true
- true
- true
- app.manifest
- true
- 1.2.1
-
-
-
-
-
-
-
-
-
- None
- All
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/MetaforceInstaller.UI/Program.cs b/MetaforceInstaller.UI/Program.cs
deleted file mode 100644
index c77ddc1..0000000
--- a/MetaforceInstaller.UI/Program.cs
+++ /dev/null
@@ -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()
- .UsePlatformDetect()
- .WithInterFont()
- .LogToTrace();
-}
\ No newline at end of file
diff --git a/MetaforceInstaller.UI/app.manifest b/MetaforceInstaller.UI/app.manifest
deleted file mode 100644
index 0543ed4..0000000
--- a/MetaforceInstaller.UI/app.manifest
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-