+// 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
)
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()
+ }
}
}
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 {
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) {
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()
}()