117 lines
2.7 KiB
Go
117 lines
2.7 KiB
Go
package main
|
|
|
|
import (
|
|
// External
|
|
"github.com/chromedp/cdproto/target"
|
|
"github.com/chromedp/chromedp"
|
|
"github.com/google/uuid"
|
|
|
|
// Standard
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
"os/exec"
|
|
"sync"
|
|
)
|
|
|
|
var (
|
|
sites map[string]*Site
|
|
siteLock *sync.Mutex
|
|
)
|
|
|
|
type Site struct {
|
|
UUID string
|
|
URL string
|
|
Watch string
|
|
WatchLoop *exec.Cmd `json:"-"`
|
|
StopLoop bool
|
|
Context context.Context `json:"-"`
|
|
}
|
|
|
|
func init() {
|
|
siteLock = &sync.Mutex{}
|
|
sites = make(map[string]*Site)
|
|
}
|
|
|
|
func NewSite(wsURL, url, watch string) (s Site, err error) {
|
|
s.UUID = uuid.NewString()
|
|
s.URL = url
|
|
s.Watch = watch
|
|
|
|
allocatorContext, _ := chromedp.NewRemoteAllocator(context.Background(), wsURL)
|
|
|
|
// Used to optionally enable debugging output.
|
|
var opts []chromedp.ContextOption
|
|
if flagVerbose {
|
|
opts = append(opts, chromedp.WithDebugf(log.Printf))
|
|
}
|
|
s.Context, _ = chromedp.NewContext(allocatorContext, opts...)
|
|
|
|
// A tab is started up and navigated to in order to get a context for this instance.
|
|
if err = chromedp.Run(
|
|
s.Context,
|
|
chromedp.Navigate(url),
|
|
); err != nil {
|
|
err = fmt.Errorf("Failed getting body of %s: %v", url, err)
|
|
return
|
|
}
|
|
|
|
siteLock.Lock()
|
|
sites[s.UUID] = &s
|
|
defer siteLock.Unlock()
|
|
|
|
// chromedp.Run crashes something really hard and undetectable with recover().
|
|
// ListenBrowser gives the possibility to cancel the watch loop.
|
|
chromedp.ListenBrowser(s.Context, func(ev any) {
|
|
_, event1 := ev.(*target.EventDetachedFromTarget)
|
|
_, event2 := ev.(*target.EventTargetCrashed)
|
|
_, event3 := ev.(*target.EventTargetDestroyed)
|
|
if event1 || event2 || event3 {
|
|
log.Printf("Stopping loop for %s\n", s.UUID)
|
|
s.StopLoop = true
|
|
if s.WatchLoop != nil {
|
|
log.Printf("Killing inotifywait for %s\n", s.UUID)
|
|
s.WatchLoop.Process.Kill()
|
|
}
|
|
|
|
}
|
|
})
|
|
|
|
// Start watching file/dir for changes.
|
|
go s.watchLoop()
|
|
|
|
return
|
|
}
|
|
|
|
func (s *Site) watchLoop() {
|
|
for !s.StopLoop {
|
|
log.Println("Starting watching " + s.Watch)
|
|
s.WatchLoop = exec.Command("inotifywait", "-e", "close_write", s.Watch)
|
|
if err := s.WatchLoop.Run(); err != nil {
|
|
log.Println(err)
|
|
}
|
|
s.ReloadCSS()
|
|
}
|
|
log.Printf("Stopping watch loop [%s]\n", s.UUID)
|
|
}
|
|
|
|
func (s *Site) ReloadCSS() {
|
|
var buf []byte
|
|
|
|
cssReloadScript := chromedp.Evaluate(`
|
|
(()=>{
|
|
const stylesheets = document.querySelectorAll('link[rel="stylesheet"]')
|
|
for (const ss of stylesheets) {
|
|
const url = URL.parse(ss.href, location.protocol + '//' + location.host)
|
|
const nextReloadCounter = parseInt(url.searchParams.get('reload') || 0) + 1
|
|
url.searchParams.set('reload', nextReloadCounter)
|
|
ss.href = url.toString()
|
|
}
|
|
})()
|
|
`, &buf)
|
|
|
|
err := chromedp.Run(s.Context, cssReloadScript)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
}
|
|
}
|