mirror of
https://github.com/simon987/beemer.git
synced 2025-04-10 13:56:42 +00:00
--tar option
This commit is contained in:
parent
1362aae61a
commit
bb3282c467
15
README.md
15
README.md
@ -4,6 +4,8 @@
|
|||||||
[](https://www.codefactor.io/repository/github/simon987/beemer)
|
[](https://www.codefactor.io/repository/github/simon987/beemer)
|
||||||
|
|
||||||
**beemer** executes a custom command on files written in the watched directory and deletes it.
|
**beemer** executes a custom command on files written in the watched directory and deletes it.
|
||||||
|
Optionally, queue files in a .tar file and execute the command when the number of files in the
|
||||||
|
archive reaches `NUMBER` (see [usage](#usage)).
|
||||||
|
|
||||||
### Usage
|
### Usage
|
||||||
|
|
||||||
@ -16,6 +18,8 @@ GLOBAL OPTIONS:
|
|||||||
--command value, -c value Will be executed on file write. You can use %file, %name and %dir. Example: "rclone move %file remote:/beem/%dir"
|
--command value, -c value Will be executed on file write. You can use %file, %name and %dir. Example: "rclone move %file remote:/beem/%dir"
|
||||||
--wait DELAY, -w DELAY Files will be beemed after DELAY of inactivity (default: 10s)
|
--wait DELAY, -w DELAY Files will be beemed after DELAY of inactivity (default: 10s)
|
||||||
--directory DIRECTORY, -d DIRECTORY DIRECTORY to watch.
|
--directory DIRECTORY, -d DIRECTORY DIRECTORY to watch.
|
||||||
|
--tar NUMBER Fill a .tar file with up to NUMBER file before executing the beem command.
|
||||||
|
Set to '1' to disable this feature (default: 1)
|
||||||
--help, -h show help
|
--help, -h show help
|
||||||
--version, -v print the version
|
--version, -v print the version
|
||||||
|
|
||||||
@ -23,6 +27,17 @@ GLOBAL OPTIONS:
|
|||||||
|
|
||||||
### Examples
|
### Examples
|
||||||
|
|
||||||
|
Bundle up to 100 files in a tar file before moving to another directory
|
||||||
|
|
||||||
|
\**Note that %dir is always `/tmp/beemer`* when `--tar` is specified
|
||||||
|
|
||||||
|
When `--tar NUM` is specified, the beem command will be called at most
|
||||||
|
every `NUM` new files.
|
||||||
|
It will also be called during cleanup when SIGINT (`Ctrl-C`) is received.
|
||||||
|
```bash
|
||||||
|
./beemer -w 1s -d ./test --tar 100 -c "mv %file /mnt/store/my_tars/"
|
||||||
|
```
|
||||||
|
|
||||||
Upload file to an rclone remote when it has been inactive for at least 30s,
|
Upload file to an rclone remote when it has been inactive for at least 30s,
|
||||||
keeps the directory structure
|
keeps the directory structure
|
||||||
```bash
|
```bash
|
||||||
|
87
beemer.go
87
beemer.go
@ -16,9 +16,13 @@ type Beemer struct {
|
|||||||
tempDir string
|
tempDir string
|
||||||
beemCommand func(string, string) (string, []string)
|
beemCommand func(string, string) (string, []string)
|
||||||
beemChan chan string
|
beemChan chan string
|
||||||
|
tarChan chan string
|
||||||
watcher *fsnotify.Watcher
|
watcher *fsnotify.Watcher
|
||||||
inactiveDelay time.Duration
|
inactiveDelay time.Duration
|
||||||
globalWg *sync.WaitGroup
|
beemWg *sync.WaitGroup
|
||||||
|
tarWg *sync.WaitGroup
|
||||||
|
tar *Tar
|
||||||
|
tarMaxCount int
|
||||||
}
|
}
|
||||||
|
|
||||||
type File struct {
|
type File struct {
|
||||||
@ -26,7 +30,7 @@ type File struct {
|
|||||||
BeemLock bool
|
BeemLock bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b Beemer) initWatchDir(watchDir string) {
|
func (b *Beemer) initWatchDir(watchDir string) {
|
||||||
|
|
||||||
logrus.WithField("dir", watchDir).Info("Watching directory for changes")
|
logrus.WithField("dir", watchDir).Info("Watching directory for changes")
|
||||||
|
|
||||||
@ -55,7 +59,7 @@ func (b Beemer) initWatchDir(watchDir string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b Beemer) getAndResetTimer(name string) *time.Timer {
|
func (b *Beemer) getAndResetTimer(name string) *time.Timer {
|
||||||
|
|
||||||
file, ok := b.fileMap[name]
|
file, ok := b.fileMap[name]
|
||||||
if ok {
|
if ok {
|
||||||
@ -74,7 +78,7 @@ func (b Beemer) getAndResetTimer(name string) *time.Timer {
|
|||||||
return newTimer
|
return newTimer
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b Beemer) handleDirChange(event fsnotify.Event) error {
|
func (b *Beemer) handleDirChange(event fsnotify.Event) error {
|
||||||
|
|
||||||
if event.Op&fsnotify.Create == fsnotify.Create {
|
if event.Op&fsnotify.Create == fsnotify.Create {
|
||||||
return b.watcher.Add(event.Name)
|
return b.watcher.Add(event.Name)
|
||||||
@ -85,7 +89,7 @@ func (b Beemer) handleDirChange(event fsnotify.Event) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b Beemer) handleFileChange(event fsnotify.Event) {
|
func (b *Beemer) handleFileChange(event fsnotify.Event) {
|
||||||
|
|
||||||
if event.Op&fsnotify.Write == fsnotify.Write || event.Op&fsnotify.Create == fsnotify.Create {
|
if event.Op&fsnotify.Write == fsnotify.Write || event.Op&fsnotify.Create == fsnotify.Create {
|
||||||
t := b.getAndResetTimer(event.Name)
|
t := b.getAndResetTimer(event.Name)
|
||||||
@ -100,7 +104,7 @@ func (b Beemer) handleFileChange(event fsnotify.Event) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b Beemer) handleWatcherEvents() {
|
func (b *Beemer) handleWatcherEvents() {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case event, ok := <-b.watcher.Events:
|
case event, ok := <-b.watcher.Events:
|
||||||
@ -134,15 +138,57 @@ func (b Beemer) handleWatcherEvents() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b Beemer) work() {
|
func (b *Beemer) work() {
|
||||||
b.globalWg.Add(1)
|
b.beemWg.Add(1)
|
||||||
for name := range b.beemChan {
|
for name := range b.beemChan {
|
||||||
b.beemFile(name)
|
b.beemFile(name)
|
||||||
}
|
}
|
||||||
b.globalWg.Done()
|
b.beemWg.Done()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b Beemer) handleFileInactive(t *time.Timer, name string) {
|
func (b *Beemer) tarWork() {
|
||||||
|
b.tarWg.Add(1)
|
||||||
|
for filename := range b.tarChan {
|
||||||
|
|
||||||
|
err := b.tar.AddFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
logrus.WithField("filename", filename).Error(err)
|
||||||
|
} else {
|
||||||
|
_ = os.Remove(filename)
|
||||||
|
}
|
||||||
|
logrus.WithFields(logrus.Fields{
|
||||||
|
"filename": filename,
|
||||||
|
"tar": b.tar.Name,
|
||||||
|
"count": b.tar.FileCount,
|
||||||
|
}).Info("Added file to tar")
|
||||||
|
|
||||||
|
if b.tar.FileCount >= b.tarMaxCount {
|
||||||
|
b.beemTar()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.tar.FileCount > 0 {
|
||||||
|
logrus.WithField("fileCount", b.tar.FileCount).Info("Beeming partial tar file")
|
||||||
|
b.beemTar()
|
||||||
|
}
|
||||||
|
|
||||||
|
b.tarWg.Done()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Beemer) beemTar() {
|
||||||
|
|
||||||
|
name := b.tar.Name
|
||||||
|
b.tar.Close()
|
||||||
|
var err error
|
||||||
|
b.tar, err = NewTar(getTarPath(b.tempDir))
|
||||||
|
if err != nil {
|
||||||
|
logrus.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
b.executeBeemCommand(name, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Beemer) handleFileInactive(t *time.Timer, name string) {
|
||||||
<-t.C
|
<-t.C
|
||||||
|
|
||||||
b.fileMap[name].BeemLock = true
|
b.fileMap[name].BeemLock = true
|
||||||
@ -154,11 +200,9 @@ func (b Beemer) handleFileInactive(t *time.Timer, name string) {
|
|||||||
b.beemChan <- name
|
b.beemChan <- name
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b Beemer) beemFile(filename string) {
|
func (b *Beemer) executeBeemCommand(oldName string, newName string) {
|
||||||
|
|
||||||
newName := moveToTempDir(filename, b.tempDir)
|
name, args := b.beemCommand(newName, filepath.Dir(oldName))
|
||||||
|
|
||||||
name, args := b.beemCommand(newName, filepath.Dir(filename))
|
|
||||||
|
|
||||||
cmd := exec.Command(name, args...)
|
cmd := exec.Command(name, args...)
|
||||||
|
|
||||||
@ -169,7 +213,7 @@ func (b Beemer) beemFile(filename string) {
|
|||||||
|
|
||||||
out, err := cmd.CombinedOutput()
|
out, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.WithField("name", filename).WithError(err).Error(string(out))
|
logrus.WithField("name", oldName).WithError(err).Error(string(out))
|
||||||
}
|
}
|
||||||
|
|
||||||
logrus.WithFields(logrus.Fields{
|
logrus.WithFields(logrus.Fields{
|
||||||
@ -181,6 +225,17 @@ func (b Beemer) beemFile(filename string) {
|
|||||||
|
|
||||||
err = os.Remove(newName)
|
err = os.Remove(newName)
|
||||||
if err != nil && !os.IsNotExist(err) {
|
if err != nil && !os.IsNotExist(err) {
|
||||||
logrus.WithField("name", filename).Error(err)
|
logrus.WithField("name", oldName).Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Beemer) beemFile(filename string) {
|
||||||
|
|
||||||
|
newName := moveToTempDir(filename, b.tempDir)
|
||||||
|
|
||||||
|
if b.tar != nil {
|
||||||
|
b.tarChan <- newName
|
||||||
|
} else {
|
||||||
|
b.executeBeemCommand(filename, newName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
24
cli.go
24
cli.go
@ -31,6 +31,7 @@ func main() {
|
|||||||
var cmdString string
|
var cmdString string
|
||||||
var watchDir string
|
var watchDir string
|
||||||
var transfers int
|
var transfers int
|
||||||
|
var tarMaxCount int
|
||||||
var inactiveDelay time.Duration
|
var inactiveDelay time.Duration
|
||||||
|
|
||||||
app.Flags = []cli.Flag{
|
app.Flags = []cli.Flag{
|
||||||
@ -57,6 +58,13 @@ func main() {
|
|||||||
Usage: "`DIRECTORY` to watch.",
|
Usage: "`DIRECTORY` to watch.",
|
||||||
Destination: &watchDir,
|
Destination: &watchDir,
|
||||||
},
|
},
|
||||||
|
cli.IntFlag{
|
||||||
|
Name: "tar",
|
||||||
|
Usage: "Fill a .tar file with up to `NUMBER` file before executing the beem command." +
|
||||||
|
"Set to '1' to disable this feature",
|
||||||
|
Value: 1,
|
||||||
|
Destination: &tarMaxCount,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
app.Action = func(c *cli.Context) error {
|
app.Action = func(c *cli.Context) error {
|
||||||
@ -68,15 +76,27 @@ func main() {
|
|||||||
beemer := Beemer{
|
beemer := Beemer{
|
||||||
fileMap: make(map[string]*File, 0),
|
fileMap: make(map[string]*File, 0),
|
||||||
beemChan: make(chan string, transfers),
|
beemChan: make(chan string, transfers),
|
||||||
|
tarChan: make(chan string, 100),
|
||||||
beemCommand: parseCommand(cmdString),
|
beemCommand: parseCommand(cmdString),
|
||||||
inactiveDelay: inactiveDelay,
|
inactiveDelay: inactiveDelay,
|
||||||
globalWg: &sync.WaitGroup{},
|
beemWg: &sync.WaitGroup{},
|
||||||
|
tarWg: &sync.WaitGroup{},
|
||||||
|
tarMaxCount: tarMaxCount,
|
||||||
}
|
}
|
||||||
|
|
||||||
beemer.initTempDir()
|
beemer.initTempDir()
|
||||||
|
|
||||||
beemer.watcher, _ = fsnotify.NewWatcher()
|
beemer.watcher, _ = fsnotify.NewWatcher()
|
||||||
|
|
||||||
|
if tarMaxCount > 1 {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
beemer.tar, err = NewTar(getTarPath(beemer.tempDir))
|
||||||
|
if err != nil {
|
||||||
|
logrus.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
go beemer.handleWatcherEvents()
|
go beemer.handleWatcherEvents()
|
||||||
|
|
||||||
beemer.initWatchDir(watchDir)
|
beemer.initWatchDir(watchDir)
|
||||||
@ -85,6 +105,8 @@ func main() {
|
|||||||
go beemer.work()
|
go beemer.work()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
go beemer.tarWork()
|
||||||
|
|
||||||
sigChan := make(chan os.Signal, 1)
|
sigChan := make(chan os.Signal, 1)
|
||||||
signal.Notify(sigChan, syscall.SIGINT)
|
signal.Notify(sigChan, syscall.SIGINT)
|
||||||
|
|
||||||
|
71
tar.go
Normal file
71
tar.go
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/tar"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Tar struct {
|
||||||
|
FileCount int
|
||||||
|
Name string
|
||||||
|
writer *tar.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTar(path string) (*Tar, error) {
|
||||||
|
|
||||||
|
fp, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0666)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
writer := tar.NewWriter(fp)
|
||||||
|
|
||||||
|
return &Tar{
|
||||||
|
0,
|
||||||
|
path,
|
||||||
|
writer,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTarPath(tempDir string) string {
|
||||||
|
return filepath.Join(tempDir, time.Now().Format("2006-01-02 15.04.05.000.tar"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tar) AddFile(path string) error {
|
||||||
|
|
||||||
|
// Header
|
||||||
|
fi, err := os.Stat(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
header, err := tar.FileInfoHeader(fi, "")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = t.writer.WriteHeader(header)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Content
|
||||||
|
in, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = io.Copy(t.writer, in)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
t.FileCount += 1
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tar) Close() {
|
||||||
|
_ = t.writer.Close()
|
||||||
|
}
|
20
util.go
20
util.go
@ -9,8 +9,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (b Beemer) initTempDir() {
|
func (b *Beemer) initTempDir() {
|
||||||
tmpdir := filepath.Join(os.TempDir(), "work")
|
tmpdir := filepath.Join(os.TempDir(), "beemer")
|
||||||
err := os.Mkdir(tmpdir, 0700)
|
err := os.Mkdir(tmpdir, 0700)
|
||||||
if err != nil && !os.IsExist(err) {
|
if err != nil && !os.IsExist(err) {
|
||||||
logrus.Fatal(err)
|
logrus.Fatal(err)
|
||||||
@ -106,15 +106,27 @@ func isDir(name string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b Beemer) dispose() {
|
func (b *Beemer) dispose() {
|
||||||
b.watcher.Close()
|
b.watcher.Close()
|
||||||
|
|
||||||
|
for _, v := range b.fileMap {
|
||||||
|
v.WaitTimer.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
close(b.beemChan)
|
close(b.beemChan)
|
||||||
|
|
||||||
logrus.WithField("chanLen", len(b.beemChan)).Info("Waiting for beem queue to drain...")
|
logrus.WithField("chanLen", len(b.beemChan)).Info("Waiting for beem queue to drain...")
|
||||||
<-b.beemChan
|
<-b.beemChan
|
||||||
|
|
||||||
logrus.Info("Waiting for current commands to finish...")
|
logrus.Info("Waiting for current commands to finish...")
|
||||||
b.globalWg.Wait()
|
b.beemWg.Wait()
|
||||||
|
|
||||||
|
close(b.tarChan)
|
||||||
|
logrus.WithField("chanLen", len(b.tarChan)).Info("Waiting for tar queue to drain...")
|
||||||
|
<-b.tarChan
|
||||||
|
|
||||||
|
logrus.Info("Waiting for current tar process finish...")
|
||||||
|
b.tarWg.Wait()
|
||||||
|
|
||||||
logrus.Info("Cleaning up temp dir...")
|
logrus.Info("Cleaning up temp dir...")
|
||||||
err := os.RemoveAll(b.tempDir)
|
err := os.RemoveAll(b.tempDir)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user