diff options
| author | Shulhan <ms@kilabit.info> | 2024-12-23 01:26:25 +0700 |
|---|---|---|
| committer | Shulhan <ms@kilabit.info> | 2024-12-28 16:26:40 +0700 |
| commit | f9cf8ab0d908a6a1b4077c5f96876cf9574e0173 (patch) | |
| tree | 66dcab7a4998aa2149811fff7a9ea9356792f398 | |
| parent | c14c0e7735778573dc4d87e39703f0524d08a1b0 (diff) | |
| download | pakakeh.go-f9cf8ab0d908a6a1b4077c5f96876cf9574e0173.tar.xz | |
lib/memfs: refactoring Watch to use "watchfs/v2"
| -rw-r--r-- | lib/memfs/doc.go | 16 | ||||
| -rw-r--r-- | lib/memfs/filestate.go | 5 | ||||
| -rw-r--r-- | lib/memfs/memfs.go | 155 | ||||
| -rw-r--r-- | lib/memfs/memfs_example_test.go | 80 | ||||
| -rw-r--r-- | lib/memfs/node.go | 5 | ||||
| -rw-r--r-- | lib/memfs/node_test.go | 5 | ||||
| -rw-r--r-- | lib/memfs/nodestate.go | 5 | ||||
| -rw-r--r-- | lib/memfs/watch_options.go | 67 |
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 } |
