From be4b86f5c51a265e75c5668f5f50f1793d1ab2fe Mon Sep 17 00:00:00 2001 From: sammyette Date: Sat, 15 Jul 2023 20:03:55 -0400 Subject: [PATCH 1/2] feat(fs): add file watching function add the fs.watch function which takes a path and a callback. it will notify of any file events for the given path. this function also returns a "watcher" object which has start and stop functions. --- go.mod | 1 + go.sum | 3 ++ golibs/fs/fs.go | 48 ++++++++++++++++++++ golibs/fs/watcher.go | 101 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 153 insertions(+) create mode 100644 golibs/fs/watcher.go diff --git a/go.mod b/go.mod index c17d906..5d0efe3 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/chuckpreslar/emission v0.0.0-20170206194824-a7ddd980baf9 github.com/maxlandon/readline v0.1.0-beta.0.20211027085530-2b76cabb8036 github.com/pborman/getopt v1.1.0 + github.com/rjeczalik/notify v0.9.3 github.com/sahilm/fuzzy v0.1.0 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a golang.org/x/term v0.0.0-20220411215600-e5f449aeb171 diff --git a/go.sum b/go.sum index 1917008..1ae270b 100644 --- a/go.sum +++ b/go.sum @@ -46,6 +46,8 @@ github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsK github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rjeczalik/notify v0.9.3 h1:6rJAzHTGKXGj76sbRgDiDcYj/HniypXmSJo1SWakZeY= +github.com/rjeczalik/notify v0.9.3/go.mod h1:gF3zSOrafR9DQEWSE8TjfI9NkooDxbyT4UgRGKZA0lc= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.1-0.20210923151022-86f73c517451 h1:d1PiN4RxzIFXCJTvRkvSkKqwtRAl5ZV4lATKtQI0B7I= github.com/rogpeppe/go-internal v1.8.1-0.20210923151022-86f73c517451/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= @@ -55,6 +57,7 @@ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cO golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220513210516-0976fa681c29 h1:w8s32wxx3sY+OjLlv9qltkLU5yvJzxjjgiHWLjdIcw4= golang.org/x/sync v0.0.0-20220513210516-0976fa681c29/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180926160741-c2ed4eda69e7/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/golibs/fs/fs.go b/golibs/fs/fs.go index 1c1a5ca..538eff2 100644 --- a/golibs/fs/fs.go +++ b/golibs/fs/fs.go @@ -17,12 +17,39 @@ import ( "github.com/arnodel/golua/lib/packagelib" ) +var rtmm *rt.Runtime +var watcherMetaKey = rt.StringValue("hshwatcher") var Loader = packagelib.Loader{ Load: loaderFunc, Name: "fs", } func loaderFunc(rtm *rt.Runtime) (rt.Value, func()) { + rtmm = rtm + watcherMethods := rt.NewTable() + watcherFuncs := map[string]util.LuaExport{ + "start": {watcherStart, 1, false}, + "stop": {watcherStop, 1, false}, + } + util.SetExports(rtm, watcherMethods, watcherFuncs) + + watcherMeta := rt.NewTable() + watcherIndex := func(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { + //ti, _ := watcherArg(c, 0) + + arg := c.Arg(1) + val := watcherMethods.Get(arg) + + if val != rt.NilValue { + return c.PushingNext1(t.Runtime, val), nil + } + + return c.PushingNext1(t.Runtime, val), nil + } + + watcherMeta.Set(rt.StringValue("__index"), rt.FunctionValue(rt.NewGoFunction(watcherIndex, "__index", 2, false))) + rtm.SetRegistry(watcherMetaKey, rt.TableValue(watcherMeta)) + exports := map[string]util.LuaExport{ "cd": util.LuaExport{fcd, 1, false}, "mkdir": util.LuaExport{fmkdir, 2, false}, @@ -33,6 +60,7 @@ func loaderFunc(rtm *rt.Runtime) (rt.Value, func()) { "dir": util.LuaExport{fdir, 1, false}, "glob": util.LuaExport{fglob, 1, false}, "join": util.LuaExport{fjoin, 0, true}, + "watch": util.LuaExport{fwatch, 2, false}, } mod := rt.NewTable() util.SetExports(rtm, mod, exports) @@ -250,3 +278,23 @@ func fjoin(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { return c.PushingNext(t.Runtime, rt.StringValue(res)), nil } + +func fwatch(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { + if err := c.CheckNArgs(2); err != nil { + return nil, err + } + + dir, err := c.StringArg(0) + if err != nil { + return nil, err + } + + watcher, err := c.ClosureArg(1) + if err != nil { + return nil, err + } + + dw := newWatcher(dir, watcher) + + return c.PushingNext1(t.Runtime, rt.UserDataValue(dw.ud)), nil +} diff --git a/golibs/fs/watcher.go b/golibs/fs/watcher.go new file mode 100644 index 0000000..8a8ce23 --- /dev/null +++ b/golibs/fs/watcher.go @@ -0,0 +1,101 @@ +package fs + +import ( + "fmt" + + "github.com/rjeczalik/notify" + rt "github.com/arnodel/golua/runtime" +) + +type pathWatcher struct{ + path string + callback *rt.Closure + paused bool + started bool + ud *rt.UserData + notifyChan chan notify.EventInfo +} + +func (w *pathWatcher) start() { + if w.callback == nil || w.started { + return + } + + w.started = true + w.notifyChan = make(chan notify.EventInfo) + notify.Watch(w.path, w.notifyChan, notify.All) + + go func() { + for notif := range w.notifyChan { + ev := notif.Event().String() + path := notif.Path() + + _, err := rt.Call1(rtmm.MainThread(), rt.FunctionValue(w.callback), rt.StringValue(ev), rt.StringValue(path)) + if err != nil { + // TODO: throw error + } + } + }() +} + +func (w *pathWatcher) stop() { + w.started = false + notify.Stop(w.notifyChan) +} + +func watcherStart(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { + pw, err := watcherArg(c, 0) + if err != nil { + return nil, err + } + + pw.start() + + return c.Next(), nil +} + +func watcherStop(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { + pw, err := watcherArg(c, 0) + if err != nil { + return nil, err + } + + pw.stop() + + return c.Next(), nil +} + +func newWatcher(path string, callback *rt.Closure) *pathWatcher { + pw := &pathWatcher{ + path: path, + callback: callback, + } + pw.ud = watcherUserData(pw) + pw.start() + + return pw +} + +func watcherArg(c *rt.GoCont, arg int) (*pathWatcher, error) { + j, ok := valueToWatcher(c.Arg(arg)) + if !ok { + return nil, fmt.Errorf("#%d must be a watcher", arg + 1) + } + + return j, nil +} + +func valueToWatcher(val rt.Value) (*pathWatcher, bool) { + u, ok := val.TryUserData() + if !ok { + return nil, false + } + + j, ok := u.Value().(*pathWatcher) + return j, ok +} + +func watcherUserData(j *pathWatcher) *rt.UserData { + watcherMeta := rtmm.Registry(watcherMetaKey) + return rt.NewUserData(j, watcherMeta.AsTable()) +} From 6fa8eea5e2bdc27f937d94af42add406dbdce1f4 Mon Sep 17 00:00:00 2001 From: sammyette Date: Sun, 22 Dec 2024 12:39:15 -0400 Subject: [PATCH 2/2] docs: document fs watcher --- golibs/fs/fs.go | 6 ++++++ golibs/fs/watcher.go | 26 +++++++++++++++++--------- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/golibs/fs/fs.go b/golibs/fs/fs.go index 4c209de..6c11e31 100644 --- a/golibs/fs/fs.go +++ b/golibs/fs/fs.go @@ -329,6 +329,12 @@ func fstat(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { return c.PushingNext1(t.Runtime, rt.TableValue(statTbl)), nil } +// watch(path, callback) +// Watches a path for changes made to it. For example, to monitor +// new files created in a folder. +// The callback passed 2 string arguments, the `event` and the absolute `path` +// #param path string +// #param callback function func fwatch(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { if err := c.CheckNArgs(2); err != nil { return nil, err diff --git a/golibs/fs/watcher.go b/golibs/fs/watcher.go index 8a8ce23..f4f34ab 100644 --- a/golibs/fs/watcher.go +++ b/golibs/fs/watcher.go @@ -7,7 +7,9 @@ import ( rt "github.com/arnodel/golua/runtime" ) -type pathWatcher struct{ +// #type +// Watcher type describes a `fs` library file watcher. +type watcher struct{ path string callback *rt.Closure paused bool @@ -16,7 +18,7 @@ type pathWatcher struct{ notifyChan chan notify.EventInfo } -func (w *pathWatcher) start() { +func (w *watcher) start() { if w.callback == nil || w.started { return } @@ -38,11 +40,14 @@ func (w *pathWatcher) start() { }() } -func (w *pathWatcher) stop() { +func (w *watcher) stop() { w.started = false notify.Stop(w.notifyChan) } +// #member +// start() +// Start/resume file watching. func watcherStart(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { pw, err := watcherArg(c, 0) if err != nil { @@ -54,6 +59,9 @@ func watcherStart(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { return c.Next(), nil } +// #member +// stop() +// Stops watching for changes. Effectively ignores changes. func watcherStop(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { pw, err := watcherArg(c, 0) if err != nil { @@ -65,8 +73,8 @@ func watcherStop(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { return c.Next(), nil } -func newWatcher(path string, callback *rt.Closure) *pathWatcher { - pw := &pathWatcher{ +func newWatcher(path string, callback *rt.Closure) *watcher { + pw := &watcher{ path: path, callback: callback, } @@ -76,7 +84,7 @@ func newWatcher(path string, callback *rt.Closure) *pathWatcher { return pw } -func watcherArg(c *rt.GoCont, arg int) (*pathWatcher, error) { +func watcherArg(c *rt.GoCont, arg int) (*watcher, error) { j, ok := valueToWatcher(c.Arg(arg)) if !ok { return nil, fmt.Errorf("#%d must be a watcher", arg + 1) @@ -85,17 +93,17 @@ func watcherArg(c *rt.GoCont, arg int) (*pathWatcher, error) { return j, nil } -func valueToWatcher(val rt.Value) (*pathWatcher, bool) { +func valueToWatcher(val rt.Value) (*watcher, bool) { u, ok := val.TryUserData() if !ok { return nil, false } - j, ok := u.Value().(*pathWatcher) + j, ok := u.Value().(*watcher) return j, ok } -func watcherUserData(j *pathWatcher) *rt.UserData { +func watcherUserData(j *watcher) *rt.UserData { watcherMeta := rtmm.Registry(watcherMetaKey) return rt.NewUserData(j, watcherMeta.AsTable()) }