diff --git a/.kanbn/index.md b/.kanbn/index.md new file mode 100644 index 0000000..8b82653 --- /dev/null +++ b/.kanbn/index.md @@ -0,0 +1,30 @@ +--- +startedColumns: + - 'In Progress' +completedColumns: + - Done +--- + +# Barcode-Host + +## Backlog + +- [update-os](tasks/update-os.md) +- [apt-get-install](tasks/apt-get-install.md) +- [configure-ufw](tasks/configure-ufw.md) +- [install-ubuntu-frame](tasks/install-ubuntu-frame.md) +- [netplan](tasks/netplan.md) +- [install-net-7-0](tasks/install-net-7-0.md) +- [pull-repo](tasks/pull-repo.md) +- [run-secrets-task](tasks/run-secrets-task.md) +- [install-vscode-extensions](tasks/install-vscode-extensions.md) +- [run-test-ports](tasks/run-test-ports.md) +- [publish](tasks/publish.md) +- [create-as-service](tasks/create-as-service.md) +- [setup-nginx](tasks/setup-nginx.md) + +## Todo + +## In Progress + +## Done diff --git a/.kanbn/tasks/apt-get-install.md b/.kanbn/tasks/apt-get-install.md new file mode 100644 index 0000000..de5b2f6 --- /dev/null +++ b/.kanbn/tasks/apt-get-install.md @@ -0,0 +1,13 @@ +--- +--- + +# apt-get-install + +sudo -i +apt-get install links unzip net-tools ufw nginx git -y + +## Sub-tasks + +- [x] phares3757 +- [x] unity4 +- [x] unity5 diff --git a/.kanbn/tasks/configure-ufw.md b/.kanbn/tasks/configure-ufw.md new file mode 100644 index 0000000..b0e93d2 --- /dev/null +++ b/.kanbn/tasks/configure-ufw.md @@ -0,0 +1,72 @@ +--- +--- + +# configure-ufw + +```bash +sudo -i +ufw allow from 192.168.0.0/24 to any port 22 comment "01) SSH" +ufw allow to 0.0.0.0/0 port 80 comment "02) HTTP" +ufw allow to 0.0.0.0/0 port 443 comment "03) HTTPS" +ufw allow to 0.0.0.0/0 port 9418 comment "04) Git" +ufw allow from 192.168.0.0/24 to any port 8005 comment "05) Pi-hole" +ufw allow from 192.168.0.0/24 to any port 8006 comment "06) Ajenti" +ufw allow from 192.168.0.0/24 to any port 8007 comment "07) code-server" +ufw allow from 192.168.0.0/24 to any port 8008 comment "08) Nginx" +ufw allow from 192.168.0.0/24 to any port 5002 comment "09) BaGet" +ufw allow to 0.0.0.0/0 port 5000 comment "10) .netCore" +ufw allow to 0.0.0.0/0 port 5001 comment "11) .netCore" +ufw allow from 192.168.0.0/24 to any port 53 comment "12) DNS" +ufw allow from 192.168.0.0/24 to any port 67 comment "13) Unknown" +ufw allow from 192.168.0.0/24 to any port 9654 comment "14) barcode-server" +ufw allow from 192.168.0.0/24 to any port 8009 comment "15) barcode-server-statistics" +ufw allow from 0.0.0.0/0 to any port 9400 comment "16) dashkiosk" +ufw delete 16 +ufw allow from 0.0.0.0/0 to any port 8010 comment "16) Test" +ufw allow from 0.0.0.0/0 to any port 5052 comment "17) NGINdeX.io" +ufw allow from 0.0.0.0/0 to any port 3000 comment "18) gogs" +ufw allow from 0.0.0.0/0 to any port 4001 comment "19) gogs" +ufw delete 19 +ufw allow from 192.168.0.0/24 to any port 4001 comment "19) photoview api" +ufw allow from 192.168.0.0/24 to any port 1234 comment "20) photoview ui" +ufw allow from 192.168.0.0/24 to any port 3306 comment "21) mysql" +ufw allow from 192.168.0.0/24 to any port 8011 comment "22) syncthing" +ufw allow from 0.0.0.0/0 to any port 5201 comment "23) iperf3" +ufw enable +ufw status numbered +``` +```conf +Status: active + + To Action From + -- ------ ---- +[ 1] 22 ALLOW IN 192.168.0.0/24 # 01) SSH +[ 2] 80 ALLOW IN Anywhere # 02) HTTP +[ 3] 443 ALLOW IN Anywhere # 03) HTTPS +[ 4] 9418 ALLOW IN Anywhere # 04) Git +[ 5] 8005 ALLOW IN 192.168.0.0/24 # 05) Pi-hole +[ 6] 8006 ALLOW IN 192.168.0.0/24 # 06) Ajenti +[ 7] 8007 ALLOW IN 192.168.0.0/24 # 07) code-server +[ 8] 8008 ALLOW IN 192.168.0.0/24 # 08) Nginx +[ 9] 5002 ALLOW IN 192.168.0.0/24 # 09) BaGet +[10] 5000 ALLOW IN Anywhere # 10) .netCore +[11] 5001 ALLOW IN Anywhere # 11) .netCore +[12] 53 ALLOW IN 192.168.0.0/24 # 12) DNS +[13] 67 ALLOW IN 192.168.0.0/24 # 13) Unknown +[14] 9654 ALLOW IN 192.168.0.0/24 # 14) barcode-server +[15] 8009 ALLOW IN 192.168.0.0/24 # 15) barcode-server-statistics +[16] 8010 ALLOW IN Anywhere # 16) Test +[17] 5052 ALLOW IN Anywhere # 17) NGINdeX.io +[18] 3000 ALLOW IN Anywhere # 18) gogs +[19] 4001 ALLOW IN 192.168.0.0/24 # 19) photoview api +[20] 1234 ALLOW IN 192.168.0.0/24 # 20) photoview ui +[21] 3306 ALLOW IN 192.168.0.0/24 # 21) mysql +[22] 8011 ALLOW IN 192.168.0.0/24 # 22) syncthing +[23] 5201 ALLOW IN Anywhere # 23) iperf3 +``` + +## Sub-tasks + +- [x] phares3757 +- [x] unity4 +- [x] unity5 diff --git a/.kanbn/tasks/create-as-service.md b/.kanbn/tasks/create-as-service.md new file mode 100644 index 0000000..6fd6437 --- /dev/null +++ b/.kanbn/tasks/create-as-service.md @@ -0,0 +1,40 @@ +--- +--- + +# Create as Service + +```bash +sudo -i +echo >/etc/systemd/system/barcode-host.service && nano /etc/systemd/system/barcode-host.service +``` +```conf +[Unit] +Description=Barcode Server +After=multi-user.target +[Service] +User=unity4 +WorkingDirectory=/var/www/Barcode-Host/Server +ExecStart=/usr/local/bin/dotnet /var/www/Barcode-Host/Server/Barcode.Host.Server.dll +Restart=always +# Environment=ASPNETCORE_ENVIRONMENT=Development +# Environment=ASPNETCORE_ENVIRONMENT=Staging +Environment=ASPNETCORE_ENVIRONMENT=Production +[Install] +WantedBy=multi-user.target +``` +```bash +systemctl daemon-reload +systemctl enable barcode-host.service +systemctl start barcode-host.service +systemctl status barcode-host.service +systemctl stop barcode-host.service +journalctl -fu barcode-host.service +journalctl --rotate +journalctl --vacuum-time=1s +``` + +## Sub-tasks + +- [ ] phares3757 +- [x] unity4 +- [x] unity5 diff --git a/.kanbn/tasks/install-net-7-0.md b/.kanbn/tasks/install-net-7-0.md new file mode 100644 index 0000000..2046bcc --- /dev/null +++ b/.kanbn/tasks/install-net-7-0.md @@ -0,0 +1,22 @@ +--- +--- + +# Install .net 7.0 + +```bask +# https://learn.microsoft.com/en-us/dotnet/core/install/linux-scripted-manual#scripted-install +cd /home/unity4 +wget https://dot.net/v1/dotnet-install.sh -O dotnet-install.sh +chmod +x ./dotnet-install.sh +./dotnet-install.sh --channel 7.0 +export DOTNET_ROOT=$HOME/.dotnet +export PATH=$PATH:$HOME/.dotnet:$HOME/.dotnet/tools +ln -s /home/unity4/.dotnet/dotnet /usr/local/bin/dotnet +dotnet --info +``` + +## Sub-tasks + +- [x] phares3757 +- [x] unity4 +- [x] unity5 diff --git a/.kanbn/tasks/install-ubuntu-frame.md b/.kanbn/tasks/install-ubuntu-frame.md new file mode 100644 index 0000000..f925493 --- /dev/null +++ b/.kanbn/tasks/install-ubuntu-frame.md @@ -0,0 +1,24 @@ +--- +--- + +# install-ubuntu-frame + +```bash +apt-get install links unzip net-tools -y +snap install dashkiosk +timedatectl set-timezone America/Phoenix +snap restart dashkiosk +snap install ubuntu-frame wpe-webkit-mir-kiosk +snap set wpe-webkit-mir-kiosk daemon=true +snap set wpe-webkit-mir-kiosk url=http://localhost:9400/receiver +wpe-webkit-mir-kiosk.cog http://localhost:9400/receiver +/snap/wpe-webkit-mir-kiosk/current/bin/setup.sh +wpe-webkit-mir-kiosk.cog http://localhost:9400/receiver +snap set ubuntu-frame daemon=true +``` + +## Sub-tasks + +- [ ] phares3757 +- [x] unity4 +- [x] unity5 diff --git a/.kanbn/tasks/install-vscode-extensions.md b/.kanbn/tasks/install-vscode-extensions.md new file mode 100644 index 0000000..6a11956 --- /dev/null +++ b/.kanbn/tasks/install-vscode-extensions.md @@ -0,0 +1,15 @@ +--- +--- + +# install-vscode-extensions + +- .NET Watch Attach (trottero.dotnetwatchattach) +- C# (ms-dotnettools.csharp) +- Git Graph (mhutchie.git-graph) +- Live Preview (ms-vscode.live-server) + +## Sub-tasks + +- [x] phares3757 +- [x] unity4 +- [x] unity5 diff --git a/.kanbn/tasks/netplan.md b/.kanbn/tasks/netplan.md new file mode 100644 index 0000000..7ddea93 --- /dev/null +++ b/.kanbn/tasks/netplan.md @@ -0,0 +1,60 @@ +--- +--- + +# netplan + +```bash +ip link +``` +```echo +1: lo: mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000 + link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 +2: eno1: mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 10 + link/ether 6c:0b:84:e3:94:11 brd ff:ff:ff:ff:ff:ff + altname enp0s31f6 +``` +```bash +https://ubuntu.com/server/docs/network-configuration +ip a +ip address show eno1 +echo >/etc/netplan/99_config.yaml && nano /etc/netplan/99_config.yaml +``` +```bash +network: + version: 2 + renderer: networkd + ethernets: + eno1: + addresses: + - 192.168.0.204/24 + routes: + - to: default + via: 192.168.0.1 + nameservers: + addresses: [192.168.0.1, 8.8.8.8, 4.4.4.4] +``` +```bash +netplan apply +ip addr flush eno1 +# soft reset +``` +```bash +network: + version: 2 + renderer: networkd + ethernets: + eno1: + addresses: + - 10.95.154.54/24 + routes: + - to: default + via: 10.95.154.1 + nameservers: + addresses: [10.95.128.11, 10.64.152.171, 8.8.8.8, 4.4.4.4] +``` + +## Sub-tasks + +- [ ] phares3757 +- [x] unity4 +- [x] unity5 diff --git a/.kanbn/tasks/publish.md b/.kanbn/tasks/publish.md new file mode 100644 index 0000000..17745a6 --- /dev/null +++ b/.kanbn/tasks/publish.md @@ -0,0 +1,35 @@ +--- +--- + +# publish + +```bash +sudo -i +systemctl stop barcode-host.service +rm -r /var/www/Barcode-Host/Server +mkdir /var/www +mkdir /var/www/Barcode-Host +mkdir /var/www/Barcode-Host/Server +cd /home/unity4/Barcode-Host/Server +dotnet publish --configuration Release --output /var/www/Barcode-Host/Server +cd /var/www/Barcode-Host/Server +dotnet /var/www/Barcode-Host/Server/Barcode.Host.Server.dll +``` +```conf +2023-06-03 16:02:25.011 -07:00 [Information] (Barcode.Host.Server.Program.) () () Starting Web Application +2023-06-03 16:02:25.062 -07:00 [Information] (Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager.) () () User profile is available. Using '"/root/.aspnet/DataProtection-Keys"' as key repository; keys will not be encrypted at rest. +2023-06-03 16:02:25.093 -07:00 [Information] (Barcode.Host.Server.HostedService.TimedHostedService.StartAsync) () () Timed Hosted Service: 1234567:3070 running. +2023-06-03 16:02:25.175 -07:00 [Information] (Microsoft.Hosting.Lifetime.) () () Now listening on: "http://localhost:5003" +2023-06-03 16:02:25.175 -07:00 [Information] (Microsoft.Hosting.Lifetime.) () () Application started. Press Ctrl+C to shut down. +2023-06-03 16:02:25.176 -07:00 [Information] (Microsoft.Hosting.Lifetime.) () () Hosting environment: "Production" +2023-06-03 16:02:25.176 -07:00 [Information] (Microsoft.Hosting.Lifetime.) () () Content root path: "/var/www/Barcode-Host/Server" +``` +```bash +links http://localhost:5003/api/lastScan +``` + +## Sub-tasks + +- [ ] phares3757 +- [x] unity4 +- [x] unity5 diff --git a/.kanbn/tasks/pull-repo.md b/.kanbn/tasks/pull-repo.md new file mode 100644 index 0000000..e7e092e --- /dev/null +++ b/.kanbn/tasks/pull-repo.md @@ -0,0 +1,17 @@ +--- +--- + +# Pull Repo + +```bash +cd /home/unity4 +git clone http://76df8eca4a6c11fe29a58c3be37543c11389ab93@192.168.0.73:3000/mikepharesjr/Barcode-Host.git +cd Barcode-Host +code . +``` + +## Sub-tasks + +- [x] phares3757 +- [x] unity4 +- [x] unity5 diff --git a/.kanbn/tasks/run-secrets-task.md b/.kanbn/tasks/run-secrets-task.md new file mode 100644 index 0000000..4ae7dde --- /dev/null +++ b/.kanbn/tasks/run-secrets-task.md @@ -0,0 +1,15 @@ +--- +--- + +# run-secrets-task + +```bash +cd Server +dotnet user-secrets set RootPassword 4hink +``` + +## Sub-tasks + +- [x] phares3757 +- [x] unity4 +- [x] unity5 diff --git a/.kanbn/tasks/run-test-ports.md b/.kanbn/tasks/run-test-ports.md new file mode 100644 index 0000000..2f8df51 --- /dev/null +++ b/.kanbn/tasks/run-test-ports.md @@ -0,0 +1,22 @@ +--- +--- + +# run-test-ports + +```bash +ls -l /dev/ttyUSB* +``` +```echo +# crw-rw---- 1 root dialout 188, 0 Jun 3 14:54 /dev/ttyUSB0 +``` +```bash +sudo -i +adduser unity4 dialout +reboot +``` + +## Sub-tasks + +- [x] phares3757 +- [x] unity4 +- [x] unity5 diff --git a/.kanbn/tasks/setup-nginx.md b/.kanbn/tasks/setup-nginx.md new file mode 100644 index 0000000..f4e4506 --- /dev/null +++ b/.kanbn/tasks/setup-nginx.md @@ -0,0 +1,113 @@ +--- +--- + +# setup-nginx + +```bash +echo >/etc/nginx/sites-available/default && nano /etc/nginx/sites-available/default +``` +```conf +server { + listen 80 default_server; + listen [::]:80 default_server; + root /var/www/html; + index index.html index.htm index.nginx-debian.html; + server_name _; + location / { + try_files $uri $uri/ =404; + } +} +``` +```bash +systemctl restart nginx +lsof -i -P -n | grep LISTEN +links http://192.168.0.204/ +nginx -t +nginx -s reload +echo >/etc/nginx/sites-available/Barcode-Host-Server && nano /etc/nginx/sites-available/Barcode-Host-Server +``` +```conf +server { + listen 80 default_server; + listen [::]:80 default_server; + root /var/www/html; + index index.html index.htm index.nginx-debian.html; + server_name _; + location / { + try_files $uri $uri/ =404; + } +} +``` +```bash +echo >/home/unity4/localhost.conf && nano /home/unity4/localhost.conf +``` +```conf +[req] +default_bits = 2048 +default_keyfile = localhost.key +distinguished_name = req_distinguished_name +req_extensions = req_ext +x509_extensions = v3_ca + +[req_distinguished_name] +countryName = Country Name (2 letter code) +countryName_default = US +stateOrProvinceName = State or Province Name (full name) +stateOrProvinceName_default = Arizona +localityName = Locality Name (eg, city) +localityName_default = Mesa +organizationName = Organization Name (eg, company) +organizationName_default = Infineon Technologies Americas Corp. +organizationalUnitName = organizationalunit +organizationalUnitName_default = Development +commonName = Common Name (e.g. server FQDN or YOUR name) +commonName_default = unity4 +commonName_max = 64 + +[req_ext] +subjectAltName = @alt_names + +[v3_ca] +subjectAltName = @alt_names + +[alt_names] +DNS.1 = unity4 +DNS.2 = localhost +DNS.3 = 127.0.0.1 +``` +```bash +openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/nginx/localhost.key -out /etc/nginx/localhost.crt -config /home/unity4/localhost.conf +ls /etc/nginx +echo >/etc/nginx/sites-available/Barcode-Host-Server && nano /etc/nginx/sites-available/Barcode-Host-Server +``` +```conf +server { + server_name _; + ssl_certificate 'localhost.crt'; + listen 443 default_server ssl http2; + ssl_certificate_key 'localhost.key'; + ssl_protocols TLSv1.2 TLSv1.1 TLSv1; + error_page 500 502 503 504 /50x.html; + listen [::]:443 default_server ssl http2; + location / { + proxy_pass http://localhost:5003; + } +} +``` +```bash +ln -s /etc/nginx/sites-available/Barcode-Host-Server /etc/nginx/sites-enabled/Barcode-Host-Server +nginx -t +nginx -s reload +links https://localhost/api/lastscan +links https://unity4/api/lastscan +``` +```conf +# C:\Windows\System32\drivers\etc\hosts +192.168.0.204 unity4 +``` + +## Sub-tasks + +- [x] phares3757 +- [x] unity4 +- [x] unity5 diff --git a/.kanbn/tasks/update-os.md b/.kanbn/tasks/update-os.md new file mode 100644 index 0000000..69c3859 --- /dev/null +++ b/.kanbn/tasks/update-os.md @@ -0,0 +1,17 @@ +--- +--- + +# update-os + +```bash +apt update +apt upgrade -y +apt install update-manager-core +do-release-upgrade +``` + +## Sub-tasks + +- [x] phares3757 +- [x] unity4 +- [x] unity5 diff --git a/.vscode/settings.json b/.vscode/settings.json index 8d9e274..def4683 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,9 +1,12 @@ { "cSpell.enabled": false, - "files.exclude": { - "**/.git": false - }, "thunder-client.saveToWorkspace": true, "thunder-client.workspaceRelativePath": ".vscode", - "coverage-gutters.coverageBaseDir": "../.vscode/TestResults/*" + "coverage-gutters.coverageBaseDir": "../.vscode/TestResults/*", + "[markdown]": { + "editor.wordWrap": "off" + }, + "files.exclude": { + "**/.git": false + } } \ No newline at end of file diff --git a/Server.Tests/Barcode.Host.Server.Tests.csproj b/Server.Tests/Barcode.Host.Server.Tests.csproj index a27f474..ba1516e 100644 --- a/Server.Tests/Barcode.Host.Server.Tests.csproj +++ b/Server.Tests/Barcode.Host.Server.Tests.csproj @@ -1,8 +1,9 @@ - net7.0 enable enable + win-x64;linux-x64 + net7.0 trx diff --git a/Server.Tests/Class1.cs b/Server.Tests/Class1.cs deleted file mode 100644 index e4ca7ff..0000000 --- a/Server.Tests/Class1.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace Server.Tests; -public class Class1 -{ - -} \ No newline at end of file diff --git a/Server.Tests/UnitTestLastScanController.cs b/Server.Tests/UnitTestLastScanController.cs new file mode 100644 index 0000000..5247513 --- /dev/null +++ b/Server.Tests/UnitTestLastScanController.cs @@ -0,0 +1,78 @@ +using Barcode.Host.Shared.DataModels; +using Barcode.Host.Shared.Models.Stateless; +using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.Extensions.DependencyInjection; +using Serilog; + +namespace Barcode.Host.Tests; + +[TestClass] +public class UnitTestLastScanController +{ + +#pragma warning disable CS8618 + + private static ILogger _Logger; + private static string _ControllerName; + private static TestContext _TestContext; + private static WebApplicationFactory _WebApplicationFactory; + +#pragma warning restore + + [ClassInitialize] + public static void ClassInitAsync(TestContext testContext) + { + _TestContext = testContext; + _Logger = Log.ForContext(); + _WebApplicationFactory = new WebApplicationFactory(); + _ControllerName = nameof(Server.ApiControllers.LastScanController)[..^10]; + } + + private static void NonThrowTryCatch() + { + try + { throw new Exception(); } + catch (Exception) { } + } + + [TestMethod] + public void TestControllerName() + { + _Logger.Information("Starting Web Application"); + Assert.AreEqual(ILastScanController.GetRouteName(), _ControllerName); + _Logger.Information($"{_TestContext?.TestName} completed"); + NonThrowTryCatch(); + } + +#if DEBUG + [Ignore] +#endif + [TestMethod] + public void GetScan() + { + _Logger.Information("Starting Web Application"); + IServiceProvider serviceProvider = _WebApplicationFactory.Services.CreateScope().ServiceProvider; + ILastScanService lastScanService = serviceProvider.GetRequiredService(); + Result result = lastScanService.GetScan(); + Assert.IsNotNull(result?.Results); + _Logger.Information($"{_TestContext?.TestName} completed"); + NonThrowTryCatch(); + } + +#if DEBUG + [Ignore] +#endif + [TestMethod] + public async Task GetScanApi() + { + HttpClient httpClient = _WebApplicationFactory.CreateClient(); + _Logger.Information("Starting Web Application"); + string? json = await httpClient.GetStringAsync($"api/{_ControllerName}/O171927.1.37/"); + File.WriteAllText(Path.Combine(AppContext.BaseDirectory, $"{_ControllerName}-{nameof(GetScan)}.json"), json); + Result? result = System.Text.Json.JsonSerializer.Deserialize>(json); + Assert.IsNotNull(result?.Results); + _Logger.Information($"{_TestContext?.TestName} completed"); + NonThrowTryCatch(); + } + +} \ No newline at end of file diff --git a/Server.Tests/Usings.cs b/Server.Tests/Usings.cs new file mode 100644 index 0000000..ab67c7e --- /dev/null +++ b/Server.Tests/Usings.cs @@ -0,0 +1 @@ +global using Microsoft.VisualStudio.TestTools.UnitTesting; \ No newline at end of file diff --git a/Server/ApiControllers/LastScanController.cs b/Server/ApiControllers/LastScanController.cs new file mode 100644 index 0000000..c8c9912 --- /dev/null +++ b/Server/ApiControllers/LastScanController.cs @@ -0,0 +1,20 @@ +using Barcode.Host.Shared.Models.Stateless; +using Microsoft.AspNetCore.Mvc; +using System.Text.Json; + +namespace Barcode.Host.Server.ApiControllers; + +[Route("api/[controller]")] +public class LastScanController : Controller, ILastScanController +{ + + private readonly ILastScanService _LastScanService; + + public LastScanController(ILastScanService lastScanService) => + _LastScanService = lastScanService; + + [HttpGet()] + public IActionResult GetScan() => + Json(_LastScanService.GetScan(), new JsonSerializerOptions { PropertyNamingPolicy = null, WriteIndented = true }); + +} \ No newline at end of file diff --git a/Server/Barcode.Host.Server.csproj b/Server/Barcode.Host.Server.csproj index 140bdf2..498fcb4 100644 --- a/Server/Barcode.Host.Server.csproj +++ b/Server/Barcode.Host.Server.csproj @@ -1,10 +1,10 @@ - net7.0 enable enable Exe win-x64;linux-x64 + net7.0 02dce973-df1d-4325-962a-ed549af8d4c5 @@ -22,6 +22,7 @@ Linux + @@ -31,9 +32,18 @@ + + + + Always + + + Always + + \ No newline at end of file diff --git a/Server/HostedService/TimedHostedService.cs b/Server/HostedService/TimedHostedService.cs new file mode 100644 index 0000000..478cc03 --- /dev/null +++ b/Server/HostedService/TimedHostedService.cs @@ -0,0 +1,186 @@ +using Barcode.Host.Server.Models; +using Barcode.Host.Shared.DataModels; +using Barcode.Host.Shared.KeyboardMouse; +using Barcode.Host.Shared.Models.Stateless; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Serilog.Context; + +namespace Barcode.Host.Server.HostedService; + +public class TimedHostedService : IHostedService, IAggregateInputReader, IDisposable +{ + + public event InputReader.RaiseKeyPress? OnKeyPress; + + private readonly int _ExecutionCount; + private readonly AppSettings _AppSettings; + private readonly ISerialService _SerialService; + private readonly ILastScanService _LastScanService; + private readonly ILogger _Logger; + private readonly ILinuxGroupManager _LinuxGroupManager; + private readonly Dictionary _Readers; + private readonly Dictionary _CharToEventCodes; + private readonly List<(string MethodName, Timer Timer)> _Timers; + + public TimedHostedService(ILogger logger, AppSettings appSettings, ILinuxGroupManager linuxGroupManager, ILastScanService lastScanService, ISerialService serialService) + { + _Readers = new(); + _Logger = logger; + _ExecutionCount = 0; + _CharToEventCodes = new(); + _AppSettings = appSettings; + _SerialService = serialService; + _LastScanService = lastScanService; + _LinuxGroupManager = linuxGroupManager; + Timer writeTimer = new(Write, null, Timeout.Infinite, Timeout.Infinite); + Timer scanForNewInputsTimer = new(ScanForNewInputs, null, Timeout.Infinite, Timeout.Infinite); + _Timers = new List<(string, Timer)>() { (nameof(Write), writeTimer), (nameof(ScanForNewInputs), scanForNewInputsTimer) }; + } + + public Task StartAsync(CancellationToken stoppingToken) + { + string? methodName = IMethodName.GetActualAsyncMethodName(); + using (LogContext.PushProperty("MethodName", methodName)) + { + _Logger.LogInformation($"Timed Hosted Service: {_AppSettings.GitCommitSeven}:{Environment.ProcessId} running."); + _SerialService.Open(); + if (!_LinuxGroupManager.IsInInputGroup().WaitAsync(stoppingToken).Result) + { + if (string.IsNullOrEmpty(_AppSettings.RootPassword)) + throw new Exception($"Please check appsettings file(s) for <{nameof(_AppSettings.RootPassword)}>!"); + _ = _LinuxGroupManager.AddUserToInputGroup(_AppSettings.RootPassword); + _ = _LinuxGroupManager.RebootSystem(_AppSettings.RootPassword); + } + List<(EventCode, char)> collection = _LastScanService.IncludeEventCodes(); + foreach ((EventCode eventCode, char @char) in collection) + _CharToEventCodes.Add(eventCode, @char); + int dueTime = 0; + foreach ((string _, Timer timer) in _Timers) + { + dueTime += 300; + _ = timer.Change(dueTime, Timeout.Infinite); + } + } + return Task.CompletedTask; + } + + public Task StopAsync(CancellationToken stoppingToken) + { + string? methodName = IMethodName.GetActualAsyncMethodName(); + using (LogContext.PushProperty("MethodName", methodName)) + { + _Logger.LogInformation($"Timed Hosted Service: {_AppSettings.GitCommitSeven}:{Environment.ProcessId} is stopping."); + for (short i = 0; i < short.MaxValue; i++) + { + Thread.Sleep(500); + if (_ExecutionCount == 0) + break; + } + } + return Task.CompletedTask; + } + + public void Dispose() + { + foreach ((string _, Timer timer) in _Timers) + timer.Dispose(); + foreach (InputReader inputReader in _Readers.Values) + { + inputReader.OnKeyPress -= ReaderOnOnKeyPress; + inputReader.Dispose(); + } + _Readers.Clear(); + _SerialService.Close(); + GC.SuppressFinalize(this); + } + + private void ReaderOnOnKeyPress(KeyPressEvent e) + { + OnKeyPress?.Invoke(e); + if (e.TimeSpan.TotalMilliseconds > _AppSettings.ClearLastScanServiceAfter) + _LastScanService.Clear(); + if (e.KeyState == KeyState.KeyUp && _CharToEventCodes.TryGetValue(e.EventCode, out char @char)) + _LastScanService.Add(e.EventCode, @char); + } + + private Timer? GetTimer(string methodName) + { + (string MethodName, Timer Timer)[] results = _Timers.Where(l => l.MethodName == methodName).ToArray(); + return !results.Any() ? null : results.First().Timer; + } + + private void ScanForNewInputs() + { + string fileName; + IEnumerable? devices = null; + string[] files = Directory.GetFiles("/dev/input/", "event*"); + foreach (string file in files) + { + if (_Readers is null || _Readers.ContainsKey(file)) + continue; + devices ??= DeviceReader.Get(_AppSettings.LinuxDevicePath); + fileName = Path.GetFileName(file); + InputReader reader = new(file, _Logger); + if (devices.Any(l => !string.IsNullOrEmpty(l.Name) && l.Name.EndsWith(_AppSettings.DeviceNameEndsWith) && l.Handlers.Any(m => m == fileName))) + reader.OnKeyPress += ReaderOnOnKeyPress; + _Readers?.Add(file, reader); + } + IEnumerable? deadReaders = _Readers?.Values.Where(r => r.Faulted); + if (deadReaders is not null) + { + foreach (InputReader? inputReader in deadReaders) + { + _ = _Readers?.Remove(inputReader.Path); + inputReader.OnKeyPress -= ReaderOnOnKeyPress; + inputReader.Dispose(); + } + } + } + + private void ScanForNewInputs(object? sender) + { + try + { ScanForNewInputs(); } + catch (Exception ex) { _Logger.LogError(ex, nameof(ScanForNewInputs)); } + try + { + Timer? timer = GetTimer(nameof(ScanForNewInputs)); + if (timer is not null) + { + TimeSpan timeSpan = new(DateTime.Now.AddSeconds(30).Ticks - DateTime.Now.Ticks); + _ = timer.Change((int)timeSpan.TotalMilliseconds, Timeout.Infinite); + } + } + catch (Exception ex) { _Logger.LogError(ex, $"{nameof(ScanForNewInputs)}-{nameof(Timer)}.{nameof(Timer.Change)}"); } + } + + private void Write() + { + int count = _LastScanService.GetCount(); + if (count > 0) + { + Result result = _LastScanService.GetScan(); + if (!string.IsNullOrEmpty(result.Results)) + _SerialService.SerialPortWrite(count, result.Results); + } + } + + private void Write(object? sender) + { + try + { Write(); } + catch (Exception ex) { _Logger.LogError(ex, nameof(Write)); } + try + { + Timer? timer = GetTimer(nameof(Write)); + if (timer is not null) + { + TimeSpan timeSpan = new(DateTime.Now.AddMilliseconds(_AppSettings.WriteToSerialEvery).Ticks - DateTime.Now.Ticks); + _ = timer.Change((int)timeSpan.TotalMilliseconds, Timeout.Infinite); + } + } + catch (Exception ex) { _Logger.LogError(ex, $"{nameof(Write)}-{nameof(Timer)}.{nameof(Timer.Change)}"); } + } + +} \ No newline at end of file diff --git a/Server/Models/AppSettings.cs b/Server/Models/AppSettings.cs index 168ca46..6c244cf 100644 --- a/Server/Models/AppSettings.cs +++ b/Server/Models/AppSettings.cs @@ -2,26 +2,23 @@ using System.Text.Json; namespace Barcode.Host.Server.Models; -public record AppSettings(string ApiExportPath, - string ApiLoggingContentTypes, - string ApiLoggingPathPrefixes, - string ApiLogPath, - string ApiUrl, - string AttachmentPath, - string BuildNumber, +public record AppSettings(string BuildNumber, string Company, - string ConnectionString, + int ClearLastScanServiceAfter, + string DeviceNameEndsWith, + int ExpectedScanLengthA, + int ExpectedScanLengthB, string GitCommitSeven, - string InboundApiAllowedIPList, + string LinuxDevicePath, bool IsDevelopment, bool IsStaging, string MockRoot, string MonAResource, string MonASite, - string OI2SqlConnectionString, - string OIExportPath, + string RootPassword, string URLs, - string WorkingDirectoryName) + string WorkingDirectoryName, + int WriteToSerialEvery) { public override string ToString() diff --git a/Server/Models/Binder/AppSettings.cs b/Server/Models/Binder/AppSettings.cs index 9011f3b..c084e52 100644 --- a/Server/Models/Binder/AppSettings.cs +++ b/Server/Models/Binder/AppSettings.cs @@ -9,26 +9,23 @@ public class AppSettings #nullable disable - [Display(Name = "Api Export Path"), Required] public string ApiExportPath { get; set; } - [Display(Name = "Api Logging Content Types"), Required] public string ApiLoggingContentTypes { get; set; } - [Display(Name = "Api Logging Path Prefixes"), Required] public string ApiLoggingPathPrefixes { get; set; } - [Display(Name = "Api Log Path"), Required] public string ApiLogPath { get; set; } - [Display(Name = "Api URL"), Required] public string ApiUrl { get; set; } - [Display(Name = "Attachment Path"), Required] public string AttachmentPath { get; set; } [Display(Name = "Build Number"), Required] public string BuildNumber { get; set; } [Display(Name = "Company"), Required] public string Company { get; set; } - [Display(Name = "Connection String"), Required] public string ConnectionString { get; set; } + [Display(Name = "Last Scan Service Clear After"), Required] public int? ClearLastScanServiceAfter { get; set; } + [Display(Name = "Device Name Ends With"), Required] public string DeviceNameEndsWith { get; set; } + [Display(Name = "ExpectedScanLengthA"), Required] public int? ExpectedScanLengthA { get; set; } + [Display(Name = "ExpectedScanLengthB"), Required] public int? ExpectedScanLengthB { get; set; } [Display(Name = "Git Commit Seven"), Required] public string GitCommitSeven { get; set; } - [Display(Name = "Inbound Api Allowed IP List"), Required] public string InboundApiAllowedIPList { get; set; } + [Display(Name = "Linux Device Path"), Required] public string LinuxDevicePath { get; set; } [Display(Name = "Is Development"), Required] public bool? IsDevelopment { get; set; } [Display(Name = "Is Staging"), Required] public bool? IsStaging { get; set; } [Display(Name = "Mock Root"), Required] public string MockRoot { get; set; } [Display(Name = "MonA Resource"), Required] public string MonAResource { get; set; } [Display(Name = "MonA Site"), Required] public string MonASite { get; set; } - [Display(Name = "Oi 2 Sql Connection String"), Required] public string Oi2SqlConnectionString { get; set; } - [Display(Name = "OI Export Path"), Required] public string OIExportPath { get; set; } + [Display(Name = "RootPassword"), Required] public string RootPassword { get; set; } [Display(Name = "URLs"), Required] public string URLs { get; set; } [Display(Name = "Working Directory Name"), Required] public string WorkingDirectoryName { get; set; } + [Display(Name = "WriteToSerialEvery"), Required] public int? WriteToSerialEvery { get; set; } #nullable restore @@ -43,28 +40,22 @@ public class AppSettings Models.AppSettings result; if (appSettings is null) throw new NullReferenceException(nameof(appSettings)); - if (appSettings.ApiExportPath is null) - throw new NullReferenceException(nameof(ApiExportPath)); - if (appSettings.ApiLoggingContentTypes is null) - throw new NullReferenceException(nameof(ApiLoggingContentTypes)); - if (appSettings.ApiLoggingPathPrefixes is null) - throw new NullReferenceException(nameof(ApiLoggingPathPrefixes)); - if (appSettings.ApiLogPath is null) - throw new NullReferenceException(nameof(ApiLogPath)); - if (appSettings.ApiUrl is null) - throw new NullReferenceException(nameof(ApiUrl)); - if (appSettings.AttachmentPath is null) - throw new NullReferenceException(nameof(AttachmentPath)); if (appSettings.BuildNumber is null) throw new NullReferenceException(nameof(BuildNumber)); if (appSettings.Company is null) throw new NullReferenceException(nameof(Company)); - if (appSettings.ConnectionString is null) - throw new NullReferenceException(nameof(ConnectionString)); + if (appSettings.ClearLastScanServiceAfter is null) + throw new NullReferenceException(nameof(ClearLastScanServiceAfter)); + if (appSettings.DeviceNameEndsWith is null) + throw new NullReferenceException(nameof(DeviceNameEndsWith)); + if (appSettings.ExpectedScanLengthA is null) + throw new NullReferenceException(nameof(ExpectedScanLengthA)); + if (appSettings.ExpectedScanLengthB is null) + throw new NullReferenceException(nameof(ExpectedScanLengthB)); if (appSettings.GitCommitSeven is null) throw new NullReferenceException(nameof(GitCommitSeven)); - if (appSettings.InboundApiAllowedIPList is null) - throw new NullReferenceException(nameof(InboundApiAllowedIPList)); + if (appSettings.LinuxDevicePath is null) + throw new NullReferenceException(nameof(LinuxDevicePath)); if (appSettings.IsDevelopment is null) throw new NullReferenceException(nameof(IsDevelopment)); if (appSettings.IsStaging is null) @@ -75,35 +66,32 @@ public class AppSettings throw new NullReferenceException(nameof(MonAResource)); if (appSettings.MonASite is null) throw new NullReferenceException(nameof(MonASite)); - if (appSettings.Oi2SqlConnectionString is null) - throw new NullReferenceException(nameof(Oi2SqlConnectionString)); - if (appSettings.OIExportPath is null) - throw new NullReferenceException(nameof(OIExportPath)); + if (appSettings.RootPassword is null) + throw new NullReferenceException(nameof(RootPassword)); if (appSettings.URLs is null) throw new NullReferenceException(nameof(URLs)); if (appSettings.WorkingDirectoryName is null) throw new NullReferenceException(nameof(WorkingDirectoryName)); + if (appSettings.WriteToSerialEvery is null) + throw new NullReferenceException(nameof(WriteToSerialEvery)); result = new( - appSettings.ApiExportPath, - appSettings.ApiLoggingContentTypes, - appSettings.ApiLoggingPathPrefixes, - appSettings.ApiLogPath, - appSettings.ApiUrl, - appSettings.AttachmentPath, appSettings.BuildNumber, appSettings.Company, - appSettings.ConnectionString, + appSettings.ClearLastScanServiceAfter.Value, + appSettings.DeviceNameEndsWith, + appSettings.ExpectedScanLengthA.Value, + appSettings.ExpectedScanLengthB.Value, appSettings.GitCommitSeven, - appSettings.InboundApiAllowedIPList, + appSettings.LinuxDevicePath, appSettings.IsDevelopment.Value, appSettings.IsStaging.Value, appSettings.MockRoot, appSettings.MonAResource, appSettings.MonASite, - appSettings.Oi2SqlConnectionString, - appSettings.OIExportPath, + appSettings.RootPassword, appSettings.URLs, - appSettings.WorkingDirectoryName); + appSettings.WorkingDirectoryName, + appSettings.WriteToSerialEvery.Value); return result; } diff --git a/Server/Program.cs b/Server/Program.cs index 04b3ded..7d7ce32 100644 --- a/Server/Program.cs +++ b/Server/Program.cs @@ -1,7 +1,9 @@ -using Barcode.Host.Server.Models; +using Barcode.Host.Server.HostedService; +using Barcode.Host.Server.Models; +using Barcode.Host.Server.Services; using Barcode.Host.Shared.Models; +using Barcode.Host.Shared.Models.Stateless; using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -35,7 +37,7 @@ public class Program public static int Main(string[] args) { LoggerConfiguration loggerConfiguration = new(); - (string assemblyName, WebApplicationOptions _) = Get(args); + (string assemblyName, _) = Get(args); WebApplicationBuilder webApplicationBuilder = WebApplication.CreateBuilder(args); _ = webApplicationBuilder.Configuration.AddUserSecrets(); AppSettings appSettings = Models.Binder.AppSettings.Get(webApplicationBuilder.Configuration); @@ -49,44 +51,35 @@ public class Program ILogger log = Log.ForContext(); try { - _ = webApplicationBuilder.Services.AddMemoryCache(); - _ = webApplicationBuilder.Services.Configure(options => options.SuppressModelStateInvalidFilter = true); + if (appSettings.IsStaging && appSettings.IsDevelopment) + throw new NotSupportedException("Please check appsettings file(s)!"); + if (appSettings.IsStaging != webApplicationBuilder.Environment.IsStaging()) + throw new NotSupportedException("Please check appsettings file(s)!"); + if (appSettings.IsDevelopment != webApplicationBuilder.Environment.IsDevelopment()) + throw new NotSupportedException("Please check appsettings file(s)!"); _ = webApplicationBuilder.Services.AddControllersWithViews(); - _ = webApplicationBuilder.Services.AddDistributedMemoryCache(); - _ = webApplicationBuilder.Services.AddSingleton(_ => appSettings); - + _ = webApplicationBuilder.Services.AddSingleton(); + _ = webApplicationBuilder.Services.AddSingleton(); + _ = webApplicationBuilder.Services.AddSingleton(); + _ = webApplicationBuilder.Services.AddHostedService(); _ = webApplicationBuilder.Services.AddSwaggerGen(); - _ = webApplicationBuilder.Services.AddSession(sessionOptions => - { - sessionOptions.IdleTimeout = TimeSpan.FromSeconds(2000); - sessionOptions.Cookie.HttpOnly = true; - sessionOptions.Cookie.IsEssential = true; - } - ); WebApplication webApplication = webApplicationBuilder.Build(); - _ = webApplication.UseCors(corsPolicyBuilder => corsPolicyBuilder.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod()); - if (!webApplicationBuilder.Environment.IsDevelopment()) + if (appSettings.IsDevelopment) { - _ = webApplication.UseExceptionHandler("/Error"); - _ = webApplication.UseHttpsRedirection(); - _ = webApplication.UseHsts(); - } - else - { - if (string.IsNullOrEmpty(appSettings.URLs)) - { - Environment.ExitCode = -1; - webApplication.Lifetime.StopApplication(); - } _ = webApplication.UseSwagger(); + _ = webApplication.UseDeveloperExceptionPage(); _ = webApplication.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "Server V1")); } + if (!appSettings.IsDevelopment) + { + _ = webApplication.UseExceptionHandler("/Error"); + _ = webApplication.UseHsts(); + } + _ = webApplication.UseCors(corsPolicyBuilder => corsPolicyBuilder.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod()); _ = webApplication.Lifetime.ApplicationStopped.Register(Log.CloseAndFlush); - _ = SerilogApplicationBuilderExtensions.UseSerilogRequestLogging(webApplication); _ = webApplication.UseFileServer(enableDirectoryBrowsing: true); _ = webApplication.UseStaticFiles(); - _ = webApplication.UseSession(); _ = webApplication.MapControllers(); log.Information("Starting Web Application"); webApplication.Run(); diff --git a/Server/Services/LastScanService.cs b/Server/Services/LastScanService.cs new file mode 100644 index 0000000..c390a3f --- /dev/null +++ b/Server/Services/LastScanService.cs @@ -0,0 +1,93 @@ +using Barcode.Host.Shared.DataModels; +using Barcode.Host.Shared.KeyboardMouse; +using Barcode.Host.Shared.Models.Stateless; + +namespace Barcode.Host.Server.Services; + +public class LastScanService : ILastScanService +{ + + private readonly List<(EventCode EventCode, char Char)> _EventCodes; + + public LastScanService() => + _EventCodes = new(); + + void ILastScanService.Clear() + { + lock (_EventCodes) + _EventCodes.Clear(); + } + + void ILastScanService.Add(EventCode eventCode, char @char) + { + lock (_EventCodes) + _EventCodes.Add((eventCode, @char)); + } + + Result ILastScanService.GetScan() + { + Result result; + char[] chars; + lock (_EventCodes) + chars = _EventCodes.Select(l => l.Char).ToArray(); + result = new() { Results = new string(chars), TotalRows = chars.Length }; + return result; + } + + int ILastScanService.GetCount() + { + int result; + lock (_EventCodes) + result = _EventCodes.Count; + return result; + } + + List<(EventCode, char)> ILastScanService.IncludeEventCodes() + { + List<(EventCode, char)> results = new() + { + (EventCode.A, 'A'), + (EventCode.B, 'B'), + (EventCode.C, 'C'), + (EventCode.D, 'D'), + (EventCode.E, 'E'), + (EventCode.F, 'F'), + (EventCode.G, 'G'), + (EventCode.H, 'H'), + (EventCode.I, 'I'), + (EventCode.J, 'J'), + (EventCode.K, 'K'), + (EventCode.L, 'L'), + (EventCode.M, 'M'), + (EventCode.N, 'N'), + (EventCode.O, 'O'), + (EventCode.P, 'P'), + (EventCode.Q, 'Q'), + (EventCode.R, 'R'), + (EventCode.S, 'S'), + (EventCode.T, 'T'), + (EventCode.U, 'U'), + (EventCode.V, 'V'), + (EventCode.W, 'W'), + (EventCode.X, 'X'), + (EventCode.Y, 'Y'), + (EventCode.Z, 'Z'), + (EventCode.Num0, '0'), + (EventCode.Num1, '1'), + (EventCode.Num2, '2'), + (EventCode.Num3, '3'), + (EventCode.Num4, '4'), + (EventCode.Num5, '5'), + (EventCode.Num6, '6'), + (EventCode.Num7, '7'), + (EventCode.Num8, '8'), + (EventCode.Num9, '9'), + (EventCode.Minus, '-'), + (EventCode.Dot, '.'), + (EventCode.Slash, '/'), + (EventCode.Space, ' '), + }; + return results; + } + +} \ No newline at end of file diff --git a/Shared/Linux/GroupManager.cs b/Server/Services/LinuxGroupManager.cs similarity index 93% rename from Shared/Linux/GroupManager.cs rename to Server/Services/LinuxGroupManager.cs index 6b87767..f3f2ecc 100644 --- a/Shared/Linux/GroupManager.cs +++ b/Server/Services/LinuxGroupManager.cs @@ -1,24 +1,23 @@ +using Barcode.Host.Shared.Models.Stateless; using CliWrap; using CliWrap.Buffered; -namespace Barcode.Host.Shared.Linux; +namespace Barcode.Host.Server.Services; public class LinuxGroupManager : ILinuxGroupManager { + public async Task IsInInputGroup() { BufferedCommandResult result = await Cli.Wrap("id") .ExecuteBufferedAsync(); - string output = result.StandardOutput; - const StringSplitOptions options = StringSplitOptions.RemoveEmptyEntries; bool inInputGroup = output.Split(new[] { ' ' }, options) .First(p => p.StartsWith("groups")) .Remove(0, "groups".Length) .Split(',', options) .Any(p => p.Contains("input")); - return inInputGroup; } @@ -26,7 +25,6 @@ public class LinuxGroupManager : ILinuxGroupManager { using CancellationTokenSource cts = new(); cts.CancelAfter(TimeSpan.FromSeconds(10)); - _ = await Cli.Wrap("bash") .WithArguments($"-c \"echo '{password}' | sudo -S gpasswd -a $USER input") .ExecuteBufferedAsync(cts.Token); @@ -36,9 +34,9 @@ public class LinuxGroupManager : ILinuxGroupManager { using CancellationTokenSource cts = new(); cts.CancelAfter(TimeSpan.FromSeconds(10)); - _ = await Cli.Wrap("bash") .WithArguments($"-c \"echo '{password}' | sudo -S reboot\"") .ExecuteBufferedAsync(cts.Token); } + } \ No newline at end of file diff --git a/Server/Services/SerialService.cs b/Server/Services/SerialService.cs new file mode 100644 index 0000000..005001a --- /dev/null +++ b/Server/Services/SerialService.cs @@ -0,0 +1,44 @@ +using Barcode.Host.Server.Models; +using Barcode.Host.Shared.Models.Stateless; +using System.Text; + +namespace Barcode.Host.Server.Services; + +public class SerialService : ISerialService +{ + + private string _LastRaw; + private readonly AppSettings _AppSettings; + private readonly System.IO.Ports.SerialPort _SerialPort; + + public SerialService(AppSettings appSettings) + { + _LastRaw = string.Empty; + _AppSettings = appSettings; + _SerialPort = new("/dev/ttyUSB0", 9600) { ReadTimeout = 2 }; + } + + void ISerialService.Open() => + _SerialPort.Open(); + + void ISerialService.Close() => + _SerialPort.Close(); + + void ISerialService.SerialPortWrite(int count, string raw) + { + if (raw != _LastRaw) + { + string message; + if (count == _AppSettings.ExpectedScanLengthA) + message = $" {raw[2..]} {DateTime.Now:h:m tt}"; + else if (count == _AppSettings.ExpectedScanLengthB) + message = $" {raw[2..]}"; + else + message = $" {raw}"; + byte[] bytes = Encoding.ASCII.GetBytes(message); + _SerialPort.Write(bytes, 0, bytes.Length); + _LastRaw = raw; + } + } + +} \ No newline at end of file diff --git a/Server/appsettings.Development.json b/Server/appsettings.Development.json index 67028c2..0f32e52 100644 --- a/Server/appsettings.Development.json +++ b/Server/appsettings.Development.json @@ -1,47 +1,13 @@ { - "ApiExportPath": "\\\\messdv002.na.infineon.com\\Candela", - "ApiUrl": "~/api", - "ConnectionString": "Data Source=MESSAD1001\\TEST1,59583;Integrated Security=True;Initial Catalog=Metrology;", - "IsDevelopment": true, - "MockRoot": "", - "MonAResource": "OI_Metrology_Viewer_IFX", - "Oi2SqlConnectionString": "Data Source=MESSAD1001\\TEST1,59583;Initial Catalog=LSL2SQL;Persist Security Info=True;User ID=srpadmin;Password=0okm9ijn;", - "Serilog": { - "Using": [ - "Serilog.Sinks.Console", - "Serilog.Sinks.File" - ], - "MinimumLevel": "Debug", - "WriteTo": [ - { - "Name": "Debug", - "Args": { - "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level}] ({SourceContext}.{MethodName}) ({InstanceId}) ({RemoteIpAddress}) {Message}{NewLine}{Exception}" - } - }, - { - "Name": "Console", - "Args": { - "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level}] ({SourceContext}.{MethodName}) ({InstanceId}) ({RemoteIpAddress}) {Message}{NewLine}{Exception}" - } - }, - { - "Name": "File", - "Args": { - "path": "%workingDirectory% - Log/log-.txt", - "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level}] ({SourceContext}.{MethodName}) ({InstanceId}) ({RemoteIpAddress}) {Message}{NewLine}{Exception}", - "rollingInterval": "Hour" - } - } - ], - "Enrich": [ - "FromLogContext", - "WithMachineName", - "WithThreadId" - ], - "Properties": { - "Application": "Sample" + "ExpectedScanLengthA": 6, + "ExpectedScanLengthB": 9, + "Logging": { + "LogLevel": { + "Log4netProvider": "Debug" } }, - "URLs": "https://localhost:7130;http://localhost:5126" + "IsDevelopment": true, + "Serilog": { + "MinimumLevel": "Debug" + } } \ No newline at end of file diff --git a/Server/appsettings.json b/Server/appsettings.json index 7d2ecc8..e5530af 100644 --- a/Server/appsettings.json +++ b/Server/appsettings.json @@ -1,31 +1,26 @@ { - "AllowedHosts": "*", - "ApiExportPath": "\\\\messv02ecc1.ec.local\\EC_Metrology_Si", - "ApiLoggingContentTypes": "application/json", - "ApiLoggingPathPrefixes": "/api/inbound", - "ApiUrl": "~/api", - "ApiLogPath": "D:\\Metrology\\MetrologyAPILogs", - "AttachmentPath": "\\\\messv02ecc1.ec.local\\EC_Metrology_Si\\MetrologyAttachments", "BuildNumber": "1", "Company": "Infineon Technologies Americas Corp.", - "ConnectionString": "Data Source=messv01ec.ec.local\\PROD1,53959;Integrated Security=True;Initial Catalog=Metrology;", + "DeviceNameEndsWith": "Symbol Bar Code Scanner", + "ExpectedScanLengthA": 8, + "ExpectedScanLengthB": 14, + "ExpectedScanLengthBExample": "1TO172125.1.11", + "ClearLastScanServiceAfter": 250, "GitCommitSeven": "1234567", + "LinuxDevicePath": "/proc/bus/input/devices", "Logging": { "LogLevel": { "Default": "Information", "Microsoft": "Warning", - "Log4netProvider": "Debug", + "Log4netProvider": "Information", "Microsoft.Hosting.Lifetime": "Information" } }, - "InboundApiAllowedIPList": "", "IsDevelopment": false, "IsStaging": false, "MockRoot": "", "MonAResource": "OI_Metrology_Viewer_EC", "MonASite": "auc", - "Oi2SqlConnectionString": "Data Source=messv01ec.ec.local\\PROD1,53959;Initial Catalog=LSL2SQL;Persist Security Info=True;User ID=srpadmin;Password=0okm9ijn;", - "OIExportPath": "\\\\openinsight-db-srv.na.infineon.com\\apps\\Metrology\\Data", "Serilog": { "Using": [ "Serilog.Sinks.Console", @@ -63,6 +58,8 @@ "Application": "Sample" } }, - "URLs": "http://localhost:5002;", - "WorkingDirectoryName": "IFXApps" + "RootPassword": "", + "URLs": "http://localhost:5003;", + "WorkingDirectoryName": "IFXApps", + "WriteToSerialEvery": 750 } \ No newline at end of file diff --git a/Shared/Barcode.Host.Shared.csproj b/Shared/Barcode.Host.Shared.csproj index 1c87be8..a85ad23 100644 --- a/Shared/Barcode.Host.Shared.csproj +++ b/Shared/Barcode.Host.Shared.csproj @@ -1,8 +1,9 @@ - net7.0 enable enable + win-x64;linux-x64 + net7.0 true @@ -19,7 +20,6 @@ Linux - diff --git a/Shared/DataModels/Result.cs b/Shared/DataModels/Result.cs new file mode 100644 index 0000000..f72e04e --- /dev/null +++ b/Shared/DataModels/Result.cs @@ -0,0 +1,7 @@ +namespace Barcode.Host.Shared.DataModels; + +public class Result +{ + public T? Results { get; set; } + public long TotalRows { get; set; } +} \ No newline at end of file diff --git a/Shared/KeyboardMouse/Abstract/IAggregateInputReader.cs b/Shared/KeyboardMouse/Abstract/IAggregateInputReader.cs deleted file mode 100644 index cf20ae6..0000000 --- a/Shared/KeyboardMouse/Abstract/IAggregateInputReader.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Barcode.Host.Shared.KeyboardMouse.Abstract; - -public interface IAggregateInputReader -{ - event InputReader.RaiseKeyPress OnKeyPress; -} \ No newline at end of file diff --git a/Shared/KeyboardMouse/AggregateInputReader.cs b/Shared/KeyboardMouse/AggregateInputReader.cs deleted file mode 100644 index 204c863..0000000 --- a/Shared/KeyboardMouse/AggregateInputReader.cs +++ /dev/null @@ -1,72 +0,0 @@ -using Barcode.Host.Shared.KeyboardMouse.Abstract; -using Microsoft.Extensions.Logging; - -namespace Barcode.Host.Shared.KeyboardMouse; - -public class AggregateInputReader : IDisposable, IAggregateInputReader -{ - private readonly ILogger _InputReaderLogger; - - private Dictionary? _Readers = new(); - - public event InputReader.RaiseKeyPress? OnKeyPress; - - public AggregateInputReader(ILogger inputReaderLogger) - { - _InputReaderLogger = inputReaderLogger; - - System.Timers.Timer timer = new() - { - Interval = 10 * 1000, - Enabled = true - }; - timer.Elapsed += (_, _) => Scan(); - timer.Start(); - } - - private void ReaderOnOnKeyPress(KeyPressEvent e) => OnKeyPress?.Invoke(e); - - private void Scan() - { - string[] files = Directory.GetFiles("/dev/input/", "event*"); - - foreach (string file in files) - { - if (_Readers is not null && _Readers.ContainsKey(file)) - { - continue; - } - - InputReader reader = new(file, _InputReaderLogger); - - reader.OnKeyPress += ReaderOnOnKeyPress; - - _Readers?.Add(file, reader); - } - - IEnumerable? deadReaders = _Readers?.Values.Where(r => r.Faulted); - if (deadReaders is not null) - { - foreach (InputReader? dr in deadReaders) - { - _ = _Readers?.Remove(dr.Path); - dr.OnKeyPress -= ReaderOnOnKeyPress; - dr.Dispose(); - } - } - } - - public void Dispose() - { - if (_Readers is not null) - { - foreach (InputReader d in _Readers.Values) - { - d.OnKeyPress -= ReaderOnOnKeyPress; - d.Dispose(); - } - } - - _Readers = null; - } -} \ No newline at end of file diff --git a/Shared/KeyboardMouse/DeviceReader.cs b/Shared/KeyboardMouse/DeviceReader.cs index 8f97752..04b5a60 100644 --- a/Shared/KeyboardMouse/DeviceReader.cs +++ b/Shared/KeyboardMouse/DeviceReader.cs @@ -2,66 +2,52 @@ namespace Barcode.Host.Shared.KeyboardMouse; public static class DeviceReader { - public static IEnumerable Get(string path = "/proc/bus/input/devices") + + public static IEnumerable Get(string path) { - List devices = new(); - - using FileStream filestream = new(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); - using StreamReader reader = new(filestream); - LinuxDevice linuxDevice = new(); - while (!reader.EndOfStream) + List linuxDevices = new(); + using FileStream filestream = new(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + using StreamReader streamReader = new(filestream); + while (!streamReader.EndOfStream) { - string? line = reader.ReadLine(); - + string? line = streamReader.ReadLine(); if (string.IsNullOrWhiteSpace(line)) { if (!string.IsNullOrWhiteSpace(linuxDevice.Name)) { - devices.Add(linuxDevice); + linuxDevices.Add(linuxDevice); linuxDevice = new LinuxDevice(); } - continue; } - if (line.StartsWith("I")) ApplyIdentifier(line, linuxDevice); - else if (line.StartsWith("N")) linuxDevice.Name = line.Substring(9, line.Length - 9 - 1); - else if (line.StartsWith("P")) linuxDevice.PhysicalPath = line[8..]; - else if (line.StartsWith("S")) linuxDevice.SysFsPath = line[9..]; - else if (line.StartsWith("U")) linuxDevice.UniqueIdentificationCode = line[8..]; - else if (line.StartsWith("H")) linuxDevice.Handlers = line[12..] .Split(" ") .Where(h => !string.IsNullOrWhiteSpace(h)) .ToList(); - else if (line.StartsWith("B")) linuxDevice.Bitmaps.Add(line[3..]); } - - return devices; + return linuxDevices; } private static void ApplyIdentifier(string line, LinuxDevice linuxDevice) { - string[] values = line[3..] - .Split(" "); - + string[] values = line[3..].Split(" "); foreach (string v in values) { string[] kvp = v.Split("="); - switch (kvp[0]) { case "Bus": @@ -79,4 +65,5 @@ public static class DeviceReader } } } + } \ No newline at end of file diff --git a/Shared/KeyboardMouse/InputReader.cs b/Shared/KeyboardMouse/InputReader.cs index 02b475a..150dcfe 100644 --- a/Shared/KeyboardMouse/InputReader.cs +++ b/Shared/KeyboardMouse/InputReader.cs @@ -4,59 +4,42 @@ namespace Barcode.Host.Shared.KeyboardMouse; public class InputReader : IDisposable { - private readonly ILogger _Logger; + + private long _Ticks; + private bool _Disposing; + private readonly int _PiOffset; private const int _BufferLength = 24; - - private static readonly int _PiOffset; - + private readonly FileStream? _FileStream; + private readonly ILogger _Logger; private readonly byte[] _Buffer = new byte[_BufferLength]; - private FileStream? _Stream; - private bool _Disposing; - - public delegate void RaiseKeyPress(KeyPressEvent e); - - public delegate void RaiseMouseMove(MouseMoveEvent e); - - public event RaiseKeyPress? OnKeyPress; - - public event RaiseMouseMove? OnMouseMove; - - public string Path { get; } - + public string Path { get; init; } public bool Faulted { get; private set; } - static InputReader() - { - if (RunningOnRaspberryPi()) - { - _PiOffset = -8; - } - } + public event RaiseKeyPress? OnKeyPress; + public event RaiseMouseMove? OnMouseMove; - public InputReader( - string path, - ILogger logger) - { - _Logger = logger; + public delegate void RaiseKeyPress(KeyPressEvent e); + public delegate void RaiseMouseMove(MouseMoveEvent e); + public InputReader(string path, ILogger logger) + { Path = path; - + _Logger = logger; + if (RunningOnRaspberryPi()) + _PiOffset = -8; try - { - _Stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); - } + { _FileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); } catch (UnauthorizedAccessException ex) { - _Logger.LogError(ex, "Current user doesn't have permissions to access input data. Add user to input group to correct this error"); + logger.LogError(ex, "Current user doesn't have permissions to access input data. Add user to input group to correct this error"); Faulted = true; } catch (IOException ex) { - _Logger.LogWarning(ex, $"Error occurred while trying to build stream for {path}"); + logger.LogWarning(ex, $"Error occurred while trying to build stream for {path}"); Faulted = true; } - _ = Task.Run(Run); } @@ -66,33 +49,27 @@ public class InputReader : IDisposable { if (_Disposing) break; - try { - if (!Faulted && _Stream is not null) - { - _ = _Stream.Read(_Buffer, 0, _BufferLength); - } + if (!Faulted && _FileStream is not null) + _ = _FileStream.Read(_Buffer, 0, _BufferLength); } catch (IOException ex) { _Logger.LogInformation(ex, $"Error occured while trying to read from the stream for {Path}"); Faulted = true; } - EventType type = GetEventType(); - short code = GetCode(); - int value = GetValue(); - switch (type) { + case EventType.EV_SYN: + _Ticks = DateTime.Now.Ticks; + break; case EventType.EV_KEY: - HandleKeyPressEvent(code, value); + HandleKeyPressEvent(); break; case EventType.EV_REL: - MouseAxis axis = (MouseAxis)code; - MouseMoveEvent e = new(axis, value); - OnMouseMove?.Invoke(e); + HandleMouseMoveEvent(); break; } } @@ -107,9 +84,7 @@ public class InputReader : IDisposable _Buffer[22 + _PiOffset], _Buffer[23 + _PiOffset] }; - int value = BitConverter.ToInt32(valueBits, 0); - return value; } @@ -120,9 +95,7 @@ public class InputReader : IDisposable _Buffer[18 + _PiOffset], _Buffer[19 + _PiOffset] }; - short code = BitConverter.ToInt16(codeBits, 0); - return code; } @@ -133,27 +106,35 @@ public class InputReader : IDisposable _Buffer[16 + _PiOffset], _Buffer[17 + _PiOffset] }; - short type = BitConverter.ToInt16(typeBits, 0); - EventType eventType = (EventType)type; - return eventType; } - private void HandleKeyPressEvent(short code, int value) + private void HandleKeyPressEvent() { - EventCode c = (EventCode)code; - KeyState s = (KeyState)value; - KeyPressEvent e = new(c, s); - OnKeyPress?.Invoke(e); + short code = GetCode(); + int value = GetValue(); + KeyState keyState = (KeyState)value; + EventCode eventCode = (EventCode)code; + KeyPressEvent keyPressEvent = new(eventCode, keyState, new TimeSpan(DateTime.Now.Ticks - _Ticks)); + OnKeyPress?.Invoke(keyPressEvent); + } + + private void HandleMouseMoveEvent() + { + short code = GetCode(); + int value = GetValue(); + MouseAxis axis = (MouseAxis)code; + MouseMoveEvent e = new(axis, value); + OnMouseMove?.Invoke(e); } public void Dispose() { _Disposing = true; - _Stream?.Dispose(); - _Stream = null; + _FileStream?.Dispose(); + GC.SuppressFinalize(this); } private static bool RunningOnRaspberryPi() @@ -163,4 +144,5 @@ public class InputReader : IDisposable bool runningOnPi = text.Any(l => l.Contains("Raspberry Pi")); return runningOnPi; } + } \ No newline at end of file diff --git a/Shared/KeyboardMouse/KeyPressEvent.cs b/Shared/KeyboardMouse/KeyPressEvent.cs index b0303fa..16b61f5 100644 --- a/Shared/KeyboardMouse/KeyPressEvent.cs +++ b/Shared/KeyboardMouse/KeyPressEvent.cs @@ -2,13 +2,16 @@ namespace Barcode.Host.Shared.KeyboardMouse; public readonly struct KeyPressEvent { - public KeyPressEvent(EventCode code, KeyState state) + + public EventCode EventCode { get; init; } + public KeyState KeyState { get; init; } + public TimeSpan TimeSpan { get; init; } + + public KeyPressEvent(EventCode eventCode, KeyState keyState, TimeSpan timeSpan) { - Code = code; - State = state; + EventCode = eventCode; + KeyState = keyState; + TimeSpan = timeSpan; } - public EventCode Code { get; } - - public KeyState State { get; } } \ No newline at end of file diff --git a/Shared/KeyboardMouse/LinuxDevice.cs b/Shared/KeyboardMouse/LinuxDevice.cs index c6321a7..7895abc 100644 --- a/Shared/KeyboardMouse/LinuxDevice.cs +++ b/Shared/KeyboardMouse/LinuxDevice.cs @@ -2,17 +2,14 @@ namespace Barcode.Host.Shared.KeyboardMouse; public class LinuxDevice { + + public List Bitmaps { get; set; } = new(); + public List Handlers { get; set; } = new(); public LinuxDeviceIdentifier Identifier { get; set; } = new(); public string? Name { get; set; } - public string? PhysicalPath { get; set; } - public string? SysFsPath { get; set; } - public string? UniqueIdentificationCode { get; set; } - public List Handlers { get; set; } = new(); - - public List Bitmaps { get; set; } = new(); } \ No newline at end of file diff --git a/Shared/KeyboardMouse/LinuxDeviceIdentifier.cs b/Shared/KeyboardMouse/LinuxDeviceIdentifier.cs index 3adafb3..0e189c8 100644 --- a/Shared/KeyboardMouse/LinuxDeviceIdentifier.cs +++ b/Shared/KeyboardMouse/LinuxDeviceIdentifier.cs @@ -2,11 +2,10 @@ namespace Barcode.Host.Shared.KeyboardMouse; public class LinuxDeviceIdentifier { + public string? Bus { get; set; } - - public string? Vendor { get; set; } - public string? Product { get; set; } - + public string? Vendor { get; set; } public string? Version { get; set; } + } \ No newline at end of file diff --git a/Shared/KeyboardMouse/MouseMoveEvent.cs b/Shared/KeyboardMouse/MouseMoveEvent.cs index 789b409..43a2762 100644 --- a/Shared/KeyboardMouse/MouseMoveEvent.cs +++ b/Shared/KeyboardMouse/MouseMoveEvent.cs @@ -2,13 +2,14 @@ namespace Barcode.Host.Shared.KeyboardMouse; public readonly struct MouseMoveEvent { + + public int Amount { get; } + public MouseAxis Axis { get; } + public MouseMoveEvent(MouseAxis axis, int amount) { Axis = axis; Amount = amount; } - public MouseAxis Axis { get; } - - public int Amount { get; } } \ No newline at end of file diff --git a/Shared/Models/Stateless/IAggregateInputReader.cs b/Shared/Models/Stateless/IAggregateInputReader.cs new file mode 100644 index 0000000..2855dcc --- /dev/null +++ b/Shared/Models/Stateless/IAggregateInputReader.cs @@ -0,0 +1,10 @@ +using Barcode.Host.Shared.KeyboardMouse; + +namespace Barcode.Host.Shared.Models.Stateless; + +public interface IAggregateInputReader +{ + + event InputReader.RaiseKeyPress OnKeyPress; + +} \ No newline at end of file diff --git a/Shared/Models/Stateless/ILastScanController.cs b/Shared/Models/Stateless/ILastScanController.cs new file mode 100644 index 0000000..9e945e5 --- /dev/null +++ b/Shared/Models/Stateless/ILastScanController.cs @@ -0,0 +1,14 @@ +namespace Barcode.Host.Shared.Models.Stateless; + +public interface ILastScanController +{ + + enum Action : int + { + Get = 0 + } + + static string GetRouteName() => nameof(ILastScanController)[1..^10]; + T GetScan(); + +} \ No newline at end of file diff --git a/Shared/Models/Stateless/ILastScanService.cs b/Shared/Models/Stateless/ILastScanService.cs new file mode 100644 index 0000000..a9434bd --- /dev/null +++ b/Shared/Models/Stateless/ILastScanService.cs @@ -0,0 +1,15 @@ +using Barcode.Host.Shared.DataModels; +using Barcode.Host.Shared.KeyboardMouse; + +namespace Barcode.Host.Shared.Models.Stateless; + +public interface ILastScanService +{ + + void Clear(); + int GetCount(); + Result GetScan(); + void Add(EventCode eventCode, char @char); + List<(EventCode, char)> IncludeEventCodes(); + +} \ No newline at end of file diff --git a/Shared/Linux/IGroupManager.cs b/Shared/Models/Stateless/ILinuxGroupManager.cs similarity index 77% rename from Shared/Linux/IGroupManager.cs rename to Shared/Models/Stateless/ILinuxGroupManager.cs index b38620a..da12ebd 100644 --- a/Shared/Linux/IGroupManager.cs +++ b/Shared/Models/Stateless/ILinuxGroupManager.cs @@ -1,10 +1,10 @@ -namespace Barcode.Host.Shared.Linux; +namespace Barcode.Host.Shared.Models.Stateless; public interface ILinuxGroupManager { - Task IsInInputGroup(); + Task IsInInputGroup(); + Task RebootSystem(string password); Task AddUserToInputGroup(string password); - Task RebootSystem(string password); } \ No newline at end of file diff --git a/Shared/Models/Stateless/ISerialService.cs b/Shared/Models/Stateless/ISerialService.cs new file mode 100644 index 0000000..fa59dcc --- /dev/null +++ b/Shared/Models/Stateless/ISerialService.cs @@ -0,0 +1,10 @@ +namespace Barcode.Host.Shared.Models.Stateless; + +public interface ISerialService +{ + + void Open(); + void Close(); + void SerialPortWrite(int count, string raw); + +} \ No newline at end of file diff --git a/Shared/Models/WorkingDirectory.cs b/Shared/Models/WorkingDirectory.cs index 287358a..16b49db 100644 --- a/Shared/Models/WorkingDirectory.cs +++ b/Shared/Models/WorkingDirectory.cs @@ -32,7 +32,7 @@ internal abstract class WorkingDirectory { if (!Directory.Exists(result)) _ = Directory.CreateDirectory(result); - traceFile = string.Concat(result, @"\", DateTime.Now.Ticks, ".txt"); + traceFile = Path.Combine(result, $"{DateTime.Now.Ticks}.txt"); File.WriteAllText(traceFile, traceFile); File.Delete(traceFile); break;