diff --git a/listen.go b/listen.go new file mode 100644 index 0000000..4b39e98 --- /dev/null +++ b/listen.go @@ -0,0 +1,157 @@ +package main + +import ( + "crypto/sha256" + "fmt" + "io" + "log" + "maps" + "os" + "os/exec" + "os/signal" + "slices" + "time" + + "github.com/fsnotify/fsnotify" +) + +type Listen struct { + FileMap map[string]bool + CksumMap map[string][]byte + Condition string + Cksum bool + Interval string + Command []string +} + +func loopFsnotify(l Listen, quit chan bool) { + watcher, err := fsnotify.NewWatcher() + if err != nil { + log.Fatal(err) + } + + defer watcher.Close() + for key := range l.FileMap { + watcher.Add(key) + fmt.Println(watcher.WatchList()) + } + + for { + var renameAdd []string = []string{} + hasher := sha256.New() + trigger := false + select { + case event, ok := <-watcher.Events: + if !ok { + return + } + + for key := range l.FileMap { + if event.Name == key { + if l.Cksum { + f, err := os.Open(key) + if err != nil { + log.Fatal(err) + } + defer f.Close() + + if _, err := io.Copy(hasher, f); err != nil { + log.Fatal(err) + } + + if !slices.Equal(l.CksumMap[key], hasher.Sum(nil)) { + if l.Condition == "any" { + trigger = true + break + } + + l.FileMap[key] = true + } + } else { + if event.Has(fsnotify.Write) { + if l.Condition == "any" { + trigger = true + break + } + + l.FileMap[key] = true + } else if event.Has(fsnotify.Rename) { + // we need to rewatch file + // sleeping small amount to allow CREATE event to propogate + time.Sleep(10 * time.Millisecond) + // ... then adding to a list to allow additional time + renameAdd = append(renameAdd, key) + if l.Condition == "any" { + trigger = true + break + } + + l.FileMap[key] = true + } + } + } + } // end for + + if l.Condition == "all" { + trigger = true + for value := range maps.Values(l.FileMap) { + if !value { + trigger = false + break + } + } + } // end if condition + // end case event + case _, ok := <-watcher.Errors: + if !ok { + return + } + } // end switch + // end case errors + + if trigger { + if len(l.Command) >= 1 { + cmd := exec.Command(l.Command[0], l.Command[1:]...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Run() + } else { + quit <- true + } + } // end if trigger + + for _, value := range renameAdd { + err := watcher.Add(value) + if err != nil { + fmt.Println(err) + } + } + } // end for +} + +func loopInterval(l Listen, quit chan bool) { + for { + fmt.Printf("running") + time.Sleep(1 * time.Second) + } +} + +func (l Listen) Run() { + // catch ^C + quit := make(chan bool) + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt) + + // main loop + if l.Interval == "" { + go loopFsnotify(l, quit) + } else { + go loopInterval(l, quit) + } + + select { + case <-c: + case <-quit: + } + +} diff --git a/main.go b/main.go index 8d78216..7129f84 100644 --- a/main.go +++ b/main.go @@ -1,17 +1,14 @@ package main import ( + "crypto/sha256" "fmt" + "io" "io/fs" "log" - "maps" "os" - "os/exec" - "os/signal" "path/filepath" - "time" - "github.com/fsnotify/fsnotify" flags "github.com/jessevdk/go-flags" ) @@ -21,7 +18,7 @@ var opts struct { Version bool `short:"v" long:"version" description:"Displays version info and exits"` Files []string `short:"f" long:"file" description:"File(s) to listen to (watch)" value-name:"FILE"` Condition string `short:"w" long:"when" description:"If multiple files are specified, choose if any file or all files specified are needed to trigger COMMAND" default:"any" choice:"any" choice:"all"` - Checksum bool `short:"c" long:"checksum" description:"Use checksum to determine when file(s) are changed instead of modification time (only used when -i is specified)"` + Checksum bool `short:"c" long:"checksum" description:"Use checksum to determine when file(s) are changed instead of writes/modification time"` Interval string `short:"i" long:"interval" description:"Use this time interval (ex. 5m30s, 1s) between filesystem checks instead of using kernel events"` } @@ -53,122 +50,6 @@ func validateFiles(files []string) error { return nil } -func listen(files map[string]bool, condition string, cksum bool, interval string, command []string) { - // catch ^C - quit := make(chan bool) - c := make(chan os.Signal, 1) - signal.Notify(c, os.Interrupt) - - // main loop - if interval == "" { - watcher, err := fsnotify.NewWatcher() - if err != nil { - log.Fatal(err) - } - - defer watcher.Close() - for key := range files { - watcher.Add(key) - fmt.Println(watcher.WatchList()) - } - - go func() { - for { - var renameAdd []string = []string{} - // hasher := sha256.New() - trigger := false - select { - case event, ok := <-watcher.Events: - if !ok { - return - } - // if _, err := io.Copy(hasher, event.); err != nil { - - // } - // fmt.Printf("%x", event) - - for key := range files { - if event.Name == key { - if event.Has(fsnotify.Write) { - if condition == "any" { - trigger = true - break - } - - files[key] = true - } - - if event.Has(fsnotify.Rename) { - // we need to rewatch file - // sleeping small amount to allow CREATE event to propogate - time.Sleep(10 * time.Millisecond) - // ... then adding to a list to allow additional time - renameAdd = append(renameAdd, key) - if condition == "any" { - trigger = true - break - } - - files[key] = true - } - } - } // end for - - if condition == "all" { - trigger = true - for value := range maps.Values(files) { - if !value { - trigger = false - break - } - } - } // end if condition - // end case event - case _, ok := <-watcher.Errors: - if !ok { - return - } - } // end switch - // end case errors - - if trigger { - if len(command) >= 1 { - cmd := exec.Command(command[0], command[1:]...) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - cmd.Run() - } else { - quit <- true - } - } // end if trigger - - for _, value := range renameAdd { - err := watcher.Add(value) - if err != nil { - fmt.Println(err) - } - } - } // end for - }() // end go func - - } else { - go func() { - for { - fmt.Printf("running") - time.Sleep(1 * time.Second) - } - - }() - - } - - select { - case <-c: - case <-quit: - } - -} - func main() { parser := flags.NewParser(&opts, flags.Default) parser.Usage = "[OPTIONS] -- [COMMAND]" @@ -179,18 +60,33 @@ func main() { fmt.Printf("listen %s\n", VERSION) } else if err := validateFiles(opts.Files); err == nil { var filesMap map[string]bool = make(map[string]bool) + var cksumMap map[string][]byte = make(map[string][]byte) success := true for _, file := range opts.Files { + hasher := sha256.New() + f, err := os.Open(file) + if err != nil { + success = false + break + } + + if _, err := io.Copy(hasher, f); err != nil { + log.Fatal(err) + } + abs, err := filepath.Abs(file) if err == nil { filesMap[abs] = false + cksumMap[abs] = hasher.Sum(nil) } else { success = false + break } } if success { - listen(filesMap, opts.Condition, opts.Checksum, opts.Interval, remaining) + l := Listen{filesMap, cksumMap, opts.Condition, opts.Checksum, opts.Interval, remaining} + l.Run() } } }