mirror of
https://github.com/Theodor-Springmann-Stiftung/musenalm.git
synced 2025-10-29 09:15:33 +00:00
hot reload, search refactor begin
This commit is contained in:
@@ -5,20 +5,64 @@ import (
|
||||
"html/template"
|
||||
"io"
|
||||
"io/fs"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/Theodor-Springmann-Stiftung/musenalm/helpers/functions"
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
"golang.org/x/net/websocket"
|
||||
)
|
||||
|
||||
const (
|
||||
ASSETS_URL_PREFIX = "/assets"
|
||||
RELOAD_TEMPLATE = `
|
||||
<script type="module">
|
||||
(function () {
|
||||
let relto = -1;
|
||||
const scheme = location.protocol === "https:" ? "wss" : "ws";
|
||||
// Hardcode port 9000 here:
|
||||
const url = scheme + "://" + location.hostname + ":9000/pb/reload";
|
||||
|
||||
function connect() {
|
||||
const socket = new WebSocket(url);
|
||||
|
||||
socket.addEventListener("open", function () {
|
||||
console.log("Reload socket connected (port 9000).");
|
||||
});
|
||||
|
||||
socket.addEventListener("message", function (evt) {
|
||||
if (evt.data === "reload") {
|
||||
console.log("Received reload signal. Reloading...");
|
||||
if (relto !== -1) clearTimeout(relto);
|
||||
relto = setTimeout(() => location.reload(), 0);
|
||||
}
|
||||
});
|
||||
|
||||
socket.addEventListener("close", function () {
|
||||
console.log("Reload socket closed. Reconnecting in 3 seconds...");
|
||||
setTimeout(connect, 3000);
|
||||
});
|
||||
|
||||
socket.addEventListener("error", function (err) {
|
||||
console.error("Reload socket error:", err);
|
||||
// We'll let onclose handle reconnection.
|
||||
});
|
||||
}
|
||||
|
||||
// Initiate the first connection attempt.
|
||||
connect();
|
||||
})();
|
||||
</script>
|
||||
`
|
||||
)
|
||||
|
||||
type Engine struct {
|
||||
regmu *sync.Mutex
|
||||
regmu *sync.Mutex
|
||||
debug bool
|
||||
ws *WsServer
|
||||
onceWS sync.Once
|
||||
|
||||
// NOTE: LayoutRegistry and TemplateRegistry have their own syncronization & cache and do not require a mutex here
|
||||
LayoutRegistry *LayoutRegistry
|
||||
@@ -44,6 +88,26 @@ func NewEngine(layouts, templates *fs.FS) *Engine {
|
||||
return &e
|
||||
}
|
||||
|
||||
func (e *Engine) Debug() {
|
||||
e.debug = true
|
||||
|
||||
e.onceWS.Do(func() {
|
||||
e.ws = NewWsServer()
|
||||
go e.startWsServerOnPort9000()
|
||||
})
|
||||
}
|
||||
|
||||
func (e *Engine) startWsServerOnPort9000() {
|
||||
// We'll create a basic default mux here and mount /pb/reload
|
||||
mux := http.NewServeMux()
|
||||
mux.Handle("/pb/reload", websocket.Handler(e.ws.Handler))
|
||||
|
||||
log.Println("[Engine Debug] Starting separate WebSocket server on :9000 for live reload...")
|
||||
if err := http.ListenAndServe(":9000", mux); err != nil {
|
||||
log.Println("[Engine Debug] WebSocket server error:", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Engine) funcs() error {
|
||||
e.mu.Lock()
|
||||
e.mu.Unlock()
|
||||
@@ -113,6 +177,12 @@ func (e *Engine) Reload() {
|
||||
e.Load()
|
||||
}
|
||||
|
||||
func (e *Engine) Refresh() {
|
||||
if e.debug && e.ws != nil {
|
||||
e.ws.BroadcastReload()
|
||||
}
|
||||
}
|
||||
|
||||
// INFO: fn is a function that returns either one value or two values, the second one being an error
|
||||
func (e *Engine) AddFunc(name string, fn interface{}) {
|
||||
e.mu.Lock()
|
||||
@@ -234,7 +304,15 @@ func (e *Engine) Response200(request *core.RequestEvent, path string, ld map[str
|
||||
return e.Response500(request, err, ld)
|
||||
}
|
||||
|
||||
return request.HTML(http.StatusOK, builder.String())
|
||||
tstring := builder.String()
|
||||
if e.debug {
|
||||
idx := strings.LastIndex(tstring, "</body>")
|
||||
if idx != -1 {
|
||||
tstring = tstring[:idx] + RELOAD_TEMPLATE + tstring[idx:]
|
||||
}
|
||||
}
|
||||
|
||||
return request.HTML(http.StatusOK, tstring)
|
||||
}
|
||||
|
||||
func requestData(request *core.RequestEvent) map[string]interface{} {
|
||||
|
||||
57
templating/ws.go
Normal file
57
templating/ws.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package templating
|
||||
|
||||
import (
|
||||
"log"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/net/websocket"
|
||||
)
|
||||
|
||||
// WsServer manages all active WebSocket connections so we can broadcast.
|
||||
type WsServer struct {
|
||||
mu sync.Mutex
|
||||
conns map[*websocket.Conn]bool
|
||||
}
|
||||
|
||||
// NewWsServer creates a WsServer.
|
||||
func NewWsServer() *WsServer {
|
||||
return &WsServer{
|
||||
conns: make(map[*websocket.Conn]bool),
|
||||
}
|
||||
}
|
||||
|
||||
// Handler is invoked for each new WebSocket connection.
|
||||
func (s *WsServer) Handler(conn *websocket.Conn) {
|
||||
s.mu.Lock()
|
||||
s.conns[conn] = true
|
||||
s.mu.Unlock()
|
||||
log.Println("[WsServer] Connected:", conn.RemoteAddr())
|
||||
|
||||
// Read in a loop until an error (client disconnect).
|
||||
var msg string
|
||||
for {
|
||||
if err := websocket.Message.Receive(conn, &msg); err != nil {
|
||||
log.Println("[WsServer] Disconnected:", conn.RemoteAddr())
|
||||
s.mu.Lock()
|
||||
delete(s.conns, conn)
|
||||
s.mu.Unlock()
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BroadcastReload sends a "reload" message to all connected clients.
|
||||
func (s *WsServer) BroadcastReload() {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
for conn := range s.conns {
|
||||
err := websocket.Message.Send(conn, "reload")
|
||||
if err != nil {
|
||||
log.Println("[WsServer] Broadcast error:", err)
|
||||
conn.Close()
|
||||
delete(s.conns, conn)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user