Skip to content

Commit

Permalink
Merge pull request #297 from arexon/fsnotify-dir-watcher
Browse files Browse the repository at this point in the history
Switch to fsnotify for watch mode
  • Loading branch information
Nusiq authored Oct 14, 2024
2 parents e51e376 + e66dcdb commit 25db602
Show file tree
Hide file tree
Showing 10 changed files with 177 additions and 220 deletions.
34 changes: 2 additions & 32 deletions CREDITS.csv
Original file line number Diff line number Diff line change
@@ -1,56 +1,26 @@
cloud.google.com/go/compute/metadata,https://github.com/googleapis/google-cloud-go/blob/compute/v1.5.0/compute/LICENSE,Apache-2.0
cloud.google.com/go/iam,https://github.com/googleapis/google-cloud-go/blob/iam/v0.3.0/iam/LICENSE,Apache-2.0
cloud.google.com/go/internal,https://github.com/googleapis/google-cloud-go/blob/v0.100.2/LICENSE,Apache-2.0
cloud.google.com/go/storage,https://github.com/googleapis/google-cloud-go/blob/storage/v1.21.0/storage/LICENSE,Apache-2.0
github.com/Bedrock-OSS/go-burrito/burrito,https://github.com/Bedrock-OSS/go-burrito/blob/v1.0.3/LICENSE,MIT
github.com/Bedrock-OSS/regolith,https://github.com/Bedrock-OSS/regolith/blob/HEAD/LICENSE,MIT
github.com/alessio/shellescape,https://github.com/alessio/shellescape/blob/v1.4.1/LICENSE,MIT
github.com/antlr/antlr4/runtime/Go/antlr/v4,https://github.com/antlr/antlr4/blob/76fa05c21b12/runtime/Go/antlr/v4/LICENSE,BSD-3-Clause
github.com/aws/aws-sdk-go,https://github.com/aws/aws-sdk-go/blob/v1.43.25/LICENSE.txt,Apache-2.0
github.com/aws/aws-sdk-go/internal/sync/singleflight,https://github.com/aws/aws-sdk-go/blob/v1.43.25/internal/sync/singleflight/LICENSE,BSD-3-Clause
github.com/bgentry/go-netrc/netrc,https://github.com/bgentry/go-netrc/blob/9fd32a8b3d3d/LICENSE,MIT
github.com/arexon/fsnotify,https://github.com/arexon/fsnotify/blob/1ebdc44d4bc2/LICENSE,BSD-3-Clause
github.com/fatih/color,https://github.com/fatih/color/blob/v1.14.1/LICENSE.md,MIT
github.com/gammazero/deque,https://github.com/gammazero/deque/blob/v0.2.1/LICENSE,MIT
github.com/golang/groupcache/lru,https://github.com/golang/groupcache/blob/41bb18bfe9da/LICENSE,Apache-2.0
github.com/golang/protobuf,https://github.com/golang/protobuf/blob/v1.5.2/LICENSE,BSD-3-Clause
github.com/google/go-cmp/cmp,https://github.com/google/go-cmp/blob/v0.5.8/LICENSE,BSD-3-Clause
github.com/google/go-github/v39/github,https://github.com/google/go-github/blob/v39.2.0/LICENSE,BSD-3-Clause
github.com/google/go-querystring/query,https://github.com/google/go-querystring/blob/v1.1.0/LICENSE,BSD-3-Clause
github.com/googleapis/gax-go/v2,https://github.com/googleapis/gax-go/blob/v2.2.0/v2/LICENSE,BSD-3-Clause
github.com/hashicorp/go-cleanhttp,https://github.com/hashicorp/go-cleanhttp/blob/v0.5.2/LICENSE,MPL-2.0
github.com/hashicorp/go-getter,https://github.com/arikkfir/go-getter/blob/281b7670b734/LICENSE,MPL-2.0
github.com/hashicorp/go-safetemp,https://github.com/hashicorp/go-safetemp/blob/v1.0.0/LICENSE,MPL-2.0
github.com/hashicorp/go-version,https://github.com/hashicorp/go-version/blob/v1.4.0/LICENSE,MPL-2.0
github.com/jmespath/go-jmespath,https://github.com/jmespath/go-jmespath/blob/v0.4.0/LICENSE,Apache-2.0
github.com/klauspost/compress,https://github.com/klauspost/compress/blob/v1.15.1/LICENSE,Apache-2.0
github.com/klauspost/compress/internal/snapref,https://github.com/klauspost/compress/blob/v1.15.1/internal/snapref/LICENSE,BSD-3-Clause
github.com/klauspost/compress/zstd/internal/xxhash,https://github.com/klauspost/compress/blob/v1.15.1/zstd/internal/xxhash/LICENSE.txt,MIT
github.com/mattn/go-colorable,https://github.com/mattn/go-colorable/blob/v0.1.13/LICENSE,MIT
github.com/mattn/go-isatty,https://github.com/mattn/go-isatty/blob/v0.0.17/LICENSE,MIT
github.com/mitchellh/go-homedir,https://github.com/mitchellh/go-homedir/blob/v1.1.0/LICENSE,MIT
github.com/mitchellh/go-testing-interface,https://github.com/mitchellh/go-testing-interface/blob/v1.14.1/LICENSE,MIT
github.com/muhammadmuzzammil1998/jsonc,https://github.com/muhammadmuzzammil1998/jsonc/blob/v1.0.0/LICENSE,MIT
github.com/nightlyone/lockfile,https://github.com/nightlyone/lockfile/blob/v1.0.0/LICENSE,MIT
github.com/otiai10/copy,https://github.com/otiai10/copy/blob/v1.7.0/LICENSE,MIT
github.com/paul-mannino/go-fuzzywuzzy,https://github.com/paul-mannino/go-fuzzywuzzy/blob/54652b135d0e/LICENSE,GPL-3.0
github.com/spf13/cobra,https://github.com/spf13/cobra/blob/v1.6.1/LICENSE.txt,Apache-2.0
github.com/spf13/pflag,https://github.com/spf13/pflag/blob/v1.0.5/LICENSE,BSD-3-Clause
github.com/stirante/go-simple-eval,https://github.com/stirante/go-simple-eval/blob/9ed520afbec1/LICENSE,GPL-3.0
github.com/ulikunitz/xz,https://github.com/ulikunitz/xz/blob/v0.5.10/LICENSE,BSD-3-Clause
go.opencensus.io,https://github.com/census-instrumentation/opencensus-go/blob/v0.23.0/LICENSE,Apache-2.0
go.uber.org/atomic,https://github.com/uber-go/atomic/blob/v1.9.0/LICENSE.txt,MIT
go.uber.org/multierr,https://github.com/uber-go/multierr/blob/v1.8.0/LICENSE.txt,MIT
go.uber.org/zap,https://github.com/uber-go/zap/blob/v1.23.0/LICENSE.txt,MIT
golang.org/x/crypto,https://cs.opensource.google/go/x/crypto/+/v0.1.0:LICENSE,BSD-3-Clause
golang.org/x/exp,https://cs.opensource.google/go/x/exp/+/aae9b4e6:LICENSE,BSD-3-Clause
golang.org/x/mod/semver,https://cs.opensource.google/go/x/mod/+/v0.6.0:LICENSE,BSD-3-Clause
golang.org/x/net,https://cs.opensource.google/go/x/net/+/v0.1.0:LICENSE,BSD-3-Clause
golang.org/x/oauth2,https://cs.opensource.google/go/x/oauth2/+/6242fa91:LICENSE,BSD-3-Clause
golang.org/x/sys/unix,https://cs.opensource.google/go/x/sys/+/v0.4.0:LICENSE,BSD-3-Clause
golang.org/x/sys/unix,https://cs.opensource.google/go/x/sys/+/v0.13.0:LICENSE,BSD-3-Clause
golang.org/x/text,https://cs.opensource.google/go/x/text/+/v0.6.0:LICENSE,BSD-3-Clause
golang.org/x/xerrors,https://cs.opensource.google/go/x/xerrors/+/5ec99f83:LICENSE,BSD-3-Clause
google.golang.org/api,https://github.com/googleapis/google-api-go-client/blob/v0.73.0/LICENSE,BSD-3-Clause
google.golang.org/api/internal/third_party/uritemplates,https://github.com/googleapis/google-api-go-client/blob/v0.73.0/internal/third_party/uritemplates/LICENSE,BSD-3-Clause
google.golang.org/genproto/googleapis,https://github.com/googleapis/go-genproto/blob/acbaeb5b85eb/LICENSE,Apache-2.0
google.golang.org/grpc,https://github.com/grpc/grpc-go/blob/v1.45.0/LICENSE,Apache-2.0
google.golang.org/protobuf,https://github.com/protocolbuffers/protobuf-go/blob/v1.28.0/LICENSE,BSD-3-Clause
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ require (
github.com/stirante/go-simple-eval v0.0.0-20230131075324-9ed520afbec1
go.uber.org/zap v1.23.0
golang.org/x/mod v0.6.0
golang.org/x/sys v0.4.0
golang.org/x/sys v0.13.0
)

replace github.com/hashicorp/go-getter => github.com/arikkfir/go-getter v1.6.3-0.20220803164326-281b7670b734
Expand All @@ -27,6 +27,7 @@ require (
cloud.google.com/go/iam v0.3.0 // indirect
cloud.google.com/go/storage v1.21.0 // indirect
github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20221202181307-76fa05c21b12 // indirect
github.com/arexon/fsnotify v0.0.0-20240929211932-1ebdc44d4bc2 // indirect
github.com/aws/aws-sdk-go v1.43.25 // indirect
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect
github.com/gammazero/deque v0.2.1 // indirect
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPp
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20221202181307-76fa05c21b12 h1:npHgfD4Tl2WJS3AJaMUi5ynGDPUBfkg3U3fCzDyXZ+4=
github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20221202181307-76fa05c21b12/go.mod h1:pSwJ0fSY5KhvocuWSx4fz3BA8OrA1bQn+K1Eli3BRwM=
github.com/arexon/fsnotify v0.0.0-20240929211932-1ebdc44d4bc2 h1:Y4fOHCKaIWeRXZ9+qqaB7UI0tjK/eMN6CZ9OCbY3FBY=
github.com/arexon/fsnotify v0.0.0-20240929211932-1ebdc44d4bc2/go.mod h1:fMK1EJDCm6IfeqTBptyizpl356fZy33nWqFKELbFouQ=
github.com/arikkfir/go-getter v1.6.3-0.20220803164326-281b7670b734 h1:csFUhbcumnsC5d0SMF8CvtR6Z/i4UeNgOZ6xUaQUYas=
github.com/arikkfir/go-getter v1.6.3-0.20220803164326-281b7670b734/go.mod h1:IZCrswsZPeWv9IkVnLElzRU/gz/QPi6pZHn4tv6vbwA=
github.com/aws/aws-sdk-go v1.15.78/go.mod h1:E3/ieXAlvM0XWO57iftYVDLLvQ824smPP3ATZkfNZeM=
Expand Down Expand Up @@ -473,6 +475,8 @@ golang.org/x/sys v0.0.0-20220517195934-5e4e11fc645e/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
Expand Down
21 changes: 0 additions & 21 deletions regolith/compatibility_other_os.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,27 +30,6 @@ func copyFileSecurityInfo(source string, target string) error {
return nil
}

type DirWatcher struct{}

func NewDirWatcher(path string) (*DirWatcher, error) {
return nil, burrito.WrappedError(notImplementedOnThisSystemError)
}

func (d *DirWatcher) WaitForChange() error {
return burrito.WrappedError(notImplementedOnThisSystemError)
}

func (d *DirWatcher) WaitForChangeGroup(
groupTimeout uint32, interruptionChannel chan string,
interruptionMessage string,
) error {
return burrito.WrappedError(notImplementedOnThisSystemError)
}

func (d *DirWatcher) Close() error {
return burrito.WrappedError(notImplementedOnThisSystemError)
}

func FindStandardMojangDir() (string, error) {
comMojang := os.Getenv("COM_MOJANG")
if comMojang == "" {
Expand Down
93 changes: 0 additions & 93 deletions regolith/compatibility_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,99 +53,6 @@ func copyFileSecurityInfo(source string, target string) error {
return nil
}

// DirWatcher is a struct that provides easy to use methods for watching a
// directory for changes. It uses FindFirstChangeNotification instead of
// ReadDirectoryChanges, so it doesn't provide any information about the
// changes, only the fact that something changed.
//
// Useful links:
// https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-findfirstchangenotificationa
//
// https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-findnextchangenotification
//
// https://pkg.go.dev/golang.org/x/sys@v0.0.0-20220412211240-33da011f77ad/windows
//
// https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-readdirectorychangesw
type DirWatcher struct {
handle windows.Handle
}

// NewDirWatcher creates a new DirWatcher for the given path. It filters out
// some of the less interesting events like FILE_NOTIFY_CHANGE_LAST_ACCESS.
func NewDirWatcher(path string) (*DirWatcher, error) {
var notifyFilter uint32 = (windows.FILE_NOTIFY_CHANGE_FILE_NAME |
windows.FILE_NOTIFY_CHANGE_DIR_NAME |
// windows.FILE_NOTIFY_CHANGE_ATTRIBUTES |
// windows.FILE_NOTIFY_CHANGE_SIZE |
windows.FILE_NOTIFY_CHANGE_LAST_WRITE |
// windows.FILE_NOTIFY_CHANGE_LAST_ACCESS |
// windows.FILE_NOTIFY_CHANGE_SECURITY |
windows.FILE_NOTIFY_CHANGE_CREATION)
handle, err := windows.FindFirstChangeNotification(
path, true, notifyFilter)
if err != nil {
return nil, err
}
return &DirWatcher{handle: handle}, nil
}

// WaitForChange locks the goroutine until a single change is detected. Note
// that some changes are reported multiple times, for example saving a file
// will cause a change to the file and a change to the directory. If you want
// to report cases like that as one event, see WaitForChangeGroup.
func (d *DirWatcher) WaitForChange() error {
_, err := windows.WaitForSingleObject(d.handle, windows.INFINITE)
if err != nil {
return err
}
err = windows.FindNextChangeNotification(d.handle)
if err != nil {
return err
}
return nil
}

// WaitForChangeGroup locks a goroutine until it receives a change notification.
// When that happens it sends the interruptionMessage to the
// interruptionChannel.
// Then it continues locking as long as other changes keep coming with
// intervals less than the given timeout, to group notifications that come
// in short intervals together.
func (d *DirWatcher) WaitForChangeGroup(
groupTimeout uint32, interruptionChannel chan string,
interruptionMessage string,
) error {
err := d.WaitForChange()
if err != nil {
return err
}
// Instantly report the change
interruptionChannel <- interruptionMessage
// Consume all changes for groupDelay duration
for {
event, err := windows.WaitForSingleObject(d.handle, groupTimeout)
if err != nil {
return err
}
// Possible options: WAIT_OBJECT_0, WAIT_ABANDONED, WAIT_TIMEOUT,
// WAIT_FAILED
if event == uint32(windows.WAIT_TIMEOUT) ||
event == uint32(windows.WAIT_ABANDONED) {
break
}
err = windows.FindNextChangeNotification(d.handle)
if err != nil {
return err
}
}
return nil
}

// Close closes DirWatcher handle.
func (d *DirWatcher) Close() error {
return windows.CloseHandle(d.handle)
}

// FindStandardMojangDir returns path to the com.mojang folder in the standard
// Minecraft build.
func FindStandardMojangDir() (string, error) {
Expand Down
75 changes: 18 additions & 57 deletions regolith/filter.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package regolith

import "github.com/Bedrock-OSS/go-burrito/burrito"
import (
"github.com/Bedrock-OSS/go-burrito/burrito"
)

type FilterDefinition struct {
Id string `json:"-"`
Expand All @@ -23,16 +25,18 @@ type RunContext struct {
DotRegolithPath string
Settings map[string]interface{}

// interruptionChannel is a channel that is used to notify about changes
// interruption is a channel that is used to notify about changes
// in the source files, in order to trigger a restart of the program in
// the watch mode. The string send to the channel is the name of the source
// of the change ("rp", "bp" or "data"), which may be used to handle
// some interruptions differently.
interruptionChannel chan string
interruption chan string

// fileWatchingErrorChannel is used to pass any errors that may occur during
// fileWatchingError is used to pass any errors that may occur during
// file watching.
fileWatchingErrorChannel chan error
fileWatchingError chan error

fileWatchingStage chan string
}

// GetProfile returns the Profile structure from the context.
Expand All @@ -48,74 +52,31 @@ func (c *RunContext) GetProfile() (Profile, error) {
// IsInWatchMode returns a value that shows whether the context is in the
// watch mode.
func (c *RunContext) IsInWatchMode() bool {
return c.interruptionChannel == nil
return c.interruption != nil
}

// StartWatchingSourceFiles causes the Context to start goroutines that watch
// for changes in the source files and report that to the
func (c *RunContext) StartWatchingSourceFiles() error {
// TODO - if you want to be able to restart the watcher, you need to handle
// closing the channels somewhere. Currently the watching goroutines yield
// their messages until the end of the program. Sending to a closed channel
// would cause panic.
if c.interruptionChannel != nil {
return burrito.WrappedError("Files are already being watched.")
}

c.interruptionChannel = make(chan string)
c.fileWatchingErrorChannel = make(chan error)
yieldChanges := func(
watcher *DirWatcher, sourceName string,
) {
for {
err := watcher.WaitForChangeGroup(100, c.interruptionChannel, sourceName)
if err != nil {
c.fileWatchingErrorChannel <- err
}
}
}

addWatcher := func(watchedPath, watcherString string) error {
watcher, err := NewDirWatcher(watchedPath)
if err != nil {
return burrito.PassError(err)
}
go yieldChanges(watcher, watcherString)
return nil
}

var err error
if c.Config.ResourceFolder != "" {
err = addWatcher(c.Config.ResourceFolder, "rp")
if err != nil {
return burrito.WrapError(err, "Could not create resource pack watcher.")
}
}
if c.Config.BehaviorFolder != "" {
err = addWatcher(c.Config.BehaviorFolder, "bp")
if err != nil {
return burrito.WrapError(err, "Could not create behavior pack watcher.")
}
c.interruption = make(chan string)
c.fileWatchingError = make(chan error)
c.fileWatchingStage = make(chan string)
err := NewDirWatcher(c.Config, c.interruption, c.fileWatchingError, c.fileWatchingStage)
if err != nil {
return err
}
if c.Config.DataPath != "" {
err = addWatcher(c.Config.DataPath, "data")
if err != nil {
return burrito.WrapError(err, "Could not create data watcher.")
}
}

return nil
}

// IsInterrupted returns true if there is a message on the interruptionChannel
// unless the source of the interruption is on the list of ignored sources.
// This function does not block.
func (c *RunContext) IsInterrupted(ignoredSource ...string) bool {
if c.interruptionChannel == nil {
if c.interruption == nil {
return false
}
select {
case source := <-c.interruptionChannel:
case source := <-c.interruption:
for _, ignored := range ignoredSource {
if ignored == source {
return false
Expand Down
14 changes: 7 additions & 7 deletions regolith/filter_profile.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ type ProfileFilter struct {
func (f *ProfileFilter) Run(context RunContext) (bool, error) {
Logger.Infof("Running %q nested profile...", f.Profile)
return RunProfileImpl(RunContext{
Profile: f.Profile,
AbsoluteLocation: context.AbsoluteLocation,
Config: context.Config,
Parent: &context,
interruptionChannel: context.interruptionChannel,
DotRegolithPath: context.DotRegolithPath,
Settings: f.Settings,
Profile: f.Profile,
AbsoluteLocation: context.AbsoluteLocation,
Config: context.Config,
Parent: &context,
interruption: context.interruption,
DotRegolithPath: context.DotRegolithPath,
Settings: f.Settings,
})
}

Expand Down
18 changes: 9 additions & 9 deletions regolith/main_functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -312,11 +312,11 @@ func Watch(profileName string, debug bool) error {
}
Logger.Info("Press Ctrl+C to stop watching.")
select {
case <-context.interruptionChannel:
case <-context.interruption:
// AwaitInterruption locks the goroutine with the interruption channel until
// the Config is interrupted and returns the interruption message.
Logger.Warn("Restarting...")
case err := <-context.fileWatchingErrorChannel:
case err := <-context.fileWatchingError:
if err != nil {
return burrito.WrapError(err, "Encountered an error during file watching")
}
Expand Down Expand Up @@ -380,13 +380,13 @@ func ApplyFilter(filterName string, filterArgs []string, debug bool) error {
// Create run context
path, _ := filepath.Abs(".")
runContext := RunContext{
Config: config,
Parent: nil,
Profile: "[dynamic profile]",
DotRegolithPath: dotRegolithPath,
interruptionChannel: nil,
AbsoluteLocation: path,
Settings: filterRunner.GetSettings(),
Config: config,
Parent: nil,
Profile: "[dynamic profile]",
DotRegolithPath: dotRegolithPath,
interruption: nil,
AbsoluteLocation: path,
Settings: filterRunner.GetSettings(),
}
// Check the filter
err = filterRunner.Check(runContext)
Expand Down
Loading

0 comments on commit 25db602

Please sign in to comment.