From 55dbb7d72f4854f5272ca31227740da046989a63 Mon Sep 17 00:00:00 2001 From: Sergey Matveev Date: Sat, 26 Nov 2022 13:08:02 +0300 Subject: [PATCH] Initial draft client --- cmd/btrtrc/README | 18 ++++ cmd/btrtrc/add.go | 130 ++++++++++++++++++++++++++ cmd/btrtrc/colour.go | 25 +++++ cmd/btrtrc/list.go | 208 ++++++++++++++++++++++++++++++++++++++++++ cmd/btrtrc/main.go | 88 ++++++++++++++++++ cmd/btrtrc/pc.go | 111 ++++++++++++++++++++++ cmd/btrtrc/sort.go | 35 +++++++ cmd/btrtrc/status.go | 40 ++++++++ cmd/btrtrc/txstats.go | 67 ++++++++++++++ cmd/btrtrc/verify.go | 54 +++++++++++ go.mod | 3 +- go.sum | 6 +- peerconn.go | 23 +++++ 13 files changed, 805 insertions(+), 3 deletions(-) create mode 100644 cmd/btrtrc/README create mode 100644 cmd/btrtrc/add.go create mode 100644 cmd/btrtrc/colour.go create mode 100644 cmd/btrtrc/list.go create mode 100644 cmd/btrtrc/main.go create mode 100644 cmd/btrtrc/pc.go create mode 100644 cmd/btrtrc/sort.go create mode 100644 cmd/btrtrc/status.go create mode 100644 cmd/btrtrc/txstats.go create mode 100644 cmd/btrtrc/verify.go diff --git a/cmd/btrtrc/README b/cmd/btrtrc/README new file mode 100644 index 00000000..a10226bf --- /dev/null +++ b/cmd/btrtrc/README @@ -0,0 +1,18 @@ +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 diff --git a/cmd/btrtrc/add.go b/cmd/btrtrc/add.go new file mode 100644 index 00000000..812176a5 --- /dev/null +++ b/cmd/btrtrc/add.go @@ -0,0 +1,130 @@ +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) + } +} diff --git a/cmd/btrtrc/colour.go b/cmd/btrtrc/colour.go new file mode 100644 index 00000000..eb664e1a --- /dev/null +++ b/cmd/btrtrc/colour.go @@ -0,0 +1,25 @@ +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) +} diff --git a/cmd/btrtrc/list.go b/cmd/btrtrc/list.go new file mode 100644 index 00000000..f97f2361 --- /dev/null +++ b/cmd/btrtrc/list.go @@ -0,0 +1,208 @@ +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) + } +} diff --git a/cmd/btrtrc/main.go b/cmd/btrtrc/main.go new file mode 100644 index 00000000..cf932b6d --- /dev/null +++ b/cmd/btrtrc/main.go @@ -0,0 +1,88 @@ +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() +} diff --git a/cmd/btrtrc/pc.go b/cmd/btrtrc/pc.go new file mode 100644 index 00000000..12911fa1 --- /dev/null +++ b/cmd/btrtrc/pc.go @@ -0,0 +1,111 @@ +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 +} diff --git a/cmd/btrtrc/sort.go b/cmd/btrtrc/sort.go new file mode 100644 index 00000000..cfb6478c --- /dev/null +++ b/cmd/btrtrc/sort.go @@ -0,0 +1,35 @@ +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[:]) +} diff --git a/cmd/btrtrc/status.go b/cmd/btrtrc/status.go new file mode 100644 index 00000000..2ffc530a --- /dev/null +++ b/cmd/btrtrc/status.go @@ -0,0 +1,40 @@ +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 + } +} diff --git a/cmd/btrtrc/txstats.go b/cmd/btrtrc/txstats.go new file mode 100644 index 00000000..2249e566 --- /dev/null +++ b/cmd/btrtrc/txstats.go @@ -0,0 +1,67 @@ +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) + } +} diff --git a/cmd/btrtrc/verify.go b/cmd/btrtrc/verify.go new file mode 100644 index 00000000..b2a8e6ff --- /dev/null +++ b/cmd/btrtrc/verify.go @@ -0,0 +1,54 @@ +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() +} diff --git a/go.mod b/go.mod index 4b16520d..d7de2161 100644 --- a/go.mod +++ b/go.mod @@ -44,6 +44,7 @@ require ( 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 ) @@ -80,7 +81,7 @@ require ( 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 diff --git a/go.sum b/go.sum index 4734a5f2..2f825cdb 100644 --- a/go.sum +++ b/go.sum @@ -601,10 +601,12 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc 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= diff --git a/peerconn.go b/peerconn.go index 3c515aaf..322e7625 100644 --- a/peerconn.go +++ b/peerconn.go @@ -283,6 +283,10 @@ func (cn *Peer) completedString() string { 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()) } @@ -348,6 +352,10 @@ func (cn *Peer) statusFlags() (ret string) { return } +func (cn *Peer) StatusFlags() string { + return cn.statusFlags() +} + func (cn *Peer) downloadRate() float64 { num := cn._stats.BytesReadUsefulData.Int64() if num == 0 { @@ -363,6 +371,17 @@ func (cn *Peer) DownloadRate() float64 { 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 @@ -1836,6 +1855,10 @@ func (cn *Peer) stats() *ConnStats { 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 -- 2.44.0