aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMartin Polden <mpolden@mpolden.no>2018-08-04 16:01:49 +0200
committerMartin Polden <mpolden@mpolden.no>2018-08-04 16:05:50 +0200
commit6d1421c1989d7aed80a1cd1c92d4f9ebafa67e65 (patch)
treedf6c3d453ea23f33d706c7ba02d939d349336650
parent70aa4e41b17b7dc8f7e3214219b35561f89f25d4 (diff)
Replace OnFile with Handler interface
-rw-r--r--cmd/unp/main.go2
-rw-r--r--rar/rar.go135
-rw-r--r--rar/rar_test.go32
-rw-r--r--watcher/watcher.go60
-rw-r--r--watcher/watcher_test.go69
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()
}
diff --git a/rar/rar.go b/rar/rar.go
index 05592b4..115b43b 100644
--- a/rar/rar.go
+++ b/rar/rar.go
@@ -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)
}