18 "github.com/anacrolix/dht/v2"
19 "github.com/anacrolix/torrent"
20 "github.com/anacrolix/torrent/metainfo"
21 "github.com/anacrolix/torrent/types/infohash"
22 "github.com/dustin/go-humanize"
32 type TorrentStat struct {
33 stats torrent.ConnStats
39 TorrentStats = map[metainfo.Hash]TorrentStat{}
40 TorrentStatsM sync.RWMutex
43 func recreateFIFO(pth string) {
45 if err := syscall.Mkfifo(pth, 0666); err != nil {
50 func shortenName(name string) string {
52 if len(s) > MaxListNameWidth {
53 s = s[:MaxListNameWidth]
58 func fifoList(c *torrent.Client) {
59 pth := path.Join(FIFOsDir, "list")
62 fd, err := os.OpenFile(pth, os.O_WRONLY|os.O_APPEND, os.FileMode(0666))
64 log.Println("OpenFile:", pth, err)
65 time.Sleep(time.Second)
69 sort.Sort(ByInfoHash(ts))
70 for _, t := range ts {
72 fmt.Fprintf(fd, "%s not ready\n", t.Name())
76 done := t.BytesCompleted() * 100 / t.Length()
81 tx := stats.BytesWrittenData.Int64()
82 tx += TxStats[t.InfoHash()]
83 ratio := float64(tx) / float64(t.Length())
85 prev := TorrentStats[t.InfoHash()]
86 TorrentStatsM.RUnlock()
88 if done < 100 && prev.rxSpeed > 0 {
89 etaRaw := time.Duration((t.Length() - t.BytesCompleted()) / prev.rxSpeed)
94 "%s%s%s %s%40s%s %8s %s%3d%%%s %4.1f %s%d%s/%s%d%s %d/%d/%d/%d %s\n",
95 Blue, t.InfoHash().HexString(), Reset,
96 Green, shortenName(t.Name()), Reset,
97 humanize.IBytes(uint64(t.Length())),
98 percColour, done, Reset,
100 Green, prev.rxSpeed/1024, Reset,
101 Magenta, prev.txSpeed/1024, Reset,
105 stats.ConnectedSeeders,
110 time.Sleep(time.Second)
114 func mustParseInt(s string) int {
115 i, err := strconv.Atoi(s)
122 func fifoPeerList(t *torrent.Torrent) {
123 pth := path.Join(FIFOsDir, PeersDir, t.InfoHash().HexString())
126 fd, err := os.OpenFile(pth, os.O_WRONLY|os.O_APPEND, os.FileMode(0666))
128 if os.IsNotExist(err) {
131 log.Println("OpenFile:", pth, err)
132 time.Sleep(time.Second)
136 sort.Sort(ByPeerID(pcs))
137 for _, pc := range pcs {
138 cols := strings.Split(pc.CompletedString(), "/")
139 done := (mustParseInt(cols[0]) * 100) / mustParseInt(cols[1])
144 stats := pc.Peer.Stats()
146 "%s%s%s %10s %s%3d%%%s %s%d%s/%s%d%s %s / %s | %s%s%s %q\n",
147 Blue, hex.EncodeToString(pc.PeerID[:]), Reset,
149 doneColour, done, Reset,
150 Green, int(pc.DownloadRate()/1024), Reset,
151 Magenta, int(pc.UploadRate()/1024), Reset,
152 humanize.IBytes(uint64(stats.BytesReadData.Int64())),
153 humanize.IBytes(uint64(stats.BytesWrittenData.Int64())),
154 Green, pc.RemoteAddr, Reset,
159 time.Sleep(time.Second)
163 func fifoFileList(t *torrent.Torrent) {
164 pth := path.Join(FIFOsDir, FilesDir, t.InfoHash().HexString())
167 fd, err := os.OpenFile(pth, os.O_WRONLY|os.O_APPEND, os.FileMode(0666))
169 if os.IsNotExist(err) {
172 log.Println("OpenFile:", pth, err)
173 time.Sleep(time.Second)
176 for n, f := range t.Files() {
179 done = (f.BytesCompleted() * 100) / f.Length()
186 "%5d %8s %3d%% | %s%s%s\n",
187 n, humanize.IBytes(uint64(f.Length())), done,
188 percColour, f.Path(), Reset,
192 time.Sleep(time.Second)
196 func fifoDHTList(c *torrent.Client) {
197 pth := path.Join(FIFOsDir, "dht")
200 fd, err := os.OpenFile(pth, os.O_WRONLY|os.O_APPEND, os.FileMode(0666))
202 if os.IsNotExist(err) {
205 log.Println("OpenFile:", pth, err)
206 time.Sleep(time.Second)
209 for _, s := range c.DhtServers() {
210 stats := s.Stats().(dht.ServerStats)
212 fd, "%s%s%s all:%d good:%d await:%d succ:%d bad:%d\n",
213 Green, s.Addr().String(), Reset,
216 stats.OutstandingTransactions,
217 stats.SuccessfulOutboundAnnouncePeerQueries,
222 time.Sleep(time.Second)
226 type topTorrent struct {
227 infoHash metainfo.Hash
232 func fifoTopSeed(c *torrent.Client) {
233 pth := path.Join(FIFOsDir, "top-seed")
236 fd, err := os.OpenFile(pth, os.O_WRONLY|os.O_APPEND, os.FileMode(0666))
238 log.Println("OpenFile:", pth, err)
239 time.Sleep(time.Second)
243 for _, t := range c.Torrents() {
248 ts = append(ts, &topTorrent{
249 infoHash: t.InfoHash(),
251 tx: stats.BytesWrittenData.Int64() + TxStats[t.InfoHash()],
254 sort.Sort(ByTxTraffic(ts))
255 for _, t := range ts {
257 "%s%s%s %s%40s%s %s\n",
258 Blue, t.infoHash.HexString(), Reset,
259 Green, shortenName(t.name), Reset,
260 humanize.IBytes(uint64(t.tx)),
264 time.Sleep(time.Second)
268 type stringAddr string
270 func (stringAddr) Network() string { return "" }
271 func (me stringAddr) String() string { return string(me) }
273 func resolveTestPeers(addrs []string) (ret []torrent.PeerInfo) {
274 for _, ta := range addrs {
275 ret = append(ret, torrent.PeerInfo{Addr: stringAddr(ta)})
280 func readLinesFromFIFO(pth string) []string {
281 fd, err := os.OpenFile(pth, os.O_RDONLY, os.FileMode(0666))
283 log.Println("OpenFile:", pth, err)
284 time.Sleep(time.Second)
288 scanner := bufio.NewScanner(fd)
292 lines = append(lines, t)
299 func saveTorrent(t *torrent.Torrent) error {
300 pth := t.Name() + TorrentExt
301 if _, err := os.Stat(pth); err == nil {
305 t.Metainfo().Write(&b)
306 return os.WriteFile(pth, b.Bytes(), 0666)
309 func fifoAdd(c *torrent.Client) {
310 pth := path.Join(FIFOsDir, "add")
313 for _, what := range readLinesFromFIFO(pth) {
314 cols := strings.Fields(what)
316 var t *torrent.Torrent
318 if strings.HasPrefix(what, "magnet:") {
319 t, err = c.AddMagnet(what)
321 log.Println("AddMagnet:", what, err)
325 metaInfo, err := metainfo.LoadFromFile(what)
327 log.Println("LoadFromFile:", what, err)
330 t, err = c.AddTorrent(metaInfo)
332 log.Println("AddTorrent:", what, err)
337 t.AddPeers(resolveTestPeers(cols[1:]))
341 log.Println("added:", t.InfoHash().HexString(), t.Name())
344 if err = saveTorrent(t); err != nil {
345 log.Println("saveTorrent:", err)
347 txStatsLoad(t.InfoHash())
351 time.Sleep(time.Second)
355 func fifoDel(c *torrent.Client) {
356 pth := path.Join(FIFOsDir, "del")
359 for _, what := range readLinesFromFIFO(pth) {
360 raw, err := hex.DecodeString(what)
365 if len(raw) != infohash.Size {
366 log.Println("bad length")
371 t, ok := c.Torrent(i)
373 log.Println("no suck torrent", what)
377 txStatsDel(t.InfoHash())
379 for _, where := range []string{"files", "peers"} {
380 pth := path.Join(where, t.InfoHash().HexString())
382 fd, err := os.Open(pth)
387 log.Println("deleted:", what, t.Name())
389 time.Sleep(time.Second)
393 func fifosPrepare() {
394 os.MkdirAll(path.Join(FIFOsDir, PeersDir), 0777)
395 os.MkdirAll(path.Join(FIFOsDir, FilesDir), 0777)
398 func fifosCleanup() {
399 os.Remove(path.Join(FIFOsDir, "list"))
400 os.Remove(path.Join(FIFOsDir, "dht"))
401 os.Remove(path.Join(FIFOsDir, "add"))
402 os.Remove(path.Join(FIFOsDir, "del"))
403 os.Remove(path.Join(FIFOsDir, "top-seed"))
404 os.RemoveAll(path.Join(FIFOsDir, PeersDir))
405 os.RemoveAll(path.Join(FIFOsDir, FilesDir))