logger, compiler logic, sse endpoints

This commit is contained in:
Maximilian Wagner
2025-12-25 20:05:33 +01:00
parent d9f7ead467
commit 891ebe0107
8 changed files with 110 additions and 26 deletions

View File

@@ -2,26 +2,45 @@ package backend
import ( import (
"os" "os"
"os/exec"
"path/filepath" "path/filepath"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt"
"git.noctra.dev/noctra/servtex/globals" "git.noctra.dev/noctra/servtex/globals"
"time"
) )
func getLocalTime() time.Time {
timezone, _ := time.LoadLocation(globals.AppConfig.Timezone)
return time.Now().In(timezone)
}
func getLocalTimePretty() string {
return getLocalTime().Format("02.01.2006 15:04")
}
func LogLine(message string) {
logline := "\n" + getLocalTime().Format(time.RFC3339) + " - " + message
globals.LogFile.WriteString(logline)
}
// Helper for configReader() that does the actual reading // Helper for configReader() that does the actual reading
// Also validates the populated fields
func configReaderParse(filePath string, configOptionStorage *globals.Config) error { func configReaderParse(filePath string, configOptionStorage *globals.Config) error {
jsonData, err := os.ReadFile(filePath) jsonData, err := os.ReadFile(filePath)
if err == nil { if err == nil {
err = json.Unmarshal(jsonData, &configOptionStorage) err = json.Unmarshal(jsonData, &configOptionStorage)
if err != nil { if err != nil {
// log error unmarshal failure
return errors.New("Config file could not be read") return errors.New("Config file could not be read")
} }
} else { } else {
return errors.New("Config file does not exist") return errors.New("Config file does not exist")
} }
return configValidator(*configOptionStorage)
}
func configValidator(config globals.Config) error {
return nil return nil
} }
@@ -48,34 +67,52 @@ func ConfigReader(configFileName string, configOptionStorage *globals.Config) er
err = configReaderParse(path, configOptionStorage) err = configReaderParse(path, configOptionStorage)
if err == nil { return nil } if err == nil { return nil }
// log error no config file found return err
return errors.New("Configuration file not found or json parsing error")
} }
// Watches a specified directory and calls a function on change // Watches a specified directory and calls a function on change
// Optionally, a minimum time to wait for consecutive changes can be specified // Optionally, a minimum time to wait for consecutive changes can be specified
func ChangeWatch(path string, callOnChange func()) { func ChangeWatch(path string, callOnChange func()) {
LogLine("File change noticed")
} }
// Intended to be run as goroutine // Intended to be run as goroutine
func LatexCompile(config *globals.Config) error { func LatexCompile(config globals.Config, execution *globals.LatexExecution) error {
if !config.ExecutionLock.TryLock() { if !execution.ExecutionLock.TryLock() {
LogLine("LaTeX execution already underway")
return errors.New("Execution already in progress") return errors.New("Execution already in progress")
} }
LogLine("LaTeX execution started")
execution.ExecutionState = "Started"
var latexCommand string var latexCommand *exec.Cmd
switch config.LatexEngine { switch config.LatexEngine {
case "lualatex": case "lualatex":
latexCommand = fmt.Sprintf( latexCommand = exec.Command(
"lualatex -output-directory=%s -jobname=servtex %s", "lualatex",
config.LatexOutputPath, "-output-directory=" + config.LatexOutputPath,
"-jobname=servtex",
config.LatexSourceFilePath, config.LatexSourceFilePath,
) )
default:
execution.ExecutionState = "Unknown Latex Engine"
execution.Output = []byte{}
execution.Timestamp = getLocalTimePretty()
execution.ExecutionLock.Unlock()
return errors.New("Unknown Latex Engine")
} }
fmt.Sprintf(latexCommand) // placeholder for compilation execution.ExecutionState = "Running"
config.ExecutionLock.Unlock() stdout, err := latexCommand.Output()
if err != nil {
execution.ExecutionState = "Failed"
return err
}
execution.Output = stdout
execution.Timestamp = getLocalTimePretty()
execution.ExecutionState = "Done"
execution.ExecutionLock.Unlock()
return nil return nil
} }

View File

@@ -9,5 +9,6 @@
"webserverDomain": "localhost:9876", "webserverDomain": "localhost:9876",
"webserverSecure": false, "webserverSecure": false,
"certificatePath": "", "certificatePath": "",
"certificateKeyPath": "" "certificateKeyPath": "",
"timezone": "Europe/Berlin"
} }

View File

@@ -12,7 +12,7 @@
<iframe class="pdf-frame" src="servetex.pdf?ts=0" hx-sse="connect:/sse/pdf" hx-swap="outerHTML"></iframe> <iframe class="pdf-frame" src="servetex.pdf?ts=0" hx-sse="connect:/sse/pdf" hx-swap="outerHTML"></iframe>
</div> </div>
</div> </div>
<div class="command-out"> <div class="command-out" hx-sse="connect:/sse/status" hx-swap="innerHTML">
compile start compile start
compile file-x compile file-x
compile file-y compile file-y

View File

@@ -1,2 +1,32 @@
package frontend package frontend
import (
"net/http"
"fmt"
"git.noctra.dev/noctra/servtex/globals"
)
// Publishes Meta Information relating to last execution
// Does not use contained Mutex, ignore warning
func SSEStatus(writer http.ResponseWriter, request *http.Request) {
writer.Header().Set("Content-Type", "text/event-stream")
writer.Header().Set("Cache-Control", "no-cache")
writer.Header().Set("Connection", "keep-alive")
fmt.Fprintf(writer, "data: Execution Time: %s\n", globals.LatexExec.Timestamp)
fmt.Fprintf(writer, "data: Execution State: %s\n\n", globals.LatexExec.ExecutionState)
writer.(http.Flusher).Flush()
}
func SSEOutput(writer http.ResponseWriter, request *http.Request) {
writer.Header().Set("Content-Type", "text/event-stream")
writer.Header().Set("Cache-Control", "no-cache")
writer.Header().Set("Connection", "keep-alive")
fmt.Fprintf(writer, "data: Execution Time: %s\n", globals.LatexExec.Timestamp)
fmt.Fprintf(writer, "data: Execution State: %s\n\n", globals.LatexExec.ExecutionState)
writer.(http.Flusher).Flush()
}

View File

@@ -1,6 +1,9 @@
package globals package globals
import "sync" import (
"os"
"sync"
)
type Config struct { type Config struct {
// general // general
@@ -17,9 +20,18 @@ type Config struct {
WebserverSecure bool `json:"webserverSecure"` WebserverSecure bool `json:"webserverSecure"`
CertificatePath string `json:"certificatePath"` CertificatePath string `json:"certificatePath"`
CertificateKeyPath string `json:"certificateKeyPath"` CertificateKeyPath string `json:"certificateKeyPath"`
Timezone string `json:"timezone"`
// to prevent competing compiles, not actual configuration
ExecutionLock sync.Mutex
} }
var AppConfig Config var AppConfig Config
type LatexExecution struct {
ExecutionLock sync.Mutex
ExecutionState string
Timestamp string
Output []byte
}
var LatexExec LatexExecution
var LogFile *os.File

2
go.mod
View File

@@ -1,3 +1,5 @@
module git.noctra.dev/noctra/servtex module git.noctra.dev/noctra/servtex
go 1.25.5 go 1.25.5
require github.com/tmaxmax/go-sse v0.11.0

2
go.sum Normal file
View File

@@ -0,0 +1,2 @@
github.com/tmaxmax/go-sse v0.11.0 h1:nogmJM6rJUoOLoAwEKeQe5XlVpt9l7N82SS1jI7lWFg=
github.com/tmaxmax/go-sse v0.11.0/go.mod h1:u/2kZQR1tyngo1lKaNCj1mJmhXGZWS1Zs5yiSOD+Eg8=

View File

@@ -2,7 +2,6 @@ package main
import ( import (
"os" "os"
//"log/slog"
"git.noctra.dev/noctra/servtex/globals" "git.noctra.dev/noctra/servtex/globals"
"git.noctra.dev/noctra/servtex/backend" "git.noctra.dev/noctra/servtex/backend"
//"net/http" //"net/http"
@@ -14,8 +13,9 @@ import (
// 1 - config file could not be read // 1 - config file could not be read
func main() { func main() {
err := backend.ConfigReader("config.json", &globals.AppConfig) err := backend.ConfigReader("config.json", &globals.AppConfig)
if err != nil { if err != nil { os.Exit(1) }
os.Exit(1)
} globals.LogFile, err = os.Open(globals.AppConfig.LogFilePath)
if err != nil { os.Exit(2) }
} }