[change] Refactoring Watch
This commit is contained in:
parent
6283b6e34c
commit
7517e4652a
7 changed files with 391 additions and 407 deletions
|
|
@ -45,6 +45,7 @@ var langs = map[string]string{
|
|||
"NEXT:": "下一步:",
|
||||
"Listening": " 监听",
|
||||
"✨LISTENING✨": "✨服务正在运行✨",
|
||||
"✨STOPPED✨": "✨服务已停止✨",
|
||||
"SessionPort": "会话服务端口",
|
||||
"Force migrate": "强制更新数据表结构",
|
||||
"Migrate is not allowed on production mode.": "Migrate 不能再生产环境下使用",
|
||||
|
|
|
|||
20
cmd/start.go
20
cmd/start.go
|
|
@ -3,8 +3,10 @@ package cmd
|
|||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/spf13/cobra"
|
||||
|
|
@ -24,6 +26,11 @@ var startCmd = &cobra.Command{
|
|||
Short: L("Start Engine"),
|
||||
Long: L("Start Engine"),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
|
||||
// recive interrupt signal
|
||||
interrupt := make(chan os.Signal, 1)
|
||||
signal.Notify(interrupt, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT)
|
||||
|
||||
// defer service.Stop(func() { fmt.Println(L("Service stopped")) })
|
||||
Boot()
|
||||
|
||||
|
|
@ -94,9 +101,18 @@ var startCmd = &cobra.Command{
|
|||
if mode == "development" {
|
||||
service.Watch(config.Conf)
|
||||
}
|
||||
|
||||
go service.Start()
|
||||
fmt.Println(color.GreenString(L("✨LISTENING✨")))
|
||||
service.Start()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-interrupt:
|
||||
service.Stop(func() {
|
||||
fmt.Println(color.GreenString(L("✨STOPPED✨")))
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
|||
4
go.mod
4
go.mod
|
|
@ -8,7 +8,7 @@ require (
|
|||
github.com/caarlos0/env/v6 v6.9.1
|
||||
github.com/elazarl/go-bindata-assetfs v1.0.1
|
||||
github.com/fatih/color v1.13.0
|
||||
github.com/fsnotify/fsnotify v1.5.1
|
||||
github.com/fsnotify/fsnotify v1.5.4
|
||||
github.com/gin-gonic/gin v1.7.7
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible
|
||||
github.com/joho/godotenv v1.3.0
|
||||
|
|
@ -63,6 +63,7 @@ require (
|
|||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/richardlehane/mscfb v1.0.3 // indirect
|
||||
github.com/richardlehane/msoleps v1.0.1 // indirect
|
||||
github.com/robfig/cron/v3 v3.0.1 // indirect
|
||||
github.com/satori/go.uuid v1.2.0 // indirect
|
||||
github.com/sirupsen/logrus v1.8.1 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
|
|
@ -76,7 +77,6 @@ require (
|
|||
github.com/tidwall/rtred v0.1.2 // indirect
|
||||
github.com/tidwall/tinyqueue v0.1.1 // indirect
|
||||
github.com/ugorji/go/codec v1.1.7 // indirect
|
||||
github.com/robfig/cron/v3 v3.0.1 // indirect
|
||||
github.com/xuri/efp v0.0.0-20210322160811-ab561f5b45e3 // indirect
|
||||
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d // indirect
|
||||
golang.org/x/mod v0.4.2 // indirect
|
||||
|
|
|
|||
4
go.sum
4
go.sum
|
|
@ -97,6 +97,8 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
|
|||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=
|
||||
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
|
||||
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
|
||||
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
|
|
@ -581,6 +583,7 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
|
|
@ -773,6 +776,7 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8
|
|||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU=
|
||||
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/yaoapp/gou"
|
||||
"github.com/yaoapp/kun/log"
|
||||
"github.com/yaoapp/yao/config"
|
||||
"github.com/yaoapp/yao/share"
|
||||
)
|
||||
|
|
@ -68,3 +71,16 @@ func Stop(onComplete func()) {
|
|||
onComplete()
|
||||
}
|
||||
}
|
||||
|
||||
// StopWithContext stop with timeout
|
||||
func StopWithContext(ctx context.Context, onComplete func()) {
|
||||
shutdown <- true
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
log.Error("[STOP] canceled (%v)", ctx.Err())
|
||||
onComplete()
|
||||
case <-shutdownComplete:
|
||||
share.SessionStop()
|
||||
onComplete()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
600
service/watch.go
600
service/watch.go
|
|
@ -1,459 +1,253 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/yaoapp/gou"
|
||||
"github.com/yaoapp/yao/chart"
|
||||
"github.com/yaoapp/kun/log"
|
||||
"github.com/yaoapp/yao/config"
|
||||
"github.com/yaoapp/yao/engine"
|
||||
"github.com/yaoapp/yao/page"
|
||||
"github.com/yaoapp/yao/share"
|
||||
"github.com/yaoapp/yao/table"
|
||||
"github.com/yaoapp/yao/workflow"
|
||||
)
|
||||
|
||||
// Watch 监听应用目录文件变更
|
||||
func Watch(cfg config.Config) {
|
||||
if os.Getenv("YAO_DEV") != "" {
|
||||
WatchEngine(filepath.Join(os.Getenv("YAO_DEV"), "/yao"))
|
||||
}
|
||||
WatchModel(filepath.Join(cfg.Root, "models"), "")
|
||||
WatchAPI(filepath.Join(cfg.Root, "apis"), "")
|
||||
WatchFlow(filepath.Join(cfg.Root, "flows"), "")
|
||||
WatchPlugin(filepath.Join(cfg.Root, "plugins"))
|
||||
WatchTable(filepath.Join(cfg.Root, "tables"), "")
|
||||
WatchChart(filepath.Join(cfg.Root, "charts"), "")
|
||||
WatchPage(filepath.Join(cfg.Root, "pages"), "")
|
||||
WatchWorkFlow(filepath.Join(cfg.Root, "workflows"), "")
|
||||
|
||||
// 看板大屏
|
||||
WatchPage(filepath.Join(cfg.Root, "kanban"), "")
|
||||
WatchPage(filepath.Join(cfg.Root, "screen"), "")
|
||||
|
||||
// 监听脚本 & libs更新
|
||||
WatchGlobal(filepath.Join(cfg.Root, "libs"))
|
||||
WatchGlobal(filepath.Join(cfg.Root, "scripts"))
|
||||
var watchShutdown = make(chan bool, 1) // shutdown signal
|
||||
var watchReady = make(chan bool, 1) // ready signal
|
||||
var excludes = map[string]bool{"ui": true, "db": true, "data": true}
|
||||
var hanlders = map[string]func(root string, file string, event string, cfg config.Config){
|
||||
"models": watchModel,
|
||||
}
|
||||
|
||||
// WatchEngine 监听监听引擎内建数据变更
|
||||
func WatchEngine(root string) {
|
||||
root = share.DirAbs(root)
|
||||
WatchModel(filepath.Join(root, "models"), "xiang.")
|
||||
WatchAPI(filepath.Join(root, "apis"), "xiang.")
|
||||
WatchFlow(filepath.Join(root, "flows"), "xiang.")
|
||||
WatchTable(filepath.Join(root, "tables"), "xiang.")
|
||||
// Watch the application code change for hot update
|
||||
func Watch(cfg config.Config) (err error) {
|
||||
go func() { err = watchStart(cfg) }()
|
||||
select {
|
||||
case <-watchReady:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WatchGlobal 监听通用程序更新
|
||||
func WatchGlobal(root string) {
|
||||
if share.DirNotExists(root) {
|
||||
return
|
||||
// StopWatch stop watching the code change
|
||||
func StopWatch() {
|
||||
watchShutdown <- true
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
}
|
||||
|
||||
func watchStart(cfg config.Config) error {
|
||||
|
||||
root := cfg.Root
|
||||
|
||||
// recive interrupt signal
|
||||
interrupt := make(chan os.Signal, 1)
|
||||
signal.Notify(interrupt, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT)
|
||||
|
||||
shutdown := make(chan bool, 1)
|
||||
|
||||
watcher, err := fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
root = share.DirAbs(root)
|
||||
go share.Watch(root, func(op string, filename string) {
|
||||
if !strings.HasSuffix(filename, ".json") && !strings.HasSuffix(filename, ".js") {
|
||||
return
|
||||
defer watcher.Close()
|
||||
|
||||
root, err = filepath.Abs(root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dirs, err := ioutil.ReadDir(root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Add path
|
||||
for _, dir := range dirs {
|
||||
if !dir.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
// 重启服务器
|
||||
if op == "write" || op == "create" || op == "remove" || op == "rename" {
|
||||
err := engine.Load(config.Conf)
|
||||
name := dir.Name()
|
||||
if _, has := hanlders[name]; !has {
|
||||
continue
|
||||
}
|
||||
|
||||
filename := filepath.Join(root, name)
|
||||
err := watcher.Add(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println(color.GreenString("[Watch] Watching %s", name))
|
||||
log.Info("[Watch] Watching: %s", filename)
|
||||
|
||||
// sub dir
|
||||
err = filepath.WalkDir(filename, func(path string, d fs.DirEntry, err error) error {
|
||||
if !d.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Info("[Watch] Watching: %s", path)
|
||||
err = watcher.Add(path)
|
||||
if err != nil {
|
||||
fmt.Println(color.RedString("Fatal: %s", err.Error()))
|
||||
return
|
||||
return err
|
||||
}
|
||||
StopWithouttSession(func() {
|
||||
fmt.Println(color.GreenString("Service Restarted"))
|
||||
go StartWithouttSession()
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
// WatchModel 监听业务接口更新
|
||||
func WatchModel(root string, prefix string) {
|
||||
if share.DirNotExists(root) {
|
||||
return
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
root = share.DirAbs(root)
|
||||
go share.Watch(root, func(op string, filename string) {
|
||||
|
||||
if !strings.HasSuffix(filename, ".json") {
|
||||
return
|
||||
}
|
||||
if op == "write" || op == "create" {
|
||||
name := prefix + share.SpecName(root, filename)
|
||||
content := share.ReadFile(filename)
|
||||
_, err := gou.LoadModelReturn(string(content), name) // Reload
|
||||
if err != nil {
|
||||
fmt.Println(color.RedString("Fatal: %s", err.Error()))
|
||||
// event hanlder
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-shutdown:
|
||||
log.Info("[Watch] The event handler exit")
|
||||
return
|
||||
}
|
||||
fmt.Println(color.GreenString("Model %s Reloaded", name))
|
||||
} else if op == "remove" || op == "rename" {
|
||||
name := prefix + share.SpecName(root, filename)
|
||||
if _, has := gou.Models[name]; has {
|
||||
delete(gou.Models, name)
|
||||
fmt.Println(color.RedString("Model %s Removed", name))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// WatchAPI 监听业务接口更新
|
||||
func WatchAPI(root string, prefix string) {
|
||||
if share.DirNotExists(root) {
|
||||
return
|
||||
}
|
||||
root = share.DirAbs(root)
|
||||
go share.Watch(root, func(op string, filename string) {
|
||||
case event, ok := <-watcher.Events:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
relpath := strings.TrimPrefix(event.Name, root)
|
||||
if strings.HasPrefix(relpath, string(os.PathSeparator)) {
|
||||
relpath = strings.TrimPrefix(relpath, string(os.PathSeparator))
|
||||
}
|
||||
|
||||
if !strings.HasSuffix(filename, ".json") {
|
||||
return
|
||||
}
|
||||
pi := strings.Split(relpath, string(os.PathSeparator))
|
||||
widget := pi[0]
|
||||
|
||||
if op == "write" || op == "create" {
|
||||
name := prefix + share.SpecName(root, filename)
|
||||
content := share.ReadFile(filename)
|
||||
_, err := gou.LoadAPIReturn(string(content), name) // Reload
|
||||
if err != nil {
|
||||
fmt.Println(color.RedString("Fatal: %s", err.Error()))
|
||||
return
|
||||
}
|
||||
fmt.Println(color.GreenString("API %s Reloaded", name))
|
||||
watchHanler := watchReload
|
||||
if hanlder, has := hanlders[widget]; has {
|
||||
watchHanler = hanlder
|
||||
}
|
||||
|
||||
} else if op == "remove" || op == "rename" {
|
||||
name := prefix + share.SpecName(root, filename)
|
||||
if _, has := gou.APIs[name]; has {
|
||||
delete(gou.APIs, name)
|
||||
fmt.Println(color.RedString("API %s Removed", name))
|
||||
}
|
||||
}
|
||||
|
||||
// 重启服务器
|
||||
if op == "write" || op == "create" || op == "remove" || op == "rename" {
|
||||
StopWithouttSession(func() {
|
||||
fmt.Println(color.GreenString("Service Restarted"))
|
||||
go StartWithouttSession()
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// WatchFlow 监听业务逻辑变更
|
||||
func WatchFlow(root string, prefix string) {
|
||||
if share.DirNotExists(root) {
|
||||
return
|
||||
}
|
||||
root = share.DirAbs(root)
|
||||
go share.Watch(root, func(op string, filename string) {
|
||||
if !strings.HasSuffix(filename, ".json") && !strings.HasSuffix(filename, ".js") {
|
||||
return
|
||||
}
|
||||
|
||||
if strings.HasSuffix(filename, ".js") {
|
||||
name := prefix + share.SpecName(root, filename)
|
||||
name = strings.ReplaceAll(name, ".", "/")
|
||||
filename = filepath.Join(root, name+".flow.json")
|
||||
}
|
||||
|
||||
if op == "write" || op == "create" {
|
||||
name := prefix + share.SpecName(root, filename)
|
||||
content := share.ReadFile(filename)
|
||||
flow, err := gou.LoadFlowReturn(string(content), name) // Reload
|
||||
if err != nil {
|
||||
fmt.Println(color.RedString("Fatal: %s", err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
if flow != nil { // Reload Script
|
||||
dir := filepath.Dir(filename)
|
||||
share.Walk(dir, ".js", func(root, filename string) {
|
||||
script := share.ScriptName(filename)
|
||||
content := share.ReadFile(filename)
|
||||
_, err := flow.LoadScriptReturn(string(content), script)
|
||||
if err != nil {
|
||||
fmt.Println(color.RedString("Fatal: %s", err.Error()))
|
||||
return
|
||||
if _, has := excludes[widget]; !has {
|
||||
base := filepath.Base(event.Name)
|
||||
isdir := true
|
||||
if strings.HasSuffix(base, ".yao") || strings.HasSuffix(base, ".json") {
|
||||
isdir = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fmt.Println(color.GreenString("Flow %s Reloaded", name))
|
||||
events := strings.Split(event.Op.String(), "|")
|
||||
for _, eventType := range events {
|
||||
|
||||
} else if op == "remove" || op == "rename" {
|
||||
name := prefix + share.SpecName(root, filename)
|
||||
if _, has := gou.Flows[name]; has {
|
||||
delete(gou.Flows, name)
|
||||
fmt.Println(color.RedString("Flow %s Removed", name))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
// ADD / REMOVE Watching dir
|
||||
if isdir {
|
||||
switch eventType {
|
||||
case "CREATE":
|
||||
log.Info("[Watch] Watching: %s", event.Name)
|
||||
watcher.Add(event.Name)
|
||||
break
|
||||
case "REMOVE":
|
||||
log.Info("[Watch] Unwatching: %s", event.Name)
|
||||
watcher.Remove(event.Name)
|
||||
break
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// WatchPlugin 监听业务插件变更
|
||||
func WatchPlugin(root string) {
|
||||
if share.DirNotExists(root) {
|
||||
return
|
||||
}
|
||||
root = share.DirAbs(root)
|
||||
go share.Watch(root, func(op string, filename string) {
|
||||
|
||||
if !strings.HasSuffix(filename, ".so") {
|
||||
return
|
||||
}
|
||||
|
||||
if op == "write" || op == "create" {
|
||||
name := share.SpecName(root, filename)
|
||||
_, err := gou.LoadPluginReturn(filename, name) // Reload
|
||||
if err != nil {
|
||||
fmt.Println(color.RedString("Fatal: %s", err.Error()))
|
||||
return
|
||||
}
|
||||
fmt.Println(color.GreenString("Plugin %s Reloaded", name))
|
||||
|
||||
} else if op == "remove" || op == "rename" {
|
||||
name := share.SpecName(root, filename)
|
||||
if _, has := gou.Plugins[name]; has {
|
||||
delete(gou.Plugins, name)
|
||||
fmt.Println(color.RedString("Plugin %s Removed", name))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// WatchTable 监听数据表格更新
|
||||
func WatchTable(root string, prefix string) {
|
||||
if share.DirNotExists(root) {
|
||||
return
|
||||
}
|
||||
root = share.DirAbs(root)
|
||||
go share.Watch(root, func(op string, filename string) {
|
||||
|
||||
if !strings.HasSuffix(filename, ".json") {
|
||||
return
|
||||
}
|
||||
|
||||
if op == "write" || op == "create" {
|
||||
name := prefix + share.SpecName(root, filename)
|
||||
content := share.ReadFile(filename)
|
||||
_, err := table.LoadTable(string(content), name) // Reload Table
|
||||
if err != nil {
|
||||
fmt.Println(color.RedString("Fatal: %s", err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
api, has := gou.APIs["xiang.table"]
|
||||
if has {
|
||||
_, err := gou.LoadAPIReturn(api.Source, api.Name)
|
||||
if err != nil {
|
||||
fmt.Println(color.RedString("Fatal: %s", err.Error()))
|
||||
return
|
||||
}
|
||||
}
|
||||
fmt.Println(color.GreenString("Table %s Reloaded", name))
|
||||
|
||||
} else if op == "remove" || op == "rename" {
|
||||
name := prefix + share.SpecName(root, filename)
|
||||
if _, has := table.Tables[name]; has {
|
||||
delete(table.Tables, name)
|
||||
fmt.Println(color.RedString("Table %s Removed", name))
|
||||
}
|
||||
}
|
||||
|
||||
// 重启服务器
|
||||
if op == "write" || op == "create" || op == "remove" || op == "rename" {
|
||||
StopWithouttSession(func() {
|
||||
fmt.Println(color.GreenString("Service Restarted"))
|
||||
go StartWithouttSession()
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// WatchChart 监听分析图表更新
|
||||
func WatchChart(root string, prefix string) {
|
||||
if share.DirNotExists(root) {
|
||||
return
|
||||
}
|
||||
root = share.DirAbs(root)
|
||||
go share.Watch(root, func(op string, filename string) {
|
||||
if !strings.HasSuffix(filename, ".json") && !strings.HasSuffix(filename, ".js") {
|
||||
return
|
||||
}
|
||||
|
||||
if strings.HasSuffix(filename, ".js") {
|
||||
name := prefix + share.SpecName(root, filename)
|
||||
name = strings.ReplaceAll(name, ".", "/")
|
||||
filename = filepath.Join(root, name+".chart.json")
|
||||
}
|
||||
|
||||
if op == "write" || op == "create" {
|
||||
name := prefix + share.SpecName(root, filename)
|
||||
content := share.ReadFile(filename)
|
||||
chart, err := chart.LoadChart(content, name) // Relaod
|
||||
if err != nil {
|
||||
fmt.Println(color.RedString("Fatal: %s", err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
if chart != nil { // Reload Script
|
||||
dir := filepath.Dir(filename)
|
||||
share.Walk(dir, ".js", func(root, filename string) {
|
||||
script := share.ScriptName(filename)
|
||||
content := share.ReadFile(filename)
|
||||
_, err := chart.LoadScriptReturn(string(content), script)
|
||||
if err != nil {
|
||||
fmt.Println(color.RedString("Fatal: %s", err.Error()))
|
||||
return
|
||||
log.Info("[Watch] %s %s", eventType, event.Name)
|
||||
watchHanler(root, event.Name, eventType, cfg)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
break
|
||||
|
||||
api, has := gou.APIs["xiang.chart"]
|
||||
if has {
|
||||
_, err := gou.LoadAPIReturn(api.Source, api.Name)
|
||||
if err != nil {
|
||||
fmt.Println(color.RedString("Fatal: %s", err.Error()))
|
||||
case err, ok := <-watcher.Errors:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
fmt.Println(color.GreenString("Chart %s Reloaded", name))
|
||||
|
||||
} else if op == "remove" || op == "rename" {
|
||||
name := prefix + share.SpecName(root, filename)
|
||||
if _, has := chart.Charts[name]; has {
|
||||
delete(chart.Charts, name)
|
||||
fmt.Println(color.RedString("Chart %s Removed", name))
|
||||
fmt.Println(color.RedString("[Watch] %s", err.Error()))
|
||||
log.Error("[Watch] %s", err.Error())
|
||||
break
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// 重启服务器
|
||||
if op == "write" || op == "create" || op == "remove" || op == "rename" {
|
||||
StopWithouttSession(func() {
|
||||
fmt.Println(color.GreenString("Service Restarted"))
|
||||
go StartWithouttSession()
|
||||
})
|
||||
fmt.Println(color.GreenString("[Watch] Started"))
|
||||
watchReady <- true
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-watchShutdown:
|
||||
shutdown <- true
|
||||
log.Info("[Watch] Stopped")
|
||||
fmt.Println(color.YellowString("[Watch] Stopped"))
|
||||
return nil
|
||||
|
||||
case <-interrupt:
|
||||
shutdown <- true
|
||||
log.Info("[Watch] Stopped")
|
||||
fmt.Println(color.YellowString("[Watch] Stopped"))
|
||||
return nil
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// WatchPage 监听页面更新
|
||||
func WatchPage(root string, prefix string) {
|
||||
if share.DirNotExists(root) {
|
||||
return
|
||||
}
|
||||
root = share.DirAbs(root)
|
||||
go share.Watch(root, func(op string, filename string) {
|
||||
if !strings.HasSuffix(filename, ".json") && !strings.HasSuffix(filename, ".js") {
|
||||
func watchModel(root string, file string, event string, cfg config.Config) {
|
||||
name := share.SpecName(root, file)
|
||||
switch event {
|
||||
case "CREATE":
|
||||
content, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
fmt.Println(color.RedString("[Watch] Model: %s %s", name, err.Error()))
|
||||
return
|
||||
}
|
||||
mod, err := gou.LoadModelReturn(string(content), name)
|
||||
if err != nil {
|
||||
fmt.Println(color.RedString("[Watch] Model: %s %s", name, err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
if strings.HasSuffix(filename, ".js") {
|
||||
name := prefix + share.SpecName(root, filename)
|
||||
name = strings.ReplaceAll(name, ".", "/")
|
||||
filename = filepath.Join(root, name+".page.json")
|
||||
}
|
||||
mod.Migrate(true)
|
||||
fmt.Println(color.GreenString("[Watch] Model: %s Created", name))
|
||||
break
|
||||
|
||||
if op == "write" || op == "create" {
|
||||
name := prefix + share.SpecName(root, filename)
|
||||
content := share.ReadFile(filename)
|
||||
page, err := page.LoadPage(content, name) // Relaod
|
||||
if err != nil {
|
||||
fmt.Println(color.RedString("Fatal: %s", err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
if page != nil { // Reload Script
|
||||
dir := filepath.Dir(filename)
|
||||
share.Walk(dir, ".js", func(root, filename string) {
|
||||
script := share.ScriptName(filename)
|
||||
content := share.ReadFile(filename)
|
||||
_, err := page.LoadScriptReturn(string(content), script)
|
||||
if err != nil {
|
||||
fmt.Println(color.RedString("Fatal: %s", err.Error()))
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
api, has := gou.APIs["xiang.page"]
|
||||
if has {
|
||||
_, err := gou.LoadAPIReturn(api.Source, api.Name)
|
||||
if err != nil {
|
||||
fmt.Println(color.RedString("Fatal: %s", err.Error()))
|
||||
return
|
||||
}
|
||||
}
|
||||
fmt.Println(color.GreenString("Page %s Reloaded", name))
|
||||
|
||||
} else if op == "remove" || op == "rename" {
|
||||
name := prefix + share.SpecName(root, filename)
|
||||
if _, has := page.Pages[name]; has {
|
||||
delete(page.Pages, name)
|
||||
fmt.Println(color.RedString("Page %s Removed", name))
|
||||
}
|
||||
}
|
||||
|
||||
// 重启服务器
|
||||
if op == "write" || op == "create" || op == "remove" || op == "rename" {
|
||||
StopWithouttSession(func() {
|
||||
fmt.Println(color.GreenString("Service Restarted"))
|
||||
go StartWithouttSession()
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// WatchWorkFlow 监听工作流更新
|
||||
func WatchWorkFlow(root string, prefix string) {
|
||||
if share.DirNotExists(root) {
|
||||
return
|
||||
}
|
||||
root = share.DirAbs(root)
|
||||
go share.Watch(root, func(op string, filename string) {
|
||||
if !strings.HasSuffix(filename, ".json") {
|
||||
case "WRITE":
|
||||
content, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
fmt.Println(color.RedString("[Watch] Model: %s %s", name, err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
if op == "write" || op == "create" {
|
||||
name := prefix + share.SpecName(root, filename)
|
||||
content := share.ReadFile(filename)
|
||||
_, err := workflow.LoadWorkFlow(content, name) // Relaod
|
||||
if err != nil {
|
||||
fmt.Println(color.RedString("Fatal: %s", err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
api, has := gou.APIs["xiang.workflow."+name]
|
||||
if has {
|
||||
_, err := gou.LoadAPIReturn(api.Source, api.Name)
|
||||
if err != nil {
|
||||
fmt.Println(color.RedString("Fatal: %s", err.Error()))
|
||||
return
|
||||
}
|
||||
}
|
||||
fmt.Println(color.GreenString("Workflow %s Reloaded", name))
|
||||
|
||||
} else if op == "remove" || op == "rename" {
|
||||
name := prefix + share.SpecName(root, filename)
|
||||
if _, has := workflow.WorkFlows[name]; has {
|
||||
delete(workflow.WorkFlows, name)
|
||||
fmt.Println(color.RedString("Workflow %s Removed", name))
|
||||
}
|
||||
mod, err := gou.LoadModelReturn(string(content), name)
|
||||
if err != nil {
|
||||
fmt.Println(color.RedString("[Watch] Model: %s %s", name, err.Error()))
|
||||
return
|
||||
}
|
||||
mod.Migrate(false)
|
||||
fmt.Println(color.GreenString("[Watch] Model: %s Reloaded", name))
|
||||
break
|
||||
|
||||
// 重启服务器
|
||||
if op == "write" || op == "create" || op == "remove" || op == "rename" {
|
||||
StopWithouttSession(func() {
|
||||
fmt.Println(color.GreenString("Service Restarted"))
|
||||
go StartWithouttSession()
|
||||
})
|
||||
}
|
||||
case "REMOVE", "RENAME":
|
||||
delete(gou.Models, name)
|
||||
fmt.Println(color.GreenString("[Watch] Model: %s Removed", name))
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
func watchReload(root string, file string, event string, cfg config.Config) {
|
||||
err := engine.Load(config.Conf) // 加载脚本等
|
||||
if err != nil {
|
||||
fmt.Println(color.RedString("[Watch] Reload: %s", err.Error()))
|
||||
}
|
||||
|
||||
// Restart Server
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
StopWithContext(ctx, func() {
|
||||
go Start()
|
||||
fmt.Println(color.GreenString("[Watch] Reload Completed"))
|
||||
})
|
||||
}
|
||||
|
|
|
|||
153
service/watch_test.go
Normal file
153
service/watch_test.go
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/yaoapp/yao/config"
|
||||
"github.com/yaoapp/yao/share"
|
||||
)
|
||||
|
||||
func TestWatch(t *testing.T) {
|
||||
|
||||
share.DBConnect(config.Conf.DB)
|
||||
err := Watch(config.Conf)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer StopWatch()
|
||||
|
||||
createDir(t)
|
||||
renameDir(t)
|
||||
|
||||
createModel(t)
|
||||
changeModel(t)
|
||||
renameModel(t)
|
||||
removeModel(t)
|
||||
|
||||
createModel(t)
|
||||
removeDir(t)
|
||||
}
|
||||
|
||||
func TestWatchReload(t *testing.T) {
|
||||
go Start()
|
||||
defer Stop(func() {})
|
||||
share.DBConnect(config.Conf.DB)
|
||||
watchReload("", "", "", config.Conf)
|
||||
}
|
||||
|
||||
func createDir(t *testing.T) {
|
||||
root := config.Conf.Root
|
||||
file := filepath.Join(root, "models", "watch", "test")
|
||||
fmt.Println("CREATE-DIR", file)
|
||||
err := os.MkdirAll(file, os.ModePerm)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
|
||||
func renameDir(t *testing.T) {
|
||||
root := config.Conf.Root
|
||||
file := filepath.Join(root, "models", "watch", "test")
|
||||
new := filepath.Join(root, "models", "watch", "test_new")
|
||||
fmt.Println("RENAME-DIR", file)
|
||||
err := os.Rename(file, new)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
|
||||
func removeDir(t *testing.T) {
|
||||
root := config.Conf.Root
|
||||
file := filepath.Join(root, "models", "watch")
|
||||
fmt.Println("REMOVE-DIR", file)
|
||||
err := os.RemoveAll(file)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
|
||||
func createModel(t *testing.T) {
|
||||
dsl := `
|
||||
{
|
||||
"name": "watch-test",
|
||||
"table": {
|
||||
"name": "watch_test",
|
||||
"comment": "WatchTest",
|
||||
"engine": "InnoDB"
|
||||
},
|
||||
"columns": [
|
||||
{ "name": "id", "type": "ID" },
|
||||
{ "label": "Name", "name": "name", "type": "string", "index": true }
|
||||
],
|
||||
"relations": {},
|
||||
"option": { "timestamps": true, "soft_deletes": true }
|
||||
}
|
||||
`
|
||||
root := config.Conf.Root
|
||||
file := filepath.Join(root, "models", "watch", "test_new", "watch.mod.json")
|
||||
fmt.Println("CREATE", file)
|
||||
err := ioutil.WriteFile(file, []byte(dsl), 0644)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
|
||||
func changeModel(t *testing.T) {
|
||||
dsl := `
|
||||
{
|
||||
"name": "watch-test",
|
||||
"table": {
|
||||
"name": "watch_test",
|
||||
"comment": "WatchTest",
|
||||
"engine": "InnoDB"
|
||||
},
|
||||
"columns": [
|
||||
{ "name": "id", "type": "ID" },
|
||||
{ "label": "Name", "name": "name", "type": "string", "index": true },
|
||||
{ "label": "Data", "name": "data", "type": "json", "nullable": true }
|
||||
],
|
||||
"relations": {},
|
||||
"option": { "timestamps": true, "soft_deletes": true }
|
||||
}
|
||||
`
|
||||
root := config.Conf.Root
|
||||
file := filepath.Join(root, "models", "watch", "test_new", "watch.mod.json")
|
||||
fmt.Println("CHANGE", file)
|
||||
err := ioutil.WriteFile(file, []byte(dsl), 0644)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
|
||||
func renameModel(t *testing.T) {
|
||||
root := config.Conf.Root
|
||||
file := filepath.Join(root, "models", "watch", "test_new", "watch.mod.json")
|
||||
new := filepath.Join(root, "models", "watch", "test_new", "watch_new.mod.json")
|
||||
fmt.Println("RENAME", new)
|
||||
err := os.Rename(file, new)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
|
||||
func removeModel(t *testing.T) {
|
||||
root := config.Conf.Root
|
||||
file := filepath.Join(root, "models", "watch", "test_new", "watch_new.mod.json")
|
||||
fmt.Println("REMOVE", file)
|
||||
err := os.Remove(file)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue