waveterm/pkg/wconfig/filewatcher.go
Evan Simkowitz 936d4bfb30
Migrate websocket eventbus messages to wps (#367)
This migrates all remaining eventbus events sent over the websocket to
use the wps interface. WPS is more flexible for registering events and
callbacks and provides support for more reliable unsubscribes and
resubscribes.
2024-09-11 18:03:55 -07:00

139 lines
2.8 KiB
Go

// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
package wconfig
import (
"log"
"path/filepath"
"regexp"
"sync"
"github.com/fsnotify/fsnotify"
"github.com/wavetermdev/waveterm/pkg/wavebase"
"github.com/wavetermdev/waveterm/pkg/wps"
)
var configDirAbsPath = filepath.Join(wavebase.GetWaveHomeDir(), wavebase.ConfigDir)
var instance *Watcher
var once sync.Once
type Watcher struct {
initialized bool
watcher *fsnotify.Watcher
mutex sync.Mutex
fullConfig FullConfigType
}
type WatcherUpdate struct {
FullConfig FullConfigType `json:"fullconfig"`
}
// GetWatcher returns the singleton instance of the Watcher
func GetWatcher() *Watcher {
once.Do(func() {
watcher, err := fsnotify.NewWatcher()
if err != nil {
log.Printf("failed to create file watcher: %v", err)
return
}
instance = &Watcher{watcher: watcher}
err = instance.watcher.Add(configDirAbsPath)
if err != nil {
log.Printf("failed to add path %s to watcher: %v", configDirAbsPath, err)
}
})
return instance
}
func (w *Watcher) Start() {
w.mutex.Lock()
defer w.mutex.Unlock()
log.Printf("starting file watcher\n")
w.initialized = true
w.sendInitialValues()
go func() {
for {
select {
case event, ok := <-w.watcher.Events:
if !ok {
return
}
w.handleEvent(event)
case err, ok := <-w.watcher.Errors:
if !ok {
return
}
log.Println("watcher error:", err)
}
}
}()
}
// for initial values, exit on first error
func (w *Watcher) sendInitialValues() error {
w.fullConfig = ReadFullConfig()
message := WatcherUpdate{
FullConfig: w.fullConfig,
}
w.broadcast(message)
return nil
}
func (w *Watcher) Close() {
w.mutex.Lock()
defer w.mutex.Unlock()
if w.watcher != nil {
w.watcher.Close()
w.watcher = nil
log.Println("file watcher closed")
}
}
func (w *Watcher) broadcast(message WatcherUpdate) {
// send to frontend
wps.Broker.Publish(wps.WaveEvent{
Event: wps.Event_Config,
Data: message,
})
}
func (w *Watcher) GetFullConfig() FullConfigType {
w.mutex.Lock()
defer w.mutex.Unlock()
return w.fullConfig
}
func (w *Watcher) handleEvent(event fsnotify.Event) {
w.mutex.Lock()
defer w.mutex.Unlock()
fileName := filepath.ToSlash(event.Name)
if event.Op == fsnotify.Chmod {
return
}
if !isValidSubSettingsFileName(fileName) {
return
}
w.handleSettingsFileEvent(event, fileName)
}
var validFileRe = regexp.MustCompile(`^[a-zA-Z0-9_@.-]+\.json$`)
func isValidSubSettingsFileName(fileName string) bool {
if filepath.Ext(fileName) != ".json" {
return false
}
baseName := filepath.Base(fileName)
return validFileRe.MatchString(baseName)
}
func (w *Watcher) handleSettingsFileEvent(event fsnotify.Event, fileName string) {
fullConfig := ReadFullConfig()
w.fullConfig = fullConfig
w.broadcast(WatcherUpdate{FullConfig: w.fullConfig})
}