diff --git a/Squirrowse.Client/Program.cs b/Squirrowse.Client/Program.cs index 88acb1c..483029b 100644 --- a/Squirrowse.Client/Program.cs +++ b/Squirrowse.Client/Program.cs @@ -1,12 +1,26 @@ -using System; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Squirrowse.Client.Service; namespace Squirrowse.Client { - class Program + public class Program { - static void Main(string[] args) + public static void Main(string[] args) { - Console.WriteLine("Hello World!"); + CreateHostBuilder(args).Build().Run(); } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureServices((hostContext, services) => + { + services.AddHostedService(); + services.AddTransient();//keep as transient for now + }); } } diff --git a/Squirrowse.Client/Properties/launchSettings.json b/Squirrowse.Client/Properties/launchSettings.json new file mode 100644 index 0000000..4858d8e --- /dev/null +++ b/Squirrowse.Client/Properties/launchSettings.json @@ -0,0 +1,10 @@ +{ + "profiles": { + "Squirrowse.Client": { + "commandName": "Project", + "environmentVariables": { + "DOTNET_ENVIRONMENT": "Development" + } + } + } +} diff --git a/Squirrowse.Client/Service/ConnectionManager.cs b/Squirrowse.Client/Service/ConnectionManager.cs new file mode 100644 index 0000000..5ce3ae9 --- /dev/null +++ b/Squirrowse.Client/Service/ConnectionManager.cs @@ -0,0 +1,21 @@ +using Microsoft.AspNetCore.SignalR.Client; +using Microsoft.Extensions.DependencyInjection; + +namespace Squirrowse.Client.Service +{ + public class ConnectionManager : IConnectionManager + { + private readonly HubConnection _connection; + + public ConnectionManager(string url, int port) + { + _connection = new HubConnectionBuilder() + .WithUrl($"{url}:{port}/StreamHub") + .AddMessagePackProtocol() + .WithAutomaticReconnect() + .Build(); + } + + public HubConnection EstablishHubConnection() => _connection; + } +} \ No newline at end of file diff --git a/Squirrowse.Client/Service/IConnectionManager.cs b/Squirrowse.Client/Service/IConnectionManager.cs new file mode 100644 index 0000000..f5d208f --- /dev/null +++ b/Squirrowse.Client/Service/IConnectionManager.cs @@ -0,0 +1,10 @@ +using Microsoft.AspNetCore.SignalR.Client; + +namespace Squirrowse.Client.Service +{ + public interface IConnectionManager + { + HubConnection EstablishHubConnection(); + + } +} \ No newline at end of file diff --git a/Squirrowse.Client/Squirrowse.Client.csproj b/Squirrowse.Client/Squirrowse.Client.csproj index 958d2f1..81c0779 100644 --- a/Squirrowse.Client/Squirrowse.Client.csproj +++ b/Squirrowse.Client/Squirrowse.Client.csproj @@ -1,8 +1,14 @@ - + - Exe netcoreapp3.0 + dotnet-Squirrowse.Client-D6805387-040A-46DF-9DAE-926B46C981A6 + + + + + + diff --git a/Squirrowse.Client/Worker.cs b/Squirrowse.Client/Worker.cs new file mode 100644 index 0000000..33a59d9 --- /dev/null +++ b/Squirrowse.Client/Worker.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace Squirrowse.Client +{ + public class Worker : BackgroundService + { + private readonly ILogger _logger; + + public Worker(ILogger logger) + { + _logger = logger; + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + while (!stoppingToken.IsCancellationRequested) + { + _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now); + await Task.Delay(1000, stoppingToken); + } + } + } +} diff --git a/Squirrowse.Client/appsettings.Development.json b/Squirrowse.Client/appsettings.Development.json new file mode 100644 index 0000000..e203e94 --- /dev/null +++ b/Squirrowse.Client/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + } +} diff --git a/Squirrowse.Client/appsettings.json b/Squirrowse.Client/appsettings.json new file mode 100644 index 0000000..8983e0f --- /dev/null +++ b/Squirrowse.Client/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} diff --git a/Squirrowse.Core.Tests/ImgExtensionTests.cs b/Squirrowse.Core.Tests/ImgExtensionTests.cs new file mode 100644 index 0000000..4fe9d7a --- /dev/null +++ b/Squirrowse.Core.Tests/ImgExtensionTests.cs @@ -0,0 +1,34 @@ +using FluentAssertions; +using OpenCvSharp; +using Squirrowse.Core.Services; +using Xunit; + +namespace Squirrowse.Core.Tests +{ + public class ImgExtensionTests + { + private readonly Mat sampleMat = new Mat(500, 600, MatType.CV_8UC3); + + [SkippableFact] + public void ByteShouldBeConvertedToMat() + { + // + Skip.If(true, "Cannot use fluent assertion in this kind of test (compare pointer to obj)"); + // + var bytes = sampleMat.ConvertToJpgByte(); + + var reMet = bytes.ConvertByteToMat(); + + reMet.Should().BeEquivalentTo(sampleMat); + } + + [Fact] + public void MatShouldBeConvertedToByteArr() + { + var newByteArr = sampleMat.ConvertToJpgByte(); + + newByteArr.Should().BeOfType(typeof(byte[])); + newByteArr.Should().NotBeNullOrEmpty(); + } + } +} \ No newline at end of file diff --git a/Squirrowse.Core.Tests/Squirrowse.Core.Tests.csproj b/Squirrowse.Core.Tests/Squirrowse.Core.Tests.csproj new file mode 100644 index 0000000..a12cfb3 --- /dev/null +++ b/Squirrowse.Core.Tests/Squirrowse.Core.Tests.csproj @@ -0,0 +1,23 @@ + + + + netcoreapp3.0 + + false + + + + + + + + + + + + + + + + + diff --git a/Squirrowse.Core/CoreModule.cs b/Squirrowse.Core/CoreModule.cs new file mode 100644 index 0000000..693e5c8 --- /dev/null +++ b/Squirrowse.Core/CoreModule.cs @@ -0,0 +1,16 @@ +using System; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Squirrowse.Core +{ + public static class CoreModule + { + public static IServiceCollection AddCoreModule(this IServiceCollection services) + { + services.AddSingleton(typeof(ILogger<>), typeof(Logger<>)); + + return services; + } + } +} diff --git a/Squirrowse.Core/Models/VideoFrame.cs b/Squirrowse.Core/Models/VideoFrame.cs new file mode 100644 index 0000000..f2aab3c --- /dev/null +++ b/Squirrowse.Core/Models/VideoFrame.cs @@ -0,0 +1,12 @@ +using System; + +namespace Squirrowse.Client.Models +{ + public class VideoFrame + { + public byte[] FrameBytes { get; set; } + public DateTime TimeStamp => DateTime.Now; + public Guid id => Guid.NewGuid(); + public string Issuer { get; set; } + } +} \ No newline at end of file diff --git a/Squirrowse.Core/Services/ImgExtensions.cs b/Squirrowse.Core/Services/ImgExtensions.cs new file mode 100644 index 0000000..1d73542 --- /dev/null +++ b/Squirrowse.Core/Services/ImgExtensions.cs @@ -0,0 +1,21 @@ +using System.Linq; +using System.Runtime.CompilerServices; +using OpenCvSharp; + +namespace Squirrowse.Core.Services +{ + public static class ImgExtensions + { + public static byte[] ConvertToJpgByte(this Mat mat) + { + Cv2.ImEncode(".jpg", mat, out var imgbuffer);//no need to dispose + return imgbuffer.Any() ? imgbuffer : new byte[] { }; + } + + public static Mat ConvertByteToMat(this byte[] bytearr) + { + using var tempMat = Cv2.ImDecode(bytearr, ImreadModes.Unchanged); //keep as disposable + return tempMat ?? new Mat(); + } + } +} \ No newline at end of file diff --git a/Squirrowse.Core/Squirrowse.Core.csproj b/Squirrowse.Core/Squirrowse.Core.csproj new file mode 100644 index 0000000..2400b66 --- /dev/null +++ b/Squirrowse.Core/Squirrowse.Core.csproj @@ -0,0 +1,11 @@ + + + + netcoreapp3.0 + + + + + + + diff --git a/Squirrowse.Service/Hubs/IStreamHub.cs b/Squirrowse.Service/Hubs/IStreamHub.cs new file mode 100644 index 0000000..b73fa0b --- /dev/null +++ b/Squirrowse.Service/Hubs/IStreamHub.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Squirrowse.Service.Hubs +{ + public interface IStreamHub + { + Task UploadByteStream(IAsyncEnumerable stream); + } +} \ No newline at end of file diff --git a/Squirrowse.Service/Hubs/StreamHub.cs b/Squirrowse.Service/Hubs/StreamHub.cs new file mode 100644 index 0000000..0db5054 --- /dev/null +++ b/Squirrowse.Service/Hubs/StreamHub.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.AccessControl; +using System.Threading.Tasks; +using Microsoft.AspNetCore.SignalR; +using Microsoft.Extensions.Logging; +using NLog; + +namespace Squirrowse.Service.Hubs +{ + public class StreamHub : Hub, IStreamHub + { + private readonly ILogger logger; + + public StreamHub(ILogger logger) + { + this.logger = logger; + } + public async Task UploadByteStream(IAsyncEnumerable stream) + { + await foreach (var frame in stream) + { + logger.LogInformation($"Got frame size: {frame.Length} "); + await Task.Delay(100); //leave some delay for debug purpose + } + } + } +} diff --git a/Squirrowse.Service/Program.cs b/Squirrowse.Service/Program.cs index 367ed08..7fc9e0e 100644 --- a/Squirrowse.Service/Program.cs +++ b/Squirrowse.Service/Program.cs @@ -1,26 +1,68 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; - +using NLog; +using NLog.Extensions.Logging; +using NLog.Web; namespace Squirrowse.Service { public class Program { + public static IConfigurationRoot Configuration { get; set; } + public static void Main(string[] args) { - CreateHostBuilder(args).Build().Run(); + Configuration = CreateConfigurationBuilder().Build(); + var logger = GetLogger(); + try + { + logger.Debug("Application started"); + CreateHostBuilder(args) + .Build() + .Run(); + } + catch (Exception ex) + { + logger.Error(ex, "Stopped program because of exception when building WebHost"); + throw; + } + finally + { + LogManager.Shutdown(); + } } - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) + public static IHostBuilder CreateHostBuilder(string[] args) + { + return Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); + webBuilder.UseConfiguration(Configuration); + webBuilder.UseNLog(); }); + } + + private static IConfigurationBuilder CreateConfigurationBuilder() + { + var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"); + return new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", false, true) + .AddJsonFile($"appsettings.{environment}.json", false, true); + } + + private static ILogger GetLogger() + { + var nlogConfigSection = Configuration.GetSection("NLog"); + LogManager.Configuration = new NLogLoggingConfiguration(nlogConfigSection); + ILogger logger = LogManager.GetCurrentClassLogger(); + return logger; + } } } diff --git a/Squirrowse.Service/Properties/launchSettings.json b/Squirrowse.Service/Properties/launchSettings.json index 8249223..c54b9fa 100644 --- a/Squirrowse.Service/Properties/launchSettings.json +++ b/Squirrowse.Service/Properties/launchSettings.json @@ -11,8 +11,8 @@ "profiles": { "IIS Express": { "commandName": "IISExpress", - "launchBrowser": true, - "launchUrl": "weatherforecast", + "launchBrowser": false, + "launchUrl": "", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } diff --git a/Squirrowse.Service/Squirrowse.Service.csproj b/Squirrowse.Service/Squirrowse.Service.csproj index d0114fa..5d22ada 100644 --- a/Squirrowse.Service/Squirrowse.Service.csproj +++ b/Squirrowse.Service/Squirrowse.Service.csproj @@ -1,4 +1,4 @@ - + netcoreapp3.0 @@ -8,4 +8,14 @@ + + + + + + + + + + diff --git a/Squirrowse.Service/Startup.cs b/Squirrowse.Service/Startup.cs index 31e5ebb..d11e0be 100644 --- a/Squirrowse.Service/Startup.cs +++ b/Squirrowse.Service/Startup.cs @@ -1,15 +1,11 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; +using System.Reflection; +using MediatR; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.HttpsPolicy; -using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; +using Squirrowse.Service.Hubs; namespace Squirrowse.Service { @@ -26,15 +22,16 @@ namespace Squirrowse.Service public void ConfigureServices(IServiceCollection services) { services.AddControllers(); + services.AddMediatR(Assembly.GetAssembly(typeof(Startup))); + services.AddSignalR() + .AddHubOptions(opt => opt.MaximumReceiveMessageSize = 102400000)//~100mb per frame instead of 32kb default + .AddMessagePackProtocol(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } + if (env.IsDevelopment()) app.UseDeveloperExceptionPage(); app.UseHttpsRedirection(); @@ -45,7 +42,8 @@ namespace Squirrowse.Service app.UseEndpoints(endpoints => { endpoints.MapControllers(); + endpoints.MapHub($"{nameof(StreamHub)}"); }); } } -} +} \ No newline at end of file diff --git a/Squirrowse.Service/appsettings.json b/Squirrowse.Service/appsettings.json index d9d9a9b..0a501d7 100644 --- a/Squirrowse.Service/appsettings.json +++ b/Squirrowse.Service/appsettings.json @@ -6,5 +6,36 @@ "Microsoft.Hosting.Lifetime": "Information" } }, + "NLog": { + "autoreload": true, + "throwConfigExceptions": true, + "variables": { + "logDirectory": "${basedir}/logs" + }, + "targets": { + "FileLogger": { + "type": "AsyncWrapper", + "target": { + "wrappedFile": { + "type": "File", + "fileName": "${logDirectory}/${machinename}.Log.txt", + "layout": "${longdate} ${level}: ${message} ${exception:format=tostring}", + "archiveFileName": "${logDirectory}/archives/${machinename}.Log_{#}.txt", + "archiveDateFormat": "yyyy-MM-dd", + "archiveAboveSize": "5242880", + "archiveEvery": "Day", + "archiveNumbering": "DateAndSequence", + "maxArchiveFiles": "90" + } + } + } + }, + "rules": [ + { + "logger": "*", + "writeTo": "FileLogger" + } + ] + }, "AllowedHosts": "*" -} +} \ No newline at end of file diff --git a/Squirrowse.sln b/Squirrowse.sln index ad1eacc..08317ed 100644 --- a/Squirrowse.sln +++ b/Squirrowse.sln @@ -3,9 +3,15 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.29411.108 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Squirrowse.Service", "Squirrowse.Service\Squirrowse.Service.csproj", "{8C085621-BAAA-4E96-B027-561BC18751EE}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squirrowse.Service", "Squirrowse.Service\Squirrowse.Service.csproj", "{8C085621-BAAA-4E96-B027-561BC18751EE}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Squirrowse.Client", "Squirrowse.Client\Squirrowse.Client.csproj", "{A523B8A9-958B-45EC-B5E4-54FDF08C1C5D}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squirrowse.Client", "Squirrowse.Client\Squirrowse.Client.csproj", "{558A5917-6AD3-4C40-ACA1-EE3B8B8927C8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Squirrowse.Core", "Squirrowse.Core\Squirrowse.Core.csproj", "{D0989FCC-484E-4ADB-BA5E-1020894F9C09}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{BB569A06-F4D1-4927-87AB-86C4BD2AFBC5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Squirrowse.Core.Tests", "Squirrowse.Core.Tests\Squirrowse.Core.Tests.csproj", "{CFA96677-EAA7-4871-AAD8-E6336973366F}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -17,14 +23,25 @@ Global {8C085621-BAAA-4E96-B027-561BC18751EE}.Debug|Any CPU.Build.0 = Debug|Any CPU {8C085621-BAAA-4E96-B027-561BC18751EE}.Release|Any CPU.ActiveCfg = Release|Any CPU {8C085621-BAAA-4E96-B027-561BC18751EE}.Release|Any CPU.Build.0 = Release|Any CPU - {A523B8A9-958B-45EC-B5E4-54FDF08C1C5D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A523B8A9-958B-45EC-B5E4-54FDF08C1C5D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A523B8A9-958B-45EC-B5E4-54FDF08C1C5D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A523B8A9-958B-45EC-B5E4-54FDF08C1C5D}.Release|Any CPU.Build.0 = Release|Any CPU + {558A5917-6AD3-4C40-ACA1-EE3B8B8927C8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {558A5917-6AD3-4C40-ACA1-EE3B8B8927C8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {558A5917-6AD3-4C40-ACA1-EE3B8B8927C8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {558A5917-6AD3-4C40-ACA1-EE3B8B8927C8}.Release|Any CPU.Build.0 = Release|Any CPU + {D0989FCC-484E-4ADB-BA5E-1020894F9C09}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D0989FCC-484E-4ADB-BA5E-1020894F9C09}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D0989FCC-484E-4ADB-BA5E-1020894F9C09}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D0989FCC-484E-4ADB-BA5E-1020894F9C09}.Release|Any CPU.Build.0 = Release|Any CPU + {CFA96677-EAA7-4871-AAD8-E6336973366F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CFA96677-EAA7-4871-AAD8-E6336973366F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CFA96677-EAA7-4871-AAD8-E6336973366F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CFA96677-EAA7-4871-AAD8-E6336973366F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {CFA96677-EAA7-4871-AAD8-E6336973366F} = {BB569A06-F4D1-4927-87AB-86C4BD2AFBC5} + EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {1CC5C3B8-3825-4EB5-ACFF-73B4CAC8945D} EndGlobalSection