diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..722d5e7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.vscode diff --git a/listen.go b/listen.go index 4b39e98..2c434a5 100644 --- a/listen.go +++ b/listen.go @@ -16,12 +16,49 @@ import ( ) type Listen struct { - FileMap map[string]bool - CksumMap map[string][]byte - Condition string - Cksum bool - Interval string - Command []string + FileMap map[string]bool + CksumMap map[string][]byte + IntervalMap map[string]time.Time + Condition string + Cksum bool + Interval time.Duration + Command []string + Quiet bool +} + +func cksumCheck(l Listen, k string, t *bool) bool { + hasher := sha256.New() + f, err := os.Open(k) + 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[k], hasher.Sum(nil)) { + l.CksumMap[k] = hasher.Sum(nil) + if l.Condition == "any" { + *t = true + return true + } + + l.FileMap[k] = true + } + return false + +} + +func allCheck(l Listen) bool { + for value := range maps.Values(l.FileMap) { + if !value { + return false + } + } + + return true } func loopFsnotify(l Listen, quit chan bool) { @@ -33,12 +70,10 @@ func loopFsnotify(l Listen, quit chan bool) { 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: @@ -49,23 +84,9 @@ func loopFsnotify(l Listen, quit chan bool) { 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 + // return value indicates a break is needed + if cksumCheck(l, key, &trigger) { + break } } else { if event.Has(fsnotify.Write) { @@ -93,14 +114,8 @@ func loopFsnotify(l Listen, quit chan bool) { } // end for if l.Condition == "all" { - trigger = true - for value := range maps.Values(l.FileMap) { - if !value { - trigger = false - break - } - } - } // end if condition + trigger = allCheck(l) + } // end case event case _, ok := <-watcher.Errors: if !ok { @@ -131,8 +146,49 @@ func loopFsnotify(l Listen, quit chan bool) { func loopInterval(l Listen, quit chan bool) { for { - fmt.Printf("running") - time.Sleep(1 * time.Second) + time.Sleep(l.Interval) + trigger := false + + for key := range l.FileMap { + if l.Cksum { + // return value indicates a break is needed + if cksumCheck(l, key, &trigger) { + break + } + } else { + s, err := os.Stat(key) + if err != nil { + log.Fatal() + } + + if l.IntervalMap[key] != s.ModTime() { + l.IntervalMap[key] = s.ModTime() + if l.Condition == "any" { + trigger = true + break + } + + l.FileMap[key] = true + } + + } + } // end for key + + if l.Condition == "all" { + trigger = allCheck(l) + } + + 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 + } } @@ -142,8 +198,8 @@ func (l Listen) Run() { c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt) - // main loop - if l.Interval == "" { + // start main loop + if l.Interval == time.Duration(0*time.Second) { go loopFsnotify(l, quit) } else { go loopInterval(l, quit) diff --git a/main.go b/main.go index 7129f84..a2dfd3c 100644 --- a/main.go +++ b/main.go @@ -8,6 +8,7 @@ import ( "log" "os" "path/filepath" + "time" flags "github.com/jessevdk/go-flags" ) @@ -16,13 +17,15 @@ const VERSION string = "v0.1.0" var opts struct { Version bool `short:"v" long:"version" description:"Displays version info and exits"` + Quiet bool `short:"q" long:"quiet" description:"Suppresses all non-error output"` 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 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"` + Interval string `short:"i" long:"interval" description:"Use this time interval (ex. 5m30s, 1s) between filesystem checks instead of watching kernel events. If the interval is effectively 0 (the default), kernel events are used" default:"0s"` } -func validateFiles(files []string) error { +func validateArgs(files []string, interval string) error { + // file checks if len(files) <= 0 { fmt.Println("listen: at least one file (-f) is required") return flags.ErrCommandRequired @@ -47,6 +50,12 @@ func validateFiles(files []string) error { } } + // interval checks + if _, err := time.ParseDuration(interval); err != nil { + fmt.Printf("listen: %s\n", err) + return err + } + return nil } @@ -58,9 +67,11 @@ func main() { if err == nil { if opts.Version { fmt.Printf("listen %s\n", VERSION) - } else if err := validateFiles(opts.Files); err == nil { + } else if err := validateArgs(opts.Files, opts.Interval); err == nil { var filesMap map[string]bool = make(map[string]bool) var cksumMap map[string][]byte = make(map[string][]byte) + var intervalMap map[string]time.Time = make(map[string]time.Time) + intervalDuration, _ := time.ParseDuration(opts.Interval) success := true for _, file := range opts.Files { hasher := sha256.New() @@ -69,6 +80,13 @@ func main() { success = false break } + defer f.Close() + + s, err := os.Stat(file) + if err != nil { + success = false + break + } if _, err := io.Copy(hasher, f); err != nil { log.Fatal(err) @@ -78,6 +96,7 @@ func main() { if err == nil { filesMap[abs] = false cksumMap[abs] = hasher.Sum(nil) + intervalMap[abs] = s.ModTime() } else { success = false break @@ -85,7 +104,7 @@ func main() { } if success { - l := Listen{filesMap, cksumMap, opts.Condition, opts.Checksum, opts.Interval, remaining} + l := Listen{filesMap, cksumMap, intervalMap, opts.Condition, opts.Checksum, intervalDuration, remaining, opts.Quiet} l.Run() } }