diff options
author | Martin Polden <mpolden@mpolden.no> | 2018-08-04 16:01:49 +0200 |
---|---|---|
committer | Martin Polden <mpolden@mpolden.no> | 2018-08-04 16:05:50 +0200 |
commit | 6d1421c1989d7aed80a1cd1c92d4f9ebafa67e65 (patch) | |
tree | df6c3d453ea23f33d706c7ba02d939d349336650 | |
parent | 70aa4e41b17b7dc8f7e3214219b35561f89f25d4 (diff) |
Replace OnFile with Handler interface
-rw-r--r-- | cmd/unp/main.go | 2 | ||||
-rw-r--r-- | rar/rar.go | 135 | ||||
-rw-r--r-- | rar/rar_test.go | 32 | ||||
-rw-r--r-- | watcher/watcher.go | 60 | ||||
-rw-r--r-- | watcher/watcher_test.go | 69 |
5 files changed, 148 insertions, 150 deletions
diff --git a/cmd/unp/main.go b/cmd/unp/main.go index 0a94bce..7e8c28f 100644 --- a/cmd/unp/main.go +++ b/cmd/unp/main.go @@ -37,6 +37,6 @@ func main() { } log := log.New(os.Stderr, "unp: ", 0) - w := watcher.New(cfg, rar.Unpack, log) + w := watcher.New(cfg, rar.NewHandler(), log) w.Start() } @@ -8,6 +8,7 @@ import ( "path/filepath" "regexp" "strings" + "sync" "text/template" "github.com/mpolden/sfv" @@ -17,49 +18,34 @@ import ( var rarPartRE = regexp.MustCompile(`\.part0*(\d+)\.rar$`) -type archive struct { - Name string - Dir string +type event struct { Base string -} - -type unpacker struct { + Dir string + Name string sfv *sfv.SFV - dir string - name string } -func newCmd(tmpl string, a archive) (*exec.Cmd, error) { - t, err := template.New("cmd").Parse(tmpl) - if err != nil { - return nil, err - } - var b bytes.Buffer - if err := t.Execute(&b, a); err != nil { - return nil, err - } - argv := strings.Split(b.String(), " ") - if len(argv) == 0 { - return nil, errors.New("template compiled to empty command") - } - cmd := exec.Command(argv[0], argv[1:]...) - cmd.Dir = a.Dir - return cmd, nil +type Handler struct { + mu sync.Mutex } -func newUnpacker(dir string) (*unpacker, error) { +func NewHandler() *Handler { return &Handler{} } + +func eventFrom(filename string) (event, error) { + dir := filepath.Dir(filename) sfv, err := sfv.Find(dir) if err != nil { - return nil, err + return event{}, err } rar, err := findFirstRAR(sfv) if err != nil { - return nil, err + return event{}, err } - return &unpacker{ + return event{ sfv: sfv, - dir: dir, - name: rar, + Base: filepath.Base(rar), + Dir: dir, + Name: rar, }, nil } @@ -89,11 +75,12 @@ func chtimes(name string, header *rardecode.FileHeader) error { return os.Chtimes(name, header.ModificationTime, header.ModificationTime) } -func (u *unpacker) unpack(name string) error { - r, err := rardecode.OpenReader(name, "") +func unpack(filename string) error { + r, err := rardecode.OpenReader(filename, "") if err != nil { - return errors.Wrapf(err, "failed to open %s", name) + return errors.Wrapf(err, "failed to open %s", filename) } + dir := filepath.Dir(filename) for { header, err := r.Next() if err == io.EOF { @@ -102,7 +89,7 @@ func (u *unpacker) unpack(name string) error { if err != nil { return err } - name := filepath.Join(u.dir, header.Name) + name := filepath.Join(dir, header.Name) // If entry is a directory, create it and set correct ctime if header.IsDir { if err := os.MkdirAll(name, 0755); err != nil { @@ -138,7 +125,7 @@ func (u *unpacker) unpack(name string) error { } // Unpack recursively if unpacked file is also a RAR if isRAR(name) { - if err := u.unpack(name); err != nil { + if err := unpack(name); err != nil { return err } } @@ -146,65 +133,61 @@ func (u *unpacker) unpack(name string) error { return nil } -func (u *unpacker) remove() error { - for _, c := range u.sfv.Checksums { +func remove(sfv *sfv.SFV) error { + for _, c := range sfv.Checksums { if err := os.Remove(c.Path); err != nil { return err } } - return os.Remove(u.sfv.Path) + return os.Remove(sfv.Path) } -func (u *unpacker) fileCount() (int, int) { +func fileCount(sfv *sfv.SFV) (int, int) { exists := 0 - for _, c := range u.sfv.Checksums { + for _, c := range sfv.Checksums { if c.IsExist() { exists++ } } - return exists, len(u.sfv.Checksums) + return exists, len(sfv.Checksums) } -func (u *unpacker) verify() error { - for _, c := range u.sfv.Checksums { +func verify(sfv *sfv.SFV) error { + for _, c := range sfv.Checksums { ok, err := c.Verify() if err != nil { return err } if !ok { - return errors.Errorf("%s: failed checksum: %s", u.sfv.Path, c.Filename) + return errors.Errorf("%s: failed checksum: %s", sfv.Path, c.Filename) } } return nil } -func (u *unpacker) Run(removeRARs bool) error { - if exists, total := u.fileCount(); exists != total { - return errors.Errorf("incomplete: %s: %d/%d files", u.dir, exists, total) - } - if err := u.verify(); err != nil { - return errors.Wrapf(err, "verification failed: %s", u.dir) +func cmdFrom(tmpl string, ev event) (*exec.Cmd, error) { + t, err := template.New("cmd").Parse(tmpl) + if err != nil { + return nil, err } - if err := u.unpack(u.name); err != nil { - return errors.Wrapf(err, "unpacking failed: %s", u.dir) + var b bytes.Buffer + if err := t.Execute(&b, ev); err != nil { + return nil, err } - if removeRARs { - if err := u.remove(); err != nil { - return errors.Wrapf(err, "removal failed: %s", u.dir) - } + argv := strings.Split(b.String(), " ") + if len(argv) == 0 { + return nil, errors.New("template compiled to empty command") } - return nil + cmd := exec.Command(argv[0], argv[1:]...) + cmd.Dir = ev.Dir + return cmd, nil } -func postProcess(u *unpacker, command string) error { +func runCmd(command string, e event) error { if command == "" { return nil } - cmd, err := newCmd(command, archive{ - Name: u.name, - Base: filepath.Base(u.dir), - Dir: u.dir, - }) + cmd, err := cmdFrom(command, e) if err != nil { return err } @@ -216,17 +199,29 @@ func postProcess(u *unpacker, command string) error { return nil } -func Unpack(name, postCommand string, remove bool) error { - path := filepath.Dir(name) - u, err := newUnpacker(path) +func (h *Handler) Handle(name, postCommand string, removeRARs bool) error { + h.mu.Lock() + defer h.mu.Unlock() + ev, err := eventFrom(name) if err != nil { return err } - if err := u.Run(remove); err != nil { - return err + if exists, total := fileCount(ev.sfv); exists != total { + return errors.Errorf("incomplete: %s: %d/%d files", ev.Dir, exists, total) + } + if err := verify(ev.sfv); err != nil { + return errors.Wrapf(err, "verification failed: %s", ev.Dir) + } + if err := unpack(ev.Name); err != nil { + return errors.Wrapf(err, "unpacking failed: %s", ev.Dir) + } + if removeRARs { + if err := remove(ev.sfv); err != nil { + return errors.Wrapf(err, "removal failed: %s", ev.Dir) + } } - if err := postProcess(u, postCommand); err != nil { - return errors.Wrapf(err, "post-process command failed: %s", path) + if err := runCmd(postCommand, ev); err != nil { + return errors.Wrapf(err, "post-process command failed: %s", ev.Dir) } return nil } diff --git a/rar/rar_test.go b/rar/rar_test.go index 82ec024..96d88df 100644 --- a/rar/rar_test.go +++ b/rar/rar_test.go @@ -10,42 +10,40 @@ import ( "github.com/mpolden/sfv" ) -func TestNewCmd(t *testing.T) { +func TestCmdFrom(t *testing.T) { tmpl := "tar -xf {{.Name}} {{.Base}} {{.Dir}}" - values := archive{ + values := event{ Name: "/foo/bar/baz.rar", Base: "baz.rar", Dir: "/foo/bar", } - - cmd, err := newCmd(tmpl, values) + cmd, err := cmdFrom(tmpl, values) if err != nil { t.Fatal(err) } if cmd.Dir != values.Dir { - t.Fatalf("Expected %s, got %s", values.Dir, cmd.Dir) + t.Fatalf("want %q, got %q", values.Dir, cmd.Dir) } if !strings.Contains(cmd.Path, string(os.PathSeparator)) { - t.Fatalf("Expected %s to contain a path separator", cmd.Path) + t.Fatalf("want %q to contain a path separator", cmd.Path) } if cmd.Args[0] != "tar" { - t.Fatalf("Expected 'tar', got '%s'", cmd.Args[0]) + t.Fatalf("want %q, got %q", "tar", cmd.Args[0]) } if cmd.Args[1] != "-xf" { - t.Fatalf("Expected '-xf', got '%s'", cmd.Args[1]) + t.Fatalf("want %q, got %q", "-xf", cmd.Args[1]) } if cmd.Args[2] != values.Name { - t.Fatalf("Expected '%s', got '%s'", values.Name, cmd.Args[2]) + t.Fatalf("want %q, got %q", values.Name, cmd.Args[2]) } if cmd.Args[3] != values.Base { - t.Fatalf("Expected '%s', got '%s'", values.Base, cmd.Args[3]) + t.Fatalf("want %q, got %q", values.Base, cmd.Args[3]) } if cmd.Args[4] != values.Dir { - t.Fatalf("Expected '%s', got '%s'", values.Base, cmd.Args[4]) + t.Fatalf("want %q, got %q", values.Base, cmd.Args[4]) } - - if _, err := newCmd("tar -xf {{.Bar}}", values); err == nil { - t.Fatal("Expected error") + if _, err := cmdFrom("tar -xf {{.Bar}}", values); err == nil { + t.Fatal("want error") } } @@ -89,7 +87,7 @@ func TestFindFirstRAR(t *testing.T) { } } -func TestUnpack(t *testing.T) { +func TestHandle(t *testing.T) { wd, err := os.Getwd() if err != nil { t.Fatal(err) @@ -115,7 +113,8 @@ func TestUnpack(t *testing.T) { }() // Trigger unpacking by passing in a file contained in testdata - if err := Unpack(tests[0].file, "", false); err != nil { + h := NewHandler() + if err := h.Handle(tests[0].file, "", false); err != nil { t.Fatal(err) } @@ -132,5 +131,4 @@ func TestUnpack(t *testing.T) { t.Errorf("#%d: want mtime = %d, got %d for file %s", i, tt.mtime, got, tt.file) } } - } diff --git a/watcher/watcher.go b/watcher/watcher.go index 0f7fee0..7a87a21 100644 --- a/watcher/watcher.go +++ b/watcher/watcher.go @@ -15,20 +15,22 @@ import ( "github.com/rjeczalik/notify" ) -type OnFile func(string, string, bool) error - -type watcher struct { - config Config - onFile OnFile - events chan notify.EventInfo - signal chan os.Signal - done chan bool - log *log.Logger - mu sync.Mutex - wg sync.WaitGroup +type Handler interface { + Handle(filename, postCommand string, remove bool) error } -func (w *watcher) handle(name string) error { +type Watcher struct { + config Config + handler Handler + events chan notify.EventInfo + signal chan os.Signal + done chan bool + log *log.Logger + mu sync.Mutex + wg sync.WaitGroup +} + +func (w *Watcher) handle(name string) error { p, ok := w.config.findPath(name) if !ok { return errors.Errorf("no configured path found: %s", name) @@ -47,10 +49,10 @@ func (w *watcher) handle(name string) error { } return errors.Errorf("no match found: %s", name) } - return w.onFile(name, p.PostCommand, p.Remove) + return w.handler.Handle(name, p.PostCommand, p.Remove) } -func (w *watcher) watch() { +func (w *Watcher) watch() { for _, path := range w.config.Paths { rpath := filepath.Join(path.Name, "...") if err := notify.Watch(rpath, w.events, writeFlag); err != nil { @@ -61,7 +63,7 @@ func (w *watcher) watch() { } } -func (w *watcher) reload() { +func (w *Watcher) reload() { cfg, err := ReadConfig(w.config.filename) if err == nil { notify.Stop(w.events) @@ -72,7 +74,7 @@ func (w *watcher) reload() { } } -func (w *watcher) rescan() { +func (w *Watcher) rescan() { for _, p := range w.config.Paths { err := filepath.Walk(p.Name, func(path string, info os.FileInfo, err error) error { if err != nil { @@ -92,7 +94,7 @@ func (w *watcher) rescan() { } } -func (w *watcher) readSignal() { +func (w *Watcher) readSignal() { for { select { case <-w.done: @@ -115,7 +117,7 @@ func (w *watcher) readSignal() { } } -func (w *watcher) readEvent() { +func (w *Watcher) readEvent() { for { select { case <-w.done: @@ -130,7 +132,7 @@ func (w *watcher) readEvent() { } } -func (w *watcher) goServe() { +func (w *Watcher) goServe() { w.wg.Add(2) go func() { defer w.wg.Done() @@ -142,30 +144,30 @@ func (w *watcher) goServe() { }() } -func (w *watcher) Start() { +func (w *Watcher) Start() { w.goServe() w.watch() w.wg.Wait() } -func (w *watcher) Stop() { +func (w *Watcher) Stop() { notify.Stop(w.events) w.done <- true w.done <- true } -func New(cfg Config, onFile OnFile, log *log.Logger) *watcher { +func New(cfg Config, handler Handler, log *log.Logger) *Watcher { // Buffer events so that we don't miss any events := make(chan notify.EventInfo, cfg.BufferSize) sig := make(chan os.Signal, 1) done := make(chan bool, 1) signal.Notify(sig) - return &watcher{ - config: cfg, - events: events, - log: log, - onFile: onFile, - signal: sig, - done: done, + return &Watcher{ + config: cfg, + events: events, + log: log, + handler: handler, + signal: sig, + done: done, } } diff --git a/watcher/watcher_test.go b/watcher/watcher_test.go index 399eba8..2e811ce 100644 --- a/watcher/watcher_test.go +++ b/watcher/watcher_test.go @@ -11,6 +11,30 @@ import ( "time" ) +type testHandler struct { + wantFile string + files []string +} + +func (h *testHandler) Handle(filename, postCommand string, remove bool) error { + if h.wantFile != "" && filename != h.wantFile { + return fmt.Errorf("unhandled file: %q", filename) + } + h.files = append(h.files, filename) + return nil +} + +func (h *testHandler) awaitFile(file string) (bool, error) { + ts := time.Now() + for len(h.files) == 0 { + time.Sleep(10 * time.Millisecond) + if time.Since(ts) > 2*time.Second { + return false, fmt.Errorf("timed out waiting for file notification") + } + } + return h.files[0] == file, nil +} + func tempDir() string { dir, err := ioutil.TempDir("", "unp") if err != nil { @@ -23,35 +47,21 @@ func tempDir() string { return path } -func testWatcher(dir string, onFile OnFile) *watcher { +func testWatcher(dir string, handler Handler) *Watcher { cfg := Config{ BufferSize: 10, Paths: []Path{{Name: dir, MaxDepth: 100, Patterns: []string{"*"}}}, } log := log.New(ioutil.Discard, "", 0) - return New(cfg, onFile, log) -} - -func awaitFile(files *[]string, file string) (bool, error) { - ts := time.Now() - for len(*files) == 0 { - time.Sleep(10 * time.Millisecond) - if time.Since(ts) > 2*time.Second { - return false, fmt.Errorf("timed out waiting for file notification") - } - } - return (*files)[0] == file, nil + return New(cfg, handler, log) } func TestWatching(t *testing.T) { dir := tempDir() defer os.RemoveAll(dir) - var files []string - w := testWatcher(dir, func(name, postCommand string, remove bool) error { - files = append(files, name) - return nil - }) + h := testHandler{} + w := testWatcher(dir, &h) w.goServe() w.watch() defer w.Stop() @@ -61,12 +71,12 @@ func TestWatching(t *testing.T) { t.Fatal(err) } - ok, err := awaitFile(&files, f) + ok, err := h.awaitFile(f) if err != nil { t.Fatal(err) } if !ok { - t.Errorf("want %s, got %s", f, files[0]) + t.Errorf("want %s, got %s", f, h.files[0]) } } @@ -77,13 +87,8 @@ func TestRescanning(t *testing.T) { f1 := filepath.Join(dir, "foo") f2 := filepath.Join(dir, "bar") var files []string - w := testWatcher(dir, func(name, postCommand string, remove bool) error { - if name != f1 { - return fmt.Errorf("unhandled file: %s", name) - } - files = append(files, name) - return nil - }) + h := &testHandler{wantFile: f1} + w := testWatcher(dir, h) defer w.Stop() // Files are written before watcher is started @@ -104,7 +109,7 @@ func TestRescanning(t *testing.T) { // USR1 triggers rescan w.signal <- syscall.SIGUSR1 - ok, err := awaitFile(&files, f1) + ok, err := h.awaitFile(f1) if err != nil { t.Fatal(err) } @@ -115,10 +120,8 @@ func TestRescanning(t *testing.T) { func TestReloading(t *testing.T) { var files []string - w := testWatcher("", func(name, postCommand string, remove bool) error { - files = append(files, name) - return nil - }) + h := &testHandler{} + w := testWatcher("", h) defer w.Stop() tmp, err := ioutil.TempFile("", "unp") @@ -159,7 +162,7 @@ func TestReloading(t *testing.T) { t.Fatal(err) } - ok, err := awaitFile(&files, f) + ok, err := h.awaitFile(f) if err != nil { t.Fatal(err) } |