complete overhaul of the whole project structure including optimizations and bug fixes
159
main.go
|
@ -2,162 +2,19 @@ package main
|
|||
|
||||
import (
|
||||
"embed"
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"velvettear/go-scan/config"
|
||||
"velvettear/go-scan/log"
|
||||
"velvettear/go-scan/util/date"
|
||||
"velvettear/go-scan/src/config"
|
||||
"velvettear/go-scan/src/util/scans"
|
||||
"velvettear/go-scan/src/web"
|
||||
)
|
||||
|
||||
//go:embed resources
|
||||
var resources embed.FS
|
||||
//go:embed static
|
||||
var files embed.FS
|
||||
|
||||
var configuration config.Config
|
||||
|
||||
func main() {
|
||||
configuration = config.New()
|
||||
startServer()
|
||||
}
|
||||
|
||||
func startServer() {
|
||||
serverAddress := configuration.ServerConfig.Address + ":" + configuration.ServerConfig.Port
|
||||
http.HandleFunc("/favicon.ico", serveResources)
|
||||
http.HandleFunc("/resources/", serveResources)
|
||||
http.HandleFunc("/", handleHTTPRequests)
|
||||
log.Info("starting server '" + serverAddress + "'...")
|
||||
error := http.ListenAndServe(serverAddress, nil)
|
||||
if error != nil {
|
||||
log.Fatal("an error occured starting the server", error.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func serveResources(writer http.ResponseWriter, request *http.Request) {
|
||||
var resource string
|
||||
if strings.HasSuffix(request.URL.Path, "favicon.ico") {
|
||||
resource = "resources/gopher.png"
|
||||
} else {
|
||||
resource = strings.TrimPrefix(request.URL.Path, "/")
|
||||
}
|
||||
bytes, error := resources.ReadFile(resource)
|
||||
if error != nil {
|
||||
log.Error("an error occured serving the resource '"+resource+"'", error.Error())
|
||||
return
|
||||
}
|
||||
log.Debug("serving '" + resource + "'...")
|
||||
if strings.HasSuffix(resource, "svg") {
|
||||
writer.Header().Set("Content-Type", "image/svg+xml")
|
||||
}
|
||||
writer.Write(bytes)
|
||||
}
|
||||
|
||||
func handleHTTPRequests(writer http.ResponseWriter, request *http.Request) {
|
||||
if strings.ToLower(request.Method) == "post" {
|
||||
result := handleScan(request)
|
||||
writer.Header().Set("Content-Type", "application/json")
|
||||
json, _ := json.Marshal(result)
|
||||
writer.Write(json)
|
||||
return
|
||||
}
|
||||
resource := "resources/index.html"
|
||||
bytes, error := resources.ReadFile(resource)
|
||||
if error != nil {
|
||||
log.Error("an error occured serving the resource '"+resource+"'", error.Error())
|
||||
return
|
||||
}
|
||||
log.Debug("serving '" + resource + "'...")
|
||||
writer.Write(bytes)
|
||||
}
|
||||
|
||||
func handleScan(request *http.Request) *result {
|
||||
log.Info("starting scan...")
|
||||
timestamp := date.Milliseconds()
|
||||
|
||||
var parameters requestParameters
|
||||
parseErr := json.NewDecoder(request.Body).Decode(¶meters)
|
||||
if parseErr != nil {
|
||||
log.Error("error parsing request parameters", parseErr.Error())
|
||||
}
|
||||
scanName := generateScanName(parameters.Filename)
|
||||
args := []string{}
|
||||
if len(configuration.ScannerConfig.Scanner) > 0 {
|
||||
args = append(args, "-d")
|
||||
args = append(args, configuration.ScannerConfig.Scanner)
|
||||
}
|
||||
args = append(args, "-o")
|
||||
args = append(args, scanName)
|
||||
extraArgs := strings.Split(configuration.ScannerConfig.Arguments, " ")
|
||||
for index := 0; index < len(extraArgs); index++ {
|
||||
args = append(args, extraArgs[index])
|
||||
}
|
||||
log.Debug("executing command 'scanimage'...", args...)
|
||||
scanCmd := exec.Command(
|
||||
"scanimage",
|
||||
args...,
|
||||
)
|
||||
_, err := scanCmd.Output()
|
||||
if err != nil {
|
||||
log.Error("an error occured executing the scan", err.Error())
|
||||
result := &result{
|
||||
State: "error",
|
||||
Message: err.Error(),
|
||||
}
|
||||
return result
|
||||
}
|
||||
result := &result{
|
||||
State: "ok",
|
||||
Message: scanName,
|
||||
}
|
||||
log.Info("finished scan after "+strconv.Itoa(date.GetTimeDifference(timestamp))+"ms", scanName)
|
||||
return result
|
||||
}
|
||||
|
||||
func generateScanName(filename string) string {
|
||||
if len(filename) == 0 {
|
||||
day := fmt.Sprint(time.Now().Day())
|
||||
if len(day) < 2 {
|
||||
day = "0" + day
|
||||
}
|
||||
month := fmt.Sprint(int(time.Now().Month()))
|
||||
if len(month) < 2 {
|
||||
month = "0" + month
|
||||
}
|
||||
hour := fmt.Sprint(time.Now().Hour())
|
||||
if len(hour) < 2 {
|
||||
hour = "0" + hour
|
||||
}
|
||||
minute := fmt.Sprint(time.Now().Minute())
|
||||
if len(minute) < 2 {
|
||||
minute = "0" + minute
|
||||
}
|
||||
second := fmt.Sprint(time.Now().Second())
|
||||
if len(second) < 2 {
|
||||
second = "0" + second
|
||||
}
|
||||
filename = day + month + fmt.Sprint(time.Now().Year()) + "-" + hour + minute + second
|
||||
}
|
||||
if !strings.HasSuffix(filename, ".png") {
|
||||
filename += ".png"
|
||||
}
|
||||
error := os.MkdirAll(configuration.ScannerConfig.OutputDirectory, 0755)
|
||||
if error != nil {
|
||||
log.Fatal("an error occurred creating the output directory", error.Error())
|
||||
}
|
||||
return configuration.ScannerConfig.OutputDirectory + filename
|
||||
}
|
||||
|
||||
// structs
|
||||
type result struct {
|
||||
State string
|
||||
Message string
|
||||
}
|
||||
|
||||
type requestParameters struct {
|
||||
Filename string
|
||||
scans.SetScannerConfig(configuration.ScannerConfig)
|
||||
server := web.Server(configuration.ServerConfig.Address, configuration.ServerConfig.Port, files)
|
||||
server.Run()
|
||||
}
|
||||
|
|
Before Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 713 B |
|
@ -3,17 +3,17 @@ package config
|
|||
import (
|
||||
"os"
|
||||
"strings"
|
||||
"velvettear/go-scan/util/environment"
|
||||
"velvettear/go-scan/src/util/environment"
|
||||
)
|
||||
|
||||
// exported functions
|
||||
func New() Config {
|
||||
config := Config{
|
||||
ServerConfig: serverConfig{
|
||||
ServerConfig: ServerConfig{
|
||||
Address: environment.New("GO_SCAN_ADDRESS", "0.0.0.0").Value(),
|
||||
Port: environment.New("GO_SCAN_PORT", "9000").Value(),
|
||||
},
|
||||
ScannerConfig: scannerConfig{
|
||||
ScannerConfig: ScannerConfig{
|
||||
Scanner: environment.New("GO_SCAN_SCANNER", "").Value(),
|
||||
OutputDirectory: environment.New("GO_SCAN_OUTPUTDIRECTORY", os.TempDir()).Value(),
|
||||
Arguments: environment.New("GO_SCAN_ARGUMENTS", "").Value(),
|
||||
|
@ -27,16 +27,16 @@ func New() Config {
|
|||
|
||||
// structs
|
||||
type Config struct {
|
||||
ServerConfig serverConfig
|
||||
ScannerConfig scannerConfig
|
||||
ServerConfig ServerConfig
|
||||
ScannerConfig ScannerConfig
|
||||
}
|
||||
|
||||
type serverConfig struct {
|
||||
type ServerConfig struct {
|
||||
Port string
|
||||
Address string
|
||||
}
|
||||
|
||||
type scannerConfig struct {
|
||||
type ScannerConfig struct {
|
||||
Scanner string
|
||||
OutputDirectory string
|
||||
Arguments string
|
|
@ -3,7 +3,7 @@ package log
|
|||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"velvettear/go-scan/util/date"
|
||||
"velvettear/go-scan/src/util/date"
|
||||
)
|
||||
|
||||
// exported functions
|
|
@ -36,12 +36,13 @@ func New() Date {
|
|||
Hour: hour,
|
||||
Minute: minute,
|
||||
Second: second,
|
||||
GetFormattedDate: func() string {
|
||||
return day + "." + month + "." + year + " " + hour + ":" + minute + ":" + second
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (date Date) GetFormattedDate() string {
|
||||
return date.Day + "." + date.Month + "." + date.Year + " " + date.Hour + ":" + date.Minute + ":" + date.Second
|
||||
}
|
||||
|
||||
func Milliseconds() int {
|
||||
return int(time.Now().UnixMilli())
|
||||
}
|
||||
|
@ -50,9 +51,6 @@ func GetTimeDifference(timestamp int) int {
|
|||
return Milliseconds() - timestamp
|
||||
}
|
||||
|
||||
// structs
|
||||
type getFormattedDate func() string
|
||||
|
||||
type Date struct {
|
||||
Day string
|
||||
Month string
|
||||
|
@ -60,5 +58,4 @@ type Date struct {
|
|||
Hour string
|
||||
Minute string
|
||||
Second string
|
||||
GetFormattedDate getFormattedDate
|
||||
}
|
114
src/util/scans/scans.go
Normal file
|
@ -0,0 +1,114 @@
|
|||
package scans
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"velvettear/go-scan/src/config"
|
||||
"velvettear/go-scan/src/log"
|
||||
"velvettear/go-scan/src/util/date"
|
||||
)
|
||||
|
||||
var scannerConfig config.ScannerConfig
|
||||
|
||||
// structs
|
||||
type scan struct {
|
||||
FileName string
|
||||
ScanName string
|
||||
}
|
||||
|
||||
type result struct {
|
||||
State string
|
||||
Message string
|
||||
}
|
||||
|
||||
// exported functions
|
||||
func New(request *http.Request) scan {
|
||||
var scan scan
|
||||
data, _ := io.ReadAll(request.Body)
|
||||
json.Unmarshal(data, &scan)
|
||||
scan.generateScanName()
|
||||
return scan
|
||||
}
|
||||
|
||||
func SetScannerConfig(config config.ScannerConfig) {
|
||||
scannerConfig = config
|
||||
}
|
||||
|
||||
func (scan scan) Execute() *result {
|
||||
log.Info("starting scan...")
|
||||
timestamp := date.Milliseconds()
|
||||
args := []string{}
|
||||
if len(scannerConfig.Scanner) > 0 {
|
||||
args = append(args, "-d")
|
||||
args = append(args, scannerConfig.Scanner)
|
||||
}
|
||||
args = append(args, "-o")
|
||||
args = append(args, scan.ScanName)
|
||||
extraArgs := strings.Split(scannerConfig.Arguments, " ")
|
||||
for index := 0; index < len(extraArgs); index++ {
|
||||
args = append(args, extraArgs[index])
|
||||
}
|
||||
log.Debug("executing command 'scanimage'...", args...)
|
||||
scanCmd := exec.Command(
|
||||
"scanimage",
|
||||
args...,
|
||||
)
|
||||
_, err := scanCmd.Output()
|
||||
if err != nil {
|
||||
log.Error("an error occured executing the scan", err.Error())
|
||||
result := &result{
|
||||
State: "error",
|
||||
Message: err.Error(),
|
||||
}
|
||||
return result
|
||||
}
|
||||
result := &result{
|
||||
State: "ok",
|
||||
Message: scan.ScanName,
|
||||
}
|
||||
log.Info("finished scan after "+strconv.Itoa(date.GetTimeDifference(timestamp))+"ms", scan.ScanName)
|
||||
return result
|
||||
}
|
||||
|
||||
// unexported functions
|
||||
func (scan scan) generateScanName() string {
|
||||
if len(scan.FileName) == 0 {
|
||||
day := fmt.Sprint(time.Now().Day())
|
||||
if len(day) < 2 {
|
||||
day = "0" + day
|
||||
}
|
||||
month := fmt.Sprint(int(time.Now().Month()))
|
||||
if len(month) < 2 {
|
||||
month = "0" + month
|
||||
}
|
||||
hour := fmt.Sprint(time.Now().Hour())
|
||||
if len(hour) < 2 {
|
||||
hour = "0" + hour
|
||||
}
|
||||
minute := fmt.Sprint(time.Now().Minute())
|
||||
if len(minute) < 2 {
|
||||
minute = "0" + minute
|
||||
}
|
||||
second := fmt.Sprint(time.Now().Second())
|
||||
if len(second) < 2 {
|
||||
second = "0" + second
|
||||
}
|
||||
scan.FileName = day + month + fmt.Sprint(time.Now().Year()) + "-" + hour + minute + second
|
||||
}
|
||||
if !strings.HasSuffix(scan.FileName, ".png") {
|
||||
scan.FileName += ".png"
|
||||
}
|
||||
error := os.MkdirAll(scannerConfig.OutputDirectory, 0755)
|
||||
if error != nil {
|
||||
log.Fatal("an error occurred creating the output directory", error.Error())
|
||||
}
|
||||
scan.ScanName = scannerConfig.OutputDirectory + scan.FileName
|
||||
return scan.ScanName
|
||||
}
|
157
src/web/contentTypes.go
Normal file
|
@ -0,0 +1,157 @@
|
|||
package web
|
||||
|
||||
import "strings"
|
||||
|
||||
func getContentType(file string) string {
|
||||
parts := strings.Split(file, ".")
|
||||
extension := parts[len(parts)-1]
|
||||
switch extension {
|
||||
case ".aac":
|
||||
return "audio/aac"
|
||||
case "abw":
|
||||
return "application/x-abiword"
|
||||
case "arc":
|
||||
return "application/x-freearc"
|
||||
case "avif":
|
||||
return "image/avif"
|
||||
case "avi":
|
||||
return "video/x-msvideo"
|
||||
case "azw":
|
||||
return "application/vnd.amazon.ebook"
|
||||
case "bin":
|
||||
return "application/octet-stream"
|
||||
case "bmp":
|
||||
return "image/bmp"
|
||||
case "bz":
|
||||
return "application/x-bzip"
|
||||
case "bz2":
|
||||
return "application/x-bzip2"
|
||||
case "cda":
|
||||
return "application/x-cdf"
|
||||
case "csh":
|
||||
return "application/x-csh"
|
||||
case "css":
|
||||
return "text/css"
|
||||
case "csv":
|
||||
return "text/csv"
|
||||
case "doc":
|
||||
return "application/msword"
|
||||
case "docx":
|
||||
return "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
|
||||
case "eot":
|
||||
return "application/vnd.ms-fontobject"
|
||||
case "epub":
|
||||
return "application/epub+zip"
|
||||
case "gz":
|
||||
return "application/gzip"
|
||||
case "gif":
|
||||
return "image/gif"
|
||||
case "htm":
|
||||
case "html":
|
||||
return "text/html"
|
||||
case "ico":
|
||||
return "image/vnd.microsoft.icon"
|
||||
case "ics":
|
||||
return "text/calendar"
|
||||
case "jar":
|
||||
return "application/java-archive"
|
||||
case "jpeg":
|
||||
case "jpg":
|
||||
return "image/jpeg"
|
||||
case "js":
|
||||
return "text/javascript"
|
||||
case "json":
|
||||
return "application/json"
|
||||
case "jsonld":
|
||||
return "application/ld+json"
|
||||
case "mid":
|
||||
case "midi":
|
||||
return "audio/midi audio/x-midi"
|
||||
case "mjs":
|
||||
return "text/javascript"
|
||||
case "mp3":
|
||||
return "audio/mpeg"
|
||||
case "mp4":
|
||||
return "video/mp4"
|
||||
case "mpeg":
|
||||
return "video/mpeg"
|
||||
case "mpkg":
|
||||
return "application/vnd.apple.installer+xml"
|
||||
case "odp":
|
||||
return "application/vnd.oasis.opendocument.presentation"
|
||||
case "ods":
|
||||
return "application/vnd.oasis.opendocument.spreadsheet"
|
||||
case "odt":
|
||||
return "application/vnd.oasis.opendocument.text"
|
||||
case "oga":
|
||||
return "audio/ogg"
|
||||
case "ogv":
|
||||
return "video/ogg"
|
||||
case "ogx":
|
||||
return "application/ogg"
|
||||
case "opus":
|
||||
return "audio/opus"
|
||||
case "otf":
|
||||
return "font/otf"
|
||||
case "png":
|
||||
return "image/png"
|
||||
case "pdf":
|
||||
return "application/pdf"
|
||||
case "php":
|
||||
return "application/x-httpd-php"
|
||||
case "ppt":
|
||||
return "application/vnd.ms-powerpoint"
|
||||
case "pptx":
|
||||
return "application/vnd.openxmlformats-officedocument.presentationml.presentation"
|
||||
case "rar":
|
||||
return "application/vnd.rar"
|
||||
case "rtf":
|
||||
return "application/rtf"
|
||||
case "sh":
|
||||
return "application/x-sh"
|
||||
case "svg":
|
||||
return "image/svg+xml"
|
||||
case "swf":
|
||||
return "application/x-shockwave-flash"
|
||||
case "tar":
|
||||
return "application/x-tar"
|
||||
case "tif":
|
||||
case "tiff":
|
||||
return "image/tiff"
|
||||
case "ts":
|
||||
return "video/mp2t"
|
||||
case "ttf":
|
||||
return "font/ttf"
|
||||
case "txt":
|
||||
return "text/plain"
|
||||
case "vsd":
|
||||
return "application/vnd.visio"
|
||||
case "wav":
|
||||
return "audio/wav"
|
||||
case "weba":
|
||||
return "audio/webm"
|
||||
case "webm":
|
||||
return "video/webm"
|
||||
case "webp":
|
||||
return "image/webp"
|
||||
case "woff":
|
||||
return "font/woff"
|
||||
case "woff2":
|
||||
return "font/woff2"
|
||||
case "xhtml":
|
||||
return "application/xhtml+xml"
|
||||
case "xls":
|
||||
return "application/vnd.ms-excel"
|
||||
case "xlsx":
|
||||
return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
||||
case "xml":
|
||||
return "application/xml"
|
||||
case "xul":
|
||||
return "application/vnd.mozilla.xul+xml"
|
||||
case "zip":
|
||||
return "application/zip"
|
||||
case "7z":
|
||||
return "application/x-7z-compressed"
|
||||
}
|
||||
return ""
|
||||
}
|
15
src/web/requestParameters.go
Normal file
|
@ -0,0 +1,15 @@
|
|||
package web
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"velvettear/go-scan/src/log"
|
||||
)
|
||||
|
||||
// exported functions
|
||||
func ParseRequestParameters(request *http.Request, parameters struct{}) {
|
||||
parseErr := json.NewDecoder(request.Body).Decode(¶meters)
|
||||
if parseErr != nil {
|
||||
log.Error("error parsing request parameters", parseErr.Error())
|
||||
}
|
||||
}
|
77
src/web/server.go
Normal file
|
@ -0,0 +1,77 @@
|
|||
package web
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strings"
|
||||
"velvettear/go-scan/src/log"
|
||||
"velvettear/go-scan/src/util/scans"
|
||||
)
|
||||
|
||||
// struct
|
||||
type server struct {
|
||||
address string
|
||||
port string
|
||||
files embed.FS
|
||||
}
|
||||
|
||||
// "constructor"
|
||||
func Server(address string, port string, files embed.FS) server {
|
||||
server := server{
|
||||
address: address,
|
||||
port: port,
|
||||
files: files,
|
||||
}
|
||||
return server
|
||||
}
|
||||
|
||||
// exported functions
|
||||
func (server server) Run() {
|
||||
if len(server.address) == 0 || len(server.port) == 0 {
|
||||
log.Fatal("could not start the server, address or port is not set")
|
||||
}
|
||||
serverAddress := server.address + ":" + server.port
|
||||
http.HandleFunc("/", server.handleRequest)
|
||||
log.Info("starting server '" + serverAddress + "'...")
|
||||
err := http.ListenAndServe(serverAddress, nil)
|
||||
if err != nil {
|
||||
log.Fatal("an error occured starting the server", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// unexported functions
|
||||
func (server server) handleRequest(writer http.ResponseWriter, request *http.Request) {
|
||||
if strings.ToLower(request.Method) == "post" {
|
||||
server.handlePost(writer, request)
|
||||
return
|
||||
}
|
||||
requestedFile := strings.TrimPrefix(request.URL.Path, "/")
|
||||
switch requestedFile {
|
||||
case "favicon.ico":
|
||||
requestedFile = "img/" + requestedFile
|
||||
case "":
|
||||
requestedFile = "html/index.html"
|
||||
}
|
||||
requestedFile = "static/" + requestedFile
|
||||
bytes, err := server.files.ReadFile(requestedFile)
|
||||
if err != nil {
|
||||
log.Error("an error occured serving the static file '"+requestedFile+"'", err.Error())
|
||||
writer.WriteHeader(404)
|
||||
return
|
||||
}
|
||||
contentType := getContentType(requestedFile)
|
||||
if len(contentType) > 0 {
|
||||
writer.Header().Set("Content-Type", contentType)
|
||||
}
|
||||
log.Debug("serving file '" + requestedFile + "'...")
|
||||
writer.Write(bytes)
|
||||
}
|
||||
|
||||
func (server server) handlePost(writer http.ResponseWriter, request *http.Request) {
|
||||
scan := scans.New(request)
|
||||
result := scan.Execute()
|
||||
writer.Header().Set("Content-Type", "application/json")
|
||||
json, _ := json.Marshal(result)
|
||||
writer.Write(json)
|
||||
}
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
<head>
|
||||
<title>go-scan</title>
|
||||
<link href="/resources/firacode.ttf" rel="stylesheet">
|
||||
<link href="/resources/styles.css" rel="stylesheet">
|
||||
<link href="/fonts/firacode.ttf" rel="stylesheet">
|
||||
<link href="/css/styles.css" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
@ -12,7 +12,7 @@
|
|||
<h1>go-scan</h1>
|
||||
</div>
|
||||
<div id="button" onclick="startScan()">
|
||||
<img id="button-img" src="/resources/icon.png">
|
||||
<img id="button-img" src="/img/icon.png">
|
||||
</div>
|
||||
<div id="input">
|
||||
<input id="input-filename" type="text" placeholder="filename...">
|
||||
|
@ -24,11 +24,11 @@
|
|||
<a href="https://velvettear.de" target="_blank">
|
||||
<div id="footer">
|
||||
<span>made with</span>
|
||||
<img id="heart-img" src="/resources/hearts-fill.svg">
|
||||
<img id="heart-img" src="/img/hearts-fill.svg">
|
||||
<span>by velvettear</span>
|
||||
</div>
|
||||
</a>
|
||||
<script src="/resources/main.js"></script>
|
||||
<script src="/js/main.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
BIN
static/img/favicon.ico
Normal file
After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 603 B After Width: | Height: | Size: 603 B |
Before Width: | Height: | Size: 9.7 KiB After Width: | Height: | Size: 9.7 KiB |