client.go | 23 +++++++++++++++++++---- storage/file.go | 46 ++++++++++++++++++++++++++++++++++++---------- torrent_test.go | 2 +- diff --git a/client.go b/client.go index 53188a582458eda353ca5b411f769d7c2d0df82d..37637244408bcc6314ad203eaca4217a562b0cf1 100644 --- a/client.go +++ b/client.go @@ -1124,7 +1124,13 @@ return false } // 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 @@ halfOpen: make(map[string]struct{}), pieceStateChanges: pubsub.NewPubSub(), - storageOpener: cl.defaultStorage, + storageOpener: storageClient, maxEstablishedConns: defaultEstablishedConnsPerTorrent, } t.setChunkSize(defaultChunkSize) @@ -1150,6 +1156,13 @@ io.ReaderAt } 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 @@ if ok { return } new = true - t = cl.newTorrent(infoHash) + t = cl.newTorrent(infoHash, specStorage) if cl.dHT != nil { go t.dhtAnnouncer() } @@ -1172,8 +1185,10 @@ // Add or merge a torrent spec. If the torrent is already present, the // 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 19e547564ac5fde487a91cc2df253f4d46e6bdce..b0a9ca1034ec933973cd989fe9b72f6f49e925b0 100644 --- a/storage/file.go +++ b/storage/file.go @@ -13,14 +13,39 @@ // 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 @@ return me.pc.Close() } 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 @@ } // 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 @@ // 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 @@ return } 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 51c26c67443f3145c740e578ff263751810cea40..5811b359516deb58631750f2e7c7ff43f3805840 100644 --- a/torrent_test.go +++ b/torrent_test.go @@ -75,7 +75,7 @@ // a large torrent with small pieces had a lot of overhead in recalculating // 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,