+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)
+ }
+}