add watch mode
This commit is contained in:
parent
88c9bd90af
commit
cc2cb0c682
@ -308,3 +308,22 @@ func RegisterStaticHandler(urlPrefix, rootPath string) {
|
|||||||
|
|
||||||
staticHandlers[urlPrefix] = fs
|
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
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
"Moonshark/modules/http"
|
"Moonshark/modules/http"
|
||||||
"Moonshark/state"
|
"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() {
|
func main() {
|
||||||
if len(os.Args) < 2 {
|
flag.Parse()
|
||||||
fmt.Fprintf(os.Stderr, "Usage: %s <script.lua>\n", filepath.Base(os.Args[0]))
|
|
||||||
|
if flag.NArg() < 1 {
|
||||||
|
fmt.Fprintf(os.Stderr, "Usage: %s [--watch|-w] <script.lua>\n", filepath.Base(os.Args[0]))
|
||||||
os.Exit(1)
|
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)
|
luaState, err := state.NewFromScript(scriptPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||||
@ -27,28 +45,96 @@ func main() {
|
|||||||
}
|
}
|
||||||
defer luaState.Close()
|
defer luaState.Close()
|
||||||
|
|
||||||
// Execute the script
|
|
||||||
if err := luaState.ExecuteFile(scriptPath); err != nil {
|
if err := luaState.ExecuteFile(scriptPath); err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if HTTP servers are running
|
|
||||||
if http.HasActiveServers() {
|
if http.HasActiveServers() {
|
||||||
// Set up signal handling for graceful shutdown
|
|
||||||
sigChan := make(chan os.Signal, 1)
|
sigChan := make(chan os.Signal, 1)
|
||||||
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
|
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
|
||||||
fmt.Println("HTTP servers running. Press Ctrl+C to exit.")
|
fmt.Println("HTTP servers running. Press Ctrl+C to exit.")
|
||||||
|
|
||||||
// Wait for either signal or servers to close
|
|
||||||
go func() {
|
go func() {
|
||||||
<-sigChan
|
<-sigChan
|
||||||
fmt.Println("\nShutting down...")
|
fmt.Println("\nShutting down...")
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Wait for all servers to finish
|
|
||||||
http.WaitForServers()
|
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