diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c082fd5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +# vscode +.vscode +.history + +# .net +obj/ +bin/ +temp/ + +# visual studio +*.user +.vs \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..c9f7965 --- /dev/null +++ b/README.md @@ -0,0 +1,52 @@ +# Scan Server + +Scan server is a Free to use alternative to many expensive solutions out there that allow scanning documents from Web applications. + +## Project Structure + +There are two projects: +1. ScanServer: Compiles into a .exe that can be run a standalone app or as a service +2. ScanServerInstaller: Create an MSI to make easier to download and install as Windows Service (Runs by default on http://localhost:5000). + +## How to use + +Just run/install the app. +List the devices on: http://localhost:5000/api/scanners + + +## Getting started + +To build this project: + +```ps +cd .\ScanServer +rm -r .\bin\ +dotnet clean +dotnet publish ScanServer.csproj -c Release +cd ..\ScanServerInstaller +rm -r .\bin\ +dotnet clean +dotnet build -c Release +``` + + +## Uninstall: + +To uninstall start a command line as administrator and run: + +```cmd +sc stop ScanServer +sc delete ScanServer +``` + +Then delete the install folder. + +# Naps2 + +A huge thank you to @cyanfish and it's [NAPS2 SDK Project](https://github.com/cyanfish/naps2/) without whom/which building this would have taken a lot of time. +If you want to donate, please donate to him using this [link](https://www.naps2.com/donate?src=scan-server) + +# TODO +Find a way to change the default port in the service either: +1. by configuring the service in the installer +2. Building the app with another default port while allowing --urls to work. \ No newline at end of file diff --git a/ScanServer.sln b/ScanServer.sln new file mode 100644 index 0000000..d0771b4 --- /dev/null +++ b/ScanServer.sln @@ -0,0 +1,61 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ScanServer", "ScanServer\ScanServer.csproj", "{E8BAF2B7-B5BC-4C85-AC30-F744962C7A51}" +EndProject +Project("{B7DD6F7E-DEF8-4E67-B5B7-07EF123DB6F0}") = "ScanServerInstaller", "ScanServerInstaller\ScanServerInstaller.wixproj", "{ABE1D0C9-7C80-4C87-A3F5-00387BB43583}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|ARM64 = Debug|ARM64 + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|ARM64 = Release|ARM64 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {E8BAF2B7-B5BC-4C85-AC30-F744962C7A51}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E8BAF2B7-B5BC-4C85-AC30-F744962C7A51}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E8BAF2B7-B5BC-4C85-AC30-F744962C7A51}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {E8BAF2B7-B5BC-4C85-AC30-F744962C7A51}.Debug|ARM64.Build.0 = Debug|Any CPU + {E8BAF2B7-B5BC-4C85-AC30-F744962C7A51}.Debug|x64.ActiveCfg = Debug|Any CPU + {E8BAF2B7-B5BC-4C85-AC30-F744962C7A51}.Debug|x64.Build.0 = Debug|Any CPU + {E8BAF2B7-B5BC-4C85-AC30-F744962C7A51}.Debug|x86.ActiveCfg = Debug|Any CPU + {E8BAF2B7-B5BC-4C85-AC30-F744962C7A51}.Debug|x86.Build.0 = Debug|Any CPU + {E8BAF2B7-B5BC-4C85-AC30-F744962C7A51}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E8BAF2B7-B5BC-4C85-AC30-F744962C7A51}.Release|Any CPU.Build.0 = Release|Any CPU + {E8BAF2B7-B5BC-4C85-AC30-F744962C7A51}.Release|ARM64.ActiveCfg = Release|Any CPU + {E8BAF2B7-B5BC-4C85-AC30-F744962C7A51}.Release|ARM64.Build.0 = Release|Any CPU + {E8BAF2B7-B5BC-4C85-AC30-F744962C7A51}.Release|x64.ActiveCfg = Release|Any CPU + {E8BAF2B7-B5BC-4C85-AC30-F744962C7A51}.Release|x64.Build.0 = Release|Any CPU + {E8BAF2B7-B5BC-4C85-AC30-F744962C7A51}.Release|x86.ActiveCfg = Release|Any CPU + {E8BAF2B7-B5BC-4C85-AC30-F744962C7A51}.Release|x86.Build.0 = Release|Any CPU + {ABE1D0C9-7C80-4C87-A3F5-00387BB43583}.Debug|Any CPU.ActiveCfg = Debug|x64 + {ABE1D0C9-7C80-4C87-A3F5-00387BB43583}.Debug|Any CPU.Build.0 = Debug|x64 + {ABE1D0C9-7C80-4C87-A3F5-00387BB43583}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {ABE1D0C9-7C80-4C87-A3F5-00387BB43583}.Debug|ARM64.Build.0 = Debug|ARM64 + {ABE1D0C9-7C80-4C87-A3F5-00387BB43583}.Debug|x64.ActiveCfg = Debug|x64 + {ABE1D0C9-7C80-4C87-A3F5-00387BB43583}.Debug|x64.Build.0 = Debug|x64 + {ABE1D0C9-7C80-4C87-A3F5-00387BB43583}.Debug|x86.ActiveCfg = Debug|x86 + {ABE1D0C9-7C80-4C87-A3F5-00387BB43583}.Debug|x86.Build.0 = Debug|x86 + {ABE1D0C9-7C80-4C87-A3F5-00387BB43583}.Release|Any CPU.ActiveCfg = Release|x64 + {ABE1D0C9-7C80-4C87-A3F5-00387BB43583}.Release|Any CPU.Build.0 = Release|x64 + {ABE1D0C9-7C80-4C87-A3F5-00387BB43583}.Release|ARM64.ActiveCfg = Release|ARM64 + {ABE1D0C9-7C80-4C87-A3F5-00387BB43583}.Release|ARM64.Build.0 = Release|ARM64 + {ABE1D0C9-7C80-4C87-A3F5-00387BB43583}.Release|x64.ActiveCfg = Release|x64 + {ABE1D0C9-7C80-4C87-A3F5-00387BB43583}.Release|x64.Build.0 = Release|x64 + {ABE1D0C9-7C80-4C87-A3F5-00387BB43583}.Release|x86.ActiveCfg = Release|x86 + {ABE1D0C9-7C80-4C87-A3F5-00387BB43583}.Release|x86.Build.0 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {8B68A425-1030-4625-ABA9-A35B7C87464F} + EndGlobalSection +EndGlobal diff --git a/ScanServer/Controllers/ScannerController.cs b/ScanServer/Controllers/ScannerController.cs new file mode 100644 index 0000000..128eeb3 --- /dev/null +++ b/ScanServer/Controllers/ScannerController.cs @@ -0,0 +1,64 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using NAPS2.Scan; +using NAPS2.Images; +using System.Drawing; +using NAPS2.Images.Gdi; +using System.IO; + +namespace ScanServer.Controllers +{ + + [ApiController] + [Route("api")] + public class ScannerController : ControllerBase + { + + private readonly ILogger _logger; + + private readonly ScanController _scanController; + + public ScannerController(ILogger logger) + { + this._logger = logger; + using ScanningContext scanningContext = new ScanningContext(new GdiImageContext()); + scanningContext.SetUpWin32Worker(); + _scanController = new ScanController(scanningContext); + } + + [HttpGet("scanners")] + public async Task>> ListDevices() + { + _logger.LogDebug($"{nameof(ListDevices)}"); + // TODO based on env Var change driver + // TODO set 32 or 64 based on OS + //var devices = await _scanController.GetDeviceList(new ScanOptions { Driver = Driver.Twain, TwainOptions = { Dsm = TwainDsm.Old, Adapter = TwainAdapter.Legacy } }); + var devices = await _scanController.GetDeviceList(Driver.Twain); + return Ok(devices); + } + + [HttpGet("scan/{deviceId}")] + public async Task Scan([FromRoute] string deviceId) + { + _logger.LogDebug($"{nameof(Scan)}"); + var devices = await _scanController.GetDeviceList(Driver.Twain); + ScanDevice device = devices.Find(d => d.ID == deviceId); + if (device == null) + { + return NotFound(); + } + var scannedImages = _scanController.Scan(new ScanOptions { Device = device, Driver = Driver.Twain, Dpi = 200 }).ToBlockingEnumerable().ToList(); + if (scannedImages.Count == 0) + { + return NotFound(); + } + return processedImageToFile(scannedImages[0]); + } + + private FileContentResult processedImageToFile(ProcessedImage processedImage) + { + var stream = processedImage.Render().SaveToMemoryStream(ImageFileFormat.Jpeg); + return File(stream.ToArray(), "image/jpeg"); + } + } +} diff --git a/ScanServer/Program.cs b/ScanServer/Program.cs new file mode 100644 index 0000000..38494f5 --- /dev/null +++ b/ScanServer/Program.cs @@ -0,0 +1,70 @@ +using Microsoft.Extensions.Hosting.WindowsServices; +using ScanServer.Controllers; +using System.Diagnostics; + +ILogger _logger; + +var options = new WebApplicationOptions +{ + Args = args, + // Setting to allow the service to run both in IDE and as service + ContentRootPath = WindowsServiceHelpers.IsWindowsService() + ? AppContext.BaseDirectory + : default +}; +var builder = WebApplication.CreateBuilder(options); +var isService = !(Debugger.IsAttached || args.Contains("--console")); +if (isService) +{ + builder.Host.UseWindowsService(); +} + +// Add services to the container. + +builder.Services.AddControllers(); +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +var allowedOrigins = args.SkipWhile(arg => arg != "--origins") + .Skip(1) // Skip the "--origins" argument itself + .ToList(); +if (allowedOrigins.Count == 0) +{ + allowedOrigins.Add("*"); // Set to wildcard if no origins specified +} + +builder.Services.AddCors(options => +{ + options.AddDefaultPolicy(builder => + { + builder.WithOrigins(allowedOrigins.ToArray()) + .AllowAnyMethod() + .AllowAnyHeader(); + }); +}); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.MapGet("/", () => +{ + return "Scan Server is Running with:" + "\nargs: " + String.Join(' ', args) + "\nContentRootPath: "+ options.ContentRootPath + "\nService: " + isService; +}); + +app.MapControllers(); +app.UseCors(); + +if (isService) +{ + await app.RunAsync(); +} else +{ + app.Run(); +} diff --git a/ScanServer/Properties/launchSettings.json b/ScanServer/Properties/launchSettings.json new file mode 100644 index 0000000..a0cd669 --- /dev/null +++ b/ScanServer/Properties/launchSettings.json @@ -0,0 +1,41 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:45941", + "sslPort": 44309 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5107", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7058;http://localhost:5107", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/ScanServer/ScanServer.csproj b/ScanServer/ScanServer.csproj new file mode 100644 index 0000000..9a404a2 --- /dev/null +++ b/ScanServer/ScanServer.csproj @@ -0,0 +1,26 @@ + + + + net7.0 + Exe + true + true + true + win-x64 + enable + enable + + + + + + + + + + + + + + + diff --git a/ScanServer/appsettings.Development.json b/ScanServer/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/ScanServer/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/ScanServer/appsettings.json b/ScanServer/appsettings.json new file mode 100644 index 0000000..10f68b8 --- /dev/null +++ b/ScanServer/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/ScanServerInstaller/Folders.wxs b/ScanServerInstaller/Folders.wxs new file mode 100644 index 0000000..cd81279 --- /dev/null +++ b/ScanServerInstaller/Folders.wxs @@ -0,0 +1,7 @@ + + + + + + + diff --git a/ScanServerInstaller/Package.en-us.wxl b/ScanServerInstaller/Package.en-us.wxl new file mode 100644 index 0000000..7fa02fa --- /dev/null +++ b/ScanServerInstaller/Package.en-us.wxl @@ -0,0 +1,8 @@ + + + + + + diff --git a/ScanServerInstaller/Package.wxs b/ScanServerInstaller/Package.wxs new file mode 100644 index 0000000..8cd4e68 --- /dev/null +++ b/ScanServerInstaller/Package.wxs @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/ScanServerInstaller/ScanServer.wxs b/ScanServerInstaller/ScanServer.wxs new file mode 100644 index 0000000..c924ded --- /dev/null +++ b/ScanServerInstaller/ScanServer.wxs @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + diff --git a/ScanServerInstaller/ScanServerInstaller.wixproj b/ScanServerInstaller/ScanServerInstaller.wixproj new file mode 100644 index 0000000..6572b3b --- /dev/null +++ b/ScanServerInstaller/ScanServerInstaller.wixproj @@ -0,0 +1,2 @@ + + \ No newline at end of file