--- /dev/null
+Peer's status string:
+
+* i -- am interested
+* c -- am chocking
+* -
+* E -- RC4 encryption
+* e -- header encryption
+* discovery source:
+ * Tr -- tracker
+ * I -- incoming
+ * Hg -- DHT get_peers
+ * Ha -- DHT announce_peer
+ * X -- PEX
+ * M -- direct (through magnet:)
+* U -- UTP
+* -
+* i -- he interested
+* c -- he chocking
--- /dev/null
+package main
+
+import (
+ "bufio"
+ "encoding/hex"
+ "log"
+ "os"
+ "path"
+ "strings"
+ "time"
+
+ "github.com/anacrolix/torrent"
+ "github.com/anacrolix/torrent/metainfo"
+ "github.com/anacrolix/torrent/types/infohash"
+)
+
+type stringAddr string
+
+func (stringAddr) Network() string { return "" }
+func (me stringAddr) String() string { return string(me) }
+
+func resolveTestPeers(addrs []string) (ret []torrent.PeerInfo) {
+ for _, ta := range addrs {
+ ret = append(ret, torrent.PeerInfo{Addr: stringAddr(ta)})
+ }
+ return
+}
+
+func readLinesFromFIFO(pth string) []string {
+ fd, err := os.OpenFile(pth, os.O_RDONLY, os.FileMode(0666))
+ if err != nil {
+ log.Println("OpenFile:", pth, err)
+ time.Sleep(time.Second)
+ return nil
+ }
+ var lines []string
+ scanner := bufio.NewScanner(fd)
+ for scanner.Scan() {
+ t := scanner.Text()
+ if len(t) > 0 {
+ lines = append(lines, t)
+ }
+ }
+ fd.Close()
+ return lines
+}
+
+func fifoAdd(c *torrent.Client) {
+ pth := path.Join(FIFOsDir, "add")
+ recreateFIFO(pth)
+ for {
+ for _, what := range readLinesFromFIFO(pth) {
+ cols := strings.Fields(what)
+ what = cols[0]
+ var t *torrent.Torrent
+ var err error
+ if strings.HasPrefix(what, "magnet:") {
+ t, err = c.AddMagnet(what)
+ if err != nil {
+ log.Println("AddMagnet:", what, err)
+ continue
+ }
+ } else {
+ metaInfo, err := metainfo.LoadFromFile(what)
+ if err != nil {
+ log.Println("LoadFromFile:", what, err)
+ continue
+ }
+ t, err = c.AddTorrent(metaInfo)
+ if err != nil {
+ log.Println("AddTorrent:", what, err)
+ continue
+ }
+ }
+ if len(cols) > 1 {
+ t.AddPeers(resolveTestPeers(cols[1:]))
+ }
+ go fifoPeerList(t)
+ go fifoFileList(t)
+ log.Println("added:", t.InfoHash().HexString(), t.Name())
+ go func() {
+ <-t.GotInfo()
+ if err = saveTorrent(t); err != nil {
+ log.Println("saveTorrent:", err)
+ }
+ txStatsLoad(t.InfoHash())
+ t.DownloadAll()
+ }()
+ }
+ time.Sleep(time.Second)
+ }
+}
+
+func fifoDel(c *torrent.Client) {
+ pth := path.Join(FIFOsDir, "del")
+ recreateFIFO(pth)
+ for {
+ for _, what := range readLinesFromFIFO(pth) {
+ raw, err := hex.DecodeString(what)
+ if err != nil {
+ log.Println(err)
+ continue
+ }
+ if len(raw) != infohash.Size {
+ log.Println("bad length")
+ continue
+ }
+ var i infohash.T
+ copy(i[:], raw)
+ t, ok := c.Torrent(i)
+ if !ok {
+ log.Println("no suck torrent", what)
+ continue
+ }
+ txStatsDump(t)
+ txStatsDel(t.InfoHash())
+ t.Drop()
+ for _, where := range []string{"files", "peers"} {
+ pth := path.Join(where, t.InfoHash().HexString())
+ os.Remove(pth)
+ fd, err := os.Open(pth)
+ if err == nil {
+ fd.Close()
+ }
+ }
+ log.Println("deleted:", what, t.Name())
+ }
+ time.Sleep(time.Second)
+ }
+}
--- /dev/null
+package main
+
+import (
+ "bytes"
+
+ "golang.org/x/term"
+)
+
+var (
+ Blue string
+ Green string
+ Magenta string
+ Red string
+ Reset string
+)
+
+func init() {
+ var b bytes.Buffer
+ t := term.NewTerminal(&b, "")
+ Blue = string(t.Escape.Blue)
+ Green = string(t.Escape.Green)
+ Magenta = string(t.Escape.Magenta)
+ Red = string(t.Escape.Red)
+ Reset = string(t.Escape.Reset)
+}
--- /dev/null
+package main
+
+import (
+ "encoding/hex"
+ "fmt"
+ "log"
+ "os"
+ "path"
+ "sort"
+ "strings"
+ "sync"
+ "syscall"
+ "time"
+
+ "github.com/anacrolix/dht/v2"
+ "github.com/anacrolix/torrent"
+ "github.com/anacrolix/torrent/metainfo"
+ "github.com/dustin/go-humanize"
+)
+
+const (
+ MaxListNameWidth = 40
+ FIFOsDir = "fifos"
+ PeersDir = "peers"
+ FilesDir = "files"
+)
+
+var (
+ TorrentStats = map[metainfo.Hash]torrent.TorrentStats{}
+ TorrentStatsM sync.RWMutex
+)
+
+func recreateFIFO(pth string) {
+ os.Remove(pth)
+ if err := syscall.Mkfifo(pth, 0666); err != nil {
+ log.Fatalln(err)
+ }
+}
+
+func shortenName(name string) string {
+ s := []rune(name)
+ if len(s) > MaxListNameWidth {
+ s = s[:MaxListNameWidth]
+ }
+ return string(s)
+}
+
+func fifoList(c *torrent.Client) {
+ pth := path.Join(FIFOsDir, "list")
+ recreateFIFO(pth)
+ for {
+ fd, err := os.OpenFile(pth, os.O_WRONLY|os.O_APPEND, os.FileMode(0666))
+ if err != nil {
+ log.Println("OpenFile:", pth, err)
+ time.Sleep(time.Second)
+ continue
+ }
+ ts := c.Torrents()
+ sort.Sort(ByInfoHash(ts))
+ for _, t := range ts {
+ if t.Info() == nil {
+ fmt.Fprintf(fd, "%s not ready\n", t.Name())
+ continue
+ }
+ stats := t.Stats()
+ done := t.BytesCompleted() * 100 / t.Length()
+ percColour := Red
+ if done == 100 {
+ percColour = Green
+ }
+ tx := stats.BytesWrittenData.Int64()
+ tx += TxStats[t.InfoHash()]
+ ratio := float64(tx) / float64(t.Length())
+ TorrentStatsM.RLock()
+ prev := TorrentStats[t.InfoHash()]
+ TorrentStatsM.RUnlock()
+ rxSpeed := stats.BytesReadData.Int64() - prev.BytesReadData.Int64()
+ txSpeed := stats.BytesWrittenData.Int64() - prev.BytesWrittenData.Int64()
+ var eta string
+ if done < 100 && rxSpeed > 0 {
+ etaRaw := time.Duration((t.Length() - t.BytesCompleted()) / rxSpeed)
+ etaRaw *= time.Second
+ eta = etaRaw.String()
+ }
+ fmt.Fprintf(fd,
+ "%s%s%s %s%40s%s %8s %s%3d%%%s %4.1f %s%d%s/%s%d%s %d/%d/%d/%d %s\n",
+ Blue, t.InfoHash().HexString(), Reset,
+ Green, shortenName(t.Name()), Reset,
+ humanize.IBytes(uint64(t.Length())),
+ percColour, done, Reset,
+ ratio,
+ Green, rxSpeed/1024, Reset,
+ Magenta, txSpeed/1024, Reset,
+ stats.TotalPeers,
+ stats.PendingPeers,
+ stats.ActivePeers,
+ stats.ConnectedSeeders,
+ eta,
+ )
+ }
+ fd.Close()
+ time.Sleep(time.Second)
+ }
+}
+
+func fifoPeerList(t *torrent.Torrent) {
+ pth := path.Join(FIFOsDir, PeersDir, t.InfoHash().HexString())
+ recreateFIFO(pth)
+ for {
+ fd, err := os.OpenFile(pth, os.O_WRONLY|os.O_APPEND, os.FileMode(0666))
+ if err != nil {
+ if os.IsNotExist(err) {
+ break
+ }
+ log.Println("OpenFile:", pth, err)
+ time.Sleep(time.Second)
+ continue
+ }
+ pcs := t.PeerConns()
+ sort.Sort(ByPeerID(pcs))
+ for _, pc := range pcs {
+ completed := pc.CompletedString()
+ completedColour := Red
+ cols := strings.Split(completed, "/")
+ if cols[0] == cols[1] {
+ completedColour = Green
+ }
+ stats := pc.Peer.Stats()
+ fmt.Fprintf(fd,
+ "%s%s%s %10s %s%11s%s %s%d%s/%s%d%s %s / %s | %s%s%s %q\n",
+ Blue, hex.EncodeToString(pc.PeerID[:]), Reset,
+ pc.StatusFlags(),
+ completedColour, pc.CompletedString(), Reset,
+ Green, int(pc.DownloadRate()/1024), Reset,
+ Magenta, int(pc.UploadRate()/1024), Reset,
+ humanize.IBytes(uint64(stats.BytesReadData.Int64())),
+ humanize.IBytes(uint64(stats.BytesWrittenData.Int64())),
+ Green, pc.RemoteAddr, Reset,
+ pc.PeerClientName,
+ )
+ }
+ fd.Close()
+ time.Sleep(time.Second)
+ }
+}
+
+func fifoFileList(t *torrent.Torrent) {
+ pth := path.Join(FIFOsDir, FilesDir, t.InfoHash().HexString())
+ recreateFIFO(pth)
+ for {
+ fd, err := os.OpenFile(pth, os.O_WRONLY|os.O_APPEND, os.FileMode(0666))
+ if err != nil {
+ if os.IsNotExist(err) {
+ break
+ }
+ log.Println("OpenFile:", pth, err)
+ time.Sleep(time.Second)
+ continue
+ }
+ for n, f := range t.Files() {
+ var done int64
+ if f.Length() > 0 {
+ done = (f.BytesCompleted() * 100) / f.Length()
+ }
+ percColour := Green
+ if done < 100 {
+ percColour = Red
+ }
+ fmt.Fprintf(fd,
+ "%5d %8s %3d%% | %s%s%s\n",
+ n, humanize.IBytes(uint64(f.Length())), done,
+ percColour, f.Path(), Reset,
+ )
+ }
+ fd.Close()
+ time.Sleep(time.Second)
+ }
+}
+
+func fifoDHTList(c *torrent.Client) {
+ pth := path.Join(FIFOsDir, "dht")
+ recreateFIFO(pth)
+ for {
+ fd, err := os.OpenFile(pth, os.O_WRONLY|os.O_APPEND, os.FileMode(0666))
+ if err != nil {
+ if os.IsNotExist(err) {
+ break
+ }
+ log.Println("OpenFile:", pth, err)
+ time.Sleep(time.Second)
+ continue
+ }
+ for _, s := range c.DhtServers() {
+ stats := s.Stats().(dht.ServerStats)
+ fmt.Fprintf(
+ fd, "%s%s%s all:%d good:%d await:%d succ:%d bad:%d\n",
+ Green, s.Addr().String(), Reset,
+ stats.Nodes,
+ stats.GoodNodes,
+ stats.OutstandingTransactions,
+ stats.SuccessfulOutboundAnnouncePeerQueries,
+ stats.BadNodes,
+ )
+ }
+ fd.Close()
+ time.Sleep(time.Second)
+ }
+}
--- /dev/null
+package main
+
+import (
+ "bytes"
+ "flag"
+ "log"
+ "net"
+ "os"
+ "os/signal"
+ "path"
+ "strings"
+ "syscall"
+
+ "github.com/anacrolix/dht/v2"
+ analog "github.com/anacrolix/log"
+
+ "github.com/anacrolix/torrent"
+ "github.com/anacrolix/torrent/storage"
+)
+
+const TorrentExt = ".torrent"
+
+func saveTorrent(t *torrent.Torrent) error {
+ pth := t.InfoHash().HexString() + TorrentExt
+ if _, err := os.Stat(pth); err == nil {
+ return nil
+ }
+ var b bytes.Buffer
+ t.Metainfo().Write(&b)
+ if err := os.WriteFile(pth, b.Bytes(), 0666); err != nil {
+ return err
+ }
+ return os.Symlink(pth, t.Name()+TorrentExt)
+}
+
+func main() {
+ log.SetFlags(log.Ldate | log.Ltime)
+ dhtBoot := flag.String("dht", "dht.cypherpunks.ru:8991", "Comma-separated list of DHT bootstrap nodes")
+ addr := flag.String("bind", "[::]:6881", "Address to bind to")
+ pub4 := flag.String("4", "", "External IPv4 address")
+ pub6 := flag.String("6", "", "External IPv6 address")
+ debug := flag.Bool("debug", false, "Enable debug messages")
+ noDHT := flag.Bool("nodht", false, "Disable DHT")
+ verify := flag.Bool("verify", false, "Force verification of provided torrents")
+ flag.Parse()
+
+ dht.DefaultGlobalBootstrapHostPorts = strings.Split(*dhtBoot, ",")
+ cc := torrent.NewDefaultClientConfig()
+ cc.Debug = *debug
+ cc.DisableAcceptRateLimiting = true
+ cc.NoDefaultPortForwarding = true
+ cc.DisableWebtorrent = true
+ cc.Logger = analog.Default.WithNames("main", "client")
+ cc.DefaultStorage = storage.NewFileWithCompletion(".", NewBoltPieceCompletion())
+ if *verify {
+ doVerify(cc, flag.Args())
+ return
+ }
+ cc.Seed = true
+ cc.PublicIp4 = net.ParseIP(*pub4).To4()
+ cc.PublicIp6 = net.ParseIP(*pub6).To16()
+ cc.NoDHT = *noDHT
+ cc.SetListenAddr(*addr)
+ client, err := torrent.NewClient(cc)
+ if err != nil {
+ log.Fatalln("torrent.NewClient:", err)
+ }
+ defer client.Close()
+
+ needsShutdown := make(chan os.Signal)
+ signal.Notify(needsShutdown, syscall.SIGTERM, syscall.SIGINT)
+ go func() {
+ <-needsShutdown
+ txStatsDumpAll(client)
+ client.Close()
+ }()
+
+ os.MkdirAll(path.Join(FIFOsDir, PeersDir), 0777)
+ os.MkdirAll(path.Join(FIFOsDir, FilesDir), 0777)
+ log.Println("started", client.PublicIPs())
+ go overallStatus(client)
+ go fifoList(client)
+ go fifoDHTList(client)
+ go fifoAdd(client)
+ go fifoDel(client)
+ go txStatsDumper(client)
+ <-client.Closed()
+}
--- /dev/null
+package main
+
+// based on storage/bolt-piece-completion.go
+
+import (
+ "encoding/binary"
+ "sync"
+ "time"
+
+ "go.etcd.io/bbolt"
+
+ "github.com/anacrolix/torrent/metainfo"
+ "github.com/anacrolix/torrent/storage"
+)
+
+const (
+ boltDbCompleteValue = "c"
+ boltDbIncompleteValue = "i"
+)
+
+var completionBucketKey = []byte("completion")
+
+type boltPieceCompletion struct {
+ dbs map[metainfo.Hash]*bbolt.DB
+ dbsLock sync.Mutex
+}
+
+var _ storage.PieceCompletion = (*boltPieceCompletion)(nil)
+
+func NewBoltPieceCompletion() *boltPieceCompletion {
+ return &boltPieceCompletion{dbs: make(map[metainfo.Hash]*bbolt.DB)}
+}
+
+func (me *boltPieceCompletion) Get(pk metainfo.PieceKey) (cn storage.Completion, err error) {
+ me.dbsLock.Lock()
+ db, ok := me.dbs[pk.InfoHash]
+ if !ok {
+ db, err = bbolt.Open(
+ pk.InfoHash.HexString()+".bolt",
+ 0o666,
+ &bbolt.Options{Timeout: time.Second},
+ )
+ if err != nil {
+ me.dbsLock.Unlock()
+ return
+ }
+ db.NoSync = true
+ me.dbs[pk.InfoHash] = db
+ }
+ me.dbsLock.Unlock()
+ err = db.View(func(tx *bbolt.Tx) error {
+ cb := tx.Bucket(completionBucketKey)
+ if cb == nil {
+ return nil
+ }
+ ih := cb.Bucket(pk.InfoHash[:])
+ if ih == nil {
+ return nil
+ }
+ var key [4]byte
+ binary.BigEndian.PutUint32(key[:], uint32(pk.Index))
+ cn.Ok = true
+ switch string(ih.Get(key[:])) {
+ case boltDbCompleteValue:
+ cn.Complete = true
+ case boltDbIncompleteValue:
+ cn.Complete = false
+ default:
+ cn.Ok = false
+ }
+ return nil
+ })
+ return
+}
+
+func (me *boltPieceCompletion) Set(pk metainfo.PieceKey, b bool) error {
+ if c, err := me.Get(pk); err == nil && c.Ok && c.Complete == b {
+ return nil
+ }
+ me.dbsLock.Lock()
+ db := me.dbs[pk.InfoHash]
+ me.dbsLock.Unlock()
+ return db.Update(func(tx *bbolt.Tx) error {
+ c, err := tx.CreateBucketIfNotExists(completionBucketKey)
+ if err != nil {
+ return err
+ }
+ ih, err := c.CreateBucketIfNotExists(pk.InfoHash[:])
+ if err != nil {
+ return err
+ }
+ var key [4]byte
+ binary.BigEndian.PutUint32(key[:], uint32(pk.Index))
+ return ih.Put(key[:], []byte(func() string {
+ if b {
+ return boltDbCompleteValue
+ } else {
+ return boltDbIncompleteValue
+ }
+ }()))
+ })
+}
+
+func (me *boltPieceCompletion) Close() error {
+ me.dbsLock.Lock()
+ for _, db := range me.dbs {
+ db.Close()
+ }
+ me.dbsLock.Unlock()
+ return nil
+}
--- /dev/null
+package main
+
+import (
+ "encoding/hex"
+
+ "github.com/anacrolix/torrent"
+)
+
+type ByInfoHash []*torrent.Torrent
+
+func (a ByInfoHash) Len() int {
+ return len(a)
+}
+
+func (a ByInfoHash) Swap(i, j int) {
+ a[i], a[j] = a[j], a[i]
+}
+
+func (a ByInfoHash) Less(i, j int) bool {
+ return a[i].InfoHash().HexString() < a[j].InfoHash().HexString()
+}
+
+type ByPeerID []*torrent.PeerConn
+
+func (a ByPeerID) Len() int {
+ return len(a)
+}
+
+func (a ByPeerID) Swap(i, j int) {
+ a[i], a[j] = a[j], a[i]
+}
+
+func (a ByPeerID) Less(i, j int) bool {
+ return hex.EncodeToString(a[i].PeerID[:]) < hex.EncodeToString(a[j].PeerID[:])
+}
--- /dev/null
+package main
+
+import (
+ "log"
+ "time"
+
+ "github.com/anacrolix/torrent"
+ "github.com/dustin/go-humanize"
+)
+
+func overallStatus(c *torrent.Client) {
+ var prev torrent.ConnStats
+ for range time.Tick(time.Second) {
+ stats := c.ConnStats()
+ var peers int
+ for _, t := range c.Torrents() {
+ if t.Info() == nil {
+ continue
+ }
+ tStats := t.Stats()
+ TorrentStatsM.Lock()
+ TorrentStats[t.InfoHash()] = tStats
+ TorrentStatsM.Unlock()
+ peers += tStats.ActivePeers
+ }
+ log.Printf(
+ "%s / %s | %d | %s%s%s / %s%s%s",
+ humanize.IBytes(uint64(stats.BytesRead.Int64())),
+ humanize.IBytes(uint64(stats.BytesWritten.Int64())),
+ peers,
+ Green,
+ humanize.IBytes(uint64(stats.BytesRead.Int64()-prev.BytesRead.Int64())),
+ Reset,
+ Magenta,
+ humanize.IBytes(uint64(stats.BytesWritten.Int64()-prev.BytesWritten.Int64())),
+ Reset,
+ )
+ prev = stats
+ }
+}
--- /dev/null
+package main
+
+import (
+ "fmt"
+ "log"
+ "os"
+ "strconv"
+ "sync"
+ "time"
+
+ "github.com/anacrolix/torrent"
+ "github.com/anacrolix/torrent/metainfo"
+)
+
+const TxExt = ".tx"
+
+var (
+ TxStats = map[metainfo.Hash]int64{}
+ TxStatsM sync.Mutex
+)
+
+func txStatsLoad(h metainfo.Hash) {
+ pth := h.HexString() + TxExt
+ data, err := os.ReadFile(pth)
+ if err != nil {
+ return
+ }
+ v, err := strconv.ParseInt(string(data[:len(data)-1]), 10, 64)
+ if err != nil {
+ log.Println("ParseInt:", pth, err)
+ return
+ }
+ TxStatsM.Lock()
+ TxStats[h] = v
+ TxStatsM.Unlock()
+}
+
+func txStatsDel(h metainfo.Hash) {
+ TxStatsM.Lock()
+ delete(TxStats, h)
+ TxStatsM.Unlock()
+}
+
+func txStatsDump(t *torrent.Torrent) {
+ stats := t.Stats()
+ TxStatsM.Lock()
+ s := stats.BytesWrittenData.Int64() + TxStats[t.InfoHash()]
+ pth := t.InfoHash().HexString() + TxExt
+ if err := os.WriteFile(pth, []byte(fmt.Sprintf("%d\n", s)), 0666); err != nil {
+ log.Println("WriteFile:", pth, err)
+ }
+ TxStatsM.Unlock()
+}
+
+func txStatsDumpAll(c *torrent.Client) {
+ for _, t := range c.Torrents() {
+ if t.Info() != nil {
+ txStatsDump(t)
+ }
+ }
+}
+
+func txStatsDumper(c *torrent.Client) {
+ for range time.Tick(10 * time.Second) {
+ txStatsDumpAll(c)
+ }
+}
--- /dev/null
+package main
+
+import (
+ "fmt"
+ "log"
+
+ "github.com/anacrolix/torrent"
+ "github.com/anacrolix/torrent/metainfo"
+)
+
+func doVerify(cc *torrent.ClientConfig, pths []string) {
+ cc.DisableTrackers = true
+ cc.NoDHT = true
+ cc.NoUpload = true
+ cc.DisableUTP = true
+ cc.DisableTCP = true
+ cc.DisableIPv6 = true
+ cc.DisableIPv4 = true
+ cc.AcceptPeerConnections = false
+ cc.DisableWebseeds = true
+ client, err := torrent.NewClient(cc)
+ if err != nil {
+ log.Fatalln("torrent.NewClient:", err)
+ }
+ for _, pth := range pths {
+ metaInfo, err := metainfo.LoadFromFile(pth)
+ if err != nil {
+ log.Fatalln("LoadFromFile:", err)
+ }
+ t, err := client.AddTorrent(metaInfo)
+ if err != nil {
+ log.Fatalln("AddTorrent:", err)
+ }
+ <-t.GotInfo()
+ if err = saveTorrent(t); err != nil {
+ log.Println("saveTorrent:", err)
+ }
+ go func() {
+ sub := t.SubscribePieceStateChanges()
+ defer sub.Close()
+ var last int
+ for piece := range sub.Values {
+ if piece.Hashing && piece.Index > last {
+ fmt.Printf("\r%s: %d / %d", pth, piece.Index, t.NumPieces())
+ last = piece.Index
+ }
+ }
+ }()
+ t.VerifyData()
+ fmt.Printf("\n")
+ }
+ client.Close()
+ <-client.Closed()
+}
go.opentelemetry.io/otel v1.8.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.8.0
go.opentelemetry.io/otel/sdk v1.8.0
+ golang.org/x/term v0.2.0
golang.org/x/time v0.0.0-20220609170525-579cf78fd858
)
golang.org/x/exp v0.0.0-20220613132600-b0d781184e0d // indirect
golang.org/x/net v0.0.0-20220630215102-69896b714898 // indirect
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f // indirect
- golang.org/x/sys v0.0.0-20220702020025-31831981b65f // indirect
+ golang.org/x/sys v0.2.0 // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f // indirect
google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1 // indirect
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220702020025-31831981b65f h1:xdsejrW/0Wf2diT5CPp3XmKUNbr7Xvw8kYilQ+6qjRY=
-golang.org/x/sys v0.0.0-20220702020025-31831981b65f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A=
+golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.2.0 h1:z85xZCsEl7bi/KwbNADeBYoOP0++7W1ipu+aGnpwzRM=
+golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
return fmt.Sprintf("%d/%d", have, cn.bestPeerNumPieces())
}
+func (cn *Peer) CompletedString() string {
+ return cn.completedString()
+}
+
func (cn *PeerConn) onGotInfo(info *metainfo.Info) {
cn.setNumPieces(info.NumPieces())
}
return
}
+func (cn *Peer) StatusFlags() string {
+ return cn.statusFlags()
+}
+
func (cn *Peer) downloadRate() float64 {
num := cn._stats.BytesReadUsefulData.Int64()
if num == 0 {
return cn.downloadRate()
}
+func (cn *Peer) UploadRate() float64 {
+ cn.locker().RLock()
+ defer cn.locker().RUnlock()
+ num := cn._stats.BytesWrittenData.Int64()
+ if num == 0 {
+ return 0
+ }
+ return float64(num) / time.Now().Sub(cn.completedHandshake).Seconds()
+}
+
+
func (cn *Peer) iterContiguousPieceRequests(f func(piece pieceIndex, count int)) {
var last Option[pieceIndex]
var count int
return &cn._stats
}
+func (cn *Peer) Stats() *ConnStats {
+ return cn.stats()
+}
+
func (p *Peer) TryAsPeerConn() (*PeerConn, bool) {
pc, ok := p.peerImpl.(*PeerConn)
return pc, ok