129 lines
3.3 KiB
Go
129 lines
3.3 KiB
Go
package learning
|
|
|
|
import (
|
|
"context"
|
|
"embed"
|
|
"io"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"codeberg.org/arimelody/ari-stream-tools/broadcast"
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
//go:embed public
|
|
var publicFS embed.FS
|
|
//go:embed pages
|
|
var pagesFS embed.FS
|
|
|
|
type (
|
|
ServiceConfig struct {
|
|
TitleFilePath string
|
|
}
|
|
|
|
Service struct {
|
|
cfg ServiceConfig
|
|
|
|
titleFileMutex sync.Mutex
|
|
titleLastModTime time.Time
|
|
titleText string
|
|
titleUpdated chan string
|
|
titleUpdatedBroadcast broadcast.BroadcastChannel[string]
|
|
}
|
|
)
|
|
|
|
func New(ctx context.Context, cfg ServiceConfig) *Service {
|
|
titleText := []byte{}
|
|
titleUpdated := make(chan string)
|
|
|
|
if _, err := os.Stat(cfg.TitleFilePath); os.IsNotExist(err) {
|
|
log.Printf("Learning title file [%s] does not exist, creating one...", cfg.TitleFilePath)
|
|
os.WriteFile(cfg.TitleFilePath, []byte("Untitled"), 0644)
|
|
titleText = []byte("Untitled")
|
|
} else if err != nil {
|
|
log.Fatalf("Failed to stat title file: %v", err)
|
|
} else {
|
|
titleText, err = os.ReadFile(cfg.TitleFilePath)
|
|
if err != nil { log.Fatalf("Failed to read title file: %v", err) }
|
|
}
|
|
|
|
srv := &Service{
|
|
cfg: cfg,
|
|
titleFileMutex: sync.Mutex{},
|
|
titleLastModTime: time.Unix(0, 0),
|
|
titleText: string(titleText),
|
|
titleUpdated: titleUpdated,
|
|
titleUpdatedBroadcast: broadcast.NewBroadcastChannel(ctx, titleUpdated),
|
|
}
|
|
|
|
return srv
|
|
}
|
|
|
|
func (srv *Service) BindRoutes(group *gin.RouterGroup) {
|
|
group.GET("/public/*path", func(ctx *gin.Context) {
|
|
path := strings.TrimPrefix(ctx.Request.URL.Path, "/learning/")
|
|
http.ServeFileFS(ctx.Writer, ctx.Request, publicFS, path)
|
|
})
|
|
|
|
group.GET("/sse", func(ctx *gin.Context) {
|
|
ctx.Header("connection", "keep-alive")
|
|
|
|
ctx.SSEvent("title-update", srv.titleText)
|
|
|
|
titleUpdated := srv.titleUpdatedBroadcast.Subscribe()
|
|
|
|
ticker := time.NewTicker(10 * time.Millisecond)
|
|
ctx.Stream(func(w io.Writer) bool {
|
|
select {
|
|
case titleText := <-titleUpdated:
|
|
ctx.SSEvent("title-update", titleText)
|
|
case <-ticker.C:
|
|
}
|
|
return true
|
|
})
|
|
|
|
srv.titleUpdatedBroadcast.Cancel(titleUpdated)
|
|
})
|
|
|
|
group.POST("/title", func(ctx *gin.Context) {
|
|
data, err := io.ReadAll(ctx.Request.Body)
|
|
if err != nil {
|
|
log.Printf("Failed to read request body: %v", err)
|
|
ctx.String(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
|
return
|
|
}
|
|
log.Printf("Waiting on title file lock...")
|
|
srv.titleFileMutex.Lock()
|
|
defer srv.titleFileMutex.Unlock()
|
|
log.Printf("Got it!")
|
|
file, err := os.OpenFile(srv.cfg.TitleFilePath, os.O_RDWR | os.O_CREATE | os.O_TRUNC, 0)
|
|
if err != nil {
|
|
log.Printf("Failed to open title file: %v", err)
|
|
ctx.String(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
|
return
|
|
}
|
|
if _, err := file.Write(data); err != nil {
|
|
log.Printf("Failed to write to title file: %v", err)
|
|
ctx.String(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
|
return
|
|
}
|
|
|
|
srv.titleUpdated<-string(data)
|
|
|
|
ctx.String(http.StatusOK, http.StatusText(http.StatusOK))
|
|
})
|
|
|
|
group.GET("/hello", func(ctx *gin.Context) {
|
|
http.ServeFileFS(ctx.Writer, ctx.Request, pagesFS, "pages/index.html")
|
|
})
|
|
group.GET("/brb", func(ctx *gin.Context) {
|
|
http.ServeFileFS(ctx.Writer, ctx.Request, pagesFS, "pages/brb.html")
|
|
})
|
|
group.GET("/bye", func(ctx *gin.Context) {
|
|
http.ServeFileFS(ctx.Writer, ctx.Request, pagesFS, "pages/bye.html")
|
|
})
|
|
}
|