diff --git a/README.md b/README.md index 4b0b166..a5370d0 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ A pure golang implementation of [BitTorrent](http://bittorrent.org/beps/bep_0000.html) library, which is inspired by [dht](https://github.com/shiyanhui/dht) and [torrent](https://github.com/anacrolix/torrent). +***WORK IN PROGRESS*** ## Features @@ -14,7 +15,7 @@ A pure golang implementation of [BitTorrent](http://bittorrent.org/beps/bep_0000 ## Install ```shell -$ go get github.com/xgfone/bt +$ go get -u github.com/xgfone/bt ``` diff --git a/downloader/file_download_handler.go b/downloader/file_download_handler.go new file mode 100644 index 0000000..9d913ec --- /dev/null +++ b/downloader/file_download_handler.go @@ -0,0 +1,197 @@ +// Copyright 2020 xgfone +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package downloader + +import ( + "sort" + + "github.com/xgfone/bt/metainfo" + pp "github.com/xgfone/bt/peerprotocol" +) + +// PieceBlock is the information of the block in the piece. +type PieceBlock struct { + Index uint32 // The index of the piece + Offset uint32 // The offset from the beginning of the piece + Length uint32 // The length of the block, which is equal to 2^14 in general +} + +// pieceIndexes is a set of the piece indexes. +type pieceIndexes []uint32 + +func (ps pieceIndexes) Len() int { return len(ps) } +func (ps pieceIndexes) Less(i, j int) bool { return ps[i] < ps[j] } +func (ps pieceIndexes) Swap(i, j int) { ps[i], ps[j] = ps[j], ps[i] } + +// Sort sorts itself. +func (ps pieceIndexes) Sort() { sort.Sort(ps) } + +// Contains reports whether it contains the given index. +func (ps pieceIndexes) Contains(index uint32) bool { + for _, p := range ps { + if p == index { + return true + } + } + return false +} + +// pieceIndexInfo is the information of the piece indexes in the remote peers. +type pieceIndexInfo struct { + Fasts pieceIndexes + Pieces pieceIndexes + Suggests pieceIndexes +} + +var _ pp.Handler = &FileDownloadHandler{} +var _ pp.Bep3Handler = &FileDownloadHandler{} +var _ pp.Bep6Handler = &FileDownloadHandler{} + +// FileDownloadHandler is used to downloads the files in the torrent file. +type FileDownloadHandler struct { + pp.NoopHandler + pp.NoopBep3Handler + pp.NoopBep6Handler + + Writer *metainfo.Writer // Required + + Logger func(format string, args ...interface{}) // Optional + GetNextBlock func(fasts, suggests, pieces []uint32) []PieceBlock // Required +} + +// NewFileDownloadHandler returns a new FileDownloadHandler. +func NewFileDownloadHandler(writer *metainfo.Writer, + getNextBlock func([]uint32, []uint32, []uint32) []PieceBlock, + log func(string, ...interface{})) *FileDownloadHandler { + return &FileDownloadHandler{ + Logger: log, + Writer: writer, + GetNextBlock: getNextBlock, + } +} + +// OnHandShake implements the interface Handler#OnHandShake. +// +// Notice: it uses the field Data to store the inner data, you mustn't override +// it. +func (fd *FileDownloadHandler) OnHandShake(c *pp.PeerConn) (err error) { + c.Data = &pieceIndexInfo{Pieces: make([]uint32, 0, fd.Writer.Info.CountPieces())} + return +} + +// OnMessage implements the interface Handler#OnMessage. +func (fd *FileDownloadHandler) OnMessage(c *pp.PeerConn, m pp.Message) error { + if log := fd.Logger; log != nil { + log("unexpected message '%s' from '%s'", m.Type, c.RemoteAddr().String()) + } + return nil +} + +// RequestPiece sends the Request to get the piece data. +func (fd *FileDownloadHandler) RequestPiece(c *pp.PeerConn) (err error) { + pi := c.Data.(*pieceIndexInfo) + blocks := fd.GetNextBlock(pi.Fasts, pi.Suggests, pi.Pieces) + for _, b := range blocks { + if err = c.SendRequest(b.Index, b.Offset, b.Length); err != nil { + break + } + } + return +} + +/// --------------------------------------------------------------------------- +/// BEP 3 + +// Piece implements the interface Bep3Handler#Piece. +func (fd *FileDownloadHandler) Piece(c *pp.PeerConn, i, b uint32, p []byte) error { + _, err := fd.Writer.WriteBlock(p, i, b) + return err +} + +// Unchoke implements the interface Bep3Handler#Unchoke. +func (fd *FileDownloadHandler) Unchoke(pc *pp.PeerConn) (err error) { + return fd.RequestPiece(pc) +} + +// Have implements the interface Bep3Handler#Have. +func (fd *FileDownloadHandler) Have(pc *pp.PeerConn, index uint32) (err error) { + pi := pc.Data.(*pieceIndexInfo) + if !pi.Pieces.Contains(index) { + pi.Pieces = append(pi.Pieces, index) + pi.Pieces.Sort() + } + return +} + +// Bitfield implements the interface Bep3Handler#Bitfield. +func (fd *FileDownloadHandler) Bitfield(pc *pp.PeerConn, bits []bool) (err error) { + pi := pc.Data.(*pieceIndexInfo) + for i, t := range bits { + if t { + pi.Pieces = append(pi.Pieces, uint32(i)) + } + } + return +} + +/// --------------------------------------------------------------------------- +/// BEP 6 + +// HaveAll implements the interface Bep6Handler#HaveAll. +func (fd *FileDownloadHandler) HaveAll(pc *pp.PeerConn) (err error) { + pi := pc.Data.(*pieceIndexInfo) + for i := range fd.Writer.Info.Pieces { + pi.Pieces = append(pi.Pieces, uint32(i)) + } + return +} + +// Suggest implements the interface Bep6Handler#Suggest. +func (fd *FileDownloadHandler) Suggest(pc *pp.PeerConn, index uint32) (err error) { + pi := pc.Data.(*pieceIndexInfo) + if !pi.Suggests.Contains(index) { + pi.Suggests = append(pi.Suggests, index) + pi.Suggests.Sort() + } + return +} + +// AllowedFast implements the interface Bep6Handler#AllowedFast. +func (fd *FileDownloadHandler) AllowedFast(pc *pp.PeerConn, index uint32) (err error) { + pi := pc.Data.(*pieceIndexInfo) + if !pi.Fasts.Contains(index) { + pi.Fasts = append(pi.Fasts, index) + pi.Fasts.Sort() + } + return +} + +// Reject implements the interface Bep6Handler#Reject. +func (fd *FileDownloadHandler) Reject(pc *pp.PeerConn, index, begin, length uint32) (err error) { + pi := pc.Data.(*pieceIndexInfo) + pi.Suggests = fd.removePiece(pi.Suggests, index) + pi.Pieces = fd.removePiece(pi.Pieces, index) + return +} + +func (fd *FileDownloadHandler) removePiece(ps pieceIndexes, index uint32) pieceIndexes { + for i, p := range ps { + if p == index { + copy(ps[i:], ps[i+1:]) + return ps[:len(ps)-1] + } + } + return ps +}