add azaion loader

This commit is contained in:
Alex Bezdieniezhnykh
2025-06-01 19:16:49 +03:00
parent 2584b4f125
commit 500db31142
54 changed files with 629 additions and 291 deletions
+94 -122
View File
@@ -1,6 +1,5 @@
using System.IO;
using System.Net.Http;
using System.Reflection;
using System.Text;
using System.Windows;
using System.Windows.Threading;
@@ -17,12 +16,12 @@ using Azaion.CommonSecurity.DTO;
using Azaion.CommonSecurity.DTO.Commands;
using Azaion.CommonSecurity.Services;
using Azaion.Dataset;
using CommandLine;
using LibVLCSharp.Shared;
using MediatR;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using Serilog;
@@ -32,131 +31,24 @@ namespace Azaion.Suite;
public partial class App
{
private IHost _host = null!;
private ILogger<App> _logger = null!;
private IMediator _mediator = null!;
private FormState _formState = null!;
private IInferenceClient _inferenceClient = null!;
private Stream _securedConfig = null!;
private Stream _systemConfig = null!;
private IHost _host = null!;
private static readonly Guid KeyPressTaskId = Guid.NewGuid();
private string _loadErrors = "";
private readonly ICache _cache = new MemoryCache();
private IAzaionApi _azaionApi = null!;
private CancellationTokenSource _mainCTokenSource = new();
private readonly CancellationTokenSource _mainCTokenSource = new();
private void OnDispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
{
_logger.LogError(e.Exception, e.Exception.Message);
Log.Logger.Error(e.Exception, "Unhandled exception");
e.Handled = true;
}
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
StartLogin();
}
private readonly List<string> _encryptedResources =
[
"Azaion.Annotator",
"Azaion.Dataset"
];
private static SecureAppConfig ReadSecureAppConfig()
{
try
{
if (!File.Exists(SecurityConstants.CONFIG_PATH))
throw new FileNotFoundException(SecurityConstants.CONFIG_PATH);
var configStr = File.ReadAllText(SecurityConstants.CONFIG_PATH);
var config = JsonConvert.DeserializeObject<SecureAppConfig>(configStr);
return config ?? SecurityConstants.DefaultSecureAppConfig;
}
catch (Exception e)
{
Console.WriteLine(e);
return SecurityConstants.DefaultSecureAppConfig;
}
}
private void StartLogin()
{
new ConfigUpdater().CheckConfig();
var secureAppConfig = ReadSecureAppConfig();
var apiDir = secureAppConfig.DirectoriesConfig.ApiResourcesDirectory;
_inferenceClient = new InferenceClient(new OptionsWrapper<InferenceClientConfig>(secureAppConfig.InferenceClientConfig), _mainCTokenSource.Token);
var login = new Login();
var loader = (IResourceLoader)_inferenceClient;
login.CredentialsEntered += (_, credentials) =>
{
_inferenceClient.Send(RemoteCommand.Create(CommandType.Login, credentials));
_azaionApi = new AzaionApi(new HttpClient { BaseAddress = new Uri(secureAppConfig.InferenceClientConfig.ApiUrl) }, _cache, credentials);
try
{
_securedConfig = loader.LoadFile("config.secured.json", apiDir);
_systemConfig = loader.LoadFile("config.system.json", apiDir);
}
catch (Exception e)
{
Console.WriteLine(e);
_securedConfig = new MemoryStream("{}"u8.ToArray());
var systemConfig = new
{
AnnotationConfig = Constants.DefaultAnnotationConfig,
AIRecognitionConfig = Constants.DefaultAIRecognitionConfig,
ThumbnailConfig = Constants.DefaultThumbnailConfig,
};
_systemConfig = new MemoryStream(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(systemConfig)));
}
AppDomain.CurrentDomain.AssemblyResolve += (_, a) =>
{
var assemblyName = a.Name.Split(',').First();
if (_encryptedResources.Contains(assemblyName))
{
try
{
var stream = loader.LoadFile($"{assemblyName}.dll", apiDir);
return Assembly.Load(stream.ToArray());
}
catch (Exception e)
{
Log.Logger.Error(e, $"Failed to load assembly {assemblyName}");
_loadErrors += $"{e.Message}{Environment.NewLine}{Environment.NewLine}";
var currentLocation = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!;
var dllPath = Path.Combine(currentLocation, SecurityConstants.DUMMY_DIR, $"{assemblyName}.dll");
return Assembly.LoadFile(dllPath);
}
}
var loadedAssembly = AppDomain.CurrentDomain.GetAssemblies()
.FirstOrDefault(a => a.GetName().Name == assemblyName);
return loadedAssembly;
};
StartMain();
_host.Start();
EventManager.RegisterClassHandler(typeof(UIElement), UIElement.PreviewKeyDownEvent, new RoutedEventHandler(GlobalKeyHandler));
_host.Services.GetRequiredService<MainSuite>().Show();
};
login.Closed += (sender, args) =>
{
if (!login.MainSuiteOpened)
_inferenceClient.Dispose();
};
login.ShowDialog();
}
private void StartMain()
{
Log.Logger = new LoggerConfiguration()
.Enrich.FromLogContext()
.MinimumLevel.Information()
@@ -166,12 +58,86 @@ public partial class App
rollingInterval: RollingInterval.Day)
.CreateLogger();
Parser.Default.ParseArguments<ApiCredentials>(e.Args)
.WithParsed(Start)
.WithNotParsed(ErrorHandling);
}
private void ErrorHandling(IEnumerable<Error> obj)
{
Log.Fatal($"Error happened: {string.Join(",", obj.Select(x => x.Tag))}");
}
private static InitConfig ReadInitConfig()
{
try
{
if (!File.Exists(SecurityConstants.CONFIG_PATH))
throw new FileNotFoundException(SecurityConstants.CONFIG_PATH);
var configStr = File.ReadAllText(SecurityConstants.CONFIG_PATH);
var config = JsonConvert.DeserializeObject<InitConfig>(configStr);
return config ?? SecurityConstants.DefaultInitConfig;
}
catch (Exception e)
{
Console.WriteLine(e);
return SecurityConstants.DefaultInitConfig;
}
}
private Stream GetSystemConfig(LoaderClient loaderClient, string apiDir)
{
try
{
return loaderClient.LoadFile("config.system.json", apiDir);
}
catch (Exception e)
{
Log.Logger.Error(e, e.Message);
return new MemoryStream(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(new
{
AnnotationConfig = Constants.DefaultAnnotationConfig,
AIRecognitionConfig = Constants.DefaultAIRecognitionConfig,
ThumbnailConfig = Constants.DefaultThumbnailConfig,
})));
}
}
private Stream GetSecuredConfig(LoaderClient loaderClient, string apiDir)
{
try
{
return loaderClient.LoadFile("config.secured.json", apiDir);
}
catch (Exception e)
{
Log.Logger.Error(e, e.Message);
throw;
}
}
private void Start(ApiCredentials credentials)
{
new ConfigUpdater().CheckConfig();
var initConfig = ReadInitConfig();
var apiDir = initConfig.DirectoriesConfig.ApiResourcesDirectory;
var loaderClient = new LoaderClient(initConfig.LoaderClientConfig, Log.Logger, _mainCTokenSource.Token);
#if DEBUG
loaderClient.StartClient();
#endif
loaderClient.Connect(); //Client app should be already started by LoaderUI
loaderClient.Send(RemoteCommand.Create(CommandType.Login, credentials));
var azaionApi = new AzaionApi(new HttpClient { BaseAddress = new Uri(initConfig.InferenceClientConfig.ApiUrl) }, _cache, credentials);
_host = Host.CreateDefaultBuilder()
.ConfigureAppConfiguration((context, config) => config
.ConfigureAppConfiguration((_, config) => config
.AddCommandLine(Environment.GetCommandLineArgs())
.AddJsonFile(SecurityConstants.CONFIG_PATH, optional: true, reloadOnChange: true)
.AddJsonStream(_securedConfig)
.AddJsonStream(_systemConfig))
.AddJsonStream(GetSystemConfig(loaderClient, apiDir))
.AddJsonStream(GetSecuredConfig(loaderClient, apiDir)))
.UseSerilog()
.ConfigureServices((context, services) =>
{
@@ -188,15 +154,20 @@ public partial class App
#region External Services
services.ConfigureSection<LoaderClientConfig>(context.Configuration);
services.AddSingleton(loaderClient);
services.ConfigureSection<InferenceClientConfig>(context.Configuration);
services.ConfigureSection<GpsDeniedClientConfig>(context.Configuration);
services.AddSingleton<IInferenceClient>(_inferenceClient);
services.AddSingleton<IGpsMatcherClient, GpsMatcherClient>();
services.AddSingleton<IInferenceClient, InferenceClient>();
services.AddSingleton<IInferenceService, InferenceService>();
services.ConfigureSection<GpsDeniedClientConfig>(context.Configuration);
services.AddSingleton<IGpsMatcherClient, GpsMatcherClient>();
services.AddSingleton<IGpsMatcherService, GpsMatcherService>();
services.AddSingleton<ISatelliteDownloader, SatelliteDownloader>();
services.AddHttpClient();
services.AddSingleton(_azaionApi);
services.AddSingleton(azaionApi);
#endregion
services.AddSingleton<IConfigUpdater, ConfigUpdater>();
@@ -235,12 +206,13 @@ public partial class App
datasetExplorer.Hide();
_mediator = _host.Services.GetRequiredService<IMediator>();
if (!string.IsNullOrEmpty(_loadErrors))
_mediator.Publish(new LoadErrorEvent(_loadErrors));
_logger = _host.Services.GetRequiredService<ILogger<App>>();
_formState = _host.Services.GetRequiredService<FormState>();
DispatcherUnhandledException += OnDispatcherUnhandledException;
_host.Start();
EventManager.RegisterClassHandler(typeof(UIElement), UIElement.PreviewKeyDownEvent, new RoutedEventHandler(GlobalKeyHandler));
_host.Services.GetRequiredService<MainSuite>().Show();
}
private void GlobalKeyHandler(object sender, RoutedEventArgs e)
+9 -23
View File
@@ -12,19 +12,21 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.9.1" />
<PackageReference Include="GMap.NET.WinPresentation" Version="2.1.7" />
<PackageReference Include="LibVLCSharp" Version="3.9.1" />
<PackageReference Include="LibVLCSharp.WPF" Version="3.9.1" />
<PackageReference Include="MediatR" Version="12.4.1" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.0" />
<PackageReference Include="MediatR" Version="12.5.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.5" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.5" />
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.5" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="RabbitMQ.Stream.Client" Version="1.8.9" />
<PackageReference Include="Serilog.Extensions.Logging" Version="8.0.0" />
<PackageReference Include="Serilog.Settings.Configuration" Version="8.0.4" />
<PackageReference Include="Serilog.Extensions.Hosting" Version="9.0.0" />
<PackageReference Include="Serilog.Extensions.Logging" Version="9.0.1" />
<PackageReference Include="Serilog.Settings.Configuration" Version="9.0.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
<PackageReference Include="SharpVectors" Version="1.8.4.2" />
<PackageReference Include="SharpVectors.Wpf" Version="1.8.4.2" />
<PackageReference Include="System.Data.SQLite" Version="1.0.119" />
@@ -51,20 +53,4 @@
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<Page Update="Login.xaml">
<Generator>MSBuild:Compile</Generator>
<XamlRuntime>Wpf</XamlRuntime>
<SubType>Designer</SubType>
</Page>
</ItemGroup>
<Target Name="PostBuild" AfterTargets="Build">
<MakeDir Directories="$(TargetDir)dummy" />
<Copy SourceFiles="$(TargetDir)Azaion.Annotator.dll" DestinationFolder="$(TargetDir)dummy" />
<Copy SourceFiles="$(TargetDir)Azaion.Dataset.dll" DestinationFolder="$(TargetDir)dummy" />
<Exec Command="postbuild.cmd $(ConfigurationName) stage" />
</Target>
</Project>
-127
View File
@@ -1,127 +0,0 @@
<Window x:Class="Azaion.Suite.Login"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
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"
WindowStyle="None"
AllowsTransparency="True"
Background="Transparent"
WindowStartupLocation="CenterScreen"
Title="Azaion Annotator Security"
Height="280" Width="350">
<Border Width="350"
Height="280"
Background="DarkGray"
CornerRadius="15"
MouseMove="MainMouseMove">
<Border.Effect>
<DropShadowEffect BlurRadius="15"
Direction ="-90"
RenderingBias ="Quality"
ShadowDepth ="2"
Color ="Gray" />
</Border.Effect>
<StackPanel Orientation="Vertical"
Margin="20">
<Canvas>
<Button Padding="5" ToolTip="Закрити" Background="DarkGray" BorderBrush="DarkGray" Canvas.Left="290" Cursor="Hand"
Name="CloseBtn"
Click="CloseClick">
<Path Stretch="Fill" Fill="LightGray" Data="M5.29289 5.29289C5.68342 4.90237 6.31658 4.90237 6.70711 5.29289L12 10.5858L17.2929 5.29289C17.6834 4.90237 18.3166
4.90237 18.7071 5.29289C19.0976 5.68342 19.0976 6.31658 18.7071 6.70711L13.4142 12L18.7071 17.2929C19.0976 17.6834 19.0976 18.3166 18.7071 18.7071C18.3166
19.0976 17.6834 19.0976 17.2929 18.7071L12 13.4142L6.70711 18.7071C6.31658 19.0976 5.68342 19.0976 5.29289 18.7071C4.90237 18.3166 4.90237 17.6834 5.29289
17.2929L10.5858 12L5.29289 6.70711C4.90237 6.31658 4.90237 5.68342 5.29289 5.29289Z" />
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border Background="{TemplateBinding Background}" BorderThickness="1">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Button.Style>
</Button>
</Canvas>
<TextBlock Text="Вхід"
FontSize="25"
HorizontalAlignment="Center"
VerticalAlignment="Top"
FontWeight="Bold"
/>
<Grid VerticalAlignment="Center">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Text="Email"
Grid.Row="0"
Margin="0, 20, 0, 5"
HorizontalAlignment="Left"/>
<TextBox
Name="TbEmail"
Grid.Row="1"
Padding="0,5"
Width="300"
FontSize="16"
Background="DarkGray"
BorderBrush="DimGray"
BorderThickness="0,0,0,1"
HorizontalAlignment="Left"
Text=""
/>
<TextBlock Text="Пароль"
Grid.Row="2"
Margin="0, 20, 0, 5"
HorizontalAlignment="Left"/>
<PasswordBox Grid.Row="3"
Name="TbPassword"
FontSize="16"
Background="DarkGray"
BorderBrush="DimGray"
Padding="0,5"
Width="300"
BorderThickness="0,0,0,1"
HorizontalAlignment="Left"
Password=""/>
</Grid>
<Button x:Name="LoginBtn"
Content="Вхід"
Foreground="White"
Background="DimGray"
Margin="0,25"
Height="35"
Width="280"
Cursor="Hand"
Click="LoginClick">
<Button.Style>
<Style TargetType="Button">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border x:Name="LoginBorder" Background="{TemplateBinding Background}"
CornerRadius="16">
<ContentPresenter HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="LightGray" TargetName="LoginBorder" />
<Setter Property="TextBlock.Foreground" Value="Black" TargetName="LoginBorder" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Button.Style>
</Button>
</StackPanel>
</Border>
</Window>
-39
View File
@@ -1,39 +0,0 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using Azaion.Common.DTO;
using Azaion.CommonSecurity.DTO;
namespace Azaion.Suite;
public partial class Login
{
public bool MainSuiteOpened { get; set; } = false;
public Login()
{
InitializeComponent();
}
public event EventHandler<ApiCredentials>? CredentialsEntered;
private void LoginClick(object sender, RoutedEventArgs e)
{
LoginBtn.Cursor = Cursors.Wait;
Cursor = Cursors.Wait;
CredentialsEntered?.Invoke(this, new ApiCredentials(TbEmail.Text, TbPassword.Password));
MainSuiteOpened = true;
Close();
}
private void CloseClick(object sender, RoutedEventArgs e) => Close();
private void MainMouseMove(object sender, MouseEventArgs e)
{
if (e.OriginalSource is Button || e.OriginalSource is TextBox)
return;
if (e.LeftButton == MouseButtonState.Pressed)
DragMove();
}
}
+6 -5
View File
@@ -1,17 +1,18 @@
{
"LoaderClientConfig": {
"ZeroMqHost": "127.0.0.1",
"ZeroMqPort": 5025,
"ApiUrl": "https://api.azaion.com"
},
"InferenceClientConfig": {
"ZeroMqHost": "127.0.0.1",
"ZeroMqPort": 5127,
"RetryCount": 25,
"TimeoutSeconds": 5,
"ApiUrl": "https://api.azaion.com"
},
"GpsDeniedClientConfig": {
"ZeroMqHost": "127.0.0.1",
"ZeroMqPort": 5555,
"ZeroMqReceiverPort": 5556,
"RetryCount": 25,
"TimeoutSeconds": 5
"ZeroMqReceiverPort": 5556
},
"DirectoriesConfig": {
"ApiResourcesDirectory": "stage",
-13
View File
@@ -1,13 +0,0 @@
@echo off
set CONFIG=%1
set RESOURCES_FOLDER=%2
set FILE1_TO_UPLOAD=%cd%\..\Azaion.Annotator\bin\%CONFIG%\net8.0-windows\Azaion.Annotator.dll
call upload-file %FILE1_TO_UPLOAD% %RESOURCES_FOLDER%
set FILE2_TO_UPLOAD=%cd%\..\Azaion.Dataset\bin\%CONFIG%\net8.0-windows\Azaion.Dataset.dll
call upload-file %FILE2_TO_UPLOAD% %RESOURCES_FOLDER%
set SUITE_FOLDER=%cd%\bin\%CONFIG%\net8.0-windows\
-28
View File
@@ -1,28 +0,0 @@
setlocal enabledelayedexpansion
set API_URL=https://api.azaion.com
set SOURCE_FILE=%1
set DESTINATION=%2
set "SOURCE_FILE=%SOURCE_FILE:\=/%"
set EMAIL=uploader@azaion.com
set PASSWORD=Az@1on_10Upl0@der
for /f "tokens=*" %%i in ('curl -s -X POST -H "Content-Type: application/json" ^
-d "{\"email\":\"%EMAIL%\",\"password\":\"%PASSWORD%\"}" %API_URL%/login') do set RESPONSE=%%i
for /f "tokens=2 delims=:" %%a in ('echo %RESPONSE% ^| findstr /i "token"') do (
set "TOKEN=%%a"
set "TOKEN=!TOKEN:~1,-1!"
set "TOKEN=!TOKEN:~0,-2!"
)
set UPLOAD_URL=%API_URL%/resources/%DESTINATION%
echo Uploading %SOURCE_FILE% to %UPLOAD_URL%...
curl --location %UPLOAD_URL% ^
-H "Authorization: Bearer %TOKEN%" ^
-H "Content-Type: multipart/form-data" ^
--form "data=@%SOURCE_FILE%"
echo Upload complete!