diff --git a/SynoAI/AIs/DeepStack/DeepStackAI.cs b/SynoAI/AIs/DeepStack/DeepStackAI.cs index 8043dd9..e281eda 100644 --- a/SynoAI/AIs/DeepStack/DeepStackAI.cs +++ b/SynoAI/AIs/DeepStack/DeepStackAI.cs @@ -20,9 +20,11 @@ public async override Task> Process(ILogger logger, Ca decimal minConfidence = camera.Threshold / 100m; - MultipartFormDataContent multipartContent = new MultipartFormDataContent(); - multipartContent.Add(new StreamContent(new MemoryStream(image)), "image", "image"); - multipartContent.Add(new StringContent(minConfidence.ToString()), "min_confidence"); // From face detection example - using JSON with MinConfidence didn't always work + MultipartFormDataContent multipartContent = new() + { + { new StreamContent(new MemoryStream(image)), "image", "image" }, + { new StringContent(minConfidence.ToString()), "min_confidence" } // From face detection example - using JSON with MinConfidence didn't always work + }; logger.LogDebug($"{camera.Name}: DeepStackAI: POSTing image with minimum confidence of {minConfidence} ({camera.Threshold}%) to {string.Join("/", Config.AIUrl, Config.AIPath)}."); @@ -69,7 +71,7 @@ public async override Task> Process(ILogger logger, Ca /// A for the combined base and resource. protected Uri GetUri(string basePath, string resourcePath) { - Uri baseUri = new Uri(basePath); + Uri baseUri = new(basePath); return new Uri(baseUri, resourcePath); } diff --git a/SynoAI/Controllers/CameraController.cs b/SynoAI/Controllers/CameraController.cs index ea26e57..baaf88c 100644 --- a/SynoAI/Controllers/CameraController.cs +++ b/SynoAI/Controllers/CameraController.cs @@ -32,8 +32,8 @@ public class CameraController : ControllerBase private readonly ISynologyService _synologyService; private readonly ILogger _logger; - private static ConcurrentDictionary _runningCameraChecks = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); - private static ConcurrentDictionary _delayedCameraChecks = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + private static ConcurrentDictionary _runningCameraChecks = new(StringComparer.OrdinalIgnoreCase); + private static ConcurrentDictionary _delayedCameraChecks = new(StringComparer.OrdinalIgnoreCase); public CameraController(IAIService aiService, ISynologyService synologyService, ILogger logger, IHubContext hubContext) { @@ -134,7 +134,7 @@ public async void Get(string id) int maxSizeX = camera.GetMaxSizeX(); int maxSizeY = camera.GetMaxSizeY(); - List validPredictions = new List(); + List validPredictions = new(); foreach (AIPrediction prediction in predictions) { // Check if the prediction label is in the list of types the camera is looking for @@ -178,14 +178,17 @@ public async void Get(string id) if (validPredictions.Count() > 0) { - // Generate text for notifications - IEnumerable labels = GetLabels(validPredictions); - // Process and save the snapshot ProcessedImage processedImage = SnapshotManager.DressImage(camera, snapshot, predictions, validPredictions, _logger); // Send Notifications - await SendNotifications(camera, processedImage, labels); + Notification notification = new() + { + ProcessedImage = processedImage, + ValidPredictions = validPredictions + }; + + await SendNotifications(camera, notification); // Inform eventual web users about this new Snapshot, for the "realtime" option thru Web await _hubContext.Clients.All.SendAsync("ReceiveSnapshot", camera.Name, processedImage.FileName); @@ -239,10 +242,10 @@ private bool ShouldIncludePrediction(string id, Camera camera, Stopwatch overall // Check if the prediction falls within the exclusion zones if (camera.Exclusions != null && camera.Exclusions.Count() > 0) { - Rectangle boundary = new Rectangle(prediction.MinX, prediction.MinY, prediction.SizeX, prediction.SizeY); + Rectangle boundary = new(prediction.MinX, prediction.MinY, prediction.SizeX, prediction.SizeY); foreach (Zone exclusion in camera.Exclusions) { - Rectangle exclusionZoneBoundary = new Rectangle(exclusion.Start.X, exclusion.Start.Y, exclusion.End.X - exclusion.Start.X, exclusion.End.Y - exclusion.Start.Y); + Rectangle exclusionZoneBoundary = new(exclusion.Start.X, exclusion.Start.Y, exclusion.End.X - exclusion.Start.X, exclusion.End.Y - exclusion.Start.Y); bool exclude = exclusion.Mode == OverlapMode.Contains ? exclusionZoneBoundary.Contains(boundary) : exclusionZoneBoundary.IntersectsWith(boundary); if (exclude) { @@ -256,43 +259,6 @@ private bool ShouldIncludePrediction(string id, Camera camera, Stopwatch overall return true; } - /// - /// Gets the labels from the predictions to use in the notifications. - /// - /// The predictions to process. - /// A list of labels. - private IEnumerable GetLabels(IEnumerable validPredictions) - { - if (Config.AlternativeLabelling && Config.DrawMode == DrawMode.Matches) - { - List labels = new List(); - if (validPredictions.Count() == 1) - { - // If there is only a single object, then don't add a correlating number and instead just - // write out the label. - decimal confidence = Math.Round(validPredictions.First().Confidence, 0, MidpointRounding.AwayFromZero); - labels.Add($"{validPredictions.First().Label.FirstCharToUpper()} {confidence}%"); - } - else - { - // Since there is more than one object detected, include correlating number - int counter = 1; - foreach (AIPrediction prediction in validPredictions) - { - decimal confidence = Math.Round(prediction.Confidence, 0, MidpointRounding.AwayFromZero); - labels.Add($"{counter}. {prediction.Label.FirstCharToUpper()} {confidence}%"); - counter++; - } - } - - return labels; - } - else - { - return validPredictions.Select(x => x.Label.FirstCharToUpper()).ToList(); - } - } - /// /// Adds a delay for the specified camera. /// @@ -332,7 +298,7 @@ private void CleanupOldImages() { _cleanupOldImagesRunning = true; - DirectoryInfo directory = new DirectoryInfo(Constants.DIRECTORY_CAPTURES); + DirectoryInfo directory = new(Constants.DIRECTORY_CAPTURES); IEnumerable files = directory.GetFiles("*", new EnumerationOptions() { RecurseSubdirectories = true }); foreach (FileInfo file in files) { @@ -350,7 +316,7 @@ private void CleanupOldImages() } } private bool _cleanupOldImagesRunning; - private object _cleanUpOldImagesLock = new object(); + private object _cleanUpOldImagesLock = new(); /// /// Handles any required preprocessing of the captured image. @@ -399,8 +365,8 @@ private SKBitmap Rotate(SKBitmap bitmap, double angle) int rotatedWidth = (int)(cosine * originalWidth + sine * originalHeight); int rotatedHeight = (int)(cosine * originalHeight + sine * originalWidth); - SKBitmap rotatedBitmap = new SKBitmap(rotatedWidth, rotatedHeight); - using (SKCanvas canvas = new SKCanvas(rotatedBitmap)) + SKBitmap rotatedBitmap = new(rotatedWidth, rotatedHeight); + using (SKCanvas canvas = new(rotatedBitmap)) { canvas.Clear(); canvas.Translate(rotatedWidth / 2, rotatedHeight / 2); @@ -417,22 +383,23 @@ private SKBitmap Rotate(SKBitmap bitmap, double angle) /// Sends notifications, if there is any configured /// /// The camera responsible for this snapshot. - /// The path information for the snapshot. - /// The text metadata for each existing valid object. - private async Task SendNotifications(Camera camera, ProcessedImage processedImage, IEnumerable labels) + /// The notification data to process. + private async Task SendNotifications(Camera camera, Notification notification) { Stopwatch stopwatch = Stopwatch.StartNew(); + IEnumerable labels = notification.ValidPredictions.Select(x => x.Label).Distinct().ToList(); + IEnumerable notifiers = Config.Notifiers .Where(x => - (x.Cameras == null || x.Cameras.Count() == 0 || x.Cameras.Any(c => c.Equals(camera.Name, StringComparison.OrdinalIgnoreCase))) && - (x.Types == null || x.Types.Count() == 0 || x.Types.Any(t => labels.Contains(t, StringComparer.OrdinalIgnoreCase))) + (x.Cameras == null || !x.Cameras.Any() || x.Cameras.Any(c => c.Equals(camera.Name, StringComparison.OrdinalIgnoreCase))) && + (x.Types == null || !x.Types.Any() || x.Types.Any(t => labels.Contains(t, StringComparer.OrdinalIgnoreCase))) ).ToList(); - List tasks = new List(); + List tasks = new(); foreach (INotifier notifier in notifiers) { - tasks.Add(notifier.SendAsync(camera, processedImage, labels, _logger)); + tasks.Add(notifier.SendAsync(camera, notification, _logger)); } await Task.WhenAll(tasks); diff --git a/SynoAI/Models/Notification.cs b/SynoAI/Models/Notification.cs new file mode 100644 index 0000000..afa6e78 --- /dev/null +++ b/SynoAI/Models/Notification.cs @@ -0,0 +1,67 @@ +using SynoAI.Extensions; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace SynoAI.Models +{ + public class Notification + { + /// + /// Object for fetching the processed image + /// + public ProcessedImage ProcessedImage { get; set; } + /// + /// The list of valid predictions. + /// + public IEnumerable ValidPredictions { get; set; } + + /// + /// The list of types that were found. + /// + public IEnumerable FoundTypes + { + get + { + return GetLabels(); + } + } + + /// + /// Gets the labels from the predictions to use in the notifications. + /// + /// The predictions to process. + /// A list of labels. + private IEnumerable GetLabels() + { + if (Config.AlternativeLabelling && Config.DrawMode == DrawMode.Matches) + { + List labels = new List(); + if (ValidPredictions.Count() == 1) + { + // If there is only a single object, then don't add a correlating number and instead just + // write out the label. + decimal confidence = Math.Round(ValidPredictions.First().Confidence, 0, MidpointRounding.AwayFromZero); + labels.Add($"{ValidPredictions.First().Label.FirstCharToUpper()} {confidence}%"); + } + else + { + // Since there is more than one object detected, include correlating number + int counter = 1; + foreach (AIPrediction prediction in ValidPredictions) + { + decimal confidence = Math.Round(prediction.Confidence, 0, MidpointRounding.AwayFromZero); + labels.Add($"{counter}. {prediction.Label.FirstCharToUpper()} {confidence}%"); + counter++; + } + } + + return labels; + } + else + { + return ValidPredictions.Select(x => x.Label.FirstCharToUpper()).ToList(); + } + } + } +} diff --git a/SynoAI/Notifiers/Email/Email.cs b/SynoAI/Notifiers/Email/Email.cs index a9604aa..4548054 100644 --- a/SynoAI/Notifiers/Email/Email.cs +++ b/SynoAI/Notifiers/Email/Email.cs @@ -54,16 +54,15 @@ public class Email : NotifierBase /// Sends a message and an image using the Pushbullet API. /// /// The camera that triggered the notification. - /// Object for fetching the processed image. - /// The list of types that were found. + /// The notification data to process. /// A logger. - public override async Task SendAsync(Camera camera, ProcessedImage processedImage, IEnumerable foundTypes, ILogger logger) + public override async Task SendAsync(Camera camera, Notification notification, ILogger logger) { using (logger.BeginScope($"Email '{Destination}'")) { // Assign camera name to variable for logger placeholder string cameraName = camera.Name; - string filePath = processedImage.FilePath; + string filePath = notification.ProcessedImage.FilePath; try { diff --git a/SynoAI/Notifiers/INotifier.cs b/SynoAI/Notifiers/INotifier.cs index d38f763..8bc12e9 100644 --- a/SynoAI/Notifiers/INotifier.cs +++ b/SynoAI/Notifiers/INotifier.cs @@ -19,6 +19,6 @@ public interface INotifier /// /// Handles the send of the notification. /// - Task SendAsync(Camera camera, ProcessedImage processedImage, IEnumerable foundTypes, ILogger logger); + Task SendAsync(Camera camera, Notification notification, ILogger logger); } } diff --git a/SynoAI/Notifiers/NotifierBase.cs b/SynoAI/Notifiers/NotifierBase.cs index fd6be0e..cccbe80 100644 --- a/SynoAI/Notifiers/NotifierBase.cs +++ b/SynoAI/Notifiers/NotifierBase.cs @@ -15,7 +15,7 @@ public abstract class NotifierBase : INotifier public IEnumerable Cameras { get; set; } public IEnumerable Types { get; set; } - public abstract Task SendAsync(Camera camera, ProcessedImage processedImage, IEnumerable foundTypes, ILogger logger); + public abstract Task SendAsync(Camera camera, Notification notification, ILogger logger); protected string GetMessage(Camera camera, IEnumerable foundTypes, string errorMessage = null) { diff --git a/SynoAI/Notifiers/NotifierFactory.cs b/SynoAI/Notifiers/NotifierFactory.cs index fad1d02..1a24fe8 100644 --- a/SynoAI/Notifiers/NotifierFactory.cs +++ b/SynoAI/Notifiers/NotifierFactory.cs @@ -37,9 +37,6 @@ public static INotifier Create(NotifierType type, ILogger logger, IConfiguration case NotifierType.Webhook: factory = new WebhookFactory(); break; - case NotifierType.WebhookLegacy: - factory = new WebhookFactoryLegacy(); - break; default: throw new NotImplementedException(type.ToString()); } diff --git a/SynoAI/Notifiers/NotifierType.cs b/SynoAI/Notifiers/NotifierType.cs index 92f71ee..b264da0 100644 --- a/SynoAI/Notifiers/NotifierType.cs +++ b/SynoAI/Notifiers/NotifierType.cs @@ -25,10 +25,6 @@ public enum NotifierType /// /// Calls a webhook with the image attached. /// - Webhook, - /// - /// Legacy implementation of Webhook - /// - WebhookLegacy + Webhook } } diff --git a/SynoAI/Notifiers/Pushbullet/Pushbullet.cs b/SynoAI/Notifiers/Pushbullet/Pushbullet.cs index 1070d9f..c03e51f 100644 --- a/SynoAI/Notifiers/Pushbullet/Pushbullet.cs +++ b/SynoAI/Notifiers/Pushbullet/Pushbullet.cs @@ -32,12 +32,12 @@ public class Pushbullet : NotifierBase /// Sends a message and an image using the Pushbullet API. /// /// The camera that triggered the notification. - /// Object for fetching the processed image. - /// The list of types that were found. /// A logger. - public override async Task SendAsync(Camera camera, ProcessedImage processedImage, IEnumerable foundTypes, ILogger logger) + public override async Task SendAsync(Camera camera, Notification notification, ILogger logger) { // Pushbullet file uploads are a two part process. First we need to request to upload a file + ProcessedImage processedImage = notification.ProcessedImage; + string fileName = processedImage.FileName; string requestJson = JsonConvert.SerializeObject(new PushbulletUploadRequest() { @@ -82,7 +82,7 @@ public override async Task SendAsync(Camera camera, ProcessedImage processedImag { Type = uploadSuccess ? "file" : "note", Title = $"{camera.Name}: Movement Detected", - Body = GetMessage(camera, foundTypes, errorMessage: uploadError), + Body = GetMessage(camera, notification.FoundTypes, errorMessage: uploadError), FileName = uploadSuccess ? uploadRequestResult.FileName : null, FileUrl = uploadSuccess ? uploadRequestResult.FileUrl : null, FileType = uploadSuccess ? uploadRequestResult.FileType : null diff --git a/SynoAI/Notifiers/Pushover/Pushover.cs b/SynoAI/Notifiers/Pushover/Pushover.cs index 03701b6..fd52f51 100644 --- a/SynoAI/Notifiers/Pushover/Pushover.cs +++ b/SynoAI/Notifiers/Pushover/Pushover.cs @@ -39,7 +39,7 @@ public class Pushover : NotifierBase /// public string Sound { get; set; } - public override async Task SendAsync(Camera camera, ProcessedImage processedImage, IEnumerable foundTypes, ILogger logger) + public override async Task SendAsync(Camera camera, Notification notification, ILogger logger) { if (string.IsNullOrWhiteSpace(ApiKey)) { @@ -55,22 +55,24 @@ public override async Task SendAsync(Camera camera, ProcessedImage processedImag // Build the form message logger.LogInformation($"{camera.Name}: Pushover: Building message"); - string message = GetMessage(camera, foundTypes); + string message = GetMessage(camera, notification.FoundTypes); string device = Devices == null || !Devices.Any() ? String.Empty : string.Join(',', Devices); string title = $"{camera.Name}: Movement Detected"; - MultipartFormDataContent form = new MultipartFormDataContent(); - form.Add(new StringContent(device), "\"device\""); - form.Add(new StringContent(message), "\"message\""); - form.Add(new StringContent(((int)Priority).ToString()), "\"priority\""); - form.Add(new StringContent(Sound ?? String.Empty), "\"sound\""); - form.Add(new StringContent(ApiKey), "\"token\""); - form.Add(new StringContent(UserKey), "\"user\""); - form.Add(new StringContent(title), "\"title\""); + MultipartFormDataContent form = new() + { + { new StringContent(device), "\"device\"" }, + { new StringContent(message), "\"message\"" }, + { new StringContent(((int)Priority).ToString()), "\"priority\"" }, + { new StringContent(Sound ?? String.Empty), "\"sound\"" }, + { new StringContent(ApiKey), "\"token\"" }, + { new StringContent(UserKey), "\"user\"" }, + { new StringContent(title), "\"title\"" } + }; // Send the message - using (FileStream imageStream = processedImage.GetReadonlyStream()) - using (StreamContent imageContent = new StreamContent(imageStream)) + using (FileStream imageStream = notification.ProcessedImage.GetReadonlyStream()) + using (StreamContent imageContent = new(imageStream)) { imageContent.Headers.ContentType = MediaTypeHeaderValue.Parse("image/png"); form.Add(imageContent, "attachment", "image.png"); diff --git a/SynoAI/Notifiers/Telegram/Telegram.cs b/SynoAI/Notifiers/Telegram/Telegram.cs index 95a2076..cc6fdba 100644 --- a/SynoAI/Notifiers/Telegram/Telegram.cs +++ b/SynoAI/Notifiers/Telegram/Telegram.cs @@ -39,19 +39,20 @@ public class Telegram : NotifierBase /// Sends a message and an image using the Telegram API. /// /// The camera that triggered the notification. - /// Object for fetching the processed image. - /// The list of types that were found. + /// The notification data to process. /// A logger. - public override async Task SendAsync(Camera camera, ProcessedImage processedImage, IEnumerable foundTypes, ILogger logger) + public override async Task SendAsync(Camera camera, Notification notification, ILogger logger) { using (logger.BeginScope($"Telegram '{ChatID}'")) { // Assign camera name to variable for logger placeholder string cameraName = camera.Name; + ProcessedImage processedImage = notification.ProcessedImage; + IEnumerable foundTypes = notification.FoundTypes; try { - TelegramBotClient bot = new TelegramBotClient(Token); + TelegramBotClient bot = new(Token); string message = GetMessage(camera, foundTypes); if (string.IsNullOrWhiteSpace(PhotoBaseURL)) diff --git a/SynoAI/Notifiers/Webhook/Webhook.cs b/SynoAI/Notifiers/Webhook/Webhook.cs index 022b2f1..b70e5b0 100644 --- a/SynoAI/Notifiers/Webhook/Webhook.cs +++ b/SynoAI/Notifiers/Webhook/Webhook.cs @@ -58,17 +58,17 @@ public class Webhook : NotifierBase /// Sends a notification to the Webhook. /// /// The camera that triggered the notification. - /// Object for fetching the processed image. - /// The list of types that were found. + /// The notification data to process. /// A logger. - public override async Task SendAsync(Camera camera, ProcessedImage processedImage, IEnumerable foundTypes, ILogger logger) + public override async Task SendAsync(Camera camera, Notification notification, ILogger logger) { logger.LogInformation($"{camera.Name}: Webhook: Processing"); - using (HttpClient client = new HttpClient()) + using (HttpClient client = new()) { FileStream fileStream = null; client.DefaultRequestHeaders.Authorization = GetAuthenticationHeader(); + IEnumerable foundTypes = notification.FoundTypes; string message = GetMessage(camera, foundTypes); HttpContent content; @@ -76,11 +76,13 @@ public override async Task SendAsync(Camera camera, ProcessedImage processedImag { // If we're sending the image, then we need to send the data as multipart/form-data. string typesJson = JsonConvert.SerializeObject(foundTypes); + string validPredictionsJson = JsonConvert.SerializeObject(notification.ValidPredictions); - MultipartFormDataContent form = new MultipartFormDataContent + MultipartFormDataContent form = new() { { new StringContent(camera.Name), "\"camera\"" }, { new StringContent(typesJson), "\"foundTypes\"" }, + { new StringContent(validPredictionsJson), "\"predictions\"" }, { new StringContent(message), "\"message\"" } }; @@ -89,6 +91,7 @@ public override async Task SendAsync(Camera camera, ProcessedImage processedImag case "PATCH": case "POST": case "PUT": + ProcessedImage processedImage = notification.ProcessedImage; fileStream = processedImage.GetReadonlyStream(); form.Add(new StreamContent(fileStream), ImageField, processedImage.FileName); break; @@ -103,6 +106,7 @@ public override async Task SendAsync(Camera camera, ProcessedImage processedImag { camera = camera.Name, foundTypes = foundTypes, + predictions = notification.ValidPredictions, message = message }; diff --git a/SynoAI/Notifiers/Webhook/WebhookFactoryLegacy.cs b/SynoAI/Notifiers/Webhook/WebhookFactoryLegacy.cs deleted file mode 100644 index 8d7de0b..0000000 --- a/SynoAI/Notifiers/Webhook/WebhookFactoryLegacy.cs +++ /dev/null @@ -1,49 +0,0 @@ -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Logging; - -namespace SynoAI.Notifiers.Webhook -{ - public class WebhookFactoryLegacy : NotifierFactory - { - public override INotifier Create(ILogger logger, IConfigurationSection section) - { - using (logger.BeginScope(nameof(WebhookFactoryLegacy))) - { - logger.LogInformation("Processing Webhook Config"); - - string url = section.GetValue("Url"); - AuthorizationMethod authentication = section.GetValue("Authorization", AuthorizationMethod.None); - string username = section.GetValue("Username", null); - string password = section.GetValue("Password", null); - string token = section.GetValue("Token", null); - string field = section.GetValue("Field", "image"); - string method = section.GetValue("Method", "POST"); - bool sendImage = section.GetValue("SendImage", true); - bool sendTypes = section.GetValue("SendTypes", false); - - WebhookLegacy webhook = new WebhookLegacy() - { - Url = url, - Authentication = authentication, - Username = username, - Password = password, - Token = token, - SendImage = sendImage, - SendTypes = sendTypes - }; - - if (!string.IsNullOrWhiteSpace(field)) - { - webhook.Field = field.Trim(); - } - - if (!string.IsNullOrWhiteSpace(method)) - { - webhook.Method = method.ToUpper().Trim(); - } - - return webhook; - } - } - } -} diff --git a/SynoAI/Notifiers/Webhook/WebhookLegacy.cs b/SynoAI/Notifiers/Webhook/WebhookLegacy.cs deleted file mode 100644 index 5fd641b..0000000 --- a/SynoAI/Notifiers/Webhook/WebhookLegacy.cs +++ /dev/null @@ -1,158 +0,0 @@ -using Microsoft.Extensions.Logging; -using Newtonsoft.Json; -using SynoAI.Models; -using SynoAI.Services; -using System; -using System.Collections.Generic; -using System.IO; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Net.Http.Json; -using System.Text; -using System.Threading.Tasks; - -namespace SynoAI.Notifiers.Webhook -{ - /// - /// Calls a third party API. - /// - public class WebhookLegacy : NotifierBase - { - /// - /// The URL to send the request to. - /// - public string Url { get; set; } - /// - /// The HTTP method (POST/PUT/etc). - /// - public string Method { get; set; } - - /// - /// The type of authentication. - /// - public AuthorizationMethod Authentication { get; set; } - /// - /// The username when using Basic authentication. - /// - public string Username {get;set;} - /// - /// The password to use when using Basic authentication. - /// - public string Password {get;set;} - /// - /// The token to use when using Bearer authentication. - /// - public string Token {get;set;} - - /// - /// The field name when posting the image. - /// - public string Field { get; set; } - /// - /// Whether the image should be sent in POST/PUT/PATCH requests. - /// - public bool SendImage { get; set; } - /// - /// Whether the message should be sent in POST/PUT/PATCH requests. - /// - public bool SendTypes { get; set; } - - /// - /// Sends a notification to the Webhook. - /// - /// The camera that triggered the notification. - /// Object for fetching the processed image. - /// The list of types that were found. - /// A logger. - public override async Task SendAsync(Camera camera, ProcessedImage processedImage, IEnumerable foundTypes, ILogger logger) - { - logger.LogInformation($"{camera.Name}: Webhook: Processing"); - using (HttpClient client = new HttpClient()) - { - client.DefaultRequestHeaders.Authorization = GetAuthenticationHeader(); - - MultipartFormDataContent data = new MultipartFormDataContent(); - if (SendTypes) - { - data.Add(JsonContent.Create(foundTypes)); - } - - FileStream fileStream = null; - switch (Method) - { - case "PATCH": - case "POST": - case "PUT": - if (SendImage) - { - fileStream = processedImage.GetReadonlyStream(); - data.Add(new StreamContent(fileStream), Field, processedImage.FileName); - } - break; - } - - logger.LogInformation($"{camera.Name}: Webhook: Calling {Method}."); - - HttpResponseMessage message; - switch (Method) - { - case "DELETE": - message = await client.DeleteAsync(Url); - break; - case "GET": - message = await client.GetAsync(Url); - break; - case "PATCH": - message = await client.PatchAsync(Url, data); - break; - case "POST": - message = await client.PostAsync(Url, data); - break; - case "PUT": - message = await client.PutAsync(Url, data); - break; - default: - logger.LogError($"{camera.Name}: Webhook: The method type '{Method}' is not supported."); - return; - } - - if (message.IsSuccessStatusCode) - { - logger.LogInformation($"{camera.Name}: Webhook: Success."); - } - else - { - logger.LogWarning($"{camera.Name}: Webhook: The end point responded with HTTP status code '{message.StatusCode}'."); - } - - if (fileStream != null) - { - fileStream.Dispose(); - } - } - } - - /// - /// Generates an authentication header for the client. - /// - /// An authentication header. - private AuthenticationHeaderValue GetAuthenticationHeader() - { - string parameter; - switch (Authentication) - { - case AuthorizationMethod.Basic: - byte[] bytes = Encoding.ASCII.GetBytes($"{Username}:{Password}"); - parameter = Convert.ToBase64String(bytes); - break; - case AuthorizationMethod.Bearer: - parameter = Token; - break; - default: - return null; - } - - return new AuthenticationHeaderValue(Authentication.ToString(), parameter); - } - } -}