2014-06-29 14:22:05 +00:00
|
|
|
package torrent
|
|
|
|
|
|
|
|
import (
|
2014-08-22 07:33:17 +00:00
|
|
|
"fmt"
|
|
|
|
"io"
|
2014-08-21 11:08:56 +00:00
|
|
|
|
|
|
|
pp "bitbucket.org/anacrolix/go.torrent/peer_protocol"
|
2014-06-29 14:22:05 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type DownloadStrategy interface {
|
|
|
|
FillRequests(t *torrent, c *connection)
|
|
|
|
TorrentStarted(t *torrent)
|
|
|
|
TorrentStopped(t *torrent)
|
|
|
|
DeleteRequest(t *torrent, r request)
|
2014-07-24 03:42:31 +00:00
|
|
|
TorrentPrioritize(t *torrent, off, _len int64)
|
|
|
|
TorrentGotChunk(t *torrent, r request)
|
|
|
|
TorrentGotPiece(t *torrent, piece int)
|
2014-08-22 07:33:17 +00:00
|
|
|
WriteStatus(w io.Writer)
|
2014-06-29 14:22:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type DefaultDownloadStrategy struct {
|
|
|
|
heat map[*torrent]map[request]int
|
|
|
|
}
|
|
|
|
|
2014-08-22 07:33:17 +00:00
|
|
|
func (me *DefaultDownloadStrategy) WriteStatus(w io.Writer) {}
|
|
|
|
|
2014-06-29 14:22:05 +00:00
|
|
|
func (s *DefaultDownloadStrategy) FillRequests(t *torrent, c *connection) {
|
2014-06-30 14:03:07 +00:00
|
|
|
if c.Interested {
|
|
|
|
if c.PeerChoked {
|
|
|
|
return
|
|
|
|
}
|
2014-07-16 07:11:45 +00:00
|
|
|
if len(c.Requests) >= (c.PeerMaxRequests+1)/2 {
|
2014-06-30 14:03:07 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
2014-06-29 14:22:05 +00:00
|
|
|
th := s.heat[t]
|
|
|
|
addRequest := func(req request) (again bool) {
|
|
|
|
piece := t.Pieces[req.Index]
|
|
|
|
if piece.Hashing || piece.QueuedForHash {
|
|
|
|
// We can't be sure we want this.
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
if piece.Complete() {
|
|
|
|
// We already have this.
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
if c.RequestPending(req) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
again = c.Request(req)
|
|
|
|
if c.RequestPending(req) {
|
|
|
|
th[req]++
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
// Then finish off incomplete pieces in order of bytes remaining.
|
2014-07-16 07:11:45 +00:00
|
|
|
for _, heatThreshold := range []int{1, 4, 15, 60} {
|
2014-07-09 14:26:58 +00:00
|
|
|
for e := t.PiecesByBytesLeft.Front(); e != nil; e = e.Next() {
|
|
|
|
pieceIndex := pp.Integer(e.Value.(int))
|
2014-07-16 07:11:45 +00:00
|
|
|
for _, chunkSpec := range t.Pieces[pieceIndex].shuffledPendingChunkSpecs() {
|
|
|
|
// for chunkSpec := range t.Pieces[pieceIndex].PendingChunkSpecs {
|
2014-06-29 14:22:05 +00:00
|
|
|
r := request{pieceIndex, chunkSpec}
|
2014-07-16 07:11:45 +00:00
|
|
|
if th[r] >= heatThreshold {
|
2014-06-29 14:22:05 +00:00
|
|
|
continue
|
|
|
|
}
|
2014-07-09 14:26:58 +00:00
|
|
|
if !addRequest(r) {
|
2014-06-29 14:22:05 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *DefaultDownloadStrategy) TorrentStarted(t *torrent) {
|
|
|
|
if s.heat[t] != nil {
|
|
|
|
panic("torrent already started")
|
|
|
|
}
|
|
|
|
if s.heat == nil {
|
|
|
|
s.heat = make(map[*torrent]map[request]int, 10)
|
|
|
|
}
|
|
|
|
s.heat[t] = make(map[request]int, t.ChunkCount())
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *DefaultDownloadStrategy) TorrentStopped(t *torrent) {
|
|
|
|
if _, ok := s.heat[t]; !ok {
|
|
|
|
panic("torrent not yet started")
|
|
|
|
}
|
|
|
|
delete(s.heat, t)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *DefaultDownloadStrategy) DeleteRequest(t *torrent, r request) {
|
|
|
|
m := s.heat[t]
|
|
|
|
if m[r] <= 0 {
|
|
|
|
panic("no pending requests")
|
|
|
|
}
|
|
|
|
m[r]--
|
|
|
|
}
|
|
|
|
|
2014-07-24 03:42:31 +00:00
|
|
|
func (me *DefaultDownloadStrategy) TorrentGotChunk(t *torrent, c request) {}
|
|
|
|
func (me *DefaultDownloadStrategy) TorrentGotPiece(t *torrent, piece int) {}
|
|
|
|
func (*DefaultDownloadStrategy) TorrentPrioritize(t *torrent, off, _len int64) {}
|
|
|
|
|
|
|
|
func NewResponsiveDownloadStrategy(readahead int) *responsiveDownloadStrategy {
|
|
|
|
return &responsiveDownloadStrategy{
|
|
|
|
Readahead: readahead,
|
|
|
|
lastReadPiece: make(map[*torrent]int),
|
2014-08-22 07:35:15 +00:00
|
|
|
priorities: make(map[*torrent]map[request]struct{}),
|
2014-07-24 03:42:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type responsiveDownloadStrategy struct {
|
2014-06-29 14:22:05 +00:00
|
|
|
// How many bytes to preemptively download starting at the beginning of
|
|
|
|
// the last piece read for a given torrent.
|
2014-07-24 03:42:31 +00:00
|
|
|
Readahead int
|
|
|
|
lastReadPiece map[*torrent]int
|
2014-08-22 07:33:17 +00:00
|
|
|
priorities map[*torrent]map[request]struct{}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (me *responsiveDownloadStrategy) WriteStatus(w io.Writer) {
|
|
|
|
fmt.Fprintf(w, "Priorities:\n")
|
|
|
|
for t, pp := range me.priorities {
|
|
|
|
fmt.Fprintf(w, "\t%s:", t.Name())
|
|
|
|
for r := range pp {
|
|
|
|
fmt.Fprintf(w, "%v ", r)
|
|
|
|
}
|
|
|
|
fmt.Fprintln(w)
|
|
|
|
}
|
2014-06-29 14:22:05 +00:00
|
|
|
}
|
|
|
|
|
2014-07-24 03:42:31 +00:00
|
|
|
func (me *responsiveDownloadStrategy) TorrentStarted(t *torrent) {
|
2014-08-22 07:35:15 +00:00
|
|
|
me.priorities[t] = make(map[request]struct{})
|
2014-07-24 03:42:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (me *responsiveDownloadStrategy) TorrentStopped(t *torrent) {
|
|
|
|
delete(me.lastReadPiece, t)
|
|
|
|
delete(me.priorities, t)
|
|
|
|
}
|
|
|
|
func (responsiveDownloadStrategy) DeleteRequest(*torrent, request) {}
|
2014-06-29 14:22:05 +00:00
|
|
|
|
2014-07-24 03:42:31 +00:00
|
|
|
func (me *responsiveDownloadStrategy) FillRequests(t *torrent, c *connection) {
|
2014-08-22 07:35:15 +00:00
|
|
|
for req := range me.priorities[t] {
|
|
|
|
if _, ok := t.Pieces[req.Index].PendingChunkSpecs[req.chunkSpec]; !ok {
|
|
|
|
panic(req)
|
|
|
|
}
|
|
|
|
if !c.Request(req) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(c.Requests) >= 32 {
|
2014-07-24 03:42:31 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Short circuit request fills at a level that might reduce receiving of
|
|
|
|
// unnecessary chunks.
|
|
|
|
requestWrapper := func(r request) bool {
|
|
|
|
if len(c.Requests) >= 64 {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return c.Request(r)
|
|
|
|
}
|
|
|
|
|
2014-08-22 07:35:15 +00:00
|
|
|
requestPiece := func(piece int) bool {
|
|
|
|
if piece >= t.NumPieces() {
|
|
|
|
return true
|
2014-06-29 14:22:05 +00:00
|
|
|
}
|
2014-08-22 07:35:15 +00:00
|
|
|
for _, cs := range t.Pieces[piece].shuffledPendingChunkSpecs() {
|
|
|
|
if !requestWrapper(request{pp.Integer(piece), cs}) {
|
|
|
|
return false
|
|
|
|
}
|
2014-06-29 14:22:05 +00:00
|
|
|
}
|
2014-08-22 07:35:15 +00:00
|
|
|
return true
|
2014-06-29 14:22:05 +00:00
|
|
|
}
|
2014-07-24 03:42:31 +00:00
|
|
|
|
|
|
|
if lastReadPiece, ok := me.lastReadPiece[t]; ok {
|
|
|
|
readaheadPieces := (me.Readahead + t.UsualPieceSize() - 1) / t.UsualPieceSize()
|
2014-08-22 07:35:15 +00:00
|
|
|
for i := 0; i < readaheadPieces; i++ {
|
|
|
|
if !requestPiece(lastReadPiece + i) {
|
|
|
|
return
|
2014-06-29 14:22:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2014-08-22 07:35:15 +00:00
|
|
|
|
2014-06-29 14:22:05 +00:00
|
|
|
// Then finish off incomplete pieces in order of bytes remaining.
|
2014-07-09 14:26:58 +00:00
|
|
|
for e := t.PiecesByBytesLeft.Front(); e != nil; e = e.Next() {
|
|
|
|
index := e.Value.(int)
|
2014-06-29 14:22:05 +00:00
|
|
|
// Stop when we're onto untouched pieces.
|
2014-07-09 14:26:58 +00:00
|
|
|
if !t.PiecePartiallyDownloaded(index) {
|
2014-06-29 14:22:05 +00:00
|
|
|
break
|
|
|
|
}
|
|
|
|
// Request chunks in random order to reduce overlap with other
|
|
|
|
// connections.
|
|
|
|
for _, cs := range t.Pieces[index].shuffledPendingChunkSpecs() {
|
2014-07-24 03:42:31 +00:00
|
|
|
if !requestWrapper(request{pp.Integer(index), cs}) {
|
2014-06-29 14:22:05 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2014-07-24 03:42:31 +00:00
|
|
|
|
|
|
|
func (me *responsiveDownloadStrategy) TorrentGotChunk(t *torrent, req request) {
|
2014-08-22 07:35:15 +00:00
|
|
|
delete(me.priorities[t], req)
|
2014-07-24 03:42:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (me *responsiveDownloadStrategy) TorrentGotPiece(t *torrent, piece int) {
|
2014-08-22 07:35:15 +00:00
|
|
|
for _, cs := range t.pieceChunks(piece) {
|
|
|
|
delete(me.priorities[t], request{pp.Integer(piece), cs})
|
2014-07-24 03:42:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *responsiveDownloadStrategy) TorrentPrioritize(t *torrent, off, _len int64) {
|
2014-08-22 07:35:15 +00:00
|
|
|
s.lastReadPiece[t] = int(off / int64(t.UsualPieceSize()))
|
2014-07-24 03:42:31 +00:00
|
|
|
for _len > 0 {
|
|
|
|
req, ok := t.offsetRequest(off)
|
|
|
|
if !ok {
|
|
|
|
panic("bad offset")
|
|
|
|
}
|
|
|
|
reqOff := t.requestOffset(req)
|
|
|
|
// Gain the alignment adjustment.
|
|
|
|
_len += off - reqOff
|
|
|
|
// Lose the length of this block.
|
|
|
|
_len -= int64(req.Length)
|
|
|
|
off = reqOff + int64(req.Length)
|
|
|
|
if t.wantChunk(req) {
|
2014-08-22 07:35:15 +00:00
|
|
|
s.priorities[t][req] = struct{}{}
|
2014-07-24 03:42:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|