listen/listen.go

213 lines
3.6 KiB
Go

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
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) {
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 {
// return value indicates a break is needed
if cksumCheck(l, key, &trigger) {
break
}
} 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 = allCheck(l)
}
// 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 {
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
}
}
func (l Listen) Run() {
// catch ^C
quit := make(chan bool)
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
// start main loop
if l.Interval == time.Duration(0*time.Second) {
go loopFsnotify(l, quit)
} else {
go loopInterval(l, quit)
}
select {
case <-c:
case <-quit:
}
}