utpSock *utp.Socket
dHT *dht.Server
ipBlockList iplist.Ranger
- bannedTorrents map[InfoHash]struct{}
+ bannedTorrents map[metainfo.InfoHash]struct{}
config Config
pruneTimer *time.Timer
extensionBytes peerExtensionBytes
event sync.Cond
closed missinggo.Event
- torrents map[InfoHash]*torrent
+ torrents map[metainfo.InfoHash]*torrent
}
func (me *Client) IPBlockList() iplist.Ranger {
}
type hashSorter struct {
- Hashes []InfoHash
+ Hashes []metainfo.InfoHash
}
func (me hashSorter) Len() int {
}
defer f.Close()
scanner := bufio.NewScanner(f)
- cl.bannedTorrents = make(map[InfoHash]struct{})
+ cl.bannedTorrents = make(map[metainfo.InfoHash]struct{})
for scanner.Scan() {
if strings.HasPrefix(strings.TrimSpace(scanner.Text()), "#") {
continue
if len(ihs) != 20 {
return errors.New("bad infohash")
}
- var ih InfoHash
+ var ih metainfo.InfoHash
CopyExact(&ih, ihs)
cl.bannedTorrents[ih] = struct{}{}
}
config: *cfg,
defaultStorage: cfg.DefaultStorage,
dopplegangerAddrs: make(map[string]struct{}),
- torrents: make(map[InfoHash]*torrent),
+ torrents: make(map[metainfo.InfoHash]*torrent),
}
CopyExact(&cl.extensionBytes, defaultExtensionBytes)
cl.event.L = &cl.mu
}
// Returns a handle to the given torrent, if it's present in the client.
-func (cl *Client) Torrent(ih InfoHash) (T Torrent, ok bool) {
+func (cl *Client) Torrent(ih metainfo.InfoHash) (T Torrent, ok bool) {
cl.mu.Lock()
defer cl.mu.Unlock()
t, ok := cl.torrents[ih]
return
}
-func (me *Client) torrent(ih InfoHash) *torrent {
+func (me *Client) torrent(ih metainfo.InfoHash) *torrent {
return me.torrents[ih]
}
type handshakeResult struct {
peerExtensionBytes
peerID
- InfoHash
+ metainfo.InfoHash
}
// ih is nil if we expect the peer to declare the InfoHash, such as when the
// peer initiated the connection. Returns ok if the handshake was successful,
// and err if there was an unexpected condition other than the peer simply
// abandoning the handshake.
-func handshake(sock io.ReadWriter, ih *InfoHash, peerID [20]byte, extensions peerExtensionBytes) (res handshakeResult, ok bool, err error) {
+func handshake(sock io.ReadWriter, ih *metainfo.InfoHash, peerID [20]byte, extensions peerExtensionBytes) (res handshakeResult, ok bool, err error) {
// Bytes to be sent to the peer. Should never block the sender.
postCh := make(chan []byte, 4)
// A single error value sent when the writer completes.
}
// Returns !ok if handshake failed for valid reasons.
-func (cl *Client) connBTHandshake(c *connection, ih *InfoHash) (ret InfoHash, ok bool, err error) {
+func (cl *Client) connBTHandshake(c *connection, ih *metainfo.InfoHash) (ret metainfo.InfoHash, ok bool, err error) {
res, ok, err := handshake(c.rw, ih, cl.peerID, cl.extensionBytes)
if err != nil || !ok {
return
func (cl *Client) completedMetadata(t *torrent) {
h := sha1.New()
h.Write(t.MetaData)
- var ih InfoHash
+ var ih metainfo.InfoHash
CopyExact(&ih, h.Sum(nil))
if ih != t.InfoHash {
log.Print("bad metadata")
}
}
-func (cl *Client) cachedMetaInfoFilename(ih InfoHash) string {
+func (cl *Client) cachedMetaInfoFilename(ih metainfo.InfoHash) string {
return filepath.Join(cl.configDir(), "torrents", ih.HexString()+".torrent")
}
// able to save the torrent, but not load it again to check it.
return nil
}
- if !bytes.Equal(mi.Info.Hash, t.InfoHash[:]) {
+ if !bytes.Equal(mi.Info.Hash.Bytes(), t.InfoHash[:]) {
log.Fatalf("%x != %x", mi.Info.Hash, t.InfoHash[:])
}
return nil
// Prepare a Torrent without any attachment to a Client. That means we can
// initialize fields all fields that don't require the Client without locking
// it.
-func newTorrent(ih InfoHash) (t *torrent) {
+func newTorrent(ih metainfo.InfoHash) (t *torrent) {
t = &torrent{
InfoHash: ih,
chunkSize: defaultChunkSize,
// Returns nil metainfo if it isn't in the cache. Checks that the retrieved
// metainfo has the correct infohash.
-func (cl *Client) torrentCacheMetaInfo(ih InfoHash) (mi *metainfo.MetaInfo, err error) {
+func (cl *Client) torrentCacheMetaInfo(ih metainfo.InfoHash) (mi *metainfo.MetaInfo, err error) {
if cl.config.DisableMetainfoCache {
return
}
if err != nil {
return
}
- if !bytes.Equal(mi.Info.Hash, ih[:]) {
+ if !bytes.Equal(mi.Info.Hash.Bytes(), ih[:]) {
err = fmt.Errorf("cached torrent has wrong infohash: %x != %x", mi.Info.Hash, ih[:])
return
}
type TorrentSpec struct {
// The tiered tracker URIs.
Trackers [][]string
- InfoHash InfoHash
+ InfoHash metainfo.InfoHash
Info *metainfo.InfoEx
// The name to use if the Name field from the Info isn't available.
DisplayName string
spec.Trackers[0] = append(spec.Trackers[0], mi.Announce)
}
- CopyExact(&spec.InfoHash, &mi.Info.Hash)
+ CopyExact(&spec.InfoHash, mi.Info.Hash)
return
}
return
}
-func (me *Client) dropTorrent(infoHash InfoHash) (err error) {
+func (me *Client) dropTorrent(infoHash metainfo.InfoHash) (err error) {
t, ok := me.torrents[infoHash]
if !ok {
err = fmt.Errorf("no such torrent")
func TestClientDefault(t *testing.T) {
cl, err := NewClient(&TestingConfig)
- if err != nil {
- t.Fatal(err)
- }
+ require.NoError(t, err)
cl.Close()
}
func TestAddDropTorrent(t *testing.T) {
cl, err := NewClient(&TestingConfig)
- if err != nil {
- t.Fatal(err)
- }
+ require.NoError(t, err)
defer cl.Close()
dir, mi := testutil.GreetingTestTorrent()
defer os.RemoveAll(dir)
tt, new, err := cl.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
- if err != nil {
- t.Fatal(err)
- }
- if !new {
- t.FailNow()
- }
+ require.NoError(t, err)
+ assert.True(t, new)
tt.Drop()
}
func TestTorrentInitialState(t *testing.T) {
dir, mi := testutil.GreetingTestTorrent()
defer os.RemoveAll(dir)
- tor := newTorrent(func() (ih InfoHash) {
+ tor := newTorrent(func() (ih metainfo.InfoHash) {
missinggo.CopyExact(ih[:], mi.Info.Hash)
return
}())
}
func TestClientTransferVarious(t *testing.T) {
- for _, responsive := range []bool{false, true} {
- testClientTransfer(t, testClientTransferParams{
- Responsive: responsive,
- })
- for _, readahead := range []int64{-1, 0, 1, 2, 3, 4, 5, 6, 9, 10, 11, 12, 13, 14, 15, 20} {
+ for _, ss := range []func(string) storage.I{
+ storage.NewFile,
+ storage.NewMMap,
+ } {
+ for _, responsive := range []bool{false, true} {
testClientTransfer(t, testClientTransferParams{
- Responsive: responsive,
- SetReadahead: true,
- Readahead: readahead,
+ Responsive: responsive,
+ SeederStorage: ss,
})
+ for _, readahead := range []int64{-1, 0, 1, 2, 3, 4, 5, 6, 9, 10, 11, 12, 13, 14, 15, 20} {
+ testClientTransfer(t, testClientTransferParams{
+ SeederStorage: ss,
+ Responsive: responsive,
+ SetReadahead: true,
+ Readahead: readahead,
+ })
+ }
}
}
}
ExportClientStatus bool
SetLeecherStorageCapacity bool
LeecherStorageCapacity int64
+ SeederStorage func(string) storage.I
}
func testClientTransfer(t *testing.T, ps testClientTransferParams) {
defer os.RemoveAll(greetingTempDir)
cfg := TestingConfig
cfg.Seed = true
- cfg.DataDir = greetingTempDir
+ if ps.SeederStorage != nil {
+ cfg.DefaultStorage = ps.SeederStorage(greetingTempDir)
+ } else {
+ cfg.DataDir = greetingTempDir
+ }
seeder, err := NewClient(&cfg)
require.NoError(t, err)
defer seeder.Close()
require.NoError(t, err)
require.True(t, new)
require.NotNil(t, tt.Info())
- _, err = os.Stat(filepath.Join(cfg.ConfigDir, "torrents", fmt.Sprintf("%x.torrent", mi.Info.Hash)))
+ _, err = os.Stat(filepath.Join(cfg.ConfigDir, "torrents", fmt.Sprintf("%x.torrent", mi.Info.Hash.Bytes())))
require.NoError(t, err)
// Contains only the infohash.
var ts TorrentSpec
"github.com/gosuri/uiprogress"
"github.com/anacrolix/torrent"
- "github.com/anacrolix/torrent/data/mmap"
"github.com/anacrolix/torrent/metainfo"
+ "github.com/anacrolix/torrent/storage"
)
func resolvedPeerAddrs(ss []string) (ret []torrent.Peer, err error) {
tagflag.Parse(&opts, tagflag.SkipBadTypes())
clientConfig := opts.Config
if opts.Mmap {
- clientConfig.TorrentDataOpener = func(info *metainfo.Info) torrent.Storage {
- ret, err := mmap.TorrentData(info, "")
- if err != nil {
- log.Fatalf("error opening torrent data for %q: %s", info.Name, err)
- }
- return ret
- }
+ clientConfig.DefaultStorage = storage.NewMMap("")
}
client, err := torrent.NewClient(&clientConfig)
}
type Piece struct {
- Info *Info
+ Info *InfoEx
i int
}
return
}
-func (me *Info) Piece(i int) Piece {
+func (me *InfoEx) Piece(i int) Piece {
return Piece{me, i}
}
// important to Bittorrent.
type InfoEx struct {
Info
- Hash []byte
+ Hash *InfoHash
Bytes []byte
}
)
func (this *InfoEx) UnmarshalBencode(data []byte) error {
- this.Bytes = make([]byte, 0, len(data))
- this.Bytes = append(this.Bytes, data...)
+ this.Bytes = append(make([]byte, 0, len(data)), data...)
h := sha1.New()
_, err := h.Write(this.Bytes)
if err != nil {
panic(err)
}
- this.Hash = h.Sum(nil)
+ this.Hash = new(InfoHash)
+ missinggo.CopyExact(this.Hash, h.Sum(nil))
return bencode.Unmarshal(data, &this.Info)
}
}
type InfoHash [20]byte
+
+func (me *InfoHash) Bytes() []byte {
+ return me[:]
+}
+
+func (ih *InfoHash) AsString() string {
+ return string(ih[:])
+}
+
+func (ih *InfoHash) HexString() string {
+ return fmt.Sprintf("%x", ih[:])
+}
import (
"crypto"
"errors"
- "fmt"
"time"
"github.com/anacrolix/torrent/metainfo"
)
type (
- InfoHash [20]byte
pieceSum [20]byte
)
-func (ih InfoHash) AsString() string {
- return string(ih[:])
-}
-
-func (ih InfoHash) HexString() string {
- return fmt.Sprintf("%x", ih[:])
-}
-
func lastChunkSpec(pieceLength, chunkSize pp.Integer) (cs chunkSpec) {
cs.Begin = (pieceLength - 1) / chunkSize * chunkSize
cs.Length = pieceLength - cs.Begin
completed map[[20]byte]bool
}
-func NewFile(baseDir string) *fileStorage {
+func NewFile(baseDir string) I {
return &fileStorage{
baseDir: baseDir,
}
}
type fileStorageTorrent struct {
- info *metainfo.Info
+ info *metainfo.InfoEx
baseDir string
}
require.NoError(t, err)
defer os.RemoveAll(td)
data := NewFile(td)
- info := &metainfo.Info{
- Name: "a",
- Length: 2,
- PieceLength: missinggo.MiB,
+ info := &metainfo.InfoEx{
+ Info: metainfo.Info{
+ Name: "a",
+ Length: 2,
+ PieceLength: missinggo.MiB,
+ },
}
f, err := os.Create(filepath.Join(td, "a"))
err = f.Truncate(1)
// Returns true if the piece is complete.
GetIsComplete() bool
}
+
+// type PieceStorage interface {
+// ReadAt(metainfo.Piece, []byte, int64) (int, error)
+// WriteAt(metainfo.Piece, []byte, int64) (int, error)
+// MarkComplete(metainfo.Piece) error
+// GetIsComplete(metainfo.Piece) bool
+// }
+
+// type wrappedPieceStorage struct {
+// ps PieceStorage
+// }
+
+// func WrapPieceStorage(ps PieceStorage) I {
+// return wrappedPieceStorage{ps}
+// }
+
+// func (me wrappedPieceStorage) Piece(metainfo.Piece)
--- /dev/null
+package storage
+
+import (
+ "fmt"
+ "io"
+ "os"
+ "path/filepath"
+
+ "github.com/anacrolix/missinggo"
+ "github.com/edsrzf/mmap-go"
+
+ "github.com/anacrolix/torrent/metainfo"
+ "github.com/anacrolix/torrent/mmap_span"
+)
+
+type mmapStorage struct {
+ baseDir string
+ spans map[metainfo.InfoHash]mmap_span.MMapSpan
+ completed map[metainfo.InfoHash]bool
+}
+
+func NewMMap(baseDir string) I {
+ return &mmapStorage{
+ baseDir: baseDir,
+ }
+}
+
+func (me *mmapStorage) lazySpan(info *metainfo.InfoEx) error {
+ if me.spans == nil {
+ me.spans = make(map[metainfo.InfoHash]mmap_span.MMapSpan)
+ }
+ if _, ok := me.spans[*info.Hash]; ok {
+ return nil
+ }
+ span, err := MMapTorrent(&info.Info, me.baseDir)
+ if err != nil {
+ return err
+ }
+ me.spans[*info.Hash] = span
+ return nil
+}
+
+func (me *mmapStorage) Piece(p metainfo.Piece) Piece {
+ err := me.lazySpan(p.Info)
+ if err != nil {
+ panic(err)
+ }
+ return mmapStoragePiece{
+ storage: me,
+ p: p,
+ ReaderAt: io.NewSectionReader(me.spans[*p.Info.Hash], p.Offset(), p.Length()),
+ WriterAt: missinggo.NewSectionWriter(me.spans[*p.Info.Hash], p.Offset(), p.Length()),
+ }
+}
+
+type mmapStoragePiece struct {
+ storage *mmapStorage
+ p metainfo.Piece
+ io.ReaderAt
+ io.WriterAt
+}
+
+func (me mmapStoragePiece) GetIsComplete() bool {
+ return me.storage.completed[me.p.Hash()]
+}
+
+func (me mmapStoragePiece) MarkComplete() error {
+ if me.storage.completed == nil {
+ me.storage.completed = make(map[metainfo.InfoHash]bool)
+ }
+ me.storage.completed[me.p.Hash()] = true
+ return nil
+}
+
+func MMapTorrent(md *metainfo.Info, location string) (mms mmap_span.MMapSpan, err error) {
+ defer func() {
+ if err != nil {
+ mms.Close()
+ }
+ }()
+ for _, miFile := range md.UpvertedFiles() {
+ fileName := filepath.Join(append([]string{location, md.Name}, miFile.Path...)...)
+ err = os.MkdirAll(filepath.Dir(fileName), 0777)
+ if err != nil {
+ err = fmt.Errorf("error creating data directory %q: %s", filepath.Dir(fileName), err)
+ return
+ }
+ var file *os.File
+ file, err = os.OpenFile(fileName, os.O_CREATE|os.O_RDWR, 0666)
+ if err != nil {
+ return
+ }
+ func() {
+ defer file.Close()
+ var fi os.FileInfo
+ fi, err = file.Stat()
+ if err != nil {
+ return
+ }
+ if fi.Size() < miFile.Length {
+ err = file.Truncate(miFile.Length)
+ if err != nil {
+ return
+ }
+ }
+ if miFile.Length == 0 {
+ // Can't mmap() regions with length 0.
+ return
+ }
+ var mMap mmap.MMap
+ mMap, err = mmap.MapRegion(file,
+ int(miFile.Length), // Probably not great on <64 bit systems.
+ mmap.RDWR, 0, 0)
+ if err != nil {
+ err = fmt.Errorf("error mapping file %q, length %d: %s", file.Name(), miFile.Length, err)
+ return
+ }
+ if int64(len(mMap)) != miFile.Length {
+ panic("mmap has wrong length")
+ }
+ mms.Append(mMap)
+ }()
+ if err != nil {
+ return
+ }
+ }
+ return
+}
// The torrent's infohash. This is fixed and cannot change. It uniquely
// identifies a torrent.
-func (t Torrent) InfoHash() InfoHash {
+func (t Torrent) InfoHash() metainfo.InfoHash {
return t.torrent.InfoHash
}
}
// Returns the metainfo info dictionary, or nil if it's not yet available.
-func (t Torrent) Info() *metainfo.Info {
+func (t Torrent) Info() *metainfo.InfoEx {
return t.torrent.Info
}
// announcing, and communicating with peers.
ceasingNetworking chan struct{}
- InfoHash InfoHash
+ InfoHash metainfo.InfoHash
Pieces []piece
// Values are the piece indices that changed.
pieceStateChanges *pubsub.PubSub
storage storage.I
// The info dict. Nil if we don't have it (yet).
- Info *metainfo.Info
+ Info *metainfo.InfoEx
// Active peer connections, running message stream loops.
Conns []*connection
// Set of addrs to which we're attempting to connect. Connections are
err = fmt.Errorf("bad info: %s", err)
return
}
- t.Info = md
+ t.Info = &metainfo.InfoEx{
+ Info: *md,
+ Bytes: infoBytes,
+ Hash: &t.InfoHash,
+ }
t.length = 0
for _, f := range t.Info.UpvertedFiles() {
t.length += f.Length
panic("info bytes not set")
}
return &metainfo.MetaInfo{
- Info: metainfo.InfoEx{
- Info: *t.Info,
- Bytes: t.MetaData,
- },
+ Info: *t.Info,
CreationDate: time.Now().Unix(),
Comment: "dynamic metainfo from client",
CreatedBy: "go.torrent",