mirror of
https://github.com/azaion/annotations.git
synced 2026-04-23 02:26:31 +00:00
failsafe load dlls
add user config queue offsets throttle improvements
This commit is contained in:
@@ -261,11 +261,11 @@ public partial class Annotator
|
|||||||
_appConfig.UIConfig.LeftPanelWidth = MainGrid.ColumnDefinitions.FirstOrDefault()!.Width.Value;
|
_appConfig.UIConfig.LeftPanelWidth = MainGrid.ColumnDefinitions.FirstOrDefault()!.Width.Value;
|
||||||
_appConfig.UIConfig.RightPanelWidth = MainGrid.ColumnDefinitions.LastOrDefault()!.Width.Value;
|
_appConfig.UIConfig.RightPanelWidth = MainGrid.ColumnDefinitions.LastOrDefault()!.Width.Value;
|
||||||
|
|
||||||
await ThrottleExt.ThrottleRunFirst(() =>
|
await ThrottleExt.Throttle(() =>
|
||||||
{
|
{
|
||||||
_configUpdater.Save(_appConfig);
|
_configUpdater.Save(_appConfig);
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}, SaveConfigTaskId, TimeSpan.FromSeconds(5));
|
}, TimeSpan.FromSeconds(5));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ShowTimeAnnotations(TimeSpan time)
|
private void ShowTimeAnnotations(TimeSpan time)
|
||||||
|
|||||||
@@ -1,15 +1,13 @@
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Reflection.Metadata;
|
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using Azaion.Annotator.DTO;
|
using Azaion.Annotator.DTO;
|
||||||
using Azaion.Common;
|
using Azaion.Common;
|
||||||
using Azaion.Common.DTO;
|
using Azaion.Common.DTO;
|
||||||
using Azaion.Common.DTO.Config;
|
|
||||||
using Azaion.Common.DTO.Queue;
|
|
||||||
using Azaion.Common.Events;
|
using Azaion.Common.Events;
|
||||||
using Azaion.Common.Extensions;
|
using Azaion.Common.Extensions;
|
||||||
using Azaion.Common.Services;
|
using Azaion.Common.Services;
|
||||||
|
using Azaion.CommonSecurity.DTO;
|
||||||
using LibVLCSharp.Shared;
|
using LibVLCSharp.Shared;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|||||||
+43
-12
@@ -1,5 +1,9 @@
|
|||||||
using System.Windows;
|
using System.Windows;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using Azaion.Common.Database;
|
||||||
using Azaion.Common.DTO;
|
using Azaion.Common.DTO;
|
||||||
|
using Azaion.Common.DTO.Config;
|
||||||
|
using Azaion.Common.Extensions;
|
||||||
|
|
||||||
namespace Azaion.Common;
|
namespace Azaion.Common;
|
||||||
|
|
||||||
@@ -21,20 +25,32 @@ public class Constants
|
|||||||
|
|
||||||
#region AnnotatorConfig
|
#region AnnotatorConfig
|
||||||
|
|
||||||
|
public static readonly AnnotationConfig DefaultAnnotationConfig = new()
|
||||||
|
{
|
||||||
|
DetectionClasses = DefaultAnnotationClasses,
|
||||||
|
VideoFormats = DefaultVideoFormats,
|
||||||
|
ImageFormats = DefaultImageFormats,
|
||||||
|
AnnotationsDbFile = DEFAULT_ANNOTATIONS_DB_FILE
|
||||||
|
};
|
||||||
|
|
||||||
public static readonly List<DetectionClass> DefaultAnnotationClasses =
|
public static readonly List<DetectionClass> DefaultAnnotationClasses =
|
||||||
[
|
[
|
||||||
new() { Id = 0, Name = "Броньована техніка", ShortName = "Бронь" },
|
new() { Id = 0, Name = "ArmorVehicle", ShortName = "Броня", Color = "#FF0000".ToColor() },
|
||||||
new() { Id = 1, Name = "Вантажівка", ShortName = "Вантаж" },
|
new() { Id = 1, Name = "Truck", ShortName = "Вантаж.", Color = "#00FF00".ToColor() },
|
||||||
new() { Id = 2, Name = "Машина легкова", ShortName = "Машина" },
|
new() { Id = 2, Name = "Vehicle", ShortName = "Машина", Color = "#0000FF".ToColor() },
|
||||||
new() { Id = 3, Name = "Артилерія", ShortName = "Арта" },
|
new() { Id = 3, Name = "Atillery", ShortName = "Арта", Color = "#FFFF00".ToColor() },
|
||||||
new() { Id = 4, Name = "Тінь від техніки", ShortName = "Тінь" },
|
new() { Id = 4, Name = "Shadow", ShortName = "Тінь", Color = "#FF00FF".ToColor() },
|
||||||
new() { Id = 5, Name = "Окопи", ShortName = "Окопи" },
|
new() { Id = 5, Name = "Trenches", ShortName = "Окопи", Color = "#00FFFF".ToColor() },
|
||||||
new() { Id = 6, Name = "Військовий", ShortName = "Військов" },
|
new() { Id = 6, Name = "MilitaryMan", ShortName = "Військов", Color = "#188021".ToColor() },
|
||||||
new() { Id = 7, Name = "Накати", ShortName = "Накати" },
|
new() { Id = 7, Name = "TyreTracks", ShortName = "Накати", Color = "#800000".ToColor() },
|
||||||
new() { Id = 8, Name = "Танк з захистом", ShortName = "Танк захист" },
|
new() { Id = 8, Name = "AdditArmoredTank", ShortName = "Танк.захист", Color = "#008000".ToColor() },
|
||||||
new() { Id = 9, Name = "Дим", ShortName = "Дим" },
|
new() { Id = 9, Name = "Smoke", ShortName = "Дим", Color = "#000080".ToColor() },
|
||||||
new() { Id = 10, Name = "Літак", ShortName = "Літак" },
|
new() { Id = 10, Name = "Plane", ShortName = "Літак", Color = "#000080".ToColor() },
|
||||||
new() { Id = 11, Name = "Мотоцикл", ShortName = "Мото" }
|
new() { Id = 11, Name = "Moto", ShortName = "Мото", Color = "#808000".ToColor() },
|
||||||
|
new() { Id = 12, Name = "CamouflageNet", ShortName = "Сітка", Color = "#800080".ToColor() },
|
||||||
|
new() { Id = 13, Name = "CamouflageBranches", ShortName = "Гілки", Color = "#2f4f4f".ToColor() },
|
||||||
|
new() { Id = 14, Name = "Roof", ShortName = "Дах", Color = "#1e90ff".ToColor() },
|
||||||
|
new() { Id = 15, Name = "Building", ShortName = "Будівля", Color = "#ffb6c1".ToColor() }
|
||||||
];
|
];
|
||||||
|
|
||||||
public static readonly List<string> DefaultVideoFormats = ["mp4", "mov", "avi"];
|
public static readonly List<string> DefaultVideoFormats = ["mp4", "mov", "avi"];
|
||||||
@@ -49,6 +65,15 @@ public class Constants
|
|||||||
|
|
||||||
# region AIRecognitionConfig
|
# region AIRecognitionConfig
|
||||||
|
|
||||||
|
public static readonly AIRecognitionConfig DefaultAIRecognitionConfig = new()
|
||||||
|
{
|
||||||
|
FrameRecognitionSeconds = DEFAULT_FRAME_RECOGNITION_SECONDS,
|
||||||
|
TrackingDistanceConfidence = TRACKING_DISTANCE_CONFIDENCE,
|
||||||
|
TrackingProbabilityIncrease = TRACKING_PROBABILITY_INCREASE,
|
||||||
|
TrackingIntersectionThreshold = TRACKING_INTERSECTION_THRESHOLD,
|
||||||
|
FramePeriodRecognition = DEFAULT_FRAME_PERIOD_RECOGNITION
|
||||||
|
};
|
||||||
|
|
||||||
public const double DEFAULT_FRAME_RECOGNITION_SECONDS = 2;
|
public const double DEFAULT_FRAME_RECOGNITION_SECONDS = 2;
|
||||||
public const double TRACKING_DISTANCE_CONFIDENCE = 0.15;
|
public const double TRACKING_DISTANCE_CONFIDENCE = 0.15;
|
||||||
public const double TRACKING_PROBABILITY_INCREASE = 15;
|
public const double TRACKING_PROBABILITY_INCREASE = 15;
|
||||||
@@ -60,6 +85,12 @@ public class Constants
|
|||||||
|
|
||||||
#region Thumbnails
|
#region Thumbnails
|
||||||
|
|
||||||
|
public static readonly ThumbnailConfig DefaultThumbnailConfig = new()
|
||||||
|
{
|
||||||
|
Size = DefaultThumbnailSize,
|
||||||
|
Border = DEFAULT_THUMBNAIL_BORDER
|
||||||
|
};
|
||||||
|
|
||||||
public static readonly Size DefaultThumbnailSize = new(240, 135);
|
public static readonly Size DefaultThumbnailSize = new(240, 135);
|
||||||
|
|
||||||
public const int DEFAULT_THUMBNAIL_BORDER = 10;
|
public const int DEFAULT_THUMBNAIL_BORDER = 10;
|
||||||
|
|||||||
@@ -45,14 +45,7 @@ public class ConfigUpdater : IConfigUpdater
|
|||||||
|
|
||||||
var appConfig = new AppConfig
|
var appConfig = new AppConfig
|
||||||
{
|
{
|
||||||
AnnotationConfig = new AnnotationConfig
|
AnnotationConfig = Constants.DefaultAnnotationConfig,
|
||||||
{
|
|
||||||
DetectionClasses = Constants.DefaultAnnotationClasses,
|
|
||||||
VideoFormats = Constants.DefaultVideoFormats,
|
|
||||||
ImageFormats = Constants.DefaultImageFormats,
|
|
||||||
|
|
||||||
AnnotationsDbFile = Constants.DEFAULT_ANNOTATIONS_DB_FILE
|
|
||||||
},
|
|
||||||
|
|
||||||
UIConfig = new UIConfig
|
UIConfig = new UIConfig
|
||||||
{
|
{
|
||||||
@@ -72,20 +65,8 @@ public class ConfigUpdater : IConfigUpdater
|
|||||||
GpsRouteDirectory = Constants.DEFAULT_GPS_ROUTE_DIRECTORY
|
GpsRouteDirectory = Constants.DEFAULT_GPS_ROUTE_DIRECTORY
|
||||||
},
|
},
|
||||||
|
|
||||||
ThumbnailConfig = new ThumbnailConfig
|
ThumbnailConfig = Constants.DefaultThumbnailConfig,
|
||||||
{
|
AIRecognitionConfig = Constants.DefaultAIRecognitionConfig
|
||||||
Size = Constants.DefaultThumbnailSize,
|
|
||||||
Border = Constants.DEFAULT_THUMBNAIL_BORDER
|
|
||||||
},
|
|
||||||
|
|
||||||
AIRecognitionConfig = new AIRecognitionConfig
|
|
||||||
{
|
|
||||||
FrameRecognitionSeconds = Constants.DEFAULT_FRAME_RECOGNITION_SECONDS,
|
|
||||||
TrackingDistanceConfidence = Constants.TRACKING_DISTANCE_CONFIDENCE,
|
|
||||||
TrackingProbabilityIncrease = Constants.TRACKING_PROBABILITY_INCREASE,
|
|
||||||
TrackingIntersectionThreshold = Constants.TRACKING_INTERSECTION_THRESHOLD,
|
|
||||||
FramePeriodRecognition = Constants.DEFAULT_FRAME_PERIOD_RECOGNITION
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
Save(appConfig);
|
Save(appConfig);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,4 +12,7 @@ public static class ColorExtensions
|
|||||||
color.A = (byte)(MIN_ALPHA + (int)Math.Round(confidence * (MAX_ALPHA - MIN_ALPHA)));
|
color.A = (byte)(MIN_ALPHA + (int)Math.Round(confidence * (MAX_ALPHA - MIN_ALPHA)));
|
||||||
return color;
|
return color;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Color ToColor(this string hexColor) =>
|
||||||
|
(Color)ColorConverter.ConvertFromString(hexColor);
|
||||||
}
|
}
|
||||||
@@ -1,57 +1,22 @@
|
|||||||
using System.Collections.Concurrent;
|
namespace Azaion.Common.Extensions;
|
||||||
|
|
||||||
namespace Azaion.Common.Extensions;
|
|
||||||
|
|
||||||
public static class ThrottleExt
|
public static class ThrottleExt
|
||||||
{
|
{
|
||||||
private static ConcurrentDictionary<Guid, bool> _taskStates = new();
|
private static readonly Dictionary<Delegate, DateTime> LastExecution = new();
|
||||||
|
private static readonly object Lock = new();
|
||||||
|
|
||||||
public static async Task ThrottleRunFirst(Func<Task> func, Guid actionId, TimeSpan? throttleTime = null, CancellationToken cancellationToken = default)
|
public static async Task Throttle(this Func<Task> func, TimeSpan interval, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
if (_taskStates.ContainsKey(actionId) && _taskStates[actionId])
|
ArgumentNullException.ThrowIfNull(func);
|
||||||
|
|
||||||
|
lock (Lock)
|
||||||
|
{
|
||||||
|
if (LastExecution.ContainsKey(func) && DateTime.UtcNow - LastExecution[func] < interval)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_taskStates[actionId] = true;
|
func();
|
||||||
try
|
LastExecution[func] = DateTime.UtcNow;
|
||||||
{
|
|
||||||
await func();
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Console.WriteLine(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
_ = Task.Run(async () =>
|
|
||||||
{
|
|
||||||
await Task.Delay(throttleTime ?? TimeSpan.FromMilliseconds(500), cancellationToken);
|
|
||||||
_taskStates[actionId] = false;
|
|
||||||
}, cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task ThrottleRunAfter(Func<Task> func, Guid actionId, TimeSpan? throttleTime = null, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
if (_taskStates.ContainsKey(actionId) && _taskStates[actionId])
|
|
||||||
return;
|
|
||||||
|
|
||||||
_taskStates[actionId] = true;
|
|
||||||
_ = Task.Run(async () =>
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await Task.Delay(throttleTime ?? TimeSpan.FromMilliseconds(500), cancellationToken);
|
|
||||||
await func();
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
_taskStates[actionId] = false;
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
_taskStates[actionId] = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
}, cancellationToken);
|
|
||||||
await Task.CompletedTask;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -26,15 +26,13 @@ public class AnnotationService : INotificationHandler<AnnotationsDeletedEvent>
|
|||||||
private readonly FailsafeAnnotationsProducer _producer;
|
private readonly FailsafeAnnotationsProducer _producer;
|
||||||
private readonly IGalleryService _galleryService;
|
private readonly IGalleryService _galleryService;
|
||||||
private readonly IMediator _mediator;
|
private readonly IMediator _mediator;
|
||||||
private readonly IHardwareService _hardwareService;
|
private readonly IAzaionApi _api;
|
||||||
private readonly IAuthProvider _authProvider;
|
|
||||||
private readonly QueueConfig _queueConfig;
|
private readonly QueueConfig _queueConfig;
|
||||||
private Consumer _consumer = null!;
|
private Consumer _consumer = null!;
|
||||||
private readonly UIConfig _uiConfig;
|
private readonly UIConfig _uiConfig;
|
||||||
private static readonly Guid SaveTaskId = Guid.NewGuid();
|
private static readonly Guid SaveTaskId = Guid.NewGuid();
|
||||||
|
|
||||||
public AnnotationService(
|
public AnnotationService(
|
||||||
IResourceLoader resourceLoader,
|
|
||||||
IDbFactory dbFactory,
|
IDbFactory dbFactory,
|
||||||
FailsafeAnnotationsProducer producer,
|
FailsafeAnnotationsProducer producer,
|
||||||
IOptions<QueueConfig> queueConfig,
|
IOptions<QueueConfig> queueConfig,
|
||||||
@@ -42,14 +40,13 @@ public class AnnotationService : INotificationHandler<AnnotationsDeletedEvent>
|
|||||||
IGalleryService galleryService,
|
IGalleryService galleryService,
|
||||||
IMediator mediator,
|
IMediator mediator,
|
||||||
IHardwareService hardwareService,
|
IHardwareService hardwareService,
|
||||||
IAuthProvider authProvider)
|
IAzaionApi api)
|
||||||
{
|
{
|
||||||
_dbFactory = dbFactory;
|
_dbFactory = dbFactory;
|
||||||
_producer = producer;
|
_producer = producer;
|
||||||
_galleryService = galleryService;
|
_galleryService = galleryService;
|
||||||
_mediator = mediator;
|
_mediator = mediator;
|
||||||
_hardwareService = hardwareService;
|
_api = api;
|
||||||
_authProvider = authProvider;
|
|
||||||
_queueConfig = queueConfig.Value;
|
_queueConfig = queueConfig.Value;
|
||||||
_uiConfig = uiConfig.Value;
|
_uiConfig = uiConfig.Value;
|
||||||
|
|
||||||
@@ -58,7 +55,7 @@ public class AnnotationService : INotificationHandler<AnnotationsDeletedEvent>
|
|||||||
|
|
||||||
private async Task Init(CancellationToken cancellationToken = default)
|
private async Task Init(CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
if (!_authProvider.CurrentUser.Role.IsValidator())
|
if (!_api.CurrentUser.Role.IsValidator())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var consumerSystem = await StreamSystem.Create(new StreamSystemConfig
|
var consumerSystem = await StreamSystem.Create(new StreamSystemConfig
|
||||||
@@ -68,13 +65,11 @@ public class AnnotationService : INotificationHandler<AnnotationsDeletedEvent>
|
|||||||
Password = _queueConfig.ConsumerPassword
|
Password = _queueConfig.ConsumerPassword
|
||||||
});
|
});
|
||||||
|
|
||||||
var offset = (await _dbFactory.Run(db => db.QueueOffsets.FirstOrDefaultAsync(
|
var offset = (ulong)(_api.CurrentUser.UserConfig?.QueueConfig?.AnnotationsOffset ?? 0);
|
||||||
x => x.QueueName == Constants.MQ_ANNOTATIONS_QUEUE, token: cancellationToken))
|
|
||||||
)?.Offset ?? 0;
|
|
||||||
|
|
||||||
_consumer = await Consumer.Create(new ConsumerConfig(consumerSystem, Constants.MQ_ANNOTATIONS_QUEUE)
|
_consumer = await Consumer.Create(new ConsumerConfig(consumerSystem, Constants.MQ_ANNOTATIONS_QUEUE)
|
||||||
{
|
{
|
||||||
Reference = _hardwareService.GetHardware().Hash,
|
Reference = _api.CurrentUser.Email,
|
||||||
OffsetSpec = new OffsetTypeOffset(offset + 1),
|
OffsetSpec = new OffsetTypeOffset(offset + 1),
|
||||||
MessageHandler = async (_, _, context, message) =>
|
MessageHandler = async (_, _, context, message) =>
|
||||||
{
|
{
|
||||||
@@ -84,13 +79,13 @@ public class AnnotationService : INotificationHandler<AnnotationsDeletedEvent>
|
|||||||
.Set(x => x.Offset, context.Offset)
|
.Set(x => x.Offset, context.Offset)
|
||||||
.UpdateAsync(token: cancellationToken));
|
.UpdateAsync(token: cancellationToken));
|
||||||
|
|
||||||
await ThrottleExt.ThrottleRunAfter(() =>
|
await ThrottleExt.Throttle(() =>
|
||||||
{
|
{
|
||||||
_dbFactory.SaveToDisk();
|
_dbFactory.SaveToDisk();
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}, SaveTaskId, TimeSpan.FromSeconds(5), cancellationToken);
|
}, TimeSpan.FromSeconds(10), cancellationToken);
|
||||||
|
|
||||||
if (msg.CreatedEmail == _authProvider.CurrentUser.Email) //Don't process messages by yourself
|
if (msg.CreatedEmail == _api.CurrentUser.Email) //Don't process messages by yourself
|
||||||
return;
|
return;
|
||||||
|
|
||||||
await SaveAnnotationInner(
|
await SaveAnnotationInner(
|
||||||
@@ -114,18 +109,18 @@ public class AnnotationService : INotificationHandler<AnnotationsDeletedEvent>
|
|||||||
{
|
{
|
||||||
a.Time = TimeSpan.FromMilliseconds(a.Milliseconds);
|
a.Time = TimeSpan.FromMilliseconds(a.Milliseconds);
|
||||||
return await SaveAnnotationInner(DateTime.Now, a.OriginalMediaName, a.Time, a.Detections.ToList(),
|
return await SaveAnnotationInner(DateTime.Now, a.OriginalMediaName, a.Time, a.Detections.ToList(),
|
||||||
SourceEnum.AI, new MemoryStream(a.Image), _authProvider.CurrentUser.Role, _authProvider.CurrentUser.Email, generateThumbnail: true, token: ct);
|
SourceEnum.AI, new MemoryStream(a.Image), _api.CurrentUser.Role, _api.CurrentUser.Email, generateThumbnail: true, token: ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Manual
|
//Manual
|
||||||
public async Task<Annotation> SaveAnnotation(string originalMediaName, TimeSpan time, List<Detection> detections, Stream? stream = null, CancellationToken token = default) =>
|
public async Task<Annotation> SaveAnnotation(string originalMediaName, TimeSpan time, List<Detection> detections, Stream? stream = null, CancellationToken token = default) =>
|
||||||
await SaveAnnotationInner(DateTime.UtcNow, originalMediaName, time, detections, SourceEnum.Manual, stream,
|
await SaveAnnotationInner(DateTime.UtcNow, originalMediaName, time, detections, SourceEnum.Manual, stream,
|
||||||
_authProvider.CurrentUser.Role, _authProvider.CurrentUser.Email, generateThumbnail: true, token: token);
|
_api.CurrentUser.Role, _api.CurrentUser.Email, generateThumbnail: true, token: token);
|
||||||
|
|
||||||
//Manual Validate existing
|
//Manual Validate existing
|
||||||
public async Task ValidateAnnotation(Annotation annotation, CancellationToken token = default) =>
|
public async Task ValidateAnnotation(Annotation annotation, CancellationToken token = default) =>
|
||||||
await SaveAnnotationInner(DateTime.UtcNow, annotation.OriginalMediaName, annotation.Time, annotation.Detections.ToList(), SourceEnum.Manual, null,
|
await SaveAnnotationInner(DateTime.UtcNow, annotation.OriginalMediaName, annotation.Time, annotation.Detections.ToList(), SourceEnum.Manual, null,
|
||||||
_authProvider.CurrentUser.Role, _authProvider.CurrentUser.Email, token: token);
|
_api.CurrentUser.Role, _api.CurrentUser.Email, token: token);
|
||||||
|
|
||||||
// Manual save from Validators -> Validated -> stream: azaion-annotations-confirm
|
// Manual save from Validators -> Validated -> stream: azaion-annotations-confirm
|
||||||
// AI, Manual save from Operators -> Created -> stream: azaion-annotations
|
// AI, Manual save from Operators -> Created -> stream: azaion-annotations
|
||||||
@@ -199,11 +194,11 @@ public class AnnotationService : INotificationHandler<AnnotationsDeletedEvent>
|
|||||||
await _producer.SendToInnerQueue(annotation, token);
|
await _producer.SendToInnerQueue(annotation, token);
|
||||||
|
|
||||||
await _mediator.Publish(new AnnotationCreatedEvent(annotation), token);
|
await _mediator.Publish(new AnnotationCreatedEvent(annotation), token);
|
||||||
await ThrottleExt.ThrottleRunAfter(() =>
|
await ThrottleExt.Throttle(() =>
|
||||||
{
|
{
|
||||||
_dbFactory.SaveToDisk();
|
_dbFactory.SaveToDisk();
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}, SaveTaskId, TimeSpan.FromSeconds(5), token);
|
}, TimeSpan.FromSeconds(5), token);
|
||||||
return annotation;
|
return annotation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using System.IO;
|
|||||||
using Azaion.Common.DTO;
|
using Azaion.Common.DTO;
|
||||||
using Azaion.Common.DTO.Config;
|
using Azaion.Common.DTO.Config;
|
||||||
using Azaion.CommonSecurity;
|
using Azaion.CommonSecurity;
|
||||||
|
using Azaion.CommonSecurity.DTO;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
namespace Azaion.Common.Services;
|
namespace Azaion.Common.Services;
|
||||||
|
|||||||
@@ -17,10 +17,11 @@ public interface IInferenceService
|
|||||||
void StopInference();
|
void StopInference();
|
||||||
}
|
}
|
||||||
|
|
||||||
public class InferenceService(ILogger<InferenceService> logger, IInferenceClient client, IOptions<AIRecognitionConfig> aiConfigOptions) : IInferenceService
|
public class InferenceService(ILogger<InferenceService> logger, IInferenceClient client, IAzaionApi azaionApi, IOptions<AIRecognitionConfig> aiConfigOptions) : IInferenceService
|
||||||
{
|
{
|
||||||
public async Task RunInference(List<string> mediaPaths, Func<AnnotationImage, Task> processAnnotation, CancellationToken detectToken = default)
|
public async Task RunInference(List<string> mediaPaths, Func<AnnotationImage, Task> processAnnotation, CancellationToken detectToken = default)
|
||||||
{
|
{
|
||||||
|
client.Send(RemoteCommand.Create(CommandType.Login, azaionApi.Credentials));
|
||||||
var aiConfig = aiConfigOptions.Value;
|
var aiConfig = aiConfigOptions.Value;
|
||||||
|
|
||||||
aiConfig.Paths = mediaPaths;
|
aiConfig.Paths = mediaPaths;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ using Azaion.Common.DTO;
|
|||||||
using Azaion.Common.DTO.Config;
|
using Azaion.Common.DTO.Config;
|
||||||
using Azaion.Common.Extensions;
|
using Azaion.Common.Extensions;
|
||||||
using Azaion.CommonSecurity;
|
using Azaion.CommonSecurity;
|
||||||
|
using Azaion.CommonSecurity.DTO;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|||||||
@@ -7,9 +7,11 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="LazyCache.AspNetCore" Version="2.4.0" />
|
||||||
<PackageReference Include="MessagePack" Version="3.1.0" />
|
<PackageReference Include="MessagePack" Version="3.1.0" />
|
||||||
<PackageReference Include="MessagePack.Annotations" Version="3.1.0" />
|
<PackageReference Include="MessagePack.Annotations" Version="3.1.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.0" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Options" Version="9.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Options" Version="9.0.0" />
|
||||||
<PackageReference Include="NetMQ" Version="4.0.1.13" />
|
<PackageReference Include="NetMQ" Version="4.0.1.13" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
namespace Azaion.CommonSecurity.DTO;
|
||||||
|
|
||||||
|
internal class BusinessExceptionDto
|
||||||
|
{
|
||||||
|
public int ErrorCode { get; set; }
|
||||||
|
public string Message { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
+3
-1
@@ -1,7 +1,9 @@
|
|||||||
namespace Azaion.Common.DTO.Config;
|
namespace Azaion.CommonSecurity.DTO;
|
||||||
|
|
||||||
public class DirectoriesConfig
|
public class DirectoriesConfig
|
||||||
{
|
{
|
||||||
|
public string ApiResourcesDirectory { get; set; } = null!;
|
||||||
|
|
||||||
public string VideosDirectory { get; set; } = null!;
|
public string VideosDirectory { get; set; } = null!;
|
||||||
public string LabelsDirectory { get; set; } = null!;
|
public string LabelsDirectory { get; set; } = null!;
|
||||||
public string ImagesDirectory { get; set; } = null!;
|
public string ImagesDirectory { get; set; } = null!;
|
||||||
@@ -8,10 +8,7 @@ public abstract class ExternalClientConfig
|
|||||||
public int RetryCount {get;set;}
|
public int RetryCount {get;set;}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class InferenceClientConfig : ExternalClientConfig
|
public class InferenceClientConfig : ExternalClientConfig;
|
||||||
{
|
|
||||||
public string ResourcesFolder { get; set; } = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
public class GpsDeniedClientConfig : ExternalClientConfig
|
public class GpsDeniedClientConfig : ExternalClientConfig
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -6,6 +6,4 @@ public class HardwareInfo
|
|||||||
public string GPU { get; set; } = null!;
|
public string GPU { get; set; } = null!;
|
||||||
public string MacAddress { get; set; } = null!;
|
public string MacAddress { get; set; } = null!;
|
||||||
public string Memory { get; set; } = null!;
|
public string Memory { get; set; } = null!;
|
||||||
|
|
||||||
public string Hash { get; set; } = null!;
|
|
||||||
}
|
}
|
||||||
@@ -4,4 +4,5 @@ public class SecureAppConfig
|
|||||||
{
|
{
|
||||||
public InferenceClientConfig InferenceClientConfig { get; set; } = null!;
|
public InferenceClientConfig InferenceClientConfig { get; set; } = null!;
|
||||||
public GpsDeniedClientConfig GpsDeniedClientConfig { get; set; } = null!;
|
public GpsDeniedClientConfig GpsDeniedClientConfig { get; set; } = null!;
|
||||||
|
public DirectoriesConfig DirectoriesConfig { get; set; } = null!;
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,21 @@
|
|||||||
using MessagePack;
|
|
||||||
|
|
||||||
namespace Azaion.CommonSecurity.DTO;
|
namespace Azaion.CommonSecurity.DTO;
|
||||||
|
|
||||||
[MessagePackObject]
|
|
||||||
public class User
|
public class User
|
||||||
{
|
{
|
||||||
[Key("i")] public string Id { get; set; } = "";
|
public string Id { get; set; } = "";
|
||||||
[Key("e")] public string Email { get; set; } = "";
|
public string Email { get; set; } = "";
|
||||||
[Key("r")]public RoleEnum Role { get; set; }
|
public RoleEnum Role { get; set; }
|
||||||
|
public UserConfig? UserConfig { get; set; } = null!;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class UserConfig
|
||||||
|
{
|
||||||
|
public UserQueueOffsets? QueueConfig { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class UserQueueOffsets
|
||||||
|
{
|
||||||
|
public int AnnotationsOffset { get; set; }
|
||||||
|
public int AnnotationsConfirmOffset { get; set; }
|
||||||
|
public int AnnotationsCommandsOffset { get; set; }
|
||||||
}
|
}
|
||||||
@@ -9,6 +9,9 @@ public class SecurityConstants
|
|||||||
public const string DUMMY_DIR = "dummy";
|
public const string DUMMY_DIR = "dummy";
|
||||||
|
|
||||||
#region ExternalClientsConfig
|
#region ExternalClientsConfig
|
||||||
|
//public const string API_URL = "http://localhost:5219";
|
||||||
|
public const string API_URL = "https://api.azaion.com";
|
||||||
|
|
||||||
public const string EXTERNAL_INFERENCE_PATH = "azaion-inference.exe";
|
public const string EXTERNAL_INFERENCE_PATH = "azaion-inference.exe";
|
||||||
public const string EXTERNAL_GPS_DENIED_FOLDER = "gps-denied";
|
public const string EXTERNAL_GPS_DENIED_FOLDER = "gps-denied";
|
||||||
public static readonly string ExternalGpsDeniedPath = Path.Combine(EXTERNAL_GPS_DENIED_FOLDER, "image-matcher.exe");
|
public static readonly string ExternalGpsDeniedPath = Path.Combine(EXTERNAL_GPS_DENIED_FOLDER, "image-matcher.exe");
|
||||||
@@ -22,6 +25,13 @@ public class SecurityConstants
|
|||||||
public const int DEFAULT_RETRY_COUNT = 25;
|
public const int DEFAULT_RETRY_COUNT = 25;
|
||||||
public const int DEFAULT_TIMEOUT_SECONDS = 5;
|
public const int DEFAULT_TIMEOUT_SECONDS = 5;
|
||||||
|
|
||||||
|
# region Cache keys
|
||||||
|
|
||||||
|
public const string CURRENT_USER_CACHE_KEY = "CurrentUser";
|
||||||
|
public const string HARDWARE_INFO_KEY = "HardwareInfo";
|
||||||
|
|
||||||
|
# endregion
|
||||||
|
|
||||||
public static readonly SecureAppConfig DefaultSecureAppConfig = new()
|
public static readonly SecureAppConfig DefaultSecureAppConfig = new()
|
||||||
{
|
{
|
||||||
InferenceClientConfig = new InferenceClientConfig
|
InferenceClientConfig = new InferenceClientConfig
|
||||||
@@ -29,8 +39,7 @@ public class SecurityConstants
|
|||||||
ZeroMqHost = DEFAULT_ZMQ_INFERENCE_HOST,
|
ZeroMqHost = DEFAULT_ZMQ_INFERENCE_HOST,
|
||||||
ZeroMqPort = DEFAULT_ZMQ_INFERENCE_PORT,
|
ZeroMqPort = DEFAULT_ZMQ_INFERENCE_PORT,
|
||||||
OneTryTimeoutSeconds = DEFAULT_TIMEOUT_SECONDS,
|
OneTryTimeoutSeconds = DEFAULT_TIMEOUT_SECONDS,
|
||||||
RetryCount = DEFAULT_RETRY_COUNT,
|
RetryCount = DEFAULT_RETRY_COUNT
|
||||||
ResourcesFolder = ""
|
|
||||||
},
|
},
|
||||||
GpsDeniedClientConfig = new GpsDeniedClientConfig
|
GpsDeniedClientConfig = new GpsDeniedClientConfig
|
||||||
{
|
{
|
||||||
@@ -38,6 +47,10 @@ public class SecurityConstants
|
|||||||
ZeroMqPort = DEFAULT_ZMQ_GPS_DENIED_PORT,
|
ZeroMqPort = DEFAULT_ZMQ_GPS_DENIED_PORT,
|
||||||
OneTryTimeoutSeconds = DEFAULT_TIMEOUT_SECONDS,
|
OneTryTimeoutSeconds = DEFAULT_TIMEOUT_SECONDS,
|
||||||
RetryCount = DEFAULT_RETRY_COUNT,
|
RetryCount = DEFAULT_RETRY_COUNT,
|
||||||
|
},
|
||||||
|
DirectoriesConfig = new DirectoriesConfig
|
||||||
|
{
|
||||||
|
ApiResourcesDirectory = ""
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
#endregion ExternalClientsConfig
|
#endregion ExternalClientsConfig
|
||||||
|
|||||||
@@ -1,26 +1,117 @@
|
|||||||
using Azaion.CommonSecurity.DTO;
|
using System.Net;
|
||||||
using Azaion.CommonSecurity.DTO.Commands;
|
using System.Net.Http.Headers;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using System.Text;
|
||||||
|
using Azaion.CommonSecurity.DTO;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace Azaion.CommonSecurity.Services;
|
namespace Azaion.CommonSecurity.Services;
|
||||||
|
|
||||||
public interface IAuthProvider
|
public interface IAzaionApi
|
||||||
{
|
{
|
||||||
void Login(ApiCredentials credentials);
|
ApiCredentials Credentials { get; }
|
||||||
User CurrentUser { get; }
|
User CurrentUser { get; }
|
||||||
|
T? Get<T>(string url);
|
||||||
|
Stream GetResource(string filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class AuthProvider(IInferenceClient inferenceClient) : IAuthProvider
|
public class AzaionApi(HttpClient client, ICache cache, ApiCredentials credentials, IHardwareService hardwareService) : IAzaionApi
|
||||||
{
|
{
|
||||||
public User CurrentUser { get; private set; } = null!;
|
private string _jwtToken = null!;
|
||||||
|
const string APP_JSON = "application/json";
|
||||||
|
public ApiCredentials Credentials => credentials;
|
||||||
|
|
||||||
public void Login(ApiCredentials credentials)
|
public User CurrentUser
|
||||||
{
|
{
|
||||||
inferenceClient.Send(RemoteCommand.Create(CommandType.Login, credentials));
|
get
|
||||||
var user = inferenceClient.Get<User>();
|
{
|
||||||
|
var user = cache.GetFromCache(SecurityConstants.CURRENT_USER_CACHE_KEY,
|
||||||
|
() => Get<User>("currentUser"));
|
||||||
if (user == null)
|
if (user == null)
|
||||||
throw new Exception("Can't get user from Auth provider");
|
throw new Exception("Can't get current user");
|
||||||
|
|
||||||
CurrentUser = user;
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private HttpResponseMessage Send(HttpRequestMessage request, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(_jwtToken))
|
||||||
|
Authorize();
|
||||||
|
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _jwtToken);
|
||||||
|
var response = client.Send(request);
|
||||||
|
|
||||||
|
if (response.StatusCode == HttpStatusCode.Unauthorized)
|
||||||
|
{
|
||||||
|
Authorize();
|
||||||
|
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _jwtToken);
|
||||||
|
response = client.Send(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.IsSuccessStatusCode)
|
||||||
|
return response;
|
||||||
|
|
||||||
|
var stream = response.Content.ReadAsStream();
|
||||||
|
var content = new StreamReader(stream).ReadToEnd();
|
||||||
|
if (response.StatusCode == HttpStatusCode.Conflict)
|
||||||
|
{
|
||||||
|
var result = JsonConvert.DeserializeObject<BusinessExceptionDto>(content);
|
||||||
|
throw new Exception($"Failed: {response.StatusCode}! Error Code: {result.ErrorCode}. Message: {result.Message}");
|
||||||
|
}
|
||||||
|
throw new Exception($"Failed: {response.StatusCode}! Result: {content}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public Stream GetResource(string filename)
|
||||||
|
{
|
||||||
|
var hardware = cache.GetFromCache(SecurityConstants.HARDWARE_INFO_KEY, hardwareService.GetHardware);
|
||||||
|
|
||||||
|
var response = Send(new HttpRequestMessage(HttpMethod.Post, $"/resources/get/{credentials.Folder}")
|
||||||
|
{
|
||||||
|
Content = new StringContent(JsonConvert.SerializeObject(new { filename, credentials.Password, hardware }), Encoding.UTF8, APP_JSON)
|
||||||
|
});
|
||||||
|
return response.Content.ReadAsStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Authorize()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(credentials.Email) || credentials.Password.Length == 0)
|
||||||
|
throw new Exception("Email or password is empty! Please do EnterCredentials first!");
|
||||||
|
|
||||||
|
var payload = new
|
||||||
|
{
|
||||||
|
email = credentials.Email,
|
||||||
|
password = credentials.Password
|
||||||
|
};
|
||||||
|
var content = new StringContent(JsonConvert.SerializeObject(payload), Encoding.UTF8, APP_JSON);
|
||||||
|
var message = new HttpRequestMessage(HttpMethod.Post, "login") { Content = content };
|
||||||
|
var response = client.Send(message);
|
||||||
|
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
throw new Exception($"EnterCredentials failed: {response.StatusCode}");
|
||||||
|
|
||||||
|
var stream = response.Content.ReadAsStream();
|
||||||
|
var json = new StreamReader(stream).ReadToEnd();
|
||||||
|
var result = JsonConvert.DeserializeObject<LoginResponse>(json);
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(result?.Token))
|
||||||
|
throw new Exception("JWT Token not found in response");
|
||||||
|
|
||||||
|
_jwtToken = result.Token;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Console.WriteLine(e);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public T? Get<T>(string url)
|
||||||
|
{
|
||||||
|
var response = Send(new HttpRequestMessage(HttpMethod.Get, url));
|
||||||
|
var stream = response.Content.ReadAsStream();
|
||||||
|
var json = new StreamReader(stream).ReadToEnd();
|
||||||
|
return JsonConvert.DeserializeObject<T>(json);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
using LazyCache;
|
||||||
|
|
||||||
|
namespace Azaion.CommonSecurity.Services;
|
||||||
|
|
||||||
|
public interface ICache
|
||||||
|
{
|
||||||
|
T GetFromCache<T>(string key, Func<T> fetchFunc, TimeSpan? expiration = null);
|
||||||
|
void Invalidate(string key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MemoryCache : ICache
|
||||||
|
{
|
||||||
|
private readonly IAppCache _cache = new CachingService();
|
||||||
|
|
||||||
|
public T GetFromCache<T>(string key, Func<T> fetchFunc, TimeSpan? expiration = null)
|
||||||
|
{
|
||||||
|
expiration ??= TimeSpan.FromHours(4);
|
||||||
|
return _cache.GetOrAdd(key, entry =>
|
||||||
|
{
|
||||||
|
var result = fetchFunc();
|
||||||
|
entry.AbsoluteExpirationRelativeToNow = expiration;
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Invalidate(string key) => _cache.Remove(key);
|
||||||
|
}
|
||||||
@@ -38,29 +38,20 @@ public class HardwareService : IHardwareService
|
|||||||
.Replace("Name=", "")
|
.Replace("Name=", "")
|
||||||
.Replace(" ", " ")
|
.Replace(" ", " ")
|
||||||
.Trim()
|
.Trim()
|
||||||
.Split(['\n', '\r'], StringSplitOptions.RemoveEmptyEntries);
|
.Split(['\n', '\r'], StringSplitOptions.RemoveEmptyEntries)
|
||||||
|
.Select(x => x.Trim())
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
var memoryStr = "Unknown RAM";
|
if (lines.Length < 3)
|
||||||
if (lines.Length > 0)
|
throw new Exception("Can't get hardware info");
|
||||||
{
|
|
||||||
memoryStr = lines[0];
|
|
||||||
if (int.TryParse(memoryStr, out var memKb))
|
|
||||||
memoryStr = $"{Math.Round(memKb / 1024.0 / 1024.0)} Gb";
|
|
||||||
}
|
|
||||||
|
|
||||||
var macAddress = MacAddress();
|
|
||||||
var hardwareInfo = new HardwareInfo
|
var hardwareInfo = new HardwareInfo
|
||||||
{
|
{
|
||||||
Memory = memoryStr,
|
CPU = lines[0],
|
||||||
CPU = lines.Length > 1 && string.IsNullOrEmpty(lines[1])
|
GPU = lines[1],
|
||||||
? "Unknown CPU"
|
Memory = lines[2],
|
||||||
: lines[1].Trim(),
|
MacAddress = GetMacAddress()
|
||||||
GPU = lines.Length > 2 && string.IsNullOrEmpty(lines[2])
|
|
||||||
? "Unknown GPU"
|
|
||||||
: lines[2],
|
|
||||||
MacAddress = macAddress
|
|
||||||
};
|
};
|
||||||
hardwareInfo.Hash = ToHash($"Az|{hardwareInfo.CPU}|{hardwareInfo.GPU}|{macAddress}");
|
|
||||||
return hardwareInfo;
|
return hardwareInfo;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -70,7 +61,7 @@ public class HardwareService : IHardwareService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private string MacAddress()
|
private string GetMacAddress()
|
||||||
{
|
{
|
||||||
var macAddress = NetworkInterface
|
var macAddress = NetworkInterface
|
||||||
.GetAllNetworkInterfaces()
|
.GetAllNetworkInterfaces()
|
||||||
|
|||||||
@@ -71,9 +71,6 @@ public class InferenceClient : IInferenceClient
|
|||||||
_dealer.SendFrame(MessagePackSerializer.Serialize(command));
|
_dealer.SendFrame(MessagePackSerializer.Serialize(command));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SendString(string text) =>
|
|
||||||
Send(new RemoteCommand(CommandType.Load, MessagePackSerializer.Serialize(text)));
|
|
||||||
|
|
||||||
public T? Get<T>(int retries = 24, int tryTimeoutSeconds = 5, CancellationToken ct = default) where T : class
|
public T? Get<T>(int retries = 24, int tryTimeoutSeconds = 5, CancellationToken ct = default) where T : class
|
||||||
{
|
{
|
||||||
var bytes = GetBytes(retries, tryTimeoutSeconds, ct);
|
var bytes = GetBytes(retries, tryTimeoutSeconds, ct);
|
||||||
@@ -83,8 +80,9 @@ public class InferenceClient : IInferenceClient
|
|||||||
public byte[]? GetBytes(int retries = 24, int tryTimeoutSeconds = 5, CancellationToken ct = default)
|
public byte[]? GetBytes(int retries = 24, int tryTimeoutSeconds = 5, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
var tryNum = 0;
|
var tryNum = 0;
|
||||||
while (!ct.IsCancellationRequested && tryNum++ < retries)
|
while (!ct.IsCancellationRequested && tryNum < retries)
|
||||||
{
|
{
|
||||||
|
tryNum++;
|
||||||
if (!_dealer.TryReceiveFrameBytes(TimeSpan.FromSeconds(tryTimeoutSeconds), out var bytes))
|
if (!_dealer.TryReceiveFrameBytes(TimeSpan.FromSeconds(tryTimeoutSeconds), out var bytes))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
@@ -92,7 +90,7 @@ public class InferenceClient : IInferenceClient
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!ct.IsCancellationRequested)
|
if (!ct.IsCancellationRequested)
|
||||||
throw new Exception($"Unable to get bytes after {tryNum} retries, {tryTimeoutSeconds} seconds each");
|
throw new Exception($"Unable to get bytes after {tryNum - 1} retries, {tryTimeoutSeconds} seconds each");
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ public class ResourceLoader([FromKeyedServices(SecurityConstants.EXTERNAL_INFERE
|
|||||||
public MemoryStream LoadFile(string fileName, string? folder = null)
|
public MemoryStream LoadFile(string fileName, string? folder = null)
|
||||||
{
|
{
|
||||||
inferenceClient.Send(RemoteCommand.Create(CommandType.Load, new LoadFileData(fileName, folder)));
|
inferenceClient.Send(RemoteCommand.Create(CommandType.Load, new LoadFileData(fileName, folder)));
|
||||||
var bytes = inferenceClient.GetBytes();
|
var bytes = inferenceClient.GetBytes(2, 3);
|
||||||
if (bytes == null)
|
if (bytes == null)
|
||||||
throw new Exception($"Unable to receive {fileName}");
|
throw new Exception($"Unable to receive {fileName}");
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ using Azaion.Common.DTO;
|
|||||||
using Azaion.Common.DTO.Config;
|
using Azaion.Common.DTO.Config;
|
||||||
using Azaion.Common.Events;
|
using Azaion.Common.Events;
|
||||||
using Azaion.Common.Services;
|
using Azaion.Common.Services;
|
||||||
|
using Azaion.CommonSecurity.DTO;
|
||||||
using LinqToDB;
|
using LinqToDB;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ from PyInstaller.utils.hooks import collect_all
|
|||||||
datas = []
|
datas = []
|
||||||
binaries = []
|
binaries = []
|
||||||
hiddenimports = ['constants', 'annotation', 'credentials', 'file_data', 'user', 'security', 'secure_model', 'api_client', 'hardware_service', 'remote_command', 'ai_config', 'inference_engine', 'inference', 'remote_command_handler']
|
hiddenimports = ['constants', 'annotation', 'credentials', 'file_data', 'user', 'security', 'secure_model', 'api_client', 'hardware_service', 'remote_command', 'ai_config', 'inference_engine', 'inference', 'remote_command_handler']
|
||||||
|
tmp_ret = collect_all('pyyaml')
|
||||||
|
datas += tmp_ret[0]; binaries += tmp_ret[1]; hiddenimports += tmp_ret[2]
|
||||||
tmp_ret = collect_all('jwt')
|
tmp_ret = collect_all('jwt')
|
||||||
datas += tmp_ret[0]; binaries += tmp_ret[1]; hiddenimports += tmp_ret[2]
|
datas += tmp_ret[0]; binaries += tmp_ret[1]; hiddenimports += tmp_ret[2]
|
||||||
tmp_ret = collect_all('requests')
|
tmp_ret = collect_all('requests')
|
||||||
|
|||||||
+34
-16
@@ -1,8 +1,12 @@
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Net.Http;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Unicode;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Threading;
|
using System.Windows.Threading;
|
||||||
using Azaion.Annotator;
|
using Azaion.Annotator;
|
||||||
|
using Azaion.Common;
|
||||||
using Azaion.Common.Database;
|
using Azaion.Common.Database;
|
||||||
using Azaion.Common.DTO;
|
using Azaion.Common.DTO;
|
||||||
using Azaion.Common.DTO.Config;
|
using Azaion.Common.DTO.Config;
|
||||||
@@ -11,8 +15,10 @@ using Azaion.Common.Extensions;
|
|||||||
using Azaion.Common.Services;
|
using Azaion.Common.Services;
|
||||||
using Azaion.CommonSecurity;
|
using Azaion.CommonSecurity;
|
||||||
using Azaion.CommonSecurity.DTO;
|
using Azaion.CommonSecurity.DTO;
|
||||||
|
using Azaion.CommonSecurity.DTO.Commands;
|
||||||
using Azaion.CommonSecurity.Services;
|
using Azaion.CommonSecurity.Services;
|
||||||
using Azaion.Dataset;
|
using Azaion.Dataset;
|
||||||
|
using LazyCache;
|
||||||
using LibVLCSharp.Shared;
|
using LibVLCSharp.Shared;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
@@ -35,12 +41,14 @@ public partial class App
|
|||||||
|
|
||||||
private IInferenceClient _inferenceClient = null!;
|
private IInferenceClient _inferenceClient = null!;
|
||||||
private IResourceLoader _resourceLoader = null!;
|
private IResourceLoader _resourceLoader = null!;
|
||||||
private IAuthProvider _authProvider = null!;
|
|
||||||
|
|
||||||
private Stream _securedConfig = null!;
|
private Stream _securedConfig = null!;
|
||||||
private Stream _systemConfig = null!;
|
private Stream _systemConfig = null!;
|
||||||
private static readonly Guid KeyPressTaskId = Guid.NewGuid();
|
private static readonly Guid KeyPressTaskId = Guid.NewGuid();
|
||||||
|
|
||||||
|
private readonly ICache _cache = new MemoryCache();
|
||||||
|
private readonly IHardwareService _hardwareService = new HardwareService();
|
||||||
|
private IAzaionApi _azaionApi = null!;
|
||||||
|
|
||||||
private void OnDispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
|
private void OnDispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
|
||||||
{
|
{
|
||||||
_logger.LogError(e.Exception, e.Exception.Message);
|
_logger.LogError(e.Exception, e.Exception.Message);
|
||||||
@@ -83,21 +91,32 @@ public partial class App
|
|||||||
var secureAppConfig = ReadSecureAppConfig();
|
var secureAppConfig = ReadSecureAppConfig();
|
||||||
_inferenceClient = new InferenceClient(new OptionsWrapper<InferenceClientConfig>(secureAppConfig.InferenceClientConfig));
|
_inferenceClient = new InferenceClient(new OptionsWrapper<InferenceClientConfig>(secureAppConfig.InferenceClientConfig));
|
||||||
_resourceLoader = new ResourceLoader(_inferenceClient);
|
_resourceLoader = new ResourceLoader(_inferenceClient);
|
||||||
_authProvider = new AuthProvider(_inferenceClient);
|
|
||||||
|
|
||||||
var login = new Login();
|
var login = new Login();
|
||||||
login.Closed += (sender, args) =>
|
|
||||||
{
|
|
||||||
if (!login.MainSuiteOpened)
|
|
||||||
_inferenceClient.Stop();
|
|
||||||
};
|
|
||||||
|
|
||||||
login.CredentialsEntered += (_, credentials) =>
|
login.CredentialsEntered += async (_, credentials) =>
|
||||||
|
{
|
||||||
|
credentials.Folder = secureAppConfig.DirectoriesConfig.ApiResourcesDirectory;
|
||||||
|
|
||||||
|
_inferenceClient.Send(RemoteCommand.Create(CommandType.Login, credentials));
|
||||||
|
_azaionApi = new AzaionApi(new HttpClient { BaseAddress = new Uri(SecurityConstants.API_URL) }, _cache, credentials, _hardwareService);
|
||||||
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
credentials.Folder = secureAppConfig.InferenceClientConfig.ResourcesFolder;
|
|
||||||
_authProvider.Login(credentials);
|
|
||||||
_securedConfig = _resourceLoader.LoadFile("config.secured.json");
|
_securedConfig = _resourceLoader.LoadFile("config.secured.json");
|
||||||
_systemConfig = _resourceLoader.LoadFile("config.system.json");
|
_systemConfig = _resourceLoader.LoadFile("config.system.json");
|
||||||
|
}
|
||||||
|
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) =>
|
AppDomain.CurrentDomain.AssemblyResolve += (_, a) =>
|
||||||
{
|
{
|
||||||
@@ -168,14 +187,13 @@ public partial class App
|
|||||||
|
|
||||||
services.ConfigureSection<InferenceClientConfig>(context.Configuration);
|
services.ConfigureSection<InferenceClientConfig>(context.Configuration);
|
||||||
services.ConfigureSection<GpsDeniedClientConfig>(context.Configuration);
|
services.ConfigureSection<GpsDeniedClientConfig>(context.Configuration);
|
||||||
services.AddSingleton<IInferenceClient>(_inferenceClient);
|
services.AddSingleton<IInferenceClient, InferenceClient>();
|
||||||
services.AddSingleton<IGpsMatcherClient, GpsMatcherClient>();
|
services.AddSingleton<IGpsMatcherClient, GpsMatcherClient>();
|
||||||
services.AddSingleton<IResourceLoader>(_resourceLoader);
|
|
||||||
services.AddSingleton<IAuthProvider>(_authProvider);
|
|
||||||
services.AddSingleton<IInferenceService, InferenceService>();
|
services.AddSingleton<IInferenceService, InferenceService>();
|
||||||
services.AddSingleton<IGpsMatcherService, GpsMatcherService>();
|
services.AddSingleton<IGpsMatcherService, GpsMatcherService>();
|
||||||
services.AddSingleton<ISatelliteDownloader, SatelliteDownloader>();
|
services.AddSingleton<ISatelliteDownloader, SatelliteDownloader>();
|
||||||
services.AddHttpClient();
|
services.AddHttpClient();
|
||||||
|
services.AddSingleton(_azaionApi);
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
services.AddSingleton<IConfigUpdater, ConfigUpdater>();
|
services.AddSingleton<IConfigUpdater, ConfigUpdater>();
|
||||||
@@ -220,7 +238,7 @@ public partial class App
|
|||||||
{
|
{
|
||||||
var args = (KeyEventArgs)e;
|
var args = (KeyEventArgs)e;
|
||||||
var keyEvent = new KeyEvent(sender, args, _formState.ActiveWindow);
|
var keyEvent = new KeyEvent(sender, args, _formState.ActiveWindow);
|
||||||
_ = ThrottleExt.ThrottleRunFirst(() => _mediator.Publish(keyEvent), KeyPressTaskId, TimeSpan.FromMilliseconds(50));
|
_ = ThrottleExt.Throttle(() => _mediator.Publish(keyEvent), TimeSpan.FromMilliseconds(50));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async void OnExit(ExitEventArgs e)
|
protected override async void OnExit(ExitEventArgs e)
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ public partial class MainSuite
|
|||||||
private readonly IGalleryService _galleryService;
|
private readonly IGalleryService _galleryService;
|
||||||
private readonly IDbFactory _dbFactory;
|
private readonly IDbFactory _dbFactory;
|
||||||
private readonly Dictionary<WindowEnum, Window> _openedWindows = new();
|
private readonly Dictionary<WindowEnum, Window> _openedWindows = new();
|
||||||
private readonly IResourceLoader _resourceLoader;
|
|
||||||
private readonly IInferenceClient _inferenceClient;
|
private readonly IInferenceClient _inferenceClient;
|
||||||
private readonly IGpsMatcherClient _gpsMatcherClient;
|
private readonly IGpsMatcherClient _gpsMatcherClient;
|
||||||
private static readonly Guid SaveConfigTaskId = Guid.NewGuid();
|
private static readonly Guid SaveConfigTaskId = Guid.NewGuid();
|
||||||
@@ -36,7 +35,6 @@ public partial class MainSuite
|
|||||||
IServiceProvider sp,
|
IServiceProvider sp,
|
||||||
IGalleryService galleryService,
|
IGalleryService galleryService,
|
||||||
IDbFactory dbFactory,
|
IDbFactory dbFactory,
|
||||||
IResourceLoader resourceLoader,
|
|
||||||
IInferenceClient inferenceClient,
|
IInferenceClient inferenceClient,
|
||||||
IGpsMatcherClient gpsMatcherClient)
|
IGpsMatcherClient gpsMatcherClient)
|
||||||
{
|
{
|
||||||
@@ -45,11 +43,10 @@ public partial class MainSuite
|
|||||||
_sp = sp;
|
_sp = sp;
|
||||||
_galleryService = galleryService;
|
_galleryService = galleryService;
|
||||||
_dbFactory = dbFactory;
|
_dbFactory = dbFactory;
|
||||||
_resourceLoader = resourceLoader;
|
|
||||||
_inferenceClient = inferenceClient;
|
_inferenceClient = inferenceClient;
|
||||||
_gpsMatcherClient = gpsMatcherClient;
|
_gpsMatcherClient = gpsMatcherClient;
|
||||||
|
|
||||||
_appConfig = appConfig.Value;
|
_appConfig = appConfig.Value;
|
||||||
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
Loaded += OnLoaded;
|
Loaded += OnLoaded;
|
||||||
Closed += OnFormClosed;
|
Closed += OnFormClosed;
|
||||||
@@ -135,11 +132,11 @@ public partial class MainSuite
|
|||||||
|
|
||||||
private async Task SaveUserSettings()
|
private async Task SaveUserSettings()
|
||||||
{
|
{
|
||||||
await ThrottleExt.ThrottleRunFirst(() =>
|
await ThrottleExt.Throttle(() =>
|
||||||
{
|
{
|
||||||
_configUpdater.Save(_appConfig);
|
_configUpdater.Save(_appConfig);
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}, SaveConfigTaskId, TimeSpan.FromSeconds(2));
|
}, TimeSpan.FromSeconds(2));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnFormClosed(object? sender, EventArgs e)
|
private void OnFormClosed(object? sender, EventArgs e)
|
||||||
|
|||||||
@@ -3,8 +3,7 @@
|
|||||||
"ZeroMqHost": "127.0.0.1",
|
"ZeroMqHost": "127.0.0.1",
|
||||||
"ZeroMqPort": 5127,
|
"ZeroMqPort": 5127,
|
||||||
"RetryCount": 25,
|
"RetryCount": 25,
|
||||||
"TimeoutSeconds": 5,
|
"TimeoutSeconds": 5
|
||||||
"ResourcesFolder": "stage"
|
|
||||||
},
|
},
|
||||||
"GpsDeniedClientConfig": {
|
"GpsDeniedClientConfig": {
|
||||||
"ZeroMqHost": "127.0.0.1",
|
"ZeroMqHost": "127.0.0.1",
|
||||||
@@ -14,6 +13,7 @@
|
|||||||
"TimeoutSeconds": 5
|
"TimeoutSeconds": 5
|
||||||
},
|
},
|
||||||
"DirectoriesConfig": {
|
"DirectoriesConfig": {
|
||||||
|
"ApiResourcesDirectory": "stage",
|
||||||
"VideosDirectory": "E:\\Azaion6",
|
"VideosDirectory": "E:\\Azaion6",
|
||||||
"LabelsDirectory": "E:\\labels",
|
"LabelsDirectory": "E:\\labels",
|
||||||
"ImagesDirectory": "E:\\images",
|
"ImagesDirectory": "E:\\images",
|
||||||
|
|||||||
@@ -3,8 +3,7 @@
|
|||||||
"ZeroMqHost": "127.0.0.1",
|
"ZeroMqHost": "127.0.0.1",
|
||||||
"ZeroMqPort": 5131,
|
"ZeroMqPort": 5131,
|
||||||
"RetryCount": 25,
|
"RetryCount": 25,
|
||||||
"TimeoutSeconds": 5,
|
"TimeoutSeconds": 5
|
||||||
"ResourcesFolder": ""
|
|
||||||
},
|
},
|
||||||
"GpsDeniedClientConfig": {
|
"GpsDeniedClientConfig": {
|
||||||
"ZeroMqHost": "127.0.0.1",
|
"ZeroMqHost": "127.0.0.1",
|
||||||
@@ -14,6 +13,7 @@
|
|||||||
"TimeoutSeconds": 5
|
"TimeoutSeconds": 5
|
||||||
},
|
},
|
||||||
"DirectoriesConfig": {
|
"DirectoriesConfig": {
|
||||||
|
"ApiResourcesDirectory": "",
|
||||||
"VideosDirectory": "videos",
|
"VideosDirectory": "videos",
|
||||||
"LabelsDirectory": "labels",
|
"LabelsDirectory": "labels",
|
||||||
"ImagesDirectory": "images",
|
"ImagesDirectory": "images",
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
{ "Id": 9, "Name": "Smoke", "ShortName": "Дим", "Color": "#000080" },
|
{ "Id": 9, "Name": "Smoke", "ShortName": "Дим", "Color": "#000080" },
|
||||||
{ "Id": 10, "Name": "Plane", "ShortName": "Літак", "Color": "#000080" },
|
{ "Id": 10, "Name": "Plane", "ShortName": "Літак", "Color": "#000080" },
|
||||||
{ "Id": 11, "Name": "Moto", "ShortName": "Мото", "Color": "#808000" },
|
{ "Id": 11, "Name": "Moto", "ShortName": "Мото", "Color": "#808000" },
|
||||||
{ "Id": 12, "Name": "CamouflageNnet", "ShortName": "Сітка", "Color": "#800080" },
|
{ "Id": 12, "Name": "CamouflageNet", "ShortName": "Сітка", "Color": "#800080" },
|
||||||
{ "Id": 13, "Name": "CamouflageBranches", "ShortName": "Гілки", "Color": "#2f4f4f" },
|
{ "Id": 13, "Name": "CamouflageBranches", "ShortName": "Гілки", "Color": "#2f4f4f" },
|
||||||
{ "Id": 14, "Name": "Roof", "ShortName": "Дах", "Color": "#1e90ff" },
|
{ "Id": 14, "Name": "Roof", "ShortName": "Дах", "Color": "#1e90ff" },
|
||||||
{ "Id": 15, "Name": "Building", "ShortName": "Будівля", "Color": "#ffb6c1" }
|
{ "Id": 15, "Name": "Building", "ShortName": "Будівля", "Color": "#ffb6c1" }
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ set SUITE_FOLDER=%cd%\bin\%CONFIG%\net8.0-windows\
|
|||||||
rem Inference
|
rem Inference
|
||||||
|
|
||||||
set INFERENCE_PATH=%cd%\..\Azaion.Inference
|
set INFERENCE_PATH=%cd%\..\Azaion.Inference
|
||||||
xcopy /E %INFERENCE_PATH%\dist\azaion-inference %SUITE_FOLDER%
|
xcopy /E /Y %INFERENCE_PATH%\dist\azaion-inference %SUITE_FOLDER%
|
||||||
copy %INFERENCE_PATH%\venv\Lib\site-packages\tensorrt_libs\nvinfer_10.dll %SUITE_FOLDER%
|
copy %INFERENCE_PATH%\venv\Lib\site-packages\tensorrt_libs\nvinfer_10.dll %SUITE_FOLDER%
|
||||||
copy %INFERENCE_PATH%\venv\Lib\site-packages\tensorrt_libs\nvinfer_plugin_10.dll %SUITE_FOLDER%
|
copy %INFERENCE_PATH%\venv\Lib\site-packages\tensorrt_libs\nvinfer_plugin_10.dll %SUITE_FOLDER%
|
||||||
copy %INFERENCE_PATH%\venv\Lib\site-packages\tensorrt_libs\nvonnxparser_10.dll %SUITE_FOLDER%
|
copy %INFERENCE_PATH%\venv\Lib\site-packages\tensorrt_libs\nvonnxparser_10.dll %SUITE_FOLDER%
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using Azaion.Common.DTO.Config;
|
using Azaion.Common.DTO.Config;
|
||||||
using Azaion.Common.Services;
|
using Azaion.Common.Services;
|
||||||
|
using Azaion.CommonSecurity.DTO;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
|||||||
Reference in New Issue
Block a user