Rework storage interfaces to make them simpler to implement

This allows lots of behaviour to be baked into the new Client, Torrent and Piece wrappers, rather than duplicating (badly) them in all the backend implementations.
This commit is contained in:
Matt Joiner 2016-09-02 15:10:57 +10:00
parent db3be3441f
commit 1e919dd6b1
15 changed files with 177 additions and 109 deletions

View File

@ -74,7 +74,7 @@ type Client struct {
dopplegangerAddrs map[string]struct{} dopplegangerAddrs map[string]struct{}
badPeerIPs map[string]struct{} badPeerIPs map[string]struct{}
defaultStorage storage.Client defaultStorage *storage.Client
mu sync.RWMutex mu sync.RWMutex
event sync.Cond event sync.Cond
@ -253,15 +253,16 @@ func NewClient(cfg *Config) (cl *Client, err error) {
cl = &Client{ cl = &Client{
halfOpenLimit: defaultHalfOpenConnsPerTorrent, halfOpenLimit: defaultHalfOpenConnsPerTorrent,
config: *cfg, config: *cfg,
defaultStorage: cfg.DefaultStorage,
dopplegangerAddrs: make(map[string]struct{}), dopplegangerAddrs: make(map[string]struct{}),
torrents: make(map[metainfo.Hash]*Torrent), torrents: make(map[metainfo.Hash]*Torrent),
} }
missinggo.CopyExact(&cl.extensionBytes, defaultExtensionBytes) missinggo.CopyExact(&cl.extensionBytes, defaultExtensionBytes)
cl.event.L = &cl.mu cl.event.L = &cl.mu
if cl.defaultStorage == nil { storageImpl := cfg.DefaultStorage
cl.defaultStorage = storage.NewFile(cfg.DataDir) if storageImpl == nil {
storageImpl = storage.NewFile(cfg.DataDir)
} }
cl.defaultStorage = storage.NewClient(storageImpl)
if cfg.IPBlocklist != nil { if cfg.IPBlocklist != nil {
cl.ipBlockList = cfg.IPBlocklist cl.ipBlockList = cfg.IPBlocklist
} }
@ -1417,7 +1418,7 @@ type TorrentSpec struct {
// The chunk size to use for outbound requests. Defaults to 16KiB if not // The chunk size to use for outbound requests. Defaults to 16KiB if not
// set. // set.
ChunkSize int ChunkSize int
Storage storage.Client Storage storage.ClientImpl
} }
func TorrentSpecFromMagnetURI(uri string) (spec *TorrentSpec, err error) { func TorrentSpecFromMagnetURI(uri string) (spec *TorrentSpec, err error) {

View File

@ -92,7 +92,7 @@ func TestTorrentInitialState(t *testing.T) {
pieceStateChanges: pubsub.NewPubSub(), pieceStateChanges: pubsub.NewPubSub(),
} }
tor.chunkSize = 2 tor.chunkSize = 2
tor.storageOpener = storage.NewFile("/dev/null") tor.storageOpener = storage.NewClient(storage.NewFile("/dev/null"))
// Needed to lock for asynchronous piece verification. // Needed to lock for asynchronous piece verification.
tor.cl = new(Client) tor.cl = new(Client)
err := tor.setInfoBytes(mi.InfoBytes) err := tor.setInfoBytes(mi.InfoBytes)
@ -241,11 +241,11 @@ func TestAddDropManyTorrents(t *testing.T) {
type FileCacheClientStorageFactoryParams struct { type FileCacheClientStorageFactoryParams struct {
Capacity int64 Capacity int64
SetCapacity bool SetCapacity bool
Wrapper func(*filecache.Cache) storage.Client Wrapper func(*filecache.Cache) storage.ClientImpl
} }
func NewFileCacheClientStorageFactory(ps FileCacheClientStorageFactoryParams) storageFactory { func NewFileCacheClientStorageFactory(ps FileCacheClientStorageFactoryParams) storageFactory {
return func(dataDir string) storage.Client { return func(dataDir string) storage.ClientImpl {
fc, err := filecache.NewCache(dataDir) fc, err := filecache.NewCache(dataDir)
if err != nil { if err != nil {
panic(err) panic(err)
@ -257,7 +257,7 @@ func NewFileCacheClientStorageFactory(ps FileCacheClientStorageFactoryParams) st
} }
} }
type storageFactory func(string) storage.Client type storageFactory func(string) storage.ClientImpl
func TestClientTransferDefault(t *testing.T) { func TestClientTransferDefault(t *testing.T) {
testClientTransfer(t, testClientTransferParams{ testClientTransfer(t, testClientTransferParams{
@ -268,11 +268,11 @@ func TestClientTransferDefault(t *testing.T) {
}) })
} }
func fileCachePieceResourceStorage(fc *filecache.Cache) storage.Client { func fileCachePieceResourceStorage(fc *filecache.Cache) storage.ClientImpl {
return storage.NewResourcePieces(fc.AsResourceProvider()) return storage.NewResourcePieces(fc.AsResourceProvider())
} }
func fileCachePieceFileStorage(fc *filecache.Cache) storage.Client { func fileCachePieceFileStorage(fc *filecache.Cache) storage.ClientImpl {
return storage.NewFileStorePieces(fc.AsFileStore()) return storage.NewFileStorePieces(fc.AsFileStore())
} }
@ -303,7 +303,7 @@ func TestClientTransferVarious(t *testing.T) {
}), }),
storage.NewBoltDB, storage.NewBoltDB,
} { } {
for _, ss := range []func(string) storage.Client{ for _, ss := range []func(string) storage.ClientImpl{
storage.NewFile, storage.NewFile,
storage.NewMMap, storage.NewMMap,
} { } {
@ -332,8 +332,8 @@ type testClientTransferParams struct {
Readahead int64 Readahead int64
SetReadahead bool SetReadahead bool
ExportClientStatus bool ExportClientStatus bool
LeecherStorage func(string) storage.Client LeecherStorage func(string) storage.ClientImpl
SeederStorage func(string) storage.Client SeederStorage func(string) storage.ClientImpl
} }
// Creates a seeder and a leecher, and ensures the data transfers when a read // Creates a seeder and a leecher, and ensures the data transfers when a read
@ -493,7 +493,7 @@ func TestMergingTrackersByAddingSpecs(t *testing.T) {
type badStorage struct{} type badStorage struct{}
func (bs badStorage) OpenTorrent(*metainfo.Info, metainfo.Hash) (storage.Torrent, error) { func (bs badStorage) OpenTorrent(*metainfo.Info, metainfo.Hash) (storage.TorrentImpl, error) {
return bs, nil return bs, nil
} }
@ -501,7 +501,7 @@ func (bs badStorage) Close() error {
return nil return nil
} }
func (bs badStorage) Piece(p metainfo.Piece) storage.Piece { func (bs badStorage) Piece(p metainfo.Piece) storage.PieceImpl {
return badStoragePiece{p} return badStoragePiece{p}
} }
@ -521,6 +521,10 @@ func (p badStoragePiece) MarkComplete() error {
return errors.New("psyyyyyyyche") return errors.New("psyyyyyyyche")
} }
func (p badStoragePiece) MarkNotComplete() error {
return errors.New("psyyyyyyyche")
}
func (p badStoragePiece) randomlyTruncatedDataString() string { func (p badStoragePiece) randomlyTruncatedDataString() string {
return "hello, world\n"[:rand.Intn(14)] return "hello, world\n"[:rand.Intn(14)]
} }
@ -709,14 +713,14 @@ func TestTorrentDroppedBeforeGotInfo(t *testing.T) {
} }
} }
func writeTorrentData(ts storage.Torrent, info metainfo.Info, b []byte) { func writeTorrentData(ts *storage.Torrent, info metainfo.Info, b []byte) {
for i := range iter.N(info.NumPieces()) { for i := range iter.N(info.NumPieces()) {
n, _ := ts.Piece(info.Piece(i)).WriteAt(b, 0) p := info.Piece(i)
b = b[n:] ts.Piece(p).WriteAt(b[p.Offset():p.Offset()+p.Length()], 0)
} }
} }
func testAddTorrentPriorPieceCompletion(t *testing.T, alreadyCompleted bool, csf func(*filecache.Cache) storage.Client) { func testAddTorrentPriorPieceCompletion(t *testing.T, alreadyCompleted bool, csf func(*filecache.Cache) storage.ClientImpl) {
fileCacheDir, err := ioutil.TempDir("", "") fileCacheDir, err := ioutil.TempDir("", "")
require.NoError(t, err) require.NoError(t, err)
defer os.RemoveAll(fileCacheDir) defer os.RemoveAll(fileCacheDir)
@ -727,7 +731,7 @@ func testAddTorrentPriorPieceCompletion(t *testing.T, alreadyCompleted bool, csf
filePieceStore := csf(fileCache) filePieceStore := csf(fileCache)
info := greetingMetainfo.UnmarshalInfo() info := greetingMetainfo.UnmarshalInfo()
ih := greetingMetainfo.HashInfoBytes() ih := greetingMetainfo.HashInfoBytes()
greetingData, err := filePieceStore.OpenTorrent(&info, ih) greetingData, err := storage.NewClient(filePieceStore).OpenTorrent(&info, ih)
require.NoError(t, err) require.NoError(t, err)
writeTorrentData(greetingData, info, []byte(testutil.GreetingFileContents)) writeTorrentData(greetingData, info, []byte(testutil.GreetingFileContents))
// require.Equal(t, len(testutil.GreetingFileContents), written) // require.Equal(t, len(testutil.GreetingFileContents), written)

View File

@ -35,7 +35,7 @@ type Config struct {
DisableTCP bool `long:"disable-tcp"` DisableTCP bool `long:"disable-tcp"`
// Called to instantiate storage for each added torrent. Provided backends // Called to instantiate storage for each added torrent. Provided backends
// are in $REPO/data. If not set, the "file" implementation is used. // are in $REPO/data. If not set, the "file" implementation is used.
DefaultStorage storage.Client DefaultStorage storage.ClientImpl
DisableEncryption bool `long:"disable-encryption"` DisableEncryption bool `long:"disable-encryption"`
IPBlocklist iplist.Ranger IPBlocklist iplist.Ranger

View File

@ -15,7 +15,7 @@ func TestHashPieceAfterStorageClosed(t *testing.T) {
td, err := ioutil.TempDir("", "") td, err := ioutil.TempDir("", "")
require.NoError(t, err) require.NoError(t, err)
defer os.RemoveAll(td) defer os.RemoveAll(td)
cs := storage.NewFile(td) cs := storage.NewClient(storage.NewFile(td))
tt := &Torrent{} tt := &Torrent{}
mi := testutil.GreetingMetaInfo() mi := testutil.GreetingMetaInfo()
info := mi.UnmarshalInfo() info := mi.UnmarshalInfo()

View File

@ -2,10 +2,8 @@ package storage
import ( import (
"encoding/binary" "encoding/binary"
"io"
"path/filepath" "path/filepath"
"github.com/anacrolix/missinggo"
"github.com/boltdb/bolt" "github.com/boltdb/bolt"
"github.com/anacrolix/torrent/metainfo" "github.com/anacrolix/torrent/metainfo"
@ -43,7 +41,7 @@ type boltDBPiece struct {
key [24]byte key [24]byte
} }
func NewBoltDB(filePath string) Client { func NewBoltDB(filePath string) ClientImpl {
ret := &boltDBClient{} ret := &boltDBClient{}
var err error var err error
ret.db, err = bolt.Open(filepath.Join(filePath, "bolt.db"), 0600, nil) ret.db, err = bolt.Open(filepath.Join(filePath, "bolt.db"), 0600, nil)
@ -53,11 +51,11 @@ func NewBoltDB(filePath string) Client {
return ret return ret
} }
func (me *boltDBClient) OpenTorrent(info *metainfo.Info, infoHash metainfo.Hash) (Torrent, error) { func (me *boltDBClient) OpenTorrent(info *metainfo.Info, infoHash metainfo.Hash) (TorrentImpl, error) {
return &boltDBTorrent{me, infoHash}, nil return &boltDBTorrent{me, infoHash}, nil
} }
func (me *boltDBTorrent) Piece(p metainfo.Piece) Piece { func (me *boltDBTorrent) Piece(p metainfo.Piece) PieceImpl {
ret := &boltDBPiece{p: p, db: me.cl.db} ret := &boltDBPiece{p: p, db: me.cl.db}
copy(ret.key[:], me.ih[:]) copy(ret.key[:], me.ih[:])
binary.BigEndian.PutUint32(ret.key[20:], uint32(p.Index())) binary.BigEndian.PutUint32(ret.key[20:], uint32(p.Index()))
@ -82,16 +80,24 @@ func (me *boltDBPiece) GetIsComplete() (complete bool) {
} }
func (me *boltDBPiece) MarkComplete() error { func (me *boltDBPiece) MarkComplete() error {
return me.db.Update(func(tx *bolt.Tx) (err error) { return me.db.Update(func(tx *bolt.Tx) error {
b, err := tx.CreateBucketIfNotExists(completed) b, err := tx.CreateBucketIfNotExists(completed)
if err != nil { if err != nil {
return return err
} }
b.Put(me.key[:], completedValue) return b.Put(me.key[:], completedValue)
return
}) })
} }
func (me *boltDBPiece) MarkNotComplete() error {
return me.db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket(completed)
if b == nil {
return nil
}
return b.Delete(me.key[:])
})
}
func (me *boltDBPiece) ReadAt(b []byte, off int64) (n int, err error) { func (me *boltDBPiece) ReadAt(b []byte, off int64) (n int, err error) {
err = me.db.View(func(tx *bolt.Tx) error { err = me.db.View(func(tx *bolt.Tx) error {
db := tx.Bucket(data) db := tx.Bucket(data)
@ -114,14 +120,6 @@ func (me *boltDBPiece) ReadAt(b []byte, off int64) (n int, err error) {
} }
return nil return nil
}) })
if n == 0 && err == nil {
if off < me.p.Length() {
err = io.ErrUnexpectedEOF
} else {
err = io.EOF
}
}
// // log.Println(n, err)
return return
} }

View File

@ -17,13 +17,13 @@ type fileStorage struct {
baseDir string baseDir string
} }
func NewFile(baseDir string) Client { func NewFile(baseDir string) ClientImpl {
return &fileStorage{ return &fileStorage{
baseDir: baseDir, baseDir: baseDir,
} }
} }
func (fs *fileStorage) OpenTorrent(info *metainfo.Info, infoHash metainfo.Hash) (Torrent, error) { func (fs *fileStorage) OpenTorrent(info *metainfo.Info, infoHash metainfo.Hash) (TorrentImpl, error) {
return &fileTorrentStorage{ return &fileTorrentStorage{
fs, fs,
info, info,
@ -40,7 +40,7 @@ type fileTorrentStorage struct {
completion pieceCompletion completion pieceCompletion
} }
func (fts *fileTorrentStorage) Piece(p metainfo.Piece) Piece { func (fts *fileTorrentStorage) Piece(p metainfo.Piece) PieceImpl {
// Create a view onto the file-based torrent storage. // Create a view onto the file-based torrent storage.
_io := fileStorageTorrent{fts} _io := fileStorageTorrent{fts}
// Return the appropriate segments of this. // Return the appropriate segments of this.

View File

@ -11,7 +11,7 @@ type fileStoragePiece struct {
*fileTorrentStorage *fileTorrentStorage
p metainfo.Piece p metainfo.Piece
io.WriterAt io.WriterAt
r io.ReaderAt io.ReaderAt
} }
func (me *fileStoragePiece) pieceKey() metainfo.PieceKey { func (me *fileStoragePiece) pieceKey() metainfo.PieceKey {
@ -45,15 +45,7 @@ func (fs *fileStoragePiece) MarkComplete() error {
return nil return nil
} }
func (fsp *fileStoragePiece) ReadAt(b []byte, off int64) (n int, err error) { func (fs *fileStoragePiece) MarkNotComplete() error {
n, err = fsp.r.ReadAt(b, off) fs.completion.Set(fs.pieceKey(), false)
if n != 0 { return nil
err = nil
return
}
if off < 0 || off >= fsp.p.Length() {
return
}
fsp.completion.Set(fsp.pieceKey(), false)
return
} }

View File

@ -7,26 +7,28 @@ import (
) )
// Represents data storage for an unspecified torrent. // Represents data storage for an unspecified torrent.
type Client interface { type ClientImpl interface {
OpenTorrent(info *metainfo.Info, infoHash metainfo.Hash) (Torrent, error) OpenTorrent(info *metainfo.Info, infoHash metainfo.Hash) (TorrentImpl, error)
} }
// Data storage bound to a torrent. // Data storage bound to a torrent.
type Torrent interface { type TorrentImpl interface {
Piece(metainfo.Piece) Piece Piece(metainfo.Piece) PieceImpl
Close() error Close() error
} }
// Interacts with torrent piece data. // Interacts with torrent piece data.
type Piece interface { type PieceImpl interface {
// Should return io.EOF only at end of torrent. Short reads due to missing // These interfaces are not as strict as normally required. They can
// data should return io.ErrUnexpectedEOF. // assume that the parameters are appropriate for the dimentions of the
// piece.
io.ReaderAt io.ReaderAt
io.WriterAt io.WriterAt
// Called when the client believes the piece data will pass a hash check. // Called when the client believes the piece data will pass a hash check.
// The storage can move or mark the piece data as read-only as it sees // The storage can move or mark the piece data as read-only as it sees
// fit. // fit.
MarkComplete() error MarkComplete() error
MarkNotComplete() error
// Returns true if the piece is complete. // Returns true if the piece is complete.
GetIsComplete() bool GetIsComplete() bool
} }

View File

@ -14,7 +14,7 @@ import (
// Two different torrents opened from the same storage. Closing one should not // Two different torrents opened from the same storage. Closing one should not
// break the piece completion on the other. // break the piece completion on the other.
func testIssue95(t *testing.T, c Client) { func testIssue95(t *testing.T, c ClientImpl) {
i1 := &metainfo.Info{ i1 := &metainfo.Info{
Files: []metainfo.FileInfo{{Path: []string{"a"}}}, Files: []metainfo.FileInfo{{Path: []string{"a"}}},
Pieces: make([]byte, 20), Pieces: make([]byte, 20),

View File

@ -10,11 +10,11 @@ import (
"github.com/anacrolix/torrent/metainfo" "github.com/anacrolix/torrent/metainfo"
) )
func testMarkedCompleteMissingOnRead(t *testing.T, csf func(string) Client) { func testMarkedCompleteMissingOnRead(t *testing.T, csf func(string) ClientImpl) {
td, err := ioutil.TempDir("", "") td, err := ioutil.TempDir("", "")
require.NoError(t, err) require.NoError(t, err)
defer os.RemoveAll(td) defer os.RemoveAll(td)
cs := csf(td) cs := NewClient(csf(td))
info := &metainfo.Info{ info := &metainfo.Info{
PieceLength: 1, PieceLength: 1,
Files: []metainfo.FileInfo{{Path: []string{"a"}, Length: 1}}, Files: []metainfo.FileInfo{{Path: []string{"a"}, Length: 1}},
@ -23,7 +23,7 @@ func testMarkedCompleteMissingOnRead(t *testing.T, csf func(string) Client) {
require.NoError(t, err) require.NoError(t, err)
p := ts.Piece(info.Piece(0)) p := ts.Piece(info.Piece(0))
require.NoError(t, p.MarkComplete()) require.NoError(t, p.MarkComplete())
require.False(t, p.GetIsComplete()) // require.False(t, p.GetIsComplete())
n, err := p.ReadAt(make([]byte, 1), 0) n, err := p.ReadAt(make([]byte, 1), 0)
require.Error(t, err) require.Error(t, err)
require.EqualValues(t, 0, n) require.EqualValues(t, 0, n)

View File

@ -17,13 +17,13 @@ type mmapStorage struct {
baseDir string baseDir string
} }
func NewMMap(baseDir string) Client { func NewMMap(baseDir string) ClientImpl {
return &mmapStorage{ return &mmapStorage{
baseDir: baseDir, baseDir: baseDir,
} }
} }
func (s *mmapStorage) OpenTorrent(info *metainfo.Info, infoHash metainfo.Hash) (t Torrent, err error) { func (s *mmapStorage) OpenTorrent(info *metainfo.Info, infoHash metainfo.Hash) (t TorrentImpl, err error) {
span, err := mMapTorrent(info, s.baseDir) span, err := mMapTorrent(info, s.baseDir)
t = &mmapTorrentStorage{ t = &mmapTorrentStorage{
span: span, span: span,
@ -37,7 +37,7 @@ type mmapTorrentStorage struct {
pc pieceCompletion pc pieceCompletion
} }
func (ts *mmapTorrentStorage) Piece(p metainfo.Piece) Piece { func (ts *mmapTorrentStorage) Piece(p metainfo.Piece) PieceImpl {
return mmapStoragePiece{ return mmapStoragePiece{
pc: ts.pc, pc: ts.pc,
p: p, p: p,
@ -73,6 +73,11 @@ func (sp mmapStoragePiece) MarkComplete() error {
return nil return nil
} }
func (sp mmapStoragePiece) MarkNotComplete() error {
sp.pc.Set(sp.pieceKey(), false)
return nil
}
func mMapTorrent(md *metainfo.Info, location string) (mms mmap_span.MMapSpan, err error) { func mMapTorrent(md *metainfo.Info, location string) (mms mmap_span.MMapSpan, err error) {
defer func() { defer func() {
if err != nil { if err != nil {

View File

@ -1,7 +1,6 @@
package storage package storage
import ( import (
"errors"
"io" "io"
"os" "os"
"path" "path"
@ -15,7 +14,7 @@ type pieceFileStorage struct {
fs missinggo.FileStore fs missinggo.FileStore
} }
func NewFileStorePieces(fs missinggo.FileStore) Client { func NewFileStorePieces(fs missinggo.FileStore) ClientImpl {
return &pieceFileStorage{ return &pieceFileStorage{
fs: fs, fs: fs,
} }
@ -25,7 +24,7 @@ type pieceFileTorrentStorage struct {
s *pieceFileStorage s *pieceFileStorage
} }
func (s *pieceFileStorage) OpenTorrent(info *metainfo.Info, infoHash metainfo.Hash) (Torrent, error) { func (s *pieceFileStorage) OpenTorrent(info *metainfo.Info, infoHash metainfo.Hash) (TorrentImpl, error) {
return &pieceFileTorrentStorage{s}, nil return &pieceFileTorrentStorage{s}, nil
} }
@ -33,7 +32,7 @@ func (s *pieceFileTorrentStorage) Close() error {
return nil return nil
} }
func (s *pieceFileTorrentStorage) Piece(p metainfo.Piece) Piece { func (s *pieceFileTorrentStorage) Piece(p metainfo.Piece) PieceImpl {
return pieceFileTorrentStoragePiece{s, p, s.s.fs} return pieceFileTorrentStoragePiece{s, p, s.s.fs}
} }
@ -60,6 +59,10 @@ func (s pieceFileTorrentStoragePiece) MarkComplete() error {
return s.fs.Rename(s.incompletePath(), s.completedPath()) return s.fs.Rename(s.incompletePath(), s.completedPath())
} }
func (s pieceFileTorrentStoragePiece) MarkNotComplete() error {
return s.fs.Remove(s.completedPath())
}
func (s pieceFileTorrentStoragePiece) openFile() (f missinggo.File, err error) { func (s pieceFileTorrentStoragePiece) openFile() (f missinggo.File, err error) {
f, err = s.fs.OpenFile(s.completedPath(), os.O_RDONLY) f, err = s.fs.OpenFile(s.completedPath(), os.O_RDONLY)
if err == nil { if err == nil {
@ -85,27 +88,14 @@ func (s pieceFileTorrentStoragePiece) ReadAt(b []byte, off int64) (n int, err er
return return
} }
defer f.Close() defer f.Close()
missinggo.LimitLen(&b, s.p.Length()-off) return f.ReadAt(b, off)
n, err = f.ReadAt(b, off)
off += int64(n)
if off >= s.p.Length() {
err = io.EOF
} else if err == io.EOF {
err = io.ErrUnexpectedEOF
}
return
} }
func (s pieceFileTorrentStoragePiece) WriteAt(b []byte, off int64) (n int, err error) { func (s pieceFileTorrentStoragePiece) WriteAt(b []byte, off int64) (n int, err error) {
if s.GetIsComplete() {
err = errors.New("piece completed")
return
}
f, err := s.fs.OpenFile(s.incompletePath(), os.O_WRONLY|os.O_CREATE) f, err := s.fs.OpenFile(s.incompletePath(), os.O_WRONLY|os.O_CREATE)
if err != nil { if err != nil {
return return
} }
defer f.Close() defer f.Close()
missinggo.LimitLen(&b, s.p.Length()-off)
return f.WriteAt(b, off) return f.WriteAt(b, off)
} }

View File

@ -1,10 +1,8 @@
package storage package storage
import ( import (
"io"
"path" "path"
"github.com/anacrolix/missinggo"
"github.com/anacrolix/missinggo/resource" "github.com/anacrolix/missinggo/resource"
"github.com/anacrolix/torrent/metainfo" "github.com/anacrolix/torrent/metainfo"
@ -14,13 +12,13 @@ type piecePerResource struct {
p resource.Provider p resource.Provider
} }
func NewResourcePieces(p resource.Provider) Client { func NewResourcePieces(p resource.Provider) ClientImpl {
return &piecePerResource{ return &piecePerResource{
p: p, p: p,
} }
} }
func (s *piecePerResource) OpenTorrent(info *metainfo.Info, infoHash metainfo.Hash) (Torrent, error) { func (s *piecePerResource) OpenTorrent(info *metainfo.Info, infoHash metainfo.Hash) (TorrentImpl, error) {
return s, nil return s, nil
} }
@ -28,7 +26,7 @@ func (s *piecePerResource) Close() error {
return nil return nil
} }
func (s *piecePerResource) Piece(p metainfo.Piece) Piece { func (s *piecePerResource) Piece(p metainfo.Piece) PieceImpl {
completed, err := s.p.NewInstance(path.Join("completed", p.Hash().HexString())) completed, err := s.p.NewInstance(path.Join("completed", p.Hash().HexString()))
if err != nil { if err != nil {
panic(err) panic(err)
@ -59,22 +57,18 @@ func (s piecePerResourcePiece) MarkComplete() error {
return resource.Move(s.i, s.c) return resource.Move(s.i, s.c)
} }
func (s piecePerResourcePiece) ReadAt(b []byte, off int64) (n int, err error) { func (s piecePerResourcePiece) MarkNotComplete() error {
missinggo.LimitLen(&b, s.p.Length()-off) return s.c.Delete()
n, err = s.c.ReadAt(b, off)
if err != nil {
n, err = s.i.ReadAt(b, off)
} }
off += int64(n)
if off >= s.p.Length() { func (s piecePerResourcePiece) ReadAt(b []byte, off int64) (int, error) {
err = io.EOF if s.GetIsComplete() {
} else if err == io.EOF { return s.c.ReadAt(b, off)
err = io.ErrUnexpectedEOF } else {
return s.i.ReadAt(b, off)
} }
return
} }
func (s piecePerResourcePiece) WriteAt(b []byte, off int64) (n int, err error) { func (s piecePerResourcePiece) WriteAt(b []byte, off int64) (n int, err error) {
missinggo.LimitLen(&b, s.p.Length()-off)
return s.i.WriteAt(b, off) return s.i.WriteAt(b, off)
} }

82
storage/wrappers.go Normal file
View File

@ -0,0 +1,82 @@
package storage
import (
"errors"
"io"
"os"
"github.com/anacrolix/missinggo"
"github.com/anacrolix/torrent/metainfo"
)
type Client struct {
ClientImpl
}
func NewClient(cl ClientImpl) *Client {
return &Client{cl}
}
func (cl Client) OpenTorrent(info *metainfo.Info, infoHash metainfo.Hash) (*Torrent, error) {
t, err := cl.ClientImpl.OpenTorrent(info, infoHash)
return &Torrent{t}, err
}
type Torrent struct {
TorrentImpl
}
func (t Torrent) Piece(p metainfo.Piece) Piece {
return Piece{t.TorrentImpl.Piece(p), p}
}
type Piece struct {
PieceImpl
mip metainfo.Piece
}
func (p Piece) WriteAt(b []byte, off int64) (n int, err error) {
if p.GetIsComplete() {
err = errors.New("piece completed")
return
}
if off+int64(len(b)) > p.mip.Length() {
panic("write overflows piece")
}
missinggo.LimitLen(&b, p.mip.Length()-off)
return p.PieceImpl.WriteAt(b, off)
}
func (p Piece) ReadAt(b []byte, off int64) (n int, err error) {
if off < 0 {
err = os.ErrInvalid
return
}
if off >= p.mip.Length() {
err = io.EOF
return
}
missinggo.LimitLen(&b, p.mip.Length()-off)
if len(b) == 0 {
return
}
n, err = p.PieceImpl.ReadAt(b, off)
if n > len(b) {
panic(n)
}
off += int64(n)
if err == io.EOF && off < p.mip.Length() {
err = io.ErrUnexpectedEOF
}
if err == nil && off >= p.mip.Length() {
err = io.EOF
}
if n == 0 && err == nil {
err = io.ErrUnexpectedEOF
}
if off < p.mip.Length() && err != nil {
p.MarkNotComplete()
}
return
}

View File

@ -57,9 +57,9 @@ type Torrent struct {
length int64 length int64
// The storage to open when the info dict becomes available. // The storage to open when the info dict becomes available.
storageOpener storage.Client storageOpener *storage.Client
// Storage for torrent data. // Storage for torrent data.
storage storage.Torrent storage *storage.Torrent
metainfo metainfo.MetaInfo metainfo metainfo.MetaInfo
@ -550,8 +550,8 @@ func (t *Torrent) numPiecesCompleted() (num int) {
func (t *Torrent) close() (err error) { func (t *Torrent) close() (err error) {
t.closed.Set() t.closed.Set()
if c, ok := t.storage.(io.Closer); ok { if t.storage != nil {
c.Close() t.storage.Close()
} }
for _, conn := range t.conns { for _, conn := range t.conns {
conn.Close() conn.Close()