From a9b7988efc58b5f387e590b84e08b3015accce9e Mon Sep 17 00:00:00 2001 From: simon Date: Tue, 16 Jul 2019 13:23:40 -0400 Subject: [PATCH] Initial commit --- main.go | 161 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ util.go | 97 ++++++++++++++++++++++++++++++++++ 2 files changed, 258 insertions(+) create mode 100644 main.go create mode 100644 util.go diff --git a/main.go b/main.go new file mode 100644 index 0000000..a2817ef --- /dev/null +++ b/main.go @@ -0,0 +1,161 @@ +package main + +import ( + "github.com/fsnotify/fsnotify" + "github.com/sirupsen/logrus" + "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 CmdString = "rclone move %file remote:/beem/%dir" +var InactiveDelay = time.Second * 5 + +var ctx = Ctx{ + FileMap: make(map[string]*File, 0), +} + +func main() { + logrus.SetLevel(logrus.TraceLevel) + + initTempDir() + ctx.BeemCommand = parseCommand(CmdString) + + watcher, err := fsnotify.NewWatcher() + if err != nil { + logrus.Fatal(err) + } + + defer watcher.Close() + + go handleFileChange(watcher) + + err = watcher.Add("./test") + if err != nil { + logrus.Fatal(err) + } + + //TODO gracefully handle SIGINT + done := make(chan bool) + <-done +} + +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 handleFileChange(watcher *fsnotify.Watcher) { + for { + select { + case event, ok := <-watcher.Events: + + if !ok { + return + } + + if event.Op&fsnotify.Write == fsnotify.Write || event.Op&fsnotify.Create == fsnotify.Create { + if stat, err := os.Stat(event.Name); err == nil && stat.IsDir() { + logrus.WithField("name", event.Name).Info("Created dir") + err = watcher.Add(event.Name) + if err != nil { + logrus.Fatal(err) + } + } else { + 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 stat, err := os.Stat(event.Name); err == nil && stat.IsDir() { + logrus.WithField("name", event.Name).Info("Removed dir") + err = watcher.Remove(event.Name) + if err != nil { + logrus.Fatal(err) + } + } else if file, ok := ctx.FileMap[event.Name]; ok { + file.WaitTimer.Stop() + delete(ctx.FileMap, event.Name) + } + } + + 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) + } +} diff --git a/util.go b/util.go new file mode 100644 index 0000000..4a56138 --- /dev/null +++ b/util.go @@ -0,0 +1,97 @@ +package main + +import ( + "github.com/cosiner/argv" + "github.com/sirupsen/logrus" + "io" + "os" + "path/filepath" + "strings" +) + +func initTempDir() { + tmpdir := filepath.Join(os.TempDir(), "beemer") + err := os.Mkdir(tmpdir, 0700) + if err != nil && !os.IsExist(err) { + logrus.Fatal(err) + } + + ctx.TempDir = tmpdir + + logrus.WithField("dir", tmpdir).Infof("Initialized temp dir") +} + +func moveToTempDir(name string) string { + + dir := filepath.Join(ctx.TempDir, filepath.Dir(name)) + newName := filepath.Join(dir, filepath.Base(name)) + err := os.MkdirAll(dir, 0700) + if err != nil && !os.IsExist(err) { + logrus.Fatal(err) + } + + absName, _ := filepath.Abs(name) + err = moveFile(absName, newName) + if err != nil { + logrus.Fatal(err) + } + + logrus.WithFields(logrus.Fields{ + "newName": newName, + }).Trace("Moved to temp dir") + + return newName +} + +func moveFile(src string, dst string) error { + + err := os.Rename(src, dst) + if err != nil { + err := copyFile(src, dst) + if err != nil { + return err + } + + err = os.Remove(src) + if err != nil { + return err + } + } + return nil +} + +func copyFile(src string, dst string) error { + + in, err := os.Open(src) + if err != nil { + return err + } + defer in.Close() + + out, err := os.Create(dst) + if err != nil { + return err + } + defer out.Close() + + _, err = io.Copy(out, in) + if err != nil { + return err + } + return out.Close() +} + +func parseCommand(command string) func(string, string) (string, []string) { + args, _ := argv.Argv([]rune(command), argv.ParseEnv(os.Environ()), argv.Run) + + return func(name string, dir string) (string, []string) { + newTokens := make([]string, len(args[0])) + copy(newTokens, args[0]) + + for i := range newTokens { + newTokens[i] = strings.Replace(newTokens[i], "%file", name,-1) + newTokens[i] = strings.Replace(newTokens[i], "%dir", dir,-1) + } + return args[0][0], newTokens[1:] + } +}