From b10910d2294431b1659aa18fa4e90eea060b759d Mon Sep 17 00:00:00 2001 From: Red Adaya Date: Mon, 15 Apr 2024 10:02:51 +0800 Subject: [PATCH] make methods cohesive --- wavesrv/pkg/configstore/configstore.go | 146 -------------------- wavesrv/pkg/configstore/termthemes.go | 184 +++++++++++++++++++++++++ 2 files changed, 184 insertions(+), 146 deletions(-) delete mode 100644 wavesrv/pkg/configstore/configstore.go create mode 100644 wavesrv/pkg/configstore/termthemes.go diff --git a/wavesrv/pkg/configstore/configstore.go b/wavesrv/pkg/configstore/configstore.go deleted file mode 100644 index 8782f51eb..000000000 --- a/wavesrv/pkg/configstore/configstore.go +++ /dev/null @@ -1,146 +0,0 @@ -package configstore - -import ( - "encoding/json" - "errors" - "fmt" - "log" - "os" - "path" - "path/filepath" - - "github.com/fsnotify/fsnotify" - "github.com/wavetermdev/waveterm/wavesrv/pkg/scbase" - "github.com/wavetermdev/waveterm/wavesrv/pkg/scbus" - "github.com/wavetermdev/waveterm/wavesrv/pkg/scws" -) - -const ( - TermThemesTypeStr = "termthemes" - TermThemeDir = "config/terminal-themes/" -) - -type TermThemesType map[string]map[string]string - -func (tt TermThemesType) GetType() string { - return TermThemesTypeStr -} - -type TermThemes struct { - Themes TermThemesType - State *scws.WSState // Using WSState to manage WebSocket operations -} - -// Factory function to create a new TermThemes instance with WSState. -func MakeTermThemes(state *scws.WSState) *TermThemes { - return &TermThemes{ - Themes: make(TermThemesType), - State: state, - } -} - -// LoadAndWatchThemes initializes the file watching and loads initial data. -func (t *TermThemes) LoadAndWatchThemes() { - dirPath := path.Join(scbase.GetWaveHomeDir(), TermThemeDir) - if _, err := os.Stat(dirPath); errors.Is(err, os.ErrNotExist) { - log.Printf("directory does not exist: %s", dirPath) - return - } - - // Load existing files and handle JSON conversion - if err := t.loadInitialFiles(dirPath); err != nil { - log.Println("failed to load initial files:", err) - return - } - - update := scbus.MakeUpdatePacket() - update.AddUpdate(t.Themes) - - if err := t.State.WriteUpdate(update); err != nil { - log.Printf("error sending initial file data via WebSocket: %v", err) - } - - t.setupFileWatcher(dirPath) -} - -// Reads all JSON files in the specified directory. -func (t *TermThemes) loadInitialFiles(dirPath string) error { - files, err := os.ReadDir(dirPath) - if err != nil { - return err - } - - for _, file := range files { - if filepath.Ext(file.Name()) == ".json" { - filePath := filepath.Join(dirPath, file.Name()) - data, err := os.ReadFile(filePath) - if err != nil { - log.Printf("error reading file %s: %v", filePath, err) - continue - } - - content := make(map[string]string) - if err := json.Unmarshal(data, &content); err != nil { - log.Printf("error unmarshalling JSON from file %s: %v", filePath, err) - continue - } - t.Themes[file.Name()] = content - } - } - return nil -} - -func (t *TermThemes) setupFileWatcher(dirPath string) { - watcher, err := fsnotify.NewWatcher() - if err != nil { - log.Println("error creating file watcher:", err) - return - } - - go func() { - defer func() { - watcher.Close() - log.Println("file watcher stopped.") - }() - - for { - select { - case event, ok := <-watcher.Events: - if !ok { - return - } - log.Printf("event: %s, Op: %v\n", event.Name, event.Op) - if event.Op&fsnotify.Write == fsnotify.Write && filepath.Ext(event.Name) == ".json" { - t.handleFileChange(event.Name) - } - case err, ok := <-watcher.Errors: - if !ok { - return - } - log.Println("watcher error:", err) - } - } - }() - - err = watcher.Add(dirPath) - if err != nil { - log.Println("error adding directory to watcher:", err) - } -} - -func (t *TermThemes) handleFileChange(filePath string) error { - data, err := os.ReadFile(filePath) - if err != nil { - return fmt.Errorf("error reading file %s: %v", filePath, err) - } - - var content map[string]string - if err := json.Unmarshal(data, &content); err != nil { - return fmt.Errorf("error unmarshalling JSON from file %s: %v", filePath, err) - } - - t.Themes[filepath.Base(filePath)] = content - update := scbus.MakeUpdatePacket() - update.AddUpdate(t.Themes) - return t.State.WriteUpdate(update) -} diff --git a/wavesrv/pkg/configstore/termthemes.go b/wavesrv/pkg/configstore/termthemes.go new file mode 100644 index 000000000..ec357feb1 --- /dev/null +++ b/wavesrv/pkg/configstore/termthemes.go @@ -0,0 +1,184 @@ +package configstore + +import ( + "encoding/json" + "errors" + "fmt" + "log" + "os" + "path" + "path/filepath" + + "github.com/fsnotify/fsnotify" + "github.com/wavetermdev/waveterm/wavesrv/pkg/scbase" + "github.com/wavetermdev/waveterm/wavesrv/pkg/scbus" + "github.com/wavetermdev/waveterm/wavesrv/pkg/scws" +) + +const ( + TermThemesTypeStr = "termthemes" + TermThemeDir = "config/terminal-themes/" +) + +type TermThemesType map[string]map[string]string + +func (tt TermThemesType) GetType() string { + return TermThemesTypeStr +} + +type TermThemes struct { + Themes TermThemesType + State *scws.WSState // Using WSState to manage WebSocket operations +} + +// Factory function to create a new TermThemes instance with WSState. +func MakeTermThemes(state *scws.WSState) *TermThemes { + return &TermThemes{ + Themes: make(TermThemesType), + State: state, + } +} + +// LoadAndWatchThemes initializes file scanning and sets up file watching. +func (t *TermThemes) LoadAndWatchThemes() { + dirPath := path.Join(scbase.GetWaveHomeDir(), TermThemeDir) + if _, err := os.Stat(dirPath); errors.Is(err, os.ErrNotExist) { + log.Printf("directory does not exist: %s", dirPath) + return + } + + if err := t.scanDirAndUpdate(dirPath); err != nil { + log.Printf("failed to scan directory and update themes: %v", err) + return + } + + t.setupFileWatcher(dirPath) +} + +// scanDirAndUpdate scans the directory and updates themes. +func (t *TermThemes) scanDirAndUpdate(dirPath string) error { + log.Println("performing directory scan...") + newThemes, err := t.scanDir(dirPath) + if err != nil { + return err + } + + t.Themes = newThemes + return t.updateThemes() +} + +// scanDir reads all JSON files in the specified directory. +func (t *TermThemes) scanDir(dirPath string) (TermThemesType, error) { + newThemes := make(TermThemesType) + + files, err := os.ReadDir(dirPath) + if err != nil { + return nil, err + } + + for _, file := range files { + if filepath.Ext(file.Name()) == ".json" { + filePath := filepath.Join(dirPath, file.Name()) + content, err := t.readFileContents(filePath) + if err != nil { + log.Printf("error reading file %s: %v", filePath, err) + continue + } + newThemes[file.Name()] = content + } + } + + return newThemes, nil +} + +// setupFileWatcher sets up a file system watcher on the given directory. +func (t *TermThemes) setupFileWatcher(dirPath string) { + watcher, err := fsnotify.NewWatcher() + if err != nil { + log.Println("error creating file watcher:", err) + return + } + go func() { + defer func() { + watcher.Close() + log.Println("file watcher stopped.") + }() + for { + select { + case event, ok := <-watcher.Events: + if !ok { + return + } + log.Printf("event: %s, Op: %v", event.Name, event.Op) + t.handleFileEvent(event) + case err, ok := <-watcher.Errors: + if !ok { + return + } + log.Println("watcher error:", err) + } + } + }() + + if err := watcher.Add(dirPath); err != nil { + log.Println("error adding directory to watcher:", err) + } +} + +// handleFileEvent handles file system events and triggers a directory rescan only on rename events. +func (t *TermThemes) handleFileEvent(event fsnotify.Event) { + filePath := event.Name + fileName := filepath.Base(filePath) + + // Normalize the file path for consistency across platforms + normalizedPath := filepath.ToSlash(filePath) + + switch event.Op { + case fsnotify.Write, fsnotify.Create: + log.Println("performing write or create event...") + // For write and create events, update or add the file to the Themes map. + content, err := t.readFileContents(normalizedPath) + if err != nil { + log.Printf("error reading file %s: %v", normalizedPath, err) + return + } + t.Themes[fileName] = content + t.updateThemes() // Update themes after adding or changing the file. + + case fsnotify.Remove: + log.Println("performing delete event...") + // For remove events, delete the file from the Themes map. + delete(t.Themes, fileName) + t.updateThemes() // Update themes after removing the file. + + case fsnotify.Rename: + // Rename might affect file identity; rescan to ensure accuracy + log.Printf("rename event detected, rescanning directory: %s", normalizedPath) + if err := t.scanDirAndUpdate(path.Dir(normalizedPath)); err != nil { + log.Printf("error rescanning directory after rename: %v", err) + } + } +} + +// readFileContents reads and unmarshals the JSON content from a file. +func (t *TermThemes) readFileContents(filePath string) (map[string]string, error) { + data, err := os.ReadFile(filePath) + if err != nil { + return nil, err + } + var content map[string]string + if err := json.Unmarshal(data, &content); err != nil { + return nil, err + } + return content, nil +} + +// updateThemes sends an update of all themes via WebSocket. +func (t *TermThemes) updateThemes() error { + update := scbus.MakeUpdatePacket() + update.AddUpdate(t.Themes) + if err := t.State.WriteUpdate(update); err != nil { + return fmt.Errorf("error sending updated themes via WebSocket: %v", err) + } + return nil +}