diff --git a/backend/backend.go b/backend/backend.go index 018dba7..fd35b3f 100644 --- a/backend/backend.go +++ b/backend/backend.go @@ -5,11 +5,13 @@ import ( "errors" "fmt" "os" + "io/fs" "os/exec" "path/filepath" "strings" "time" "git.noctra.dev/noctra/servtex/globals" + "github.com/fsnotify/fsnotify" ) // Returns the current time in the timezone specified in the config file @@ -111,15 +113,89 @@ func ConfigReader(configFileName string, configOptionStorage *globals.Config) er return err } +// Contents copied from https://github.com/fsnotify/fsnotify/blob/main/cmd/fsnotify/watch.go +func watchLoop(watcher *fsnotify.Watcher, callOnChange func() error) { + defer watcher.Close() + for { + select { + // Errors + case err, ok := <-watcher.Errors: + if !ok { + LogLine(fmt.Sprintf("Watcher crashed on error, manual compile only: %s", err), 4) + return + } + LogLine(fmt.Sprintf("Watcher Error: %s", err), 3) + + // Events + case event, ok := <-watcher.Events: + if !ok { + LogLine(fmt.Sprintf("Watcher crashed on event, manual compile only: %s", event), 4) + return + } + LogLine(fmt.Sprintf("File change noticed: %s", event.Name), 2) + + err := callOnChange() + if err != nil { + LogLine(fmt.Sprintf("Latex compilation failed: %s", err), 4) + } + + if err = watcher.Add(event.Name); err != nil { + LogLine(fmt.Sprintf("File could not be re-added to watcher: %s", event.Name), 4) + } + } + } +} + + +func texFinder(path string) []string { + var texFiles []string + err := filepath.WalkDir(path, func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + if !d.IsDir() && filepath.Ext(path) == ".tex" { + texFiles = append(texFiles, path) + } + return nil + }) + if err != nil { + LogLine("Some paths could not be traversed while search for .tex files", 3) + } + return texFiles +} + // Watches a specified directory and calls a function on change // // Optionally, a minimum time to wait for consecutive changes can be specified -func ChangeWatch(path string, callOnChange func()) { - LogLine("File change noticed", 2) +func ChangeWatch(path string, callOnChange func() error) { + watcher, err := fsnotify.NewWatcher() + if err != nil { + LogLine("File system watcher could not be initialised, manual compile only", 4) + return + } + + for _, file := range texFinder(path) { + if err = watcher.Add(file); err != nil { + LogLine(fmt.Sprintf("File could not be added to watcher: %s", err), 4) + } + LogLine(fmt.Sprintf("File added to watcher: %s", file), 1) + } + + go watchLoop(watcher, callOnChange) + + LogLine("Watcher initialised", 1) +} + + +func updateExecutionTimestamp(execution *globals.LatexExecution) { + execution.Timestamp = GetLocalTimePretty() + execution.TimestampRFC = GetLocalTimeRFC() } // Intended to be run as goroutine -func LatexCompile(config globals.Config, execution *globals.LatexExecution) error { +func LatexCompile() error { + execution := &globals.LatexExec + config := globals.AppConfig if execution.ExecutionLock.TryLock() { defer execution.ExecutionLock.Unlock() } else { @@ -143,7 +219,8 @@ func LatexCompile(config globals.Config, execution *globals.LatexExecution) erro default: execution.ExecutionState = "Unknown Latex Engine" execution.Output = []byte{} - execution.Timestamp = GetLocalTimePretty() + updateExecutionTimestamp(execution) + LogLine("Unsupported LaTeX Engine", 4) return errors.New("Unsupported LaTeX Engine") } @@ -156,8 +233,9 @@ func LatexCompile(config globals.Config, execution *globals.LatexExecution) erro } execution.Output = stdout - execution.Timestamp = GetLocalTimePretty() execution.ExecutionState = "Done" + updateExecutionTimestamp(execution) + LogLine("LaTeX execution finished", 2) return nil } diff --git a/frontend/webserver.go b/frontend/webserver.go index 1bca983..3d2f3a3 100644 --- a/frontend/webserver.go +++ b/frontend/webserver.go @@ -77,16 +77,17 @@ func SSEventHandler(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") + ssePing(&writer) + lastExecution := "startup" for range time.Tick(time.Second) { - if lastExecution == globals.LatexExec.Timestamp { + if lastExecution == globals.LatexExec.TimestampRFC { ssePing(&writer) } else { sseStatusSend(&writer) ssePDFSend(&writer) sseOutputSend(&writer) - // comment out for testing purposes - lastExecution = globals.LatexExec.Timestamp + lastExecution = globals.LatexExec.TimestampRFC } } } @@ -114,6 +115,6 @@ func MainHandler(writer http.ResponseWriter, request *http.Request) { } func PDFCompile(writer http.ResponseWriter, request *http.Request) { - backend.LatexCompile(globals.AppConfig, &globals.LatexExec) + backend.LatexCompile() } diff --git a/globals/memory.go b/globals/memory.go index cffad4d..7243df7 100644 --- a/globals/memory.go +++ b/globals/memory.go @@ -32,6 +32,7 @@ type LatexExecution struct { ExecutionLock sync.Mutex ExecutionState string Timestamp string + TimestampRFC string Output []byte } var LatexExec LatexExecution diff --git a/main.go b/main.go index 008c660..490ccbe 100644 --- a/main.go +++ b/main.go @@ -1,14 +1,16 @@ package main import ( - "os" - "os/signal" - "syscall" - "io/fs" "context" "fmt" - "time" + "io/fs" "net/http" + "os" + "os/signal" + "path/filepath" + "syscall" + "time" + "git.noctra.dev/noctra/servtex/backend" "git.noctra.dev/noctra/servtex/frontend" "git.noctra.dev/noctra/servtex/globals" @@ -49,6 +51,15 @@ func main() { if globals.AppConfig.WebserverSecure { go serverSecure.ListenAndServeTLS(globals.AppConfig.CertificatePath, globals.AppConfig.CertificateKeyPath) } + + // file system change watch + absTexPath, err := filepath.Abs(globals.AppConfig.LatexSourceFilePath) + if err == nil { + backend.ChangeWatch(filepath.Dir(absTexPath), backend.LatexCompile) + } else { + backend.LogLine("Absolute TeX file path could not be gotten", 4) + } + backend.LogLine("Started", 2) fmt.Println("Press CTRL-C to Exit ServTeX") @@ -61,7 +72,7 @@ func main() { fmt.Print("\r") // shutdown - context, cancel := context.WithTimeout(context.Background(), 15*time.Second) + context, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() if err = server.Shutdown(context); err != nil { backend.LogLine("Graceful Shutdown failed", 4) diff --git a/testfiles/Example.tex b/testfiles/Example.tex index 6fa16b1..7f0cc2d 100644 --- a/testfiles/Example.tex +++ b/testfiles/Example.tex @@ -1,4 +1,8 @@ \documentclass{article} \begin{document} (This is an Example Document) +Filewatcher Test Document +Filewatcher Test Document +Filewatcher Test Document +Filewatcher Test Document \end{document}