summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorShulhan <ms@kilabit.info>2024-12-23 01:26:25 +0700
committerShulhan <ms@kilabit.info>2024-12-28 16:26:40 +0700
commitf9cf8ab0d908a6a1b4077c5f96876cf9574e0173 (patch)
tree66dcab7a4998aa2149811fff7a9ea9356792f398
parentc14c0e7735778573dc4d87e39703f0524d08a1b0 (diff)
downloadpakakeh.go-f9cf8ab0d908a6a1b4077c5f96876cf9574e0173.tar.xz
lib/memfs: refactoring Watch to use "watchfs/v2"
-rw-r--r--lib/memfs/doc.go16
-rw-r--r--lib/memfs/filestate.go5
-rw-r--r--lib/memfs/memfs.go155
-rw-r--r--lib/memfs/memfs_example_test.go80
-rw-r--r--lib/memfs/node.go5
-rw-r--r--lib/memfs/node_test.go5
-rw-r--r--lib/memfs/nodestate.go5
-rw-r--r--lib/memfs/watch_options.go67
8 files changed, 191 insertions, 147 deletions
diff --git a/lib/memfs/doc.go b/lib/memfs/doc.go
index fc892ff3..b4934c51 100644
--- a/lib/memfs/doc.go
+++ b/lib/memfs/doc.go
@@ -1,6 +1,5 @@
-// Copyright 2018, Shulhan <ms@kilabit.info>. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
+// SPDX-FileCopyrightText: 2018 M. Shulhan <ms@kilabit.info>
+// SPDX-License-Identifier: BSD-3-Clause
// Package memfs provide a library for mapping file system into memory and/or
// to embed it inside go source file.
@@ -26,7 +25,8 @@
//
// By default only file with size less or equal to 5 MB will be included in
// memory.
-// To increase the default size set the [Options.MaxFileSize] (in bytes).
+// To increase the default size set the [memfs.Options.MaxFileSize] (in
+// bytes).
// For example, to change maximum file size to 10 MB,
//
// var opts = memfs.Options{
@@ -56,8 +56,8 @@
//
// The memfs package support embedding the files into Go generated source
// code.
-// After we create the MemFS instance, call the [GoEmbed] method to dump all
-// directory and files into Go source code.
+// After we create the MemFS instance, call the [memfs.GoEmbed] method to
+// dump all directory and files into Go source code.
//
// First, define global variable as container for all files to be embedded
// in the same package as generated code,
@@ -66,7 +66,7 @@
//
// var myFS *memfs.MemFS
//
-// Second, create new instance of MemFS with [Options.Embed] is set,
+// Next, create new instance of MemFS with [memfs.Options.Embed] is set,
//
// var opts = &Options{
// Embed: EmbedOptions{
@@ -88,7 +88,7 @@
// mfs, err = memfs.New(opts)
// ...
//
-// Third, call method [MemFS.GoEmbed] from the instance,
+// Finally, call method [MemFS.GoEmbed] from the instance,
//
// mfs.GoEmbed()
//
diff --git a/lib/memfs/filestate.go b/lib/memfs/filestate.go
index 9374678b..56a961dc 100644
--- a/lib/memfs/filestate.go
+++ b/lib/memfs/filestate.go
@@ -1,6 +1,5 @@
-// Copyright 2019, Shulhan <ms@kilabit.info>. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
+// SPDX-FileCopyrightText: 2019 M. Shulhan <ms@kilabit.info>
+// SPDX-License-Identifier: BSD-3-Clause
package memfs
diff --git a/lib/memfs/memfs.go b/lib/memfs/memfs.go
index 9b3ec668..ba5f9cbe 100644
--- a/lib/memfs/memfs.go
+++ b/lib/memfs/memfs.go
@@ -1,6 +1,5 @@
-// Copyright 2018, Shulhan <ms@kilabit.info>. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
+// SPDX-FileCopyrightText: 2018 M. Shulhan <ms@kilabit.info>
+// SPDX-License-Identifier: BSD-3-Clause
package memfs
@@ -21,6 +20,7 @@ import (
libhtml "git.sr.ht/~shulhan/pakakeh.go/lib/html"
libints "git.sr.ht/~shulhan/pakakeh.go/lib/ints"
libstrings "git.sr.ht/~shulhan/pakakeh.go/lib/strings"
+ "git.sr.ht/~shulhan/pakakeh.go/lib/watchfs/v2"
)
const (
@@ -29,17 +29,17 @@ const (
// MemFS contains directory tree of file system in memory.
type MemFS struct {
- http.FileSystem
-
PathNodes *PathNode
Root *Node
Opts *Options
- dw *DirWatcher
- watchopts *WatchOptions
+ dw *watchfs.DirWatcher
// subfs contains another MemFS instances.
// During Get, it will evaluated in order.
subfs []*MemFS
+
+ dwOptions watchfs.DirWatcherOptions
+ watchopts WatchOptions
}
// New create and initialize new memory file system from directory Root using
@@ -74,17 +74,6 @@ func (mfs *MemFS) AddChild(parent *Node, fi os.FileInfo) (child *Node, err error
if mfs.Opts.isExcluded(sysPath) {
return nil, nil
}
- if mfs.watchopts != nil && mfs.watchopts.isWatched(sysPath) {
- child, err = parent.addChild(sysPath, fi, mfs.Opts.MaxFileSize)
- if err != nil {
- if errors.Is(err, fs.ErrNotExist) {
- return nil, nil
- }
- return nil, fmt.Errorf(`%s %q: %w`, logp, sysPath, err)
- }
-
- mfs.PathNodes.Set(child.Path, child)
- }
if !mfs.Opts.isIncluded(sysPath, fi) {
if child != nil {
// The path being watched, but not included.
@@ -397,11 +386,10 @@ func (mfs *MemFS) Search(words []string, snippetLen int) (results []SearchResult
// StopWatch stop watching for update, from calling Watch.
func (mfs *MemFS) StopWatch() {
- if mfs.dw == nil {
- return
+ if mfs.dw != nil {
+ mfs.dw.Stop()
+ mfs.dw = nil
}
- mfs.dw.Stop()
- mfs.dw = nil
}
// Update the node content and information in memory based on new file
@@ -615,32 +603,26 @@ func (mfs *MemFS) resetAllModTime(t time.Time) {
mfs.Root.resetAllModTime(t)
}
-// Watch create and start the DirWatcher that monitor the memfs Root
-// directory based on the list of pattern on WatchOptions.Watches and
-// Options.Includes.
+// Watch create and start the [watchfs/v2.DirWatcher] that re-scan the content
+// of Root directory recursively on every [memfs.WatchOptions.Interval],
+// triggered by changes on [memfs.WatchOptions.File].
//
-// The MemFS will remove or update the tree and node content automatically if
-// the file being watched get deleted or updated.
+// The watcher will remove or update the tree and node content automatically
+// if the included files is being deleted, created, or updated.
//
-// The returned DirWatcher is ready to use.
-// To stop watching for update call the StopWatch.
-func (mfs *MemFS) Watch(watchopts WatchOptions) (dw *DirWatcher, err error) {
+// The returned channel changes return list of Node that has been deleted,
+// created, or updated.
+// To stop watching for update call the [MemFS.StopWatch].
+func (mfs *MemFS) Watch(watchopts WatchOptions) (
+ changes <-chan []*Node, err error,
+) {
var logp = `Watch`
- err = watchopts.init()
- if err != nil {
- return nil, fmt.Errorf(`%s: %w`, logp, err)
- }
- mfs.watchopts = &watchopts
+ watchopts.init(mfs.Opts.Root)
+ mfs.watchopts = watchopts
if mfs.dw != nil {
- return mfs.dw, nil
- }
-
- mfs.dw = &DirWatcher{
- fs: mfs,
- Delay: watchopts.Delay,
- Options: *mfs.Opts,
+ mfs.StopWatch()
}
err = mfs.scanDir(mfs.Root)
@@ -648,12 +630,95 @@ func (mfs *MemFS) Watch(watchopts WatchOptions) (dw *DirWatcher, err error) {
return nil, fmt.Errorf("%s: %w", logp, err)
}
- err = mfs.dw.Start()
+ mfs.dwOptions = watchfs.DirWatcherOptions{
+ FileWatcherOptions: watchopts.FileWatcherOptions,
+ Root: mfs.Opts.Root,
+ Includes: mfs.Opts.Includes,
+ Excludes: mfs.Opts.Excludes,
+ }
+
+ mfs.dw, err = watchfs.WatchDir(mfs.dwOptions)
if err != nil {
// There should be no error here, since we already check and
// filled the required fields for DirWatcher.
- return nil, fmt.Errorf("%s: %w", logp, err)
+ return nil, fmt.Errorf(`%s: %w`, logp, err)
+ }
+
+ var c = make(chan []*Node, 1)
+ go mfs.watch(c)
+
+ return c, nil
+}
+
+func (mfs *MemFS) watch(c chan []*Node) {
+ var (
+ changes []os.FileInfo
+ fi os.FileInfo
+ node *Node
+ err error
+ )
+ for changes = range mfs.dw.C {
+ var listNode []*Node
+ for _, fi = range changes {
+ var sysPath = fi.Name()
+ var intPath = strings.TrimPrefix(sysPath, mfs.Opts.Root)
+ intPath = filepath.Join(`/`, intPath)
+ node, err = mfs.Get(intPath)
+ if err != nil {
+ if !errors.Is(err, fs.ErrNotExist) {
+ // Nothing we can do.
+ continue
+ }
+ node, err = mfs.addWatchFile(intPath, fi)
+ if err != nil {
+ log.Println(err)
+ continue
+ }
+ listNode = append(listNode, node)
+ continue
+ }
+ if fi.Size() == watchfs.FileFlagDeleted {
+ if mfs.watchopts.Verbose {
+ fmt.Printf("MemFS: file deleted: %q\n", intPath)
+ }
+ mfs.RemoveChild(node.Parent, node)
+ listNode = append(listNode, node)
+ continue
+ }
+ if mfs.watchopts.Verbose {
+ fmt.Printf("MemFS: file updated: %q\n", intPath)
+ }
+ mfs.Update(node, fi)
+ listNode = append(listNode, node)
+ }
+ c <- listNode
}
+ close(c)
+}
- return mfs.dw, nil
+func (mfs *MemFS) addWatchFile(intPath string, watchfi os.FileInfo) (node *Node, err error) {
+ var dirPath = filepath.Dir(intPath)
+ var parent *Node
+ parent, err = mfs.Get(dirPath)
+ if err != nil {
+ return nil, err
+ }
+ if parent == nil {
+ return nil, fmt.Errorf(`addWatchFile: cannot find parent of %q`, intPath)
+ }
+ var fi = &Node{
+ modTime: watchfi.ModTime(),
+ Path: intPath,
+ name: filepath.Base(intPath),
+ size: watchfi.Size(),
+ mode: watchfi.Mode(),
+ }
+ node, err = mfs.AddChild(parent, fi)
+ if err != nil {
+ return nil, err
+ }
+ if mfs.watchopts.Verbose {
+ fmt.Printf("MemFS: file created: %q\n", intPath)
+ }
+ return node, nil
}
diff --git a/lib/memfs/memfs_example_test.go b/lib/memfs/memfs_example_test.go
index 72a32ea4..cfe1f551 100644
--- a/lib/memfs/memfs_example_test.go
+++ b/lib/memfs/memfs_example_test.go
@@ -1,6 +1,5 @@
-// Copyright 2020, Shulhan <ms@kilabit.info>. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
+// SPDX-FileCopyrightText: 2020 M. Shulhan <ms@kilabit.info>
+// SPDX-License-Identifier: BSD-3-Clause
package memfs_test
@@ -12,6 +11,7 @@ import (
"time"
"git.sr.ht/~shulhan/pakakeh.go/lib/memfs"
+ "git.sr.ht/~shulhan/pakakeh.go/lib/watchfs/v2"
)
func ExampleNew() {
@@ -99,71 +99,73 @@ func ExampleMemFS_Search() {
func ExampleMemFS_Watch() {
var (
- watchOpts = memfs.WatchOptions{
- Delay: 200 * time.Millisecond,
- }
-
- mfs *memfs.MemFS
- dw *memfs.DirWatcher
- node *memfs.Node
opts memfs.Options
- ns memfs.NodeState
err error
)
- opts.Root, err = os.MkdirTemp(``, `memfs_watch`)
+ opts.Root, err = os.MkdirTemp(``, `ExampleMemFS_Watch`)
if err != nil {
- log.Println(err)
- return
+ log.Fatal(err)
}
defer func() {
_ = os.RemoveAll(opts.Root)
}()
+ var mfs *memfs.MemFS
+
mfs, err = memfs.New(&opts)
if err != nil {
- log.Println(err)
- return
+ log.Fatal(err)
}
- dw, err = mfs.Watch(watchOpts)
- if err != nil {
- log.Println(err)
- return
+ var fileToWatch = filepath.Join(opts.Root, memfs.DefaultWatchFile)
+ var watchOpts = memfs.WatchOptions{
+ FileWatcherOptions: watchfs.FileWatcherOptions{
+ File: fileToWatch,
+ Interval: 200 * time.Millisecond,
+ },
+ Verbose: true,
}
- // Wait for the goroutine on Watch run.
- time.Sleep(200 * time.Millisecond)
+ var changesq <-chan []*memfs.Node
- testFile := filepath.Join(opts.Root, `file`)
+ changesq, err = mfs.Watch(watchOpts)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ var testFile = filepath.Join(opts.Root, `file`)
err = os.WriteFile(testFile, []byte(`dummy content`), 0600)
if err != nil {
- log.Println(err)
- return
+ log.Fatal(err)
}
- ns = <-dw.C
+ err = os.WriteFile(fileToWatch, []byte(`x`), 0600)
+ if err != nil {
+ log.Fatal(err)
+ }
+ <-changesq
- node, err = mfs.Get(`/file`)
+ _, err = mfs.Get(`/file`)
if err != nil {
- log.Println(err)
- return
+ log.Fatal(err)
}
- fmt.Printf("Node: %s: %s\n", node.Path, ns.State)
err = os.Remove(testFile)
if err != nil {
- log.Println(err)
- return
+ log.Fatal(err)
}
- ns = <-dw.C
- fmt.Printf("Node: %s: %s\n", ns.Node.Path, ns.State)
-
- dw.Stop()
+ err = os.WriteFile(fileToWatch, []byte(`xx`), 0600)
+ if err != nil {
+ log.Fatal(err)
+ }
+ <-changesq
+ mfs.StopWatch()
+ <-changesq
- //Output:
- //Node: /file: FileStateCreated
- //Node: /file: FileStateDeleted
+ // Output:
+ // MemFS: file created: "/file"
+ // MemFS: file deleted: "/file"
}
diff --git a/lib/memfs/node.go b/lib/memfs/node.go
index 5ed87b02..ae0bfda0 100644
--- a/lib/memfs/node.go
+++ b/lib/memfs/node.go
@@ -1,6 +1,5 @@
-// Copyright 2018, Shulhan <ms@kilabit.info>. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
+// SPDX-FileCopyrightText: 2018 M. Shulhan <ms@kilabit.info>
+// SPDX-License-Identifier: BSD-3-Clause
package memfs
diff --git a/lib/memfs/node_test.go b/lib/memfs/node_test.go
index 5665b7a8..ad330a96 100644
--- a/lib/memfs/node_test.go
+++ b/lib/memfs/node_test.go
@@ -1,6 +1,5 @@
-// Copyright 2019, Shulhan <ms@kilabit.info>. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
+// SPDX-FileCopyrightText: 2019 M. Shulhan <ms@kilabit.info>
+// SPDX-License-Identifier: BSD-3-Clause
package memfs
diff --git a/lib/memfs/nodestate.go b/lib/memfs/nodestate.go
index 793e2868..2cbfe24f 100644
--- a/lib/memfs/nodestate.go
+++ b/lib/memfs/nodestate.go
@@ -1,6 +1,5 @@
-// Copyright 2019, Shulhan <ms@kilabit.info>. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
+// SPDX-FileCopyrightText: 2019 M. Shulhan <ms@kilabit.info>
+// SPDX-License-Identifier: BSD-3-Clause
package memfs
diff --git a/lib/memfs/watch_options.go b/lib/memfs/watch_options.go
index 412e8843..d6f17d40 100644
--- a/lib/memfs/watch_options.go
+++ b/lib/memfs/watch_options.go
@@ -1,58 +1,39 @@
-// Copyright 2022, Shulhan <ms@kilabit.info>. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
+// SPDX-FileCopyrightText: 2022 M. Shulhan <ms@kilabit.info>
+// SPDX-License-Identifier: BSD-3-Clause
package memfs
import (
- "regexp"
+ "path/filepath"
"time"
+
+ "git.sr.ht/~shulhan/pakakeh.go/lib/watchfs/v2"
)
+// DefaultWatchFile define default file name to be watch for changes.
+// Any update to this file will trigger rescan on the memfs tree.
+const DefaultWatchFile = `.memfs_rescan`
+
+const defWatchInterval = 5 * time.Second
+
// WatchOptions define an options for the MemFS Watch method.
+//
+// If the [watchfs.FileWatcherOptions.File] is empty it will default to
+// [DefaultWatchFile] inside the [memfs.Options.Root].
+// The [watchfs.FileWatcherOptions.Interval] must be greater than 10
+// milliseconds, otherwise it will default to 5 seconds.
type WatchOptions struct {
- // Watches contain list of regular expressions for files to be watched
- // inside the Root, as addition to Includes pattern.
- // If this field is empty, only files pass the Includes filter will be
- // watched.
- Watches []string
-
- watchRE []*regexp.Regexp
+ watchfs.FileWatcherOptions
- // Delay define the duration when the new changes will be checked from
- // system.
- // This field set the DirWatcher.Delay returned from Watch().
- // This field is optional, default is 5 seconds.
- Delay time.Duration
+ // Verbose if true print the file changes information to stdout.
+ Verbose bool
}
-func (watchopts *WatchOptions) init() (err error) {
- if watchopts.Delay < 100*time.Millisecond {
- watchopts.Delay = defWatchDelay
+func (watchopts *WatchOptions) init(root string) {
+ if len(watchopts.File) == 0 {
+ watchopts.File = filepath.Join(root, DefaultWatchFile)
}
-
- var (
- v string
- re *regexp.Regexp
- )
- watchopts.watchRE = nil
- for _, v = range watchopts.Watches {
- re, err = regexp.Compile(v)
- if err != nil {
- return err
- }
- watchopts.watchRE = append(watchopts.watchRE, re)
- }
- return nil
-}
-
-// isWatched return true if the sysPath is filtered to be watched.
-func (watchopts *WatchOptions) isWatched(sysPath string) bool {
- var re *regexp.Regexp
- for _, re = range watchopts.watchRE {
- if re.MatchString(sysPath) {
- return true
- }
+ if watchopts.Interval < 10*time.Millisecond {
+ watchopts.Interval = defWatchInterval
}
- return false
}