beemer/main.go
2019-07-16 15:28:32 -04:00

224 lines
4.5 KiB
Go
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package main
import (
"github.com/fsnotify/fsnotify"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/urfave/cli"
"os"
"os/exec"
"path/filepath"
"time"
)
type Ctx struct {
GlobalUploadTicker time.Ticker
FileMap map[string]*File
TempDir string
BeemCommand func(string, string) (string, []string)
}
type File struct {
WaitTimer *time.Timer
BeemLock bool
}
var InactiveDelay time.Duration
var ctx = Ctx{
FileMap: make(map[string]*File, 0),
}
func main() {
logrus.SetLevel(logrus.TraceLevel)
app := cli.NewApp()
app.Name = "beemer"
app.Usage = "Execute a command on a file after a delay of inactivity"
app.Email = "me@simon987.net"
app.Author = "simon987"
app.Version = "1.0"
var cmdString string
var watchDir string
app.Flags = []cli.Flag{
cli.StringFlag{
Name: "command, c",
Usage: "Will be executed on file write. You can use %file, %name and %dir. " +
"Example: \"rclone move %file remote:/beem/%dir\"",
Destination: &cmdString,
},
cli.DurationFlag{
Name: "wait, w",
Usage: "Files will be beemed after `DELAY` of inactivity",
Destination: &InactiveDelay,
Value: time.Second * 10,
},
cli.StringFlag{
Name: "directory, d",
Usage: "`DIRECTORY` to watch. If non-empty, its current files & subdirectories will be ignored",
Destination: &watchDir,
},
}
app.Action = func(c *cli.Context) error {
if !c.IsSet("directory") {
return errors.New("Directory must be specified")
}
initTempDir()
ctx.BeemCommand = parseCommand(cmdString)
watcher, err := fsnotify.NewWatcher()
if err != nil {
logrus.Fatal(err)
}
defer watcher.Close()
go handleWatcherEvents(watcher)
logrus.WithField("dir", watchDir).Info("Watching directory for changes")
err = watcher.Add(watchDir)
if err != nil {
logrus.Fatal(err)
}
//TODO gracefully handle SIGINT
done := make(chan bool)
<-done
return nil
}
err := app.Run(os.Args)
if err != nil {
logrus.Fatal(app.OnUsageError)
}
}
func getAndResetTimer(name string) *time.Timer {
file, ok := ctx.FileMap[name]
if ok {
file.WaitTimer.Stop()
if file.BeemLock == true {
return nil
}
}
newTimer := time.NewTimer(InactiveDelay)
ctx.FileMap[name] = &File{
newTimer,
false,
}
return newTimer
}
func isDir(name string) bool {
if stat, err := os.Stat(name); err == nil && stat.IsDir() {
return true
}
return false
}
func handleDirChange(event fsnotify.Event, watcher *fsnotify.Watcher) error {
if event.Op&fsnotify.Create == fsnotify.Create {
return watcher.Add(event.Name)
} else if event.Op&fsnotify.Remove == fsnotify.Remove || event.Op&fsnotify.Rename == fsnotify.Rename {
return watcher.Remove(event.Name)
}
return nil
}
func handleFileChange(event fsnotify.Event) {
if event.Op&fsnotify.Write == fsnotify.Write || event.Op&fsnotify.Create == fsnotify.Create {
t := getAndResetTimer(event.Name)
if t != nil {
go handleFileInactive(t, event.Name)
}
} else if event.Op&fsnotify.Remove == fsnotify.Remove || event.Op&fsnotify.Rename == fsnotify.Rename {
if file, ok := ctx.FileMap[event.Name]; ok {
file.WaitTimer.Stop()
delete(ctx.FileMap, event.Name)
}
}
}
func handleWatcherEvents(watcher *fsnotify.Watcher) {
for {
select {
case event, ok := <-watcher.Events:
if !ok {
return
}
if isDir(event.Name) {
err := handleDirChange(event, watcher)
if err != nil {
logrus.Fatal(err)
}
} else {
handleFileChange(event)
}
if event.Op&fsnotify.Chmod != fsnotify.Chmod {
logrus.WithFields(logrus.Fields{
"name": event.Name,
"op": event.Op,
}).Trace("fsnotify")
}
case err, ok := <-watcher.Errors:
if !ok {
return
}
logrus.WithError(err).Error("error with Watcher")
}
}
}
func handleFileInactive(t *time.Timer, name string) {
<-t.C
ctx.FileMap[name].BeemLock = true
logrus.WithFields(logrus.Fields{
"name": name,
}).Infof("has been inactive for %s and will be beemed", InactiveDelay)
beemFile(name)
}
func beemFile(filename string) {
newName := moveToTempDir(filename)
name, args := ctx.BeemCommand(newName, filepath.Dir(filename))
cmd := exec.Command(name, args...)
out, err := cmd.CombinedOutput()
if err != nil {
logrus.WithField("name", filename).WithError(err).Error(string(out))
}
logrus.WithFields(logrus.Fields{
"name": newName,
"command": name,
"args": args,
"out": string(out),
}).Trace("Executing beem command")
err = os.Remove(newName)
if err != nil && !os.IsNotExist(err) {
logrus.WithField("name", filename).Error(err)
}
}