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.
This commit is contained in:
Chris Walker 2017-03-16 14:24:54 +00:00
parent ba3e798b5f
commit 9d96cd659f
3 changed files with 56 additions and 15 deletions

View File

@ -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)
}

View File

@ -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...)...)
}

View File

@ -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,