complete overhaul of the whole project structure including optimizations and bug fixes

This commit is contained in:
Daniel Sommer 2022-08-03 12:23:39 +02:00
parent c53a2c2e72
commit 59de45dd22
18 changed files with 394 additions and 177 deletions

159
main.go
View file

@ -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(&parameters)
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()
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 713 B

View file

@ -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

View file

@ -3,7 +3,7 @@ package log
import (
"fmt"
"os"
"velvettear/go-scan/util/date"
"velvettear/go-scan/src/util/date"
)
// exported functions

View file

@ -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
View 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
View 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 ""
}

View 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(&parameters)
if parseErr != nil {
log.Error("error parsing request parameters", parseErr.Error())
}
}

77
src/web/server.go Normal file
View 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)
}

View file

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View file

Before

Width:  |  Height:  |  Size: 603 B

After

Width:  |  Height:  |  Size: 603 B

View file

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 9.7 KiB