implemented SSE logic
This commit is contained in:
@@ -10,21 +10,29 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getLocalTime() time.Time {
|
// Returns the current time in the timezone specified in the config file
|
||||||
|
func GetLocalTime() time.Time {
|
||||||
timezone, _ := time.LoadLocation(globals.AppConfig.Timezone)
|
timezone, _ := time.LoadLocation(globals.AppConfig.Timezone)
|
||||||
return time.Now().In(timezone)
|
return time.Now().In(timezone)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getLocalTimePretty() string {
|
// Returns the current localtime in the RFC3339 format
|
||||||
return getLocalTime().Format("02.01.2006 15:04")
|
func GetLocalTimeRFC() string {
|
||||||
|
return GetLocalTime().Format(time.RFC3339)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the current localtime in custom pretty print format
|
||||||
|
func GetLocalTimePretty() string {
|
||||||
|
return GetLocalTime().Format("02.01.2006 15:04")
|
||||||
}
|
}
|
||||||
|
|
||||||
func LogLine(message string) {
|
func LogLine(message string) {
|
||||||
logline := "\n" + getLocalTime().Format(time.RFC3339) + " - " + message
|
logline := "\n" + GetLocalTimeRFC() + " - " + message
|
||||||
globals.LogFile.WriteString(logline)
|
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
|
// 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)
|
||||||
@@ -45,9 +53,11 @@ func configValidator(config globals.Config) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Reads config file and stores the options in configOptionStorage
|
// Reads config file and stores the options in configOptionStorage
|
||||||
// Tries executable/path/configFileName
|
//
|
||||||
// %LOCALAPPDATA%\servtex\configFileName
|
// Tries the following paths
|
||||||
// ~/.config/servtex/configFileName
|
// executable/path/configFileName
|
||||||
|
// %LOCALAPPDATA%\servtex\configFileName
|
||||||
|
// ~/.config/servtex/configFileName
|
||||||
func ConfigReader(configFileName string, configOptionStorage *globals.Config) error {
|
func ConfigReader(configFileName string, configOptionStorage *globals.Config) error {
|
||||||
exePath, err := os.Executable()
|
exePath, err := os.Executable()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@@ -71,6 +81,7 @@ func ConfigReader(configFileName string, configOptionStorage *globals.Config) er
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 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")
|
LogLine("File change noticed")
|
||||||
@@ -97,7 +108,7 @@ func LatexCompile(config globals.Config, execution *globals.LatexExecution) erro
|
|||||||
default:
|
default:
|
||||||
execution.ExecutionState = "Unknown Latex Engine"
|
execution.ExecutionState = "Unknown Latex Engine"
|
||||||
execution.Output = []byte{}
|
execution.Output = []byte{}
|
||||||
execution.Timestamp = getLocalTimePretty()
|
execution.Timestamp = GetLocalTimePretty()
|
||||||
execution.ExecutionLock.Unlock()
|
execution.ExecutionLock.Unlock()
|
||||||
return errors.New("Unknown Latex Engine")
|
return errors.New("Unknown Latex Engine")
|
||||||
}
|
}
|
||||||
@@ -110,7 +121,7 @@ func LatexCompile(config globals.Config, execution *globals.LatexExecution) erro
|
|||||||
}
|
}
|
||||||
|
|
||||||
execution.Output = stdout
|
execution.Output = stdout
|
||||||
execution.Timestamp = getLocalTimePretty()
|
execution.Timestamp = GetLocalTimePretty()
|
||||||
execution.ExecutionState = "Done"
|
execution.ExecutionState = "Done"
|
||||||
execution.ExecutionLock.Unlock()
|
execution.ExecutionLock.Unlock()
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -2,17 +2,17 @@
|
|||||||
<body>
|
<body>
|
||||||
<div class="container-fluid h-100 d-flex flex-column">
|
<div class="container-fluid h-100 d-flex flex-column">
|
||||||
<div class="row output flex-grow-1">
|
<div class="row output flex-grow-1">
|
||||||
<div class="col-3 left-sidebar" hx-sse="connect:/sse/status" hx-swap="innerHTML">
|
<div class="col-3 left-sidebar" hx-sse="connect:/sse on:status" hx-swap="innerHTML">
|
||||||
Status <br>
|
Status <br>
|
||||||
Last Compilation <br>
|
Last Compilation <br>
|
||||||
Such Info <br>
|
Such Info <br>
|
||||||
Much wow
|
Much wow
|
||||||
</div>
|
</div>
|
||||||
<div class="col-9 p-0">
|
<div class="col-9 p-0">
|
||||||
<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 on:pdf" hx-swap="outerHTML"></iframe>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="command-out" hx-sse="connect:/sse/status" hx-swap="innerHTML">
|
<div class="command-out" hx-sse="connect:/sse on:output" hx-swap="innerHTML">
|
||||||
compile start
|
compile start
|
||||||
compile file-x
|
compile file-x
|
||||||
compile file-y
|
compile file-y
|
||||||
|
|||||||
@@ -1,32 +1,91 @@
|
|||||||
package frontend
|
package frontend
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"time"
|
||||||
|
"strings"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"git.noctra.dev/noctra/servtex/backend"
|
||||||
"git.noctra.dev/noctra/servtex/globals"
|
"git.noctra.dev/noctra/servtex/globals"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Publishes Meta Information relating to last execution
|
// Adds necessary Headers for SSE
|
||||||
// Does not use contained Mutex, ignore warning
|
func sseHeadersAdd(writer *http.ResponseWriter) {
|
||||||
func SSEStatus(writer http.ResponseWriter, request *http.Request) {
|
(*writer).Header().Set("Content-Type", "text/event-stream")
|
||||||
writer.Header().Set("Content-Type", "text/event-stream")
|
(*writer).Header().Set("Cache-Control", "no-cache")
|
||||||
writer.Header().Set("Cache-Control", "no-cache")
|
(*writer).Header().Set("Connection", "keep-alive")
|
||||||
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) {
|
// Sends a Ping to keep the connection alive
|
||||||
writer.Header().Set("Content-Type", "text/event-stream")
|
func ssePing(writer *http.ResponseWriter) {
|
||||||
writer.Header().Set("Cache-Control", "no-cache")
|
sseHeadersAdd(writer)
|
||||||
writer.Header().Set("Connection", "keep-alive")
|
|
||||||
|
|
||||||
fmt.Fprintf(writer, "data: Execution Time: %s\n", globals.LatexExec.Timestamp)
|
fmt.Fprintf((*writer), ": ping\n\n")
|
||||||
fmt.Fprintf(writer, "data: Execution State: %s\n\n", globals.LatexExec.ExecutionState)
|
|
||||||
|
|
||||||
writer.(http.Flusher).Flush()
|
(*writer).(http.Flusher).Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sends new Event
|
||||||
|
//
|
||||||
|
// Reads globals.LatexExec
|
||||||
|
func sseStatusSend(writer *http.ResponseWriter) {
|
||||||
|
sseHeadersAdd(writer)
|
||||||
|
|
||||||
|
fmt.Fprintf((*writer), "event: status\n")
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sends new Event
|
||||||
|
//
|
||||||
|
// Reads globals.LatexExec
|
||||||
|
func ssePDFSend(writer *http.ResponseWriter) {
|
||||||
|
sseHeadersAdd(writer)
|
||||||
|
|
||||||
|
fmt.Fprintf((*writer), "event: pdf\n")
|
||||||
|
iframe := fmt.Sprintf(
|
||||||
|
`<iframe class="pdf-frame" src="servetex.pdf?ts=%s" hx-sse="connect:/sse/pdf" hx-swap="outerHTML"></iframe>`,
|
||||||
|
url.QueryEscape(backend.GetLocalTimeRFC()),
|
||||||
|
)
|
||||||
|
fmt.Fprintf((*writer), "data: %s\n\n", iframe)
|
||||||
|
|
||||||
|
(*writer).(http.Flusher).Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sends new Event
|
||||||
|
//
|
||||||
|
// Reads globals.LatexExec
|
||||||
|
func sseOutputSend(writer *http.ResponseWriter) {
|
||||||
|
sseHeadersAdd(writer)
|
||||||
|
|
||||||
|
fmt.Fprintf((*writer), "event: output\n")
|
||||||
|
lines := strings.SplitSeq(string(globals.LatexExec.Output), "\n")
|
||||||
|
for line := range lines {
|
||||||
|
fmt.Fprintf((*writer), "data: %s\n", line)
|
||||||
|
}
|
||||||
|
fmt.Fprintf((*writer), "\n")
|
||||||
|
|
||||||
|
(*writer).(http.Flusher).Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server Side Event Handler
|
||||||
|
//
|
||||||
|
// Sends a Ping instead of actual data when no new data available to save bandwidth
|
||||||
|
func SSEEventSender(writer http.ResponseWriter, request *http.Request) {
|
||||||
|
lastExecution := ""
|
||||||
|
for range time.Tick(time.Second) {
|
||||||
|
if lastExecution == globals.LatexExec.Timestamp {
|
||||||
|
ssePing(&writer)
|
||||||
|
} else {
|
||||||
|
sseStatusSend(&writer)
|
||||||
|
ssePDFSend(&writer)
|
||||||
|
sseOutputSend(&writer)
|
||||||
|
// comment out for testing purposes
|
||||||
|
lastExecution = globals.LatexExec.Timestamp
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user