2025-06-29 10:33:58 -06:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"crypto/sha256"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"log"
|
|
|
|
"maps"
|
|
|
|
"os"
|
|
|
|
"os/exec"
|
|
|
|
"os/signal"
|
|
|
|
"slices"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/fsnotify/fsnotify"
|
|
|
|
)
|
|
|
|
|
|
|
|
type Listen struct {
|
2025-06-29 11:25:24 -06:00
|
|
|
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
|
2025-06-29 10:33:58 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
|
|
|
for {
|
|
|
|
var renameAdd []string = []string{}
|
|
|
|
trigger := false
|
|
|
|
select {
|
|
|
|
case event, ok := <-watcher.Events:
|
|
|
|
if !ok {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
for key := range l.FileMap {
|
|
|
|
if event.Name == key {
|
|
|
|
if l.Cksum {
|
2025-06-29 11:25:24 -06:00
|
|
|
// return value indicates a break is needed
|
|
|
|
if cksumCheck(l, key, &trigger) {
|
|
|
|
break
|
2025-06-29 10:33:58 -06:00
|
|
|
}
|
|
|
|
} 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" {
|
2025-06-29 11:25:24 -06:00
|
|
|
trigger = allCheck(l)
|
|
|
|
}
|
2025-06-29 10:33:58 -06:00
|
|
|
// 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 {
|
2025-06-29 11:25:24 -06:00
|
|
|
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
|
|
|
|
|
2025-06-29 10:33:58 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (l Listen) Run() {
|
|
|
|
// catch ^C
|
|
|
|
quit := make(chan bool)
|
|
|
|
c := make(chan os.Signal, 1)
|
|
|
|
signal.Notify(c, os.Interrupt)
|
|
|
|
|
2025-06-29 11:25:24 -06:00
|
|
|
// start main loop
|
|
|
|
if l.Interval == time.Duration(0*time.Second) {
|
2025-06-29 10:33:58 -06:00
|
|
|
go loopFsnotify(l, quit)
|
|
|
|
} else {
|
|
|
|
go loopInterval(l, quit)
|
|
|
|
}
|
|
|
|
|
|
|
|
select {
|
|
|
|
case <-c:
|
|
|
|
case <-quit:
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|