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