From: Matt Joiner <anacrolix@gmail.com>
Date: Fri, 22 Aug 2014 07:31:03 +0000 (+1000)
Subject: Simplify dirwatch to scan the whole directory on changes
X-Git-Tag: v1.0.0~1621
X-Git-Url: http://www.git.stargrave.org/?a=commitdiff_plain;h=60df4e100c6b0c2aea3d583f5bd55eee2a4aaa5e;p=btrtrc.git

Simplify dirwatch to scan the whole directory on changes
---

diff --git a/cmd/torrentfs/main.go b/cmd/torrentfs/main.go
index 63ec59df..1b3d9797 100644
--- a/cmd/torrentfs/main.go
+++ b/cmd/torrentfs/main.go
@@ -128,8 +128,8 @@ func main() {
 					if err != nil {
 						log.Printf("error adding torrent to client: %s", err)
 					}
-				} else if ev.Magnet != "" {
-					err := client.AddMagnet(ev.Magnet)
+				} else if ev.MagnetURI != "" {
+					err := client.AddMagnet(ev.MagnetURI)
 					if err != nil {
 						log.Printf("error adding magnet: %s", err)
 					}
diff --git a/util/dirwatch/dirwatch.go b/util/dirwatch/dirwatch.go
index 05458217..afc7653d 100644
--- a/util/dirwatch/dirwatch.go
+++ b/util/dirwatch/dirwatch.go
@@ -1,13 +1,18 @@
+// Package dirwatch provides filesystem-notification based tracking of torrent
+// info files and magnet URIs in a directory.
 package dirwatch
 
 import (
-	"bitbucket.org/anacrolix/go.torrent"
 	"bufio"
-	"github.com/anacrolix/libtorgo/metainfo"
-	"github.com/go-fsnotify/fsnotify"
 	"log"
 	"os"
 	"path/filepath"
+
+	"bitbucket.org/anacrolix/go.torrent/util"
+
+	"bitbucket.org/anacrolix/go.torrent"
+	"github.com/anacrolix/libtorgo/metainfo"
+	"github.com/go-fsnotify/fsnotify"
 )
 
 type Change uint
@@ -18,24 +23,38 @@ const (
 )
 
 type Event struct {
-	Magnet string
+	MagnetURI string
 	Change
 	TorrentFilePath string
 	InfoHash        torrent.InfoHash
 }
 
+type entity struct {
+	torrent.InfoHash
+	MagnetURI       string
+	TorrentFilePath string
+}
+
 type Instance struct {
-	w                     *fsnotify.Watcher
-	dirName               string
-	Events                chan Event
-	torrentFileInfoHashes map[string]torrent.InfoHash
-	magnetFileInfoHashes  map[string]map[torrent.InfoHash]struct{}
+	w        *fsnotify.Watcher
+	dirName  string
+	Events   chan Event
+	dirState map[torrent.InfoHash]entity
+}
+
+func (me *Instance) Close() {
+	me.w.Close()
 }
 
 func (me *Instance) handleEvents() {
+	defer close(me.Events)
 	for e := range me.w.Events {
 		log.Printf("event: %s", e)
-		me.processFile(e.Name)
+		if e.Op == fsnotify.Write {
+			// TODO: Special treatment as an existing torrent may have changed.
+		} else {
+			me.refresh()
+		}
 	}
 }
 
@@ -50,13 +69,68 @@ func torrentFileInfoHash(fileName string) (ih torrent.InfoHash, ok bool) {
 	if mi == nil {
 		return
 	}
-	if 20 != copy(ih[:], mi.Info.Hash) {
-		panic(mi.Info.Hash)
-	}
+	util.CopyExact(ih[:], mi.Info.Hash)
 	ok = true
 	return
 }
 
+func scanDir(dirName string) (ee map[torrent.InfoHash]entity) {
+	d, err := os.Open(dirName)
+	if err != nil {
+		log.Print(err)
+		return
+	}
+	defer d.Close()
+	names, err := d.Readdirnames(-1)
+	if err != nil {
+		log.Print(err)
+		return
+	}
+	ee = make(map[torrent.InfoHash]entity, len(names))
+	addEntity := func(e entity) {
+		e0, ok := ee[e.InfoHash]
+		if ok {
+			if e0.MagnetURI != "" && len(e.MagnetURI) < len(e0.MagnetURI) {
+				return
+			}
+		}
+		ee[e.InfoHash] = e
+	}
+	for _, n := range names {
+		fullName := filepath.Join(dirName, n)
+		switch filepath.Ext(n) {
+		case ".torrent":
+			ih, ok := torrentFileInfoHash(fullName)
+			if !ok {
+				break
+			}
+			e := entity{
+				TorrentFilePath: fullName,
+			}
+			util.CopyExact(e.InfoHash, ih)
+			addEntity(e)
+		case ".magnet":
+			uris, err := magnetFileURIs(fullName)
+			if err != nil {
+				log.Print(err)
+				break
+			}
+			for _, uri := range uris {
+				m, err := torrent.ParseMagnetURI(uri)
+				if err != nil {
+					log.Print(err)
+					continue
+				}
+				addEntity(entity{
+					InfoHash:  m.InfoHash,
+					MagnetURI: uri,
+				})
+			}
+		}
+	}
+	return
+}
+
 func magnetFileURIs(name string) (uris []string, err error) {
 	f, err := os.Open(name)
 	if err != nil {
@@ -71,83 +145,42 @@ func magnetFileURIs(name string) (uris []string, err error) {
 	return
 }
 
-func (me *Instance) removeAllFileMagnets(name string) {
-	for ih := range me.magnetFileInfoHashes[name] {
-		me.Events <- Event{
-			InfoHash: ih,
-			Change:   Removed,
-		}
+func (me *Instance) torrentRemoved(ih torrent.InfoHash) {
+	me.Events <- Event{
+		InfoHash: ih,
+		Change:   Removed,
 	}
 }
 
-func (me *Instance) removeTorrent(ih torrent.InfoHash) {
+func (me *Instance) torrentAdded(e entity) {
 	me.Events <- Event{
-		InfoHash: ih,
-		Change:   Removed,
+		InfoHash:        e.InfoHash,
+		Change:          Added,
+		MagnetURI:       e.MagnetURI,
+		TorrentFilePath: e.TorrentFilePath,
 	}
 }
 
-func (me *Instance) processFile(name string) {
-	name = filepath.Clean(name)
-	log.Print(name)
-	switch filepath.Ext(name) {
-	case ".torrent":
-		ih, ok := me.torrentFileInfoHashes[name]
-		if ok {
-			me.Events <- Event{
-				TorrentFilePath: name,
-				Change:          Removed,
-				InfoHash:        ih,
-			}
+func (me *Instance) refresh() {
+	_new := scanDir(me.dirName)
+	old := me.dirState
+	for ih, _ := range old {
+		_, ok := _new[ih]
+		if !ok {
+			me.torrentRemoved(ih)
 		}
-		delete(me.torrentFileInfoHashes, name)
-		ih, ok = torrentFileInfoHash(name)
+	}
+	for ih, newE := range _new {
+		oldE, ok := old[ih]
 		if ok {
-			me.torrentFileInfoHashes[name] = ih
-			me.Events <- Event{
-				TorrentFilePath: name,
-				Change:          Added,
-				InfoHash:        ih,
-			}
-		}
-	case ".magnet":
-		me.removeAllFileMagnets(name)
-		uris, err := magnetFileURIs(name)
-		if err != nil {
-			log.Print(err)
-			break
-		}
-		for _, uri := range uris {
-			m, err := torrent.ParseMagnetURI(uri)
-			if err != nil {
-				log.Printf("bad magnet uri in magnet file: %s", err)
+			if newE == oldE {
 				continue
 			}
-			me.removeTorrent(m.InfoHash)
-			me.Events <- Event{
-				Magnet: uri,
-				Change: Added,
-			}
+			me.torrentRemoved(ih)
 		}
-	default:
-		return
-	}
-}
-
-func (me *Instance) addDir() (err error) {
-	f, err := os.Open(me.dirName)
-	if err != nil {
-		return
+		me.torrentAdded(newE)
 	}
-	defer f.Close()
-	names, err := f.Readdirnames(-1)
-	if err != nil {
-		return
-	}
-	for _, n := range names {
-		me.processFile(filepath.Join(me.dirName, n))
-	}
-	return
+	me.dirState = _new
 }
 
 func New(dirName string) (i *Instance, err error) {
@@ -161,13 +194,13 @@ func New(dirName string) (i *Instance, err error) {
 		return
 	}
 	i = &Instance{
-		w:                     w,
-		dirName:               dirName,
-		Events:                make(chan Event),
-		torrentFileInfoHashes: make(map[string]torrent.InfoHash, 20),
+		w:        w,
+		dirName:  dirName,
+		Events:   make(chan Event),
+		dirState: make(map[torrent.InfoHash]entity, 0),
 	}
 	go func() {
-		i.addDir()
+		i.refresh()
 		go i.handleEvents()
 		go i.handleErrors()
 	}()
diff --git a/util/dirwatch/dirwatch_test.go b/util/dirwatch/dirwatch_test.go
new file mode 100644
index 00000000..7a975702
--- /dev/null
+++ b/util/dirwatch/dirwatch_test.go
@@ -0,0 +1,20 @@
+package dirwatch
+
+import (
+	"io/ioutil"
+	"os"
+	"testing"
+)
+
+func TestDirwatch(t *testing.T) {
+	tempDirName, err := ioutil.TempDir("", "")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.RemoveAll(tempDirName)
+	dw, err := New(tempDirName)
+	if err != nil {
+		t.Fatal(err)
+	}
+	dw.Close()
+}