client.go | 52 ++++++++++++++++++---------------------------------- client_test.go | 38 ++++++++++++++++++++++---------------- t.go | 2 +- torrent.go | 63 +++++++++++++++++++++++++++-------------------------- diff --git a/client.go b/client.go index 6876f6b13ae0d10e3ab7587f8885991453c106f2..6201f3872ffce120e6ff06a711c8d5f41cc500b3 100644 --- a/client.go +++ b/client.go @@ -1421,16 +1421,6 @@ t.addPeer(p, cl) } } -func (cl *Client) setMetaData(t *Torrent, md *metainfo.Info, bytes []byte) (err error) { - err = t.setMetadata(md, bytes) - if err != nil { - return - } - cl.event.Broadcast() - close(t.gotMetainfo) - return -} - // 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. @@ -1443,8 +1433,6 @@ peers: make(map[peersKey]Peer), closing: make(chan struct{}), ceasingNetworking: make(chan struct{}), - - gotMetainfo: make(chan struct{}), halfOpen: make(map[string]struct{}), pieceStateChanges: pubsub.NewPubSub(), @@ -1552,6 +1540,13 @@ return } new = true t = cl.newTorrent(infoHash) + if !cl.config.DisableTrackers { + go cl.announceTorrentTrackers(t) + } + if cl.dHT != nil { + go cl.announceTorrentDHT(t, true) + } + cl.torrents[infoHash] = t return } @@ -1561,36 +1556,25 @@ // 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. func (cl *Client) AddTorrentSpec(spec *TorrentSpec) (t *Torrent, new bool, err error) { t, new = cl.AddTorrentInfoHash(spec.InfoHash) - - cl.mu.Lock() - defer cl.mu.Unlock() if spec.DisplayName != "" { - t.setDisplayName(spec.DisplayName) - } - // Try to merge in info we have on the torrent. Any err left will - // terminate the function. - if t.info == nil && spec.Info != nil { - err = cl.setMetaData(t, &spec.Info.Info, spec.Info.Bytes) + t.SetDisplayName(spec.DisplayName) } - if err != nil { - return + + if spec.Info != nil { + err = t.SetInfoBytes(spec.Info.Bytes) + if err != nil { + return + } } + + cl.mu.Lock() + defer cl.mu.Unlock() + t.addTrackers(spec.Trackers) - cl.torrents[spec.InfoHash] = t t.maybeNewConns() - // From this point onwards, we can consider the torrent a part of the - // client. - if new { - if !cl.config.DisableTrackers { - go cl.announceTorrentTrackers(t) - } - if cl.dHT != nil { - go cl.announceTorrentDHT(t, true) - } - } return } diff --git a/client_test.go b/client_test.go index 2fedb5a952bc52a8db90cfa98b1cd118385bf580..28392e610e02ae15e7974d0b74104a87679c11cf 100644 --- a/client_test.go +++ b/client_test.go @@ -93,7 +93,7 @@ tor.chunkSize = 2 tor.storageOpener = storage.NewFile(dir) // Needed to lock for asynchronous piece verification. tor.cl = new(Client) - err := tor.setMetadata(&mi.Info.Info, mi.Info.Bytes) + err := tor.setInfoBytes(mi.Info.Bytes) require.NoError(t, err) require.Len(t, tor.pieces, 3) tor.pendAllChunkSpecs(0) @@ -487,16 +487,19 @@ cfg := TestingConfig cfg.DefaultStorage = badStorage{} cl, _ := NewClient(&cfg) defer cl.Close() - tt, new, err := cl.AddTorrentSpec(&TorrentSpec{ - Info: &metainfo.InfoEx{ - Info: metainfo.Info{ - PieceLength: 15, - Pieces: make([]byte, 20), - Files: []metainfo.FileInfo{ - metainfo.FileInfo{Path: []string{"greeting"}, Length: 13}, - }, + ie := metainfo.InfoEx{ + Info: metainfo.Info{ + PieceLength: 15, + Pieces: make([]byte, 20), + Files: []metainfo.FileInfo{ + metainfo.FileInfo{Path: []string{"greeting"}, Length: 13}, }, }, + } + ie.UpdateBytes() + tt, new, err := cl.AddTorrentSpec(&TorrentSpec{ + Info: &ie, + InfoHash: ie.Hash(), }) require.NoError(t, err) defer tt.Drop() @@ -835,14 +838,17 @@ func TestPeerInvalidHave(t *testing.T) { cl, err := NewClient(&TestingConfig) require.NoError(t, err) defer cl.Close() + ie := metainfo.InfoEx{ + Info: metainfo.Info{ + PieceLength: 1, + Pieces: make([]byte, 20), + Files: []metainfo.FileInfo{{Length: 1}}, + }, + } + ie.UpdateBytes() tt, _new, err := cl.AddTorrentSpec(&TorrentSpec{ - Info: &metainfo.InfoEx{ - Info: metainfo.Info{ - PieceLength: 1, - Pieces: make([]byte, 20), - Files: []metainfo.FileInfo{{Length: 1}}, - }, - }, + Info: &ie, + InfoHash: ie.Hash(), }) require.NoError(t, err) assert.True(t, _new) diff --git a/t.go b/t.go index 3702e656e3131464be01d51d7763bc4224935f77..afc8feb29e76fd1733ef5732b578ca0fd35b9d2e 100644 --- a/t.go +++ b/t.go @@ -18,7 +18,7 @@ // Returns a channel that is closed when the info (.Info()) for the torrent // has become available. func (t *Torrent) GotInfo() <-chan struct{} { - return t.gotMetainfo + return t.gotMetainfo.C() } // Returns the metainfo info dictionary, or nil if it's not yet available. diff --git a/torrent.go b/torrent.go index d89f25c6dbbbf75efc1b9fe781609b1e636cb774..056f4b4747d0237e7a4169c6f3404e646b1f9b32 100644 --- a/torrent.go +++ b/torrent.go @@ -2,7 +2,6 @@ package torrent import ( "container/heap" - "crypto/sha1" "fmt" "io" "log" @@ -84,8 +83,8 @@ // Each element corresponds to the 16KiB metadata pieces. If true, we have // received that piece. metadataCompletedChunks []bool - // Closed when .Info is set. - gotMetainfo chan struct{} + // Set when .Info is obtained. + gotMetainfo missinggo.Event readers map[*Reader]struct{} @@ -216,27 +215,36 @@ return } // Called when metadata for a torrent becomes available. -func (t *Torrent) setMetadata(md *metainfo.Info, infoBytes []byte) (err error) { - err = validateInfo(md) +func (t *Torrent) setInfoBytes(b []byte) error { + if t.haveInfo() { + return nil + } + var ie *metainfo.InfoEx + err := bencode.Unmarshal(b, &ie) if err != nil { - err = fmt.Errorf("bad info: %s", err) - return + return fmt.Errorf("error unmarshalling info bytes: %s", err) } - t.info = &metainfo.InfoEx{ - Info: *md, - Bytes: infoBytes, + if ie.Hash() != t.infoHash { + return fmt.Errorf("info bytes have wrong hash") } + err = validateInfo(&ie.Info) + if err != nil { + return fmt.Errorf("bad info: %s", err) + } + t.info = ie + t.cl.event.Broadcast() + t.gotMetainfo.Set() t.storage, err = t.storageOpener.OpenTorrent(t.info) if err != nil { - return + return fmt.Errorf("error opening torrent storage: %s", err) } t.length = 0 for _, f := range t.info.UpvertedFiles() { t.length += f.Length } - t.metadataBytes = infoBytes + t.metadataBytes = b t.metadataCompletedChunks = nil - hashes := infoPieceHashes(md) + hashes := infoPieceHashes(&t.info.Info) t.pieces = make([]piece, len(hashes)) for i, hash := range hashes { piece := &t.pieces[i] @@ -260,7 +268,7 @@ for i := range t.pieces { t.verifyPiece(i) } }() - return + return nil } func (t *Torrent) verifyPiece(piece int) { @@ -1037,25 +1045,9 @@ if !t.haveAllMetadataPieces() { // Don't have enough metadata pieces. return } - h := sha1.New() - h.Write(t.metadataBytes) - var ih metainfo.Hash - missinggo.CopyExact(&ih, h.Sum(nil)) - if ih != t.infoHash { - log.Printf("torrent %q: metadata bytes failed hash check", t) - t.invalidateMetadata() - return - } - var info metainfo.Info - err := bencode.Unmarshal(t.metadataBytes, &info) - if err != nil { - log.Printf("error unmarshalling metadata: %s", err) - t.invalidateMetadata() - return - } // TODO(anacrolix): If this fails, I think something harsher should be // done. - err = t.cl.setMetaData(t, &info, t.metadataBytes) + err := t.setInfoBytes(t.metadataBytes) if err != nil { log.Printf("error setting metadata: %s", err) t.invalidateMetadata() @@ -1106,3 +1098,12 @@ return 0 } return t.info.TotalLength() - t.bytesLeft() } + +func (t *Torrent) SetInfoBytes(b []byte) (err error) { + t.cl.mu.Lock() + defer t.cl.mu.Unlock() + if t.info != nil { + return + } + return t.setInfoBytes(b) +}