From 9d96cd659f4f9beb2cfe72d43e40972a6d69af80 Mon Sep 17 00:00:00 2001 From: Chris Walker Date: Thu, 16 Mar 2017 14:24:54 +0000 Subject: [PATCH] fix `Spec.Storage` and allow per-torrent dir `TorrentSpec.Storage` was not honored when calling `Client.AddTorrentSpec` and the configured `cfg.DefaultStorage` was always used. Now if you construct your `TorrentSpec` you can specify any `StorageImpl` Also, the most common use case for custom storage being per-torrent paths for FileStorage, this adds a `pathMaker` function to the File implementation that allows customization, along with the default (always use base path) and my use case (which seemed common enough from the Gitter chat) which is infohash based subdirectories. All Public methods have not changed signature, but 1 private method did, hence the test update. --- client.go | 23 +++++++++++++++++++---- storage/file.go | 46 ++++++++++++++++++++++++++++++++++++---------- torrent_test.go | 2 +- 3 files changed, 56 insertions(+), 15 deletions(-) diff --git a/client.go b/client.go index 53188a58..37637244 100644 --- a/client.go +++ b/client.go @@ -1124,7 +1124,13 @@ func (cl *Client) badPeerIPPort(ip net.IP, port int) bool { } // Return a Torrent ready for insertion into a Client. -func (cl *Client) newTorrent(ih metainfo.Hash) (t *Torrent) { +func (cl *Client) newTorrent(ih metainfo.Hash, specStorage storage.ClientImpl) (t *Torrent) { + // use provided storage, if provided + storageClient := cl.defaultStorage + if specStorage != nil { + storageClient = storage.NewClient(specStorage) + } + t = &Torrent{ cl: cl, infoHash: ih, @@ -1134,7 +1140,7 @@ func (cl *Client) newTorrent(ih metainfo.Hash) (t *Torrent) { halfOpen: make(map[string]struct{}), pieceStateChanges: pubsub.NewPubSub(), - storageOpener: cl.defaultStorage, + storageOpener: storageClient, maxEstablishedConns: defaultEstablishedConnsPerTorrent, } t.setChunkSize(defaultChunkSize) @@ -1150,6 +1156,13 @@ type Handle interface { } func (cl *Client) AddTorrentInfoHash(infoHash metainfo.Hash) (t *Torrent, new bool) { + return cl.AddTorrentInfoHashWithStorage(infoHash, nil) +} + +// Adds a torrent by InfoHash with a custom Storage implementation. +// If the torrent already exists then this Storage is ignored and the +// existing torrent returned with `new` set to `false` +func (cl *Client) AddTorrentInfoHashWithStorage(infoHash metainfo.Hash, specStorage storage.ClientImpl) (t *Torrent, new bool) { cl.mu.Lock() defer cl.mu.Unlock() t, ok := cl.torrents[infoHash] @@ -1157,7 +1170,7 @@ func (cl *Client) AddTorrentInfoHash(infoHash metainfo.Hash) (t *Torrent, new bo return } new = true - t = cl.newTorrent(infoHash) + t = cl.newTorrent(infoHash, specStorage) if cl.dHT != nil { go t.dhtAnnouncer() } @@ -1172,8 +1185,10 @@ func (cl *Client) AddTorrentInfoHash(infoHash metainfo.Hash) (t *Torrent, new bo // trackers will be merged with the existing ones. If the Info isn't yet // known, it will be set. The display name is replaced if the new spec // provides one. Returns new if the torrent wasn't already in the client. +// Note that any `Storage` defined on the spec will be ignored if the +// torrent is already present (i.e. `new` return value is `true`) func (cl *Client) AddTorrentSpec(spec *TorrentSpec) (t *Torrent, new bool, err error) { - t, new = cl.AddTorrentInfoHash(spec.InfoHash) + t, new = cl.AddTorrentInfoHashWithStorage(spec.InfoHash, spec.Storage) if spec.DisplayName != "" { t.SetDisplayName(spec.DisplayName) } diff --git a/storage/file.go b/storage/file.go index 19e54756..b0a9ca10 100644 --- a/storage/file.go +++ b/storage/file.go @@ -13,14 +13,39 @@ import ( // File-based storage for torrents, that isn't yet bound to a particular // torrent. type fileClientImpl struct { - baseDir string - pc pieceCompletion + baseDir string + pathMaker func(baseDir string, info *metainfo.Info, infoHash metainfo.Hash) string + pc pieceCompletion } +// The Default path maker just returns the current path +func defaultPathMaker(baseDir string, info *metainfo.Info, infoHash metainfo.Hash) string { + return baseDir +} + +func infoHashPathMaker(baseDir string, info *metainfo.Info, infoHash metainfo.Hash) string { + return filepath.Join(baseDir, infoHash.HexString()) +} + +// All Torrent data stored in this baseDir func NewFile(baseDir string) ClientImpl { + return NewFileWithCustomPathMaker(baseDir, nil) +} + +// All Torrent data stored in subdirectorys by infohash +func NewFileByInfoHash(baseDir string) ClientImpl { + return NewFileWithCustomPathMaker(baseDir, infoHashPathMaker) +} + +// Allows passing a function to determine the path for storing torrent data +func NewFileWithCustomPathMaker(baseDir string, pathMaker func(baseDir string, info *metainfo.Info, infoHash metainfo.Hash) string) ClientImpl { + if pathMaker == nil { + pathMaker = defaultPathMaker + } return &fileClientImpl{ - baseDir: baseDir, - pc: pieceCompletionForDir(baseDir), + baseDir: baseDir, + pathMaker: pathMaker, + pc: pieceCompletionForDir(baseDir), } } @@ -29,12 +54,13 @@ func (me *fileClientImpl) Close() error { } func (fs *fileClientImpl) OpenTorrent(info *metainfo.Info, infoHash metainfo.Hash) (TorrentImpl, error) { - err := CreateNativeZeroLengthFiles(info, fs.baseDir) + dir := fs.pathMaker(fs.baseDir, info, infoHash) + err := CreateNativeZeroLengthFiles(info, dir) if err != nil { return nil, err } return &fileTorrentImpl{ - fs, + dir, info, infoHash, fs.pc, @@ -43,7 +69,7 @@ func (fs *fileClientImpl) OpenTorrent(info *metainfo.Info, infoHash metainfo.Has // File-based torrent storage, not yet bound to a Torrent. type fileTorrentImpl struct { - fs *fileClientImpl + dir string info *metainfo.Info infoHash metainfo.Hash completion pieceCompletion @@ -68,12 +94,12 @@ func (fs *fileTorrentImpl) Close() error { // Creates natives files for any zero-length file entries in the info. This is // a helper for file-based storages, which don't address or write to zero- // length files because they have no corresponding pieces. -func CreateNativeZeroLengthFiles(info *metainfo.Info, baseDir string) (err error) { +func CreateNativeZeroLengthFiles(info *metainfo.Info, dir string) (err error) { for _, fi := range info.UpvertedFiles() { if fi.Length != 0 { continue } - name := filepath.Join(append([]string{baseDir, info.Name}, fi.Path...)...) + name := filepath.Join(append([]string{dir, info.Name}, fi.Path...)...) os.MkdirAll(filepath.Dir(name), 0750) var f io.Closer f, err = os.Create(name) @@ -181,5 +207,5 @@ func (fst fileTorrentImplIO) WriteAt(p []byte, off int64) (n int, err error) { } func (fts *fileTorrentImpl) fileInfoName(fi metainfo.FileInfo) string { - return filepath.Join(append([]string{fts.fs.baseDir, fts.info.Name}, fi.Path...)...) + return filepath.Join(append([]string{fts.dir, fts.info.Name}, fi.Path...)...) } diff --git a/torrent_test.go b/torrent_test.go index 51c26c67..5811b359 100644 --- a/torrent_test.go +++ b/torrent_test.go @@ -75,7 +75,7 @@ func TestTorrentString(t *testing.T) { // piece priorities everytime a reader (possibly in another Torrent) changed. func BenchmarkUpdatePiecePriorities(b *testing.B) { cl := &Client{} - t := cl.newTorrent(metainfo.Hash{}) + t := cl.newTorrent(metainfo.Hash{}, nil) t.info = &metainfo.Info{ Pieces: make([]byte, 20*13410), PieceLength: 256 << 10,