add watch mode
This commit is contained in:
parent
88c9bd90af
commit
cc2cb0c682
@ -308,3 +308,22 @@ func RegisterStaticHandler(urlPrefix, rootPath string) {
|
||||
|
||||
staticHandlers[urlPrefix] = fs
|
||||
}
|
||||
|
||||
func StopAllServers() {
|
||||
globalMu.Lock()
|
||||
defer globalMu.Unlock()
|
||||
|
||||
if globalWorkerPool != nil {
|
||||
globalWorkerPool.Close()
|
||||
globalWorkerPool = nil
|
||||
}
|
||||
|
||||
if globalServer != nil {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
globalServer.ShutdownWithContext(ctx)
|
||||
globalServer = nil
|
||||
}
|
||||
|
||||
serverRunning = false
|
||||
}
|
||||
|
104
moonshark.go
104
moonshark.go
@ -1,25 +1,43 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"Moonshark/modules/http"
|
||||
"Moonshark/state"
|
||||
)
|
||||
|
||||
var (
|
||||
watchFlag = flag.Bool("watch", false, "Watch script files for changes and restart")
|
||||
wFlag = flag.Bool("w", false, "Watch script files for changes and restart (short)")
|
||||
)
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 2 {
|
||||
fmt.Fprintf(os.Stderr, "Usage: %s <script.lua>\n", filepath.Base(os.Args[0]))
|
||||
flag.Parse()
|
||||
|
||||
if flag.NArg() < 1 {
|
||||
fmt.Fprintf(os.Stderr, "Usage: %s [--watch|-w] <script.lua>\n", filepath.Base(os.Args[0]))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
scriptPath := os.Args[1]
|
||||
scriptPath := flag.Arg(0)
|
||||
watchMode := *watchFlag || *wFlag
|
||||
|
||||
// Create state configured for the script
|
||||
if watchMode {
|
||||
runWithWatcher(scriptPath)
|
||||
} else {
|
||||
runOnce(scriptPath)
|
||||
}
|
||||
}
|
||||
|
||||
func runOnce(scriptPath string) {
|
||||
luaState, err := state.NewFromScript(scriptPath)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||
@ -27,28 +45,96 @@ func main() {
|
||||
}
|
||||
defer luaState.Close()
|
||||
|
||||
// Execute the script
|
||||
if err := luaState.ExecuteFile(scriptPath); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Check if HTTP servers are running
|
||||
if http.HasActiveServers() {
|
||||
// Set up signal handling for graceful shutdown
|
||||
sigChan := make(chan os.Signal, 1)
|
||||
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
|
||||
|
||||
fmt.Println("HTTP servers running. Press Ctrl+C to exit.")
|
||||
|
||||
// Wait for either signal or servers to close
|
||||
go func() {
|
||||
<-sigChan
|
||||
fmt.Println("\nShutting down...")
|
||||
os.Exit(0)
|
||||
}()
|
||||
|
||||
// Wait for all servers to finish
|
||||
http.WaitForServers()
|
||||
}
|
||||
}
|
||||
|
||||
func runWithWatcher(scriptPath string) {
|
||||
watcher, err := NewFileWatcher(500) // 500ms debounce
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to create file watcher: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer watcher.Close()
|
||||
|
||||
if err := watcher.DiscoverRequiredFiles(scriptPath); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to watch files: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
restartCh := watcher.Start()
|
||||
sigChan := make(chan os.Signal, 1)
|
||||
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
|
||||
|
||||
fmt.Printf("Starting %s in watch mode...\n", scriptPath)
|
||||
|
||||
for {
|
||||
// Clear cache before each run
|
||||
state.ClearCache()
|
||||
|
||||
// Create and run state
|
||||
luaState, err := state.NewFromScript(scriptPath)
|
||||
if err != nil {
|
||||
log.Printf("Error creating state: %v", err)
|
||||
time.Sleep(1 * time.Second)
|
||||
continue
|
||||
}
|
||||
|
||||
if err := luaState.ExecuteFile(scriptPath); err != nil {
|
||||
log.Printf("Execution error: %v", err)
|
||||
luaState.Close()
|
||||
time.Sleep(1 * time.Second)
|
||||
continue
|
||||
}
|
||||
|
||||
// If not a long-running process, wait for changes and restart
|
||||
if !http.HasActiveServers() {
|
||||
fmt.Println("Script completed. Waiting for changes...")
|
||||
luaState.Close()
|
||||
|
||||
select {
|
||||
case <-restartCh:
|
||||
fmt.Println("Files changed, restarting...")
|
||||
continue
|
||||
case <-sigChan:
|
||||
fmt.Println("\nExiting...")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Long-running process - wait for restart signal or exit signal
|
||||
fmt.Println("HTTP servers running. Watching for file changes...")
|
||||
|
||||
select {
|
||||
case <-restartCh:
|
||||
fmt.Println("Files changed, restarting...")
|
||||
http.StopAllServers()
|
||||
luaState.Close()
|
||||
time.Sleep(100 * time.Millisecond) // Brief pause for cleanup
|
||||
continue
|
||||
|
||||
case <-sigChan:
|
||||
fmt.Println("\nShutting down...")
|
||||
http.StopAllServers()
|
||||
luaState.Close()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
136
watcher.go
Normal file
136
watcher.go
Normal file
@ -0,0 +1,136 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type FileWatcher struct {
|
||||
files map[string]time.Time
|
||||
mu sync.RWMutex
|
||||
restartCh chan bool
|
||||
stopCh chan bool
|
||||
debounceMs int
|
||||
lastEvent time.Time
|
||||
pollMs int
|
||||
}
|
||||
|
||||
func NewFileWatcher(debounceMs int) (*FileWatcher, error) {
|
||||
return &FileWatcher{
|
||||
files: make(map[string]time.Time),
|
||||
restartCh: make(chan bool, 1),
|
||||
stopCh: make(chan bool, 1),
|
||||
debounceMs: debounceMs,
|
||||
pollMs: 250, // Poll every 250ms
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (fw *FileWatcher) AddFile(path string) error {
|
||||
absPath, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
info, err := os.Stat(absPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fw.mu.Lock()
|
||||
fw.files[absPath] = info.ModTime()
|
||||
fw.mu.Unlock()
|
||||
|
||||
fmt.Printf("Watching: %s\n", absPath)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fw *FileWatcher) AddDirectory(dir string) error {
|
||||
return filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !info.IsDir() && strings.HasSuffix(path, ".lua") {
|
||||
return fw.AddFile(path)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (fw *FileWatcher) Start() <-chan bool {
|
||||
go fw.pollLoop()
|
||||
return fw.restartCh
|
||||
}
|
||||
|
||||
func (fw *FileWatcher) pollLoop() {
|
||||
ticker := time.NewTicker(time.Duration(fw.pollMs) * time.Millisecond)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-fw.stopCh:
|
||||
return
|
||||
case <-ticker.C:
|
||||
fw.checkFiles()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (fw *FileWatcher) checkFiles() {
|
||||
fw.mu.RLock()
|
||||
files := make(map[string]time.Time, len(fw.files))
|
||||
for path, modTime := range fw.files {
|
||||
files[path] = modTime
|
||||
}
|
||||
fw.mu.RUnlock()
|
||||
|
||||
changed := false
|
||||
for path, lastMod := range files {
|
||||
info, err := os.Stat(path)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if info.ModTime().After(lastMod) {
|
||||
fw.mu.Lock()
|
||||
fw.files[path] = info.ModTime()
|
||||
fw.mu.Unlock()
|
||||
|
||||
now := time.Now()
|
||||
if now.Sub(fw.lastEvent) > time.Duration(fw.debounceMs)*time.Millisecond {
|
||||
fw.lastEvent = now
|
||||
log.Printf("File changed: %s", path)
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if changed {
|
||||
select {
|
||||
case fw.restartCh <- true:
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (fw *FileWatcher) Close() error {
|
||||
select {
|
||||
case fw.stopCh <- true:
|
||||
default:
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fw *FileWatcher) DiscoverRequiredFiles(scriptPath string) error {
|
||||
if err := fw.AddFile(scriptPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
scriptDir := filepath.Dir(scriptPath)
|
||||
return fw.AddDirectory(scriptDir)
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user