diff --git a/main.go b/main.go index 9af1eff..780a362 100644 --- a/main.go +++ b/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() } diff --git a/resources/gopher.png b/resources/gopher.png deleted file mode 100644 index 8f29b63..0000000 Binary files a/resources/gopher.png and /dev/null differ diff --git a/resources/heart.png b/resources/heart.png deleted file mode 100644 index 9b7f140..0000000 Binary files a/resources/heart.png and /dev/null differ diff --git a/config/config.go b/src/config/config.go similarity index 78% rename from config/config.go rename to src/config/config.go index 6762c90..6643ea6 100644 --- a/config/config.go +++ b/src/config/config.go @@ -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 diff --git a/log/log.go b/src/log/log.go similarity index 97% rename from log/log.go rename to src/log/log.go index 7bf3c65..bc54d25 100644 --- a/log/log.go +++ b/src/log/log.go @@ -3,7 +3,7 @@ package log import ( "fmt" "os" - "velvettear/go-scan/util/date" + "velvettear/go-scan/src/util/date" ) // exported functions diff --git a/util/date/date.go b/src/util/date/date.go similarity index 69% rename from util/date/date.go rename to src/util/date/date.go index aa4a28a..014faab 100644 --- a/util/date/date.go +++ b/src/util/date/date.go @@ -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,15 +51,11 @@ func GetTimeDifference(timestamp int) int { return Milliseconds() - timestamp } -// structs -type getFormattedDate func() string - type Date struct { - Day string - Month string - Year string - Hour string - Minute string - Second string - GetFormattedDate getFormattedDate + Day string + Month string + Year string + Hour string + Minute string + Second string } diff --git a/util/environment/environment.go b/src/util/environment/environment.go similarity index 100% rename from util/environment/environment.go rename to src/util/environment/environment.go diff --git a/src/util/scans/scans.go b/src/util/scans/scans.go new file mode 100644 index 0000000..10b75d9 --- /dev/null +++ b/src/util/scans/scans.go @@ -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 +} diff --git a/src/web/contentTypes.go b/src/web/contentTypes.go new file mode 100644 index 0000000..64bd162 --- /dev/null +++ b/src/web/contentTypes.go @@ -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 "" +} diff --git a/src/web/requestParameters.go b/src/web/requestParameters.go new file mode 100644 index 0000000..8a98cc0 --- /dev/null +++ b/src/web/requestParameters.go @@ -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()) + } +} diff --git a/src/web/server.go b/src/web/server.go new file mode 100644 index 0000000..4d716e9 --- /dev/null +++ b/src/web/server.go @@ -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) +} diff --git a/resources/styles.css b/static/css/styles.css similarity index 100% rename from resources/styles.css rename to static/css/styles.css diff --git a/resources/firacode.ttf b/static/fonts/firacode.ttf similarity index 100% rename from resources/firacode.ttf rename to static/fonts/firacode.ttf diff --git a/resources/index.html b/static/html/index.html similarity index 67% rename from resources/index.html rename to static/html/index.html index 166f4c8..4b9c505 100644 --- a/resources/index.html +++ b/static/html/index.html @@ -2,8 +2,8 @@ go-scan - - + + @@ -12,7 +12,7 @@

go-scan

- +
@@ -24,11 +24,11 @@ - + \ No newline at end of file diff --git a/static/img/favicon.ico b/static/img/favicon.ico new file mode 100644 index 0000000..ad0b1aa Binary files /dev/null and b/static/img/favicon.ico differ diff --git a/resources/hearts-fill.svg b/static/img/hearts-fill.svg similarity index 100% rename from resources/hearts-fill.svg rename to static/img/hearts-fill.svg diff --git a/resources/icon.png b/static/img/icon.png similarity index 100% rename from resources/icon.png rename to static/img/icon.png diff --git a/resources/main.js b/static/js/main.js similarity index 100% rename from resources/main.js rename to static/js/main.js