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:
parent
db3be3441f
commit
1e919dd6b1
11
client.go
11
client.go
|
@ -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) {
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
func (s piecePerResourcePiece) ReadAt(b []byte, off int64) (int, error) {
|
||||||
|
if s.GetIsComplete() {
|
||||||
|
return s.c.ReadAt(b, off)
|
||||||
|
} else {
|
||||||
|
return s.i.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 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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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()
|
||||||
|
|
Loading…
Reference in New Issue