diff --git a/Azaion.Annotator/Azaion.Annotator.csproj b/Azaion.Annotator/Azaion.Annotator.csproj
index 38968a5..0c50406 100644
--- a/Azaion.Annotator/Azaion.Annotator.csproj
+++ b/Azaion.Annotator/Azaion.Annotator.csproj
@@ -12,19 +12,12 @@
-
-
-
-
-
+
+
+
+
-
-
-
-
-
-
diff --git a/Azaion.Common/Azaion.Common.csproj b/Azaion.Common/Azaion.Common.csproj
index 96f371b..301ad17 100644
--- a/Azaion.Common/Azaion.Common.csproj
+++ b/Azaion.Common/Azaion.Common.csproj
@@ -9,12 +9,12 @@
-
+
-
-
-
-
+
+
+
+
diff --git a/Azaion.Common/Services/GpsMatcherClient.cs b/Azaion.Common/Services/GpsMatcherClient.cs
index 0ed3c15..9676752 100644
--- a/Azaion.Common/Services/GpsMatcherClient.cs
+++ b/Azaion.Common/Services/GpsMatcherClient.cs
@@ -129,7 +129,7 @@ public class GpsMatcherClient : IGpsMatcherClient
if (response != "OK")
{
_logger.LogError(response);
- await _mediator.Publish(new SetStatusTextEvent(response, true));
+ await _mediator.Publish(new SetStatusTextEvent(response ?? "", true));
}
}
diff --git a/Azaion.CommonSecurity/Azaion.CommonSecurity.csproj b/Azaion.CommonSecurity/Azaion.CommonSecurity.csproj
index 4bd3564..e48a1e7 100644
--- a/Azaion.CommonSecurity/Azaion.CommonSecurity.csproj
+++ b/Azaion.CommonSecurity/Azaion.CommonSecurity.csproj
@@ -7,15 +7,17 @@
+
-
+
-
-
-
+
+
+
+
diff --git a/Azaion.CommonSecurity/DTO/ApiCredentials.cs b/Azaion.CommonSecurity/DTO/ApiCredentials.cs
index 77fca61..92f0eb6 100644
--- a/Azaion.CommonSecurity/DTO/ApiCredentials.cs
+++ b/Azaion.CommonSecurity/DTO/ApiCredentials.cs
@@ -1,13 +1,16 @@
-using MessagePack;
+using CommandLine;
+using MessagePack;
namespace Azaion.CommonSecurity.DTO;
[MessagePackObject]
-public class ApiCredentials(string email, string password) : EventArgs
+public class ApiCredentials : EventArgs
{
[Key(nameof(Email))]
- public string Email { get; set; } = email;
+ [Option('e', "email", Required = true, HelpText = "User Email")]
+ public string Email { get; set; } = null!;
[Key(nameof(Password))]
- public string Password { get; set; } = password;
+ [Option('p', "pass", Required = true, HelpText = "User Password")]
+ public string Password { get; set; } = null!;
}
\ No newline at end of file
diff --git a/Azaion.CommonSecurity/DTO/ExternalClientsConfig.cs b/Azaion.CommonSecurity/DTO/ExternalClientsConfig.cs
index 9d545ba..e522c68 100644
--- a/Azaion.CommonSecurity/DTO/ExternalClientsConfig.cs
+++ b/Azaion.CommonSecurity/DTO/ExternalClientsConfig.cs
@@ -4,8 +4,11 @@ public abstract class ExternalClientConfig
{
public string ZeroMqHost { get; set; } = "";
public int ZeroMqPort { get; set; }
- public double OneTryTimeoutSeconds { get; set; }
- public int RetryCount {get;set;}
+}
+
+public class LoaderClientConfig : ExternalClientConfig
+{
+ public string ApiUrl { get; set; } = null!;
}
public class InferenceClientConfig : ExternalClientConfig
diff --git a/Azaion.CommonSecurity/DTO/SecureAppConfig.cs b/Azaion.CommonSecurity/DTO/InitConfig.cs
similarity index 73%
rename from Azaion.CommonSecurity/DTO/SecureAppConfig.cs
rename to Azaion.CommonSecurity/DTO/InitConfig.cs
index a40d736..d424f88 100644
--- a/Azaion.CommonSecurity/DTO/SecureAppConfig.cs
+++ b/Azaion.CommonSecurity/DTO/InitConfig.cs
@@ -1,7 +1,8 @@
namespace Azaion.CommonSecurity.DTO;
-public class SecureAppConfig
+public class InitConfig
{
+ public LoaderClientConfig LoaderClientConfig { get; set; } = null!;
public InferenceClientConfig InferenceClientConfig { get; set; } = null!;
public GpsDeniedClientConfig GpsDeniedClientConfig { get; set; } = null!;
public DirectoriesConfig DirectoriesConfig { get; set; } = null!;
diff --git a/Azaion.CommonSecurity/SecurityConstants.cs b/Azaion.CommonSecurity/SecurityConstants.cs
index 8da1753..9beea40 100644
--- a/Azaion.CommonSecurity/SecurityConstants.cs
+++ b/Azaion.CommonSecurity/SecurityConstants.cs
@@ -8,8 +8,14 @@ public class SecurityConstants
public const string DUMMY_DIR = "dummy";
+ private const string DEFAULT_API_URL = "https://api.azaion.com";
+
#region ExternalClientsConfig
+ private const string DEFAULT_ZMQ_LOADER_HOST = "127.0.0.1";
+ private const int DEFAULT_ZMQ_LOADER_PORT = 5025;
+
+ public const string EXTERNAL_LOADER_PATH = "azaion-loader.exe";
public const string EXTERNAL_INFERENCE_PATH = "azaion-inference.exe";
public const string EXTERNAL_GPS_DENIED_FOLDER = "gps-denied";
public static readonly string ExternalGpsDeniedPath = Path.Combine(EXTERNAL_GPS_DENIED_FOLDER, "image-matcher.exe");
@@ -20,9 +26,6 @@ public class SecurityConstants
public const string DEFAULT_ZMQ_GPS_DENIED_HOST = "127.0.0.1";
public const int DEFAULT_ZMQ_GPS_DENIED_PORT = 5227;
- public const int DEFAULT_RETRY_COUNT = 25;
- public const int DEFAULT_TIMEOUT_SECONDS = 5;
-
# region Cache keys
public const string CURRENT_USER_CACHE_KEY = "CurrentUser";
@@ -30,21 +33,24 @@ public class SecurityConstants
# endregion
- public static readonly SecureAppConfig DefaultSecureAppConfig = new()
+ public static readonly InitConfig DefaultInitConfig = new()
{
+ LoaderClientConfig = new LoaderClientConfig
+ {
+ ZeroMqHost = DEFAULT_ZMQ_LOADER_HOST,
+ ZeroMqPort = DEFAULT_ZMQ_LOADER_PORT,
+ ApiUrl = DEFAULT_API_URL
+ },
InferenceClientConfig = new InferenceClientConfig
{
ZeroMqHost = DEFAULT_ZMQ_INFERENCE_HOST,
ZeroMqPort = DEFAULT_ZMQ_INFERENCE_PORT,
- OneTryTimeoutSeconds = DEFAULT_TIMEOUT_SECONDS,
- RetryCount = DEFAULT_RETRY_COUNT
+ ApiUrl = DEFAULT_API_URL
},
GpsDeniedClientConfig = new GpsDeniedClientConfig
{
ZeroMqHost = DEFAULT_ZMQ_GPS_DENIED_HOST,
- ZeroMqPort = DEFAULT_ZMQ_GPS_DENIED_PORT,
- OneTryTimeoutSeconds = DEFAULT_TIMEOUT_SECONDS,
- RetryCount = DEFAULT_RETRY_COUNT,
+ ZeroMqPort = DEFAULT_ZMQ_GPS_DENIED_PORT
},
DirectoriesConfig = new DirectoriesConfig
{
diff --git a/Azaion.CommonSecurity/Services/LoaderClient.cs b/Azaion.CommonSecurity/Services/LoaderClient.cs
index 7fc231f..62c1d7b 100644
--- a/Azaion.CommonSecurity/Services/LoaderClient.cs
+++ b/Azaion.CommonSecurity/Services/LoaderClient.cs
@@ -1,30 +1,30 @@
using System.Diagnostics;
+using System.Text;
using Azaion.CommonSecurity.DTO;
-using MediatR;
-using Microsoft.Extensions.Logging;
-using Microsoft.Extensions.Options;
+using Azaion.CommonSecurity.DTO.Commands;
+using MessagePack;
+using NetMQ;
+using NetMQ.Sockets;
+using Serilog;
+using Exception = System.Exception;
namespace Azaion.CommonSecurity.Services;
-public class LoaderClient
+public class LoaderClient(LoaderClientConfig config, ILogger logger, CancellationToken ct = default)
{
- private readonly IMediator _mediator;
- private readonly ILogger _logger;
+ private readonly DealerSocket _dealer = new();
+ private readonly Guid _clientId = Guid.NewGuid();
- public LoaderClient(IMediator mediator, IOptions config, ILogger logger)
+ public void StartClient()
{
- _mediator = mediator;
- _logger = logger;
try
{
using var process = new Process();
process.StartInfo = new ProcessStartInfo
{
- FileName = SecurityConstants.LoaderPath,
- Arguments = $"--port {config.Value.ZeroMqPort} --api {config.Value.ApiUrl}",
- //RedirectStandardOutput = true,
- //RedirectStandardError = true,
- //CreateNoWindow = true
+ FileName = SecurityConstants.EXTERNAL_LOADER_PATH,
+ Arguments = $"--port {config.ZeroMqPort} --api {config.ApiUrl}",
+ CreateNoWindow = true
};
process.OutputDataReceived += (_, e) => { if (e.Data != null) Console.WriteLine(e.Data); };
@@ -33,19 +33,44 @@ public class LoaderClient
}
catch (Exception e)
{
- Console.WriteLine(e);
- //throw;
+ logger.Error(e.Message);
+ throw;
}
+ }
- _requestAddress = $"tcp://{gpsConfig.Value.ZeroMqHost}:{gpsConfig.Value.ZeroMqPort}";
- _requestSocket.Connect(_requestAddress);
+ public void Connect()
+ {
+ _dealer.Options.Identity = Encoding.UTF8.GetBytes(_clientId.ToString("N"));
+ _dealer.Connect($"tcp://{config.ZeroMqHost}:{config.ZeroMqPort}");
+ }
- _subscriberAddress = $"tcp://{gpsConfig.Value.ZeroMqHost}:{gpsConfig.Value.ZeroMqReceiverPort}";
- _subscriberSocket.Connect(_subscriberAddress);
- _subscriberSocket.Subscribe("");
- _subscriberSocket.ReceiveReady += async (sender, e) => await ProcessClientCommand(sender, e);
+ public void Send(RemoteCommand command) =>
+ _dealer.SendFrame(MessagePackSerializer.Serialize(command));
- _poller.Add(_subscriberSocket);
- _poller.RunAsync();
+ public MemoryStream LoadFile(string filename, string folder)
+ {
+ try
+ {
+ Send(RemoteCommand.Create(CommandType.Load, new LoadFileData(filename, folder)));
+
+ var retries = 10;
+ var tryNum = 0;
+ while (!ct.IsCancellationRequested && tryNum++ < retries)
+ {
+ if (!_dealer.TryReceiveFrameBytes(TimeSpan.FromMilliseconds(150), out var bytes))
+ continue;
+ var resultCommand = MessagePackSerializer.Deserialize(bytes, cancellationToken: ct);
+ if (resultCommand.Data?.Length == 0)
+ throw new Exception($"Can't load {filename}. Returns 0 bytes");
+ return new MemoryStream(resultCommand.Data!);
+ }
+
+ throw new Exception($"Can't load file {filename} after {retries} retries");
+ }
+ catch (Exception e)
+ {
+ logger.Error(e, e.Message);
+ throw;
+ }
}
}
\ No newline at end of file
diff --git a/Azaion.Dataset/Azaion.Dataset.csproj b/Azaion.Dataset/Azaion.Dataset.csproj
index f2fdf82..8ab5bcf 100644
--- a/Azaion.Dataset/Azaion.Dataset.csproj
+++ b/Azaion.Dataset/Azaion.Dataset.csproj
@@ -16,8 +16,8 @@
-
-
+
+
diff --git a/Azaion.Inference/azaion-inference.spec b/Azaion.Inference/azaion-inference.spec
index c71752a..1b17195 100644
--- a/Azaion.Inference/azaion-inference.spec
+++ b/Azaion.Inference/azaion-inference.spec
@@ -26,8 +26,6 @@ tmp_ret = collect_all('pycuda')
datas += tmp_ret[0]; binaries += tmp_ret[1]; hiddenimports += tmp_ret[2]
tmp_ret = collect_all('pynvml')
datas += tmp_ret[0]; binaries += tmp_ret[1]; hiddenimports += tmp_ret[2]
-tmp_ret = collect_all('boto3')
-datas += tmp_ret[0]; binaries += tmp_ret[1]; hiddenimports += tmp_ret[2]
tmp_ret = collect_all('jwt')
datas += tmp_ret[0]; binaries += tmp_ret[1]; hiddenimports += tmp_ret[2]
diff --git a/Azaion.Inference/build_inference.cmd b/Azaion.Inference/build_inference.cmd
index eb4b677..5835a10 100644
--- a/Azaion.Inference/build_inference.cmd
+++ b/Azaion.Inference/build_inference.cmd
@@ -33,7 +33,6 @@ venv\Scripts\pyinstaller --name=azaion-inference ^
--collect-all tensorrt ^
--collect-all pycuda ^
--collect-all pynvml ^
---collect-all boto3 ^
--collect-all jwt ^
--hidden-import constants ^
--hidden-import annotation ^
diff --git a/Azaion.Inference/constants.pyx b/Azaion.Inference/constants.pyx
index 80f5f2c..8c5ed80 100644
--- a/Azaion.Inference/constants.pyx
+++ b/Azaion.Inference/constants.pyx
@@ -2,12 +2,7 @@ import time
cdef str CONFIG_FILE = "config.yaml" # Port for the zmq
-cdef int QUEUE_MAXSIZE = 1000 # Maximum size of the command queue
-cdef str COMMANDS_QUEUE = "azaion-commands"
-cdef str ANNOTATIONS_QUEUE = "azaion-annotations"
-
cdef str QUEUE_CONFIG_FILENAME = "secured-config.json"
-
cdef str AI_ONNX_MODEL_FILE = "azaion.onnx"
cdef str CDN_CONFIG = "cdn.yaml"
diff --git a/Azaion.Inference/main.pyx b/Azaion.Inference/main.pyx
index 7ed4306..ecaa8ea 100644
--- a/Azaion.Inference/main.pyx
+++ b/Azaion.Inference/main.pyx
@@ -6,27 +6,24 @@ from threading import Thread
import yaml
-from api_client cimport ApiClient
from annotation cimport Annotation
from inference cimport Inference
from remote_command cimport RemoteCommand, CommandType
from remote_command_handler cimport RemoteCommandHandler
-from credentials cimport Credentials
-from file_data cimport FileData
cdef class CommandProcessor:
- cdef ApiClient api_client
+
cdef RemoteCommandHandler remote_handler
cdef object inference_queue
cdef bint running
cdef Inference inference
def __init__(self, int zmq_port, str api_url):
- self.api_client = ApiClient(api_url)
self.remote_handler = RemoteCommandHandler(zmq_port, self.on_command)
self.inference_queue = Queue(maxsize=constants.QUEUE_MAXSIZE)
self.remote_handler.start()
self.running = True
+ #TODO: replace api_client to azaion_loader.exe call
self.inference = Inference(self.api_client, self.on_annotation)
def start(self):
diff --git a/Azaion.Inference/api_client.pxd b/Azaion.Loader/api_client.pxd
similarity index 91%
rename from Azaion.Inference/api_client.pxd
rename to Azaion.Loader/api_client.pxd
index d05f072..9e77294 100644
--- a/Azaion.Inference/api_client.pxd
+++ b/Azaion.Loader/api_client.pxd
@@ -16,5 +16,6 @@ cdef class ApiClient:
cdef load_bytes(self, str filename, str folder)
cdef upload_file(self, str filename, bytes resource, str folder)
+ cdef load_big_file_cdn(self, str folder, str big_part)
cdef load_big_small_resource(self, str resource_name, str folder, str key)
cdef upload_big_small_resource(self, bytes resource, str resource_name, str folder, str key)
diff --git a/Azaion.Inference/api_client.pyx b/Azaion.Loader/api_client.pyx
similarity index 99%
rename from Azaion.Inference/api_client.pyx
rename to Azaion.Loader/api_client.pyx
index 4227b4f..2fe3162 100644
--- a/Azaion.Inference/api_client.pyx
+++ b/Azaion.Loader/api_client.pyx
@@ -159,7 +159,7 @@ cdef class ApiClient:
print('Local file doesnt match with api file, old version')
remote_bytes_big = self.load_big_file_cdn(folder, big_part)
- return Security.decrypt_to(encrypted_bytes_small + remote_bytes_big)
+ return Security.decrypt_to(encrypted_bytes_small + remote_bytes_big, key)
cdef upload_big_small_resource(self, bytes resource, str resource_name, str folder, str key):
cdef str big_part_name = f'{resource_name}.big'
diff --git a/Azaion.Loader/build_loader.cmd b/Azaion.Loader/build_loader.cmd
new file mode 100644
index 0000000..0ed743b
--- /dev/null
+++ b/Azaion.Loader/build_loader.cmd
@@ -0,0 +1,24 @@
+echo Build Cython app
+set CURRENT_DIR=%cd%
+
+REM Change to the parent directory of the current location
+cd /d %~dp0
+
+echo remove dist folder:
+if exist dist rmdir dist /s /q
+if exist build rmdir build /s /q
+
+echo install python and dependencies
+if not exist venv (
+ python -m venv venv
+)
+
+venv\Scripts\python -m pip install --upgrade pip
+venv\Scripts\pip install -r requirements.txt
+venv\Scripts\pip install --upgrade pyinstaller pyinstaller-hooks-contrib
+
+venv\Scripts\python setup.py build_ext --inplace
+
+echo install azaion-loader
+venv\Scripts\pyinstaller --name=azaion-loader ^
+--collect-all boto3 ^
\ No newline at end of file
diff --git a/Azaion.Inference/cdn_manager.pxd b/Azaion.Loader/cdn_manager.pxd
similarity index 100%
rename from Azaion.Inference/cdn_manager.pxd
rename to Azaion.Loader/cdn_manager.pxd
diff --git a/Azaion.Inference/cdn_manager.pyx b/Azaion.Loader/cdn_manager.pyx
similarity index 100%
rename from Azaion.Inference/cdn_manager.pyx
rename to Azaion.Loader/cdn_manager.pyx
diff --git a/Azaion.Loader/constants.pxd b/Azaion.Loader/constants.pxd
new file mode 100644
index 0000000..6cd4339
--- /dev/null
+++ b/Azaion.Loader/constants.pxd
@@ -0,0 +1,17 @@
+cdef str CONFIG_FILE # Port for the zmq
+
+cdef int QUEUE_MAXSIZE # Maximum size of the command queue
+cdef str COMMANDS_QUEUE # Name of the commands queue in rabbit
+cdef str ANNOTATIONS_QUEUE # Name of the annotations queue in rabbit
+
+cdef str QUEUE_CONFIG_FILENAME # queue config filename to load from api
+
+cdef str AI_ONNX_MODEL_FILE
+
+cdef str CDN_CONFIG
+cdef str MODELS_FOLDER
+
+cdef int SMALL_SIZE_KB
+
+
+cdef log(str log_message, bytes client_id=*)
\ No newline at end of file
diff --git a/Azaion.Loader/constants.pyx b/Azaion.Loader/constants.pyx
new file mode 100644
index 0000000..8c5ed80
--- /dev/null
+++ b/Azaion.Loader/constants.pyx
@@ -0,0 +1,16 @@
+import time
+
+cdef str CONFIG_FILE = "config.yaml" # Port for the zmq
+
+cdef str QUEUE_CONFIG_FILENAME = "secured-config.json"
+cdef str AI_ONNX_MODEL_FILE = "azaion.onnx"
+
+cdef str CDN_CONFIG = "cdn.yaml"
+cdef str MODELS_FOLDER = "models"
+
+cdef int SMALL_SIZE_KB = 3
+
+cdef log(str log_message, bytes client_id=None):
+ local_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())
+ client_str = '' if client_id is None else f' {client_id}'
+ print(f'[{local_time}{client_str}]: {log_message}')
\ No newline at end of file
diff --git a/Azaion.Inference/credentials.pxd b/Azaion.Loader/credentials.pxd
similarity index 100%
rename from Azaion.Inference/credentials.pxd
rename to Azaion.Loader/credentials.pxd
diff --git a/Azaion.Inference/credentials.pyx b/Azaion.Loader/credentials.pyx
similarity index 100%
rename from Azaion.Inference/credentials.pyx
rename to Azaion.Loader/credentials.pyx
diff --git a/Azaion.Inference/file_data.pxd b/Azaion.Loader/file_data.pxd
similarity index 100%
rename from Azaion.Inference/file_data.pxd
rename to Azaion.Loader/file_data.pxd
diff --git a/Azaion.Inference/file_data.pyx b/Azaion.Loader/file_data.pyx
similarity index 100%
rename from Azaion.Inference/file_data.pyx
rename to Azaion.Loader/file_data.pyx
diff --git a/Azaion.Inference/hardware_service.pxd b/Azaion.Loader/hardware_service.pxd
similarity index 100%
rename from Azaion.Inference/hardware_service.pxd
rename to Azaion.Loader/hardware_service.pxd
diff --git a/Azaion.Inference/hardware_service.pyx b/Azaion.Loader/hardware_service.pyx
similarity index 100%
rename from Azaion.Inference/hardware_service.pyx
rename to Azaion.Loader/hardware_service.pyx
diff --git a/Azaion.Loader/main.pyx b/Azaion.Loader/main.pyx
new file mode 100644
index 0000000..b891954
--- /dev/null
+++ b/Azaion.Loader/main.pyx
@@ -0,0 +1,61 @@
+import threading
+from threading import Thread
+import traceback
+from credentials cimport Credentials
+from remote_command cimport RemoteCommand, CommandType
+from remote_command_handler cimport RemoteCommandHandler
+from file_data cimport FileData
+from api_client cimport ApiClient
+
+cdef class CommandProcessor:
+ cdef RemoteCommandHandler remote_handler
+ cdef ApiClient api_client
+ cdef bint running
+ cdef object shutdown_event
+
+ def __init__(self, int zmq_port, str api_url):
+ self.api_client = ApiClient(api_url)
+ self.shutdown_event = threading.Event()
+ self.remote_handler = RemoteCommandHandler(zmq_port, self.on_command)
+ self.remote_handler.start()
+ self.running = True
+
+ def start(self):
+ while self.running:
+ try:
+ while not self.shutdown_event.is_set():
+ self.shutdown_event.wait(timeout=1.0)
+ except Exception as e:
+ traceback.print_exc()
+ print('EXIT!')
+
+ cdef on_command(self, RemoteCommand command):
+ try:
+ if command.command_type == CommandType.LOGIN:
+ self.api_client.set_credentials(Credentials.from_msgpack(command.data))
+ elif command.command_type == CommandType.LOAD:
+ self.load_file(command)
+ elif command.command_type == CommandType.EXIT:
+ t = Thread(target=self.stop) # non-block worker:
+ t.start()
+ else:
+ pass
+ except Exception as e:
+ print(f"Error handling client: {e}")
+
+ cdef load_file(self, RemoteCommand command):
+ cdef RemoteCommand response
+ cdef FileData file_data
+ cdef bytes file_bytes
+ try:
+ file_data = FileData.from_msgpack(command.data)
+ file_bytes = self.api_client.load_bytes(file_data.filename, file_data.folder)
+ response = RemoteCommand(CommandType.DATA_BYTES, file_bytes)
+ except Exception as e:
+ response = RemoteCommand(CommandType.DATA_BYTES, None, str(e))
+ self.remote_handler.send(command.client_id, response.serialize())
+
+ def stop(self):
+ self.shutdown_event.set()
+ self.remote_handler.stop()
+ self.running = False
diff --git a/Azaion.Loader/remote_command.pxd b/Azaion.Loader/remote_command.pxd
new file mode 100644
index 0000000..5f5b708
--- /dev/null
+++ b/Azaion.Loader/remote_command.pxd
@@ -0,0 +1,22 @@
+cdef enum CommandType:
+ LOGIN = 10
+ LOAD = 20
+ DATA_BYTES = 25
+ INFERENCE = 30
+ INFERENCE_DATA = 35
+ STOP_INFERENCE = 40
+ AI_AVAILABILITY_CHECK = 80
+ AI_AVAILABILITY_RESULT = 85
+ ERROR = 90
+ EXIT = 100
+
+cdef class RemoteCommand:
+ cdef public bytes client_id
+ cdef CommandType command_type
+ cdef str message
+ cdef bytes data
+
+ @staticmethod
+ cdef from_msgpack(bytes data)
+
+ cdef bytes serialize(self)
diff --git a/Azaion.Loader/remote_command.pyx b/Azaion.Loader/remote_command.pyx
new file mode 100644
index 0000000..333b34a
--- /dev/null
+++ b/Azaion.Loader/remote_command.pyx
@@ -0,0 +1,35 @@
+import msgpack
+
+cdef class RemoteCommand:
+ def __init__(self, CommandType command_type, bytes data, str message=None):
+ self.command_type = command_type
+ self.data = data
+ self.message = message
+
+ def __str__(self):
+ command_type_names = {
+ 10: "LOGIN",
+ 20: "LOAD",
+ 25: "DATA_BYTES",
+ 30: "INFERENCE",
+ 35: "INFERENCE_DATA",
+ 40: "STOP_INFERENCE",
+ 80: "AI_AVAILABILITY_CHECK",
+ 85: "AI_AVAILABILITY_RESULT",
+ 90: "ERROR",
+ 100: "EXIT"
+ }
+ data_str = f'{len(self.data)} bytes' if self.data else ''
+ return f'{command_type_names[self.command_type]} ({data_str})'
+
+ @staticmethod
+ cdef from_msgpack(bytes data):
+ unpacked = msgpack.unpackb(data, strict_map_key=False)
+ return RemoteCommand(unpacked.get("CommandType"), unpacked.get("Data"), unpacked.get("Message"))
+
+ cdef bytes serialize(self):
+ return msgpack.packb({
+ "CommandType": self.command_type,
+ "Data": self.data,
+ "Message": self.message
+ })
diff --git a/Azaion.Loader/remote_command_handler.pxd b/Azaion.Loader/remote_command_handler.pxd
new file mode 100644
index 0000000..b4aaba9
--- /dev/null
+++ b/Azaion.Loader/remote_command_handler.pxd
@@ -0,0 +1,16 @@
+cdef class RemoteCommandHandler:
+ cdef object _context
+ cdef object _router
+ cdef object _dealer
+ cdef object _control
+ cdef object _shutdown_event
+ cdef object _on_command
+
+ cdef object _proxy_thread
+ cdef object _workers
+
+ cdef start(self)
+ cdef _proxy_loop(self)
+ cdef _worker_loop(self)
+ cdef send(self, bytes client_id, bytes data)
+ cdef stop(self)
diff --git a/Azaion.Loader/remote_command_handler.pyx b/Azaion.Loader/remote_command_handler.pyx
new file mode 100644
index 0000000..86877ed
--- /dev/null
+++ b/Azaion.Loader/remote_command_handler.pyx
@@ -0,0 +1,94 @@
+import time
+import zmq
+from threading import Thread, Event
+from remote_command cimport RemoteCommand
+cimport constants
+import yaml
+
+cdef class RemoteCommandHandler:
+ def __init__(self, int zmq_port, object on_command):
+ self._on_command = on_command
+ self._context = zmq.Context.instance()
+
+ self._router = self._context.socket(zmq.ROUTER)
+ self._router.setsockopt(zmq.LINGER, 0)
+ self._router.bind(f'tcp://*:{zmq_port}')
+
+ self._dealer = self._context.socket(zmq.DEALER)
+ self._dealer.setsockopt(zmq.LINGER, 0)
+ self._dealer.bind("inproc://backend")
+
+ self._control = self._context.socket(zmq.PAIR)
+ self._control.bind("inproc://control")
+ self._shutdown_event = Event()
+
+ self._proxy_thread = Thread(target=self._proxy_loop, daemon=True)
+
+ self._workers = []
+ for _ in range(4): # 4 worker threads
+ worker = Thread(target=self._worker_loop, daemon=True)
+ self._workers.append(worker)
+ print(f'Listening to commands on port {zmq_port}...')
+
+ cdef start(self):
+ self._proxy_thread.start()
+ for worker in self._workers:
+ worker.start()
+
+ cdef _proxy_loop(self):
+ try:
+ zmq.proxy_steerable(self._router, self._dealer, control=self._control)
+ except zmq.error.ZMQError as e:
+ if self._shutdown_event.is_set():
+ print("Shutdown, exit proxy loop.")
+ else:
+ raise
+
+ cdef _worker_loop(self):
+ worker_socket = self._context.socket(zmq.DEALER)
+ worker_socket.setsockopt(zmq.LINGER, 0)
+ worker_socket.connect("inproc://backend")
+ poller = zmq.Poller()
+ poller.register(worker_socket, zmq.POLLIN)
+ try:
+
+ while not self._shutdown_event.is_set():
+ try:
+ socks = dict(poller.poll(500))
+ if worker_socket in socks:
+ client_id, message = worker_socket.recv_multipart()
+ cmd = RemoteCommand.from_msgpack( message)
+ cmd.client_id = client_id
+ constants.log(f'{cmd}', client_id)
+ self._on_command(cmd)
+ except Exception as e:
+ if not self._shutdown_event.is_set():
+ constants.log(f"Worker error: {e}")
+ import traceback
+ traceback.print_exc()
+ finally:
+ worker_socket.close()
+
+ cdef send(self, bytes client_id, bytes data):
+ self._router.send_multipart([client_id, data])
+
+ # with self._context.socket(zmq.DEALER) as socket:
+ # socket.connect("inproc://backend")
+ # socket.send_multipart([client_id, data])
+ # # constants.log(f'Sent {len(data)} bytes.', client_id)
+
+ cdef stop(self):
+ self._shutdown_event.set()
+ try:
+ self._control.send(b"TERMINATE", flags=zmq.DONTWAIT)
+ except zmq.error.ZMQError:
+ pass
+ self._router.close(linger=0)
+ self._dealer.close(linger=0)
+ self._control.close(linger=0)
+
+ self._proxy_thread.join(timeout=2)
+ while any(w.is_alive() for w in self._workers):
+ time.sleep(0.1)
+
+ self._context.term()
diff --git a/Azaion.Loader/requirements.txt b/Azaion.Loader/requirements.txt
new file mode 100644
index 0000000..ea86d64
--- /dev/null
+++ b/Azaion.Loader/requirements.txt
@@ -0,0 +1,9 @@
+pyinstaller
+Cython
+psutil
+msgpack
+pyjwt
+zmq
+requests
+pyyaml
+boto3
diff --git a/Azaion.Inference/security.pxd b/Azaion.Loader/security.pxd
similarity index 100%
rename from Azaion.Inference/security.pxd
rename to Azaion.Loader/security.pxd
diff --git a/Azaion.Inference/security.pyx b/Azaion.Loader/security.pyx
similarity index 100%
rename from Azaion.Inference/security.pyx
rename to Azaion.Loader/security.pyx
diff --git a/Azaion.Loader/setup.py b/Azaion.Loader/setup.py
new file mode 100644
index 0000000..acce980
--- /dev/null
+++ b/Azaion.Loader/setup.py
@@ -0,0 +1,31 @@
+from setuptools import setup, Extension
+from Cython.Build import cythonize
+
+extensions = [
+ Extension('constants', ['constants.pyx']),
+ Extension('credentials', ['credentials.pyx']),
+ Extension('file_data', ['file_data.pyx']),
+ Extension('hardware_service', ['hardware_service.pyx'], extra_compile_args=["-g"], extra_link_args=["-g"]),
+ Extension('security', ['security.pyx']),
+ Extension('remote_command', ['remote_command.pyx']),
+ Extension('remote_command_handler', ['remote_command_handler.pyx']),
+ Extension('user', ['user.pyx']),
+ Extension('cdn_manager', ['cdn_manager.pyx']),
+ Extension('api_client', ['api_client.pyx']),
+ Extension('main', ['main.pyx']),
+]
+
+setup(
+ name="azaion.loader",
+ ext_modules=cythonize(
+ extensions,
+ compiler_directives={
+ "language_level": 3,
+ "emit_code_comments" : False,
+ "binding": True,
+ 'boundscheck': False,
+ 'wraparound': False
+ }
+ ),
+ zip_safe=False
+)
\ No newline at end of file
diff --git a/Azaion.Loader/start.py b/Azaion.Loader/start.py
new file mode 100644
index 0000000..b2d033f
--- /dev/null
+++ b/Azaion.Loader/start.py
@@ -0,0 +1,15 @@
+from main import CommandProcessor
+import argparse
+
+
+if __name__ == '__main__':
+ parser = argparse.ArgumentParser()
+ parser.add_argument("-p", "--port", type=str, default="5025", help="zero mq port")
+ parser.add_argument("-a", "--api", type=str, default="https://api.azaion.com", help="api url")
+ args = parser.parse_args()
+
+ processor = CommandProcessor(int(args.port), args.api)
+ try:
+ processor.start()
+ except KeyboardInterrupt:
+ processor.stop()
diff --git a/Azaion.Inference/user.pxd b/Azaion.Loader/user.pxd
similarity index 100%
rename from Azaion.Inference/user.pxd
rename to Azaion.Loader/user.pxd
diff --git a/Azaion.Inference/user.pyx b/Azaion.Loader/user.pyx
similarity index 100%
rename from Azaion.Inference/user.pyx
rename to Azaion.Loader/user.pyx
diff --git a/Azaion.LoaderUI/App.xaml b/Azaion.LoaderUI/App.xaml
new file mode 100644
index 0000000..db0e726
--- /dev/null
+++ b/Azaion.LoaderUI/App.xaml
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/Azaion.LoaderUI/App.xaml.cs b/Azaion.LoaderUI/App.xaml.cs
new file mode 100644
index 0000000..6ead66e
--- /dev/null
+++ b/Azaion.LoaderUI/App.xaml.cs
@@ -0,0 +1,26 @@
+using System.Windows;
+using Serilog;
+
+namespace Azaion.LoaderUI;
+
+public partial class App
+{
+ protected override void OnStartup(StartupEventArgs e)
+ {
+ base.OnStartup(e);
+ Start();
+ }
+
+ private void Start()
+ {
+ Log.Logger = new LoggerConfiguration()
+ .Enrich.FromLogContext()
+ .MinimumLevel.Information()
+ .WriteTo.Console()
+ .WriteTo.File(
+ path: "Logs/log.txt",
+ rollingInterval: RollingInterval.Day)
+ .CreateLogger();
+
+ }
+}
\ No newline at end of file
diff --git a/Azaion.LoaderUI/AssemblyInfo.cs b/Azaion.LoaderUI/AssemblyInfo.cs
new file mode 100644
index 0000000..b0ec827
--- /dev/null
+++ b/Azaion.LoaderUI/AssemblyInfo.cs
@@ -0,0 +1,10 @@
+using System.Windows;
+
+[assembly: ThemeInfo(
+ ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
+ //(used if a resource is not found in the page,
+ // or application resource dictionaries)
+ ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
+ //(used if a resource is not found in the page,
+ // app, or any theme specific resource dictionaries)
+)]
diff --git a/Azaion.LoaderUI/Azaion.LoaderUI.csproj b/Azaion.LoaderUI/Azaion.LoaderUI.csproj
new file mode 100644
index 0000000..35c3af2
--- /dev/null
+++ b/Azaion.LoaderUI/Azaion.LoaderUI.csproj
@@ -0,0 +1,26 @@
+
+
+
+ WinExe
+ net8.0-windows
+ enable
+ enable
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Azaion.Suite/Login.xaml b/Azaion.LoaderUI/Login.xaml
similarity index 99%
rename from Azaion.Suite/Login.xaml
rename to Azaion.LoaderUI/Login.xaml
index 8b419d6..d2c6414 100644
--- a/Azaion.Suite/Login.xaml
+++ b/Azaion.LoaderUI/Login.xaml
@@ -1,4 +1,4 @@
-? 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)
+ private void MainMouseMove(object sende0r, MouseEventArgs e)
{
if (e.OriginalSource is Button || e.OriginalSource is TextBox)
return;
diff --git a/Azaion.Suite/App.xaml.cs b/Azaion.Suite/App.xaml.cs
index 2d0c1ce..5611f5b 100644
--- a/Azaion.Suite/App.xaml.cs
+++ b/Azaion.Suite/App.xaml.cs
@@ -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 _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 _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(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(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().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(e.Args)
+ .WithParsed(Start)
+ .WithNotParsed(ErrorHandling);
+ }
+
+ private void ErrorHandling(IEnumerable 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(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(context.Configuration);
+ services.AddSingleton(loaderClient);
+
services.ConfigureSection(context.Configuration);
- services.ConfigureSection(context.Configuration);
- services.AddSingleton(_inferenceClient);
- services.AddSingleton();
+ services.AddSingleton();
services.AddSingleton();
+
+ services.ConfigureSection(context.Configuration);
+ services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
+
services.AddHttpClient();
- services.AddSingleton(_azaionApi);
+ services.AddSingleton(azaionApi);
#endregion
services.AddSingleton();
@@ -235,12 +206,13 @@ public partial class App
datasetExplorer.Hide();
_mediator = _host.Services.GetRequiredService();
- if (!string.IsNullOrEmpty(_loadErrors))
- _mediator.Publish(new LoadErrorEvent(_loadErrors));
- _logger = _host.Services.GetRequiredService>();
_formState = _host.Services.GetRequiredService();
DispatcherUnhandledException += OnDispatcherUnhandledException;
+
+ _host.Start();
+ EventManager.RegisterClassHandler(typeof(UIElement), UIElement.PreviewKeyDownEvent, new RoutedEventHandler(GlobalKeyHandler));
+ _host.Services.GetRequiredService().Show();
}
private void GlobalKeyHandler(object sender, RoutedEventArgs e)
diff --git a/Azaion.Suite/Azaion.Suite.csproj b/Azaion.Suite/Azaion.Suite.csproj
index 0a1e040..9af9aa0 100644
--- a/Azaion.Suite/Azaion.Suite.csproj
+++ b/Azaion.Suite/Azaion.Suite.csproj
@@ -12,19 +12,21 @@
+
-
-
-
-
+
+
+
+
-
-
+
+
+
-
+
@@ -51,20 +53,4 @@
PreserveNewest
-
-
-
- MSBuild:Compile
- Wpf
- Designer
-
-
-
-
-
-
-
-
-
-
diff --git a/Azaion.Suite/config.json b/Azaion.Suite/config.json
index d4652ef..422921b 100644
--- a/Azaion.Suite/config.json
+++ b/Azaion.Suite/config.json
@@ -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",
diff --git a/Azaion.Suite/postbuild.cmd b/Azaion.Suite/postbuild.cmd
deleted file mode 100644
index b97ba0d..0000000
--- a/Azaion.Suite/postbuild.cmd
+++ /dev/null
@@ -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\
-
diff --git a/Azaion.Suite/upload-file.cmd b/Azaion.Suite/upload-file.cmd
deleted file mode 100644
index b32d6e5..0000000
--- a/Azaion.Suite/upload-file.cmd
+++ /dev/null
@@ -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!
diff --git a/Azaion.Test/Azaion.Test.csproj b/Azaion.Test/Azaion.Test.csproj
index e0cc5b7..6c13800 100644
--- a/Azaion.Test/Azaion.Test.csproj
+++ b/Azaion.Test/Azaion.Test.csproj
@@ -13,7 +13,7 @@
-
+
diff --git a/Dummy/Azaion.Annotator/Azaion.Annotator.csproj b/Dummy/Azaion.Annotator/Azaion.Annotator.csproj
index 0005c54..daeb91e 100644
--- a/Dummy/Azaion.Annotator/Azaion.Annotator.csproj
+++ b/Dummy/Azaion.Annotator/Azaion.Annotator.csproj
@@ -11,19 +11,12 @@
-
-
-
-
-
+
+
+
+
-
-
-
-
-
-
diff --git a/Dummy/Azaion.Dataset/Azaion.Dataset.csproj b/Dummy/Azaion.Dataset/Azaion.Dataset.csproj
index 85c6517..04f54b9 100644
--- a/Dummy/Azaion.Dataset/Azaion.Dataset.csproj
+++ b/Dummy/Azaion.Dataset/Azaion.Dataset.csproj
@@ -16,7 +16,7 @@
-
+
diff --git a/build/build_dotnet.cmd b/build/build_dotnet.cmd
index 70b7729..c0302c6 100644
--- a/build/build_dotnet.cmd
+++ b/build/build_dotnet.cmd
@@ -16,9 +16,8 @@ xcopy Azaion.Suite\bin\Release\net8.0-windows\win-x64\publish dist\ /s /e /q
del dist\config.json
move dist\config.production.json dist\config.json
-mkdir dist-azaion\dummy
-robocopy "dist" "dist-azaion\dummy" "Azaion.Annotator.dll" "Azaion.Dataset.dll" /MOV
-robocopy "dist" "dist-azaion" "Azaion.Common.dll" "Azaion.CommonSecurity.dll" "Azaion.Suite.deps.json" "Azaion.Suite.dll" "Azaion.Suite.exe" "Azaion.Suite.runtimeconfig.json" "config.json" "logo.png" /MOV
+robocopy "dist" "dist-azaion" "Azaion.Annotator.dll" "Azaion.Dataset.dll" "Azaion.Common.dll" "Azaion.CommonSecurity.dll" /MOV
+robocopy "dist" "dist-azaion" "Azaion.Suite.deps.json" "Azaion.Suite.dll" "Azaion.Suite.exe" "Azaion.Suite.runtimeconfig.json" "config.json" "logo.png" /MOV
if exist dist\libvlc\win-x86 rmdir dist\libvlc\win-x86 /s /q
robocopy "dist" "dist-dlls" /E /MOVE