Categorygithub.com/go-i2p/go-i2p-bt
repository
0.33.9
Repository: https://github.com/go-i2p/go-i2p-bt.git
Documentation: pkg.go.dev

# Packages

No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author

# README

BT - Another Implementation For Golang Build Status GoDoc License

A pure golang implementation of BitTorrent library, which is inspired by dht and torrent.

Install

$ go get -u github.com/xgfone/bt

Features

  • Support Go1.9+.
  • Support IPv4/IPv6.
  • Multi-BEPs implementation.
  • Pure Go implementation without CGO.
  • Only library without any denpendencies. For the command tools, see bttools.

The Implemented Specifications

Example

See godoc or bttools.

Example 1: Download the file from the remote peer

package main

import (
	"context"
	"flag"
	"fmt"
	"io"
	"log"
	"net/url"
	"os"
	"time"

	"github.com/xgfone/bt/downloader"
	"github.com/xgfone/bt/metainfo"
	pp "github.com/xgfone/bt/peerprotocol"
	"github.com/xgfone/bt/tracker"
)

var peeraddr string

func init() {
	flag.StringVar(&peeraddr, "peeraddr", "", "The address of the peer storing the file.")
}

func getPeersFromTrackers(id, infohash metainfo.Hash, trackers []string) (peers []string) {
	c, cancel := context.WithTimeout(context.Background(), time.Second*3)
	defer cancel()

	resp := tracker.GetPeers(c, id, infohash, trackers)
	for r := range resp {
		for _, addr := range r.Resp.Addresses {
			addrs := addr.String()
			nonexist := true
			for _, peer := range peers {
				if peer == addrs {
					nonexist = false
					break
				}
			}

			if nonexist {
				peers = append(peers, addrs)
			}
		}
	}

	return
}

func main() {
	flag.Parse()

	torrentfile := os.Args[1]
	mi, err := metainfo.LoadFromFile(torrentfile)
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	id := metainfo.NewRandomHash()
	infohash := mi.InfoHash()
	info, err := mi.Info()
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	var peers []string
	if peeraddr != "" {
		peers = []string{peeraddr}
	} else {
		// Get the peers from the trackers in the torrent file.
		trackers := mi.Announces().Unique()
		if len(trackers) == 0 {
			fmt.Println("no trackers")
			return
		}

		peers = getPeersFromTrackers(id, infohash, trackers)
		if len(peers) == 0 {
			fmt.Println("no peers")
			return
		}
	}

	// We save the downloaded file to the current directory.
	w := metainfo.NewWriter("", info, 0)
	defer w.Close()

	// We don't request the blocks from the remote peers concurrently,
	// and it is only an example. But you can do it concurrently.
	dm := newDownloadManager(w, info)
	for peerslen := len(peers); peerslen > 0 && !dm.IsFinished(); {
		peerslen--
		peer := peers[peerslen]
		peers = peers[:peerslen]
		downloadFileFromPeer(peer, id, infohash, dm)
	}
}

func downloadFileFromPeer(peer string, id, infohash metainfo.Hash, dm *downloadManager) {
	pc, err := pp.NewPeerConnByDial(peer, id, infohash, time.Second*3)
	if err != nil {
		log.Printf("fail to dial '%s'", peer)
		return
	}
	defer pc.Close()

	dm.doing = false
	pc.Timeout = time.Second * 10
	if err = pc.Handshake(); err != nil {
		log.Printf("fail to handshake with '%s': %s", peer, err)
		return
	}

	info := dm.writer.Info()
	bdh := downloader.NewBlockDownloadHandler(info, dm.OnBlock, dm.RequestBlock)
	if err = bdh.OnHandShake(pc); err != nil {
		log.Printf("handshake error with '%s': %s", peer, err)
		return
	}

	var msg pp.Message
	for !dm.IsFinished() {
		switch msg, err = pc.ReadMsg(); err {
		case nil:
			switch err = pc.HandleMessage(msg, bdh); err {
			case nil, pp.ErrChoked:
			default:
				log.Printf("fail to handle the msg from '%s': %s", peer, err)
				return
			}
		case io.EOF:
			log.Printf("got EOF from '%s'", peer)
			return
		default:
			log.Printf("fail to read the msg from '%s': %s", peer, err)
			return
		}
	}
}

func newDownloadManager(w metainfo.Writer, info metainfo.Info) *downloadManager {
	length := info.Piece(0).Length()
	return &downloadManager{writer: w, plength: length}
}

type downloadManager struct {
	writer  metainfo.Writer
	pindex  uint32
	poffset uint32
	plength int64
	doing   bool
}

func (dm *downloadManager) IsFinished() bool {
	if dm.pindex >= uint32(dm.writer.Info().CountPieces()) {
		return true
	}
	return false
}

func (dm *downloadManager) OnBlock(index, offset uint32, b []byte) (err error) {
	if dm.pindex != index {
		return fmt.Errorf("inconsistent piece: old=%d, new=%d", dm.pindex, index)
	} else if dm.poffset != offset {
		return fmt.Errorf("inconsistent offset for piece '%d': old=%d, new=%d",
			index, dm.poffset, offset)
	}

	dm.doing = false
	n, err := dm.writer.WriteBlock(index, offset, b)
	if err == nil {
		dm.poffset = offset + uint32(n)
		dm.plength -= int64(n)
	}
	return
}

func (dm *downloadManager) RequestBlock(pc *pp.PeerConn) (err error) {
	if dm.doing {
		return
	}

	if dm.plength <= 0 {
		dm.pindex++
		if dm.IsFinished() {
			return
		}

		dm.poffset = 0
		dm.plength = dm.writer.Info().Piece(int(dm.pindex)).Length()
	}

	index := dm.pindex
	begin := dm.poffset
	length := uint32(downloader.BlockSize)
	if length > uint32(dm.plength) {
		length = uint32(dm.plength)
	}

	log.Printf("Request Block from '%s': index=%d, offset=%d, length=%d",
		pc.RemoteAddr().String(), index, begin, length)
	if err = pc.SendRequest(index, begin, length); err == nil {
		dm.doing = true
	}
	return
}