interval loop implemented, consolidated code

This commit is contained in:
Bryson Steck 2025-06-29 11:25:24 -06:00
parent 4f2d3acf7c
commit f766c14846
Signed by: bryson
SSH key fingerprint: SHA256:XpKABw/nP4z8UVaH+weLaBnEOD86+cVwif+QjuYLGT4
3 changed files with 117 additions and 41 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
.vscode

130
listen.go
View file

@ -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)

27
main.go
View file

@ -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()
}
}