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,