[change] Refactoring Watch

This commit is contained in:
Max 2022-08-20 21:25:32 +08:00
parent 6283b6e34c
commit 7517e4652a
7 changed files with 391 additions and 407 deletions

View file

@ -45,6 +45,7 @@ var langs = map[string]string{
"NEXT:": "下一步:",
"Listening": " 监听",
"✨LISTENING✨": "✨服务正在运行✨",
"✨STOPPED✨": "✨服务已停止✨",
"SessionPort": "会话服务端口",
"Force migrate": "强制更新数据表结构",
"Migrate is not allowed on production mode.": "Migrate 不能再生产环境下使用",

View file

@ -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
View file

@ -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
View file

@ -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=

View file

@ -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()
}
}

View file

@ -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
View 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)
}