From a45a09b877be6e52525625c06608c6b9590d62d0 Mon Sep 17 00:00:00 2001 From: Matt Joiner Date: Mon, 9 May 2016 15:47:39 +1000 Subject: [PATCH] Rework setting of info bytes --- client.go | 52 +++++++++++++++-------------------------- client_test.go | 38 +++++++++++++++++------------- t.go | 2 +- torrent.go | 63 +++++++++++++++++++++++++------------------------- 4 files changed, 73 insertions(+), 82 deletions(-) diff --git a/client.go b/client.go index 6876f6b1..6201f387 100644 --- a/client.go +++ b/client.go @@ -1421,16 +1421,6 @@ func (cl *Client) addPeers(t *Torrent, peers []Peer) { } } -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. @@ -1444,8 +1434,6 @@ func (cl *Client) newTorrent(ih metainfo.Hash) (t *Torrent) { closing: make(chan struct{}), ceasingNetworking: make(chan struct{}), - gotMetainfo: make(chan struct{}), - halfOpen: make(map[string]struct{}), pieceStateChanges: pubsub.NewPubSub(), @@ -1552,6 +1540,13 @@ func (cl *Client) AddTorrentInfoHash(infoHash metainfo.Hash) (t *Torrent, new bo } 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 } @@ -1562,35 +1557,24 @@ func (cl *Client) AddTorrentInfoHash(infoHash metainfo.Hash) (t *Torrent, new bo 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) + 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) - } - 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 2fedb5a9..28392e61 100644 --- a/client_test.go +++ b/client_test.go @@ -93,7 +93,7 @@ func TestTorrentInitialState(t *testing.T) { 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 @@ func TestCompletedPieceWrongSize(t *testing.T) { 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() - tt, _new, err := cl.AddTorrentSpec(&TorrentSpec{ - Info: &metainfo.InfoEx{ - Info: metainfo.Info{ - PieceLength: 1, - Pieces: make([]byte, 20), - Files: []metainfo.FileInfo{{Length: 1}}, - }, + 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: &ie, + InfoHash: ie.Hash(), }) require.NoError(t, err) assert.True(t, _new) diff --git a/t.go b/t.go index 3702e656..afc8feb2 100644 --- a/t.go +++ b/t.go @@ -18,7 +18,7 @@ func (t *Torrent) InfoHash() metainfo.Hash { // 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 d89f25c6..056f4b47 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 @@ type Torrent struct { // 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 @@ func infoPieceHashes(info *metainfo.Info) (ret []string) { } // 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 @@ func (t *Torrent) setMetadata(md *metainfo.Info, infoBytes []byte) (err error) { t.verifyPiece(i) } }() - return + return nil } func (t *Torrent) verifyPiece(piece int) { @@ -1037,25 +1045,9 @@ func (t *Torrent) maybeMetadataCompleted() { // 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 @@ func (t *Torrent) bytesCompleted() int64 { } 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) +} -- 2.44.0