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
41 Torrents []metainfo.Hash
42 TorrentsM sync.RWMutex
45 func recreateFIFO(pth string) {
47 if err := syscall.Mkfifo(pth, 0666); err != nil {
52 func shortenName(name string) string {
54 if len(s) > MaxListNameWidth {
55 s = s[:MaxListNameWidth]
60 func fifoList(c *torrent.Client) {
61 pth := path.Join(FIFOsDir, "list")
64 fd, err := os.OpenFile(pth, os.O_WRONLY|os.O_APPEND, os.FileMode(0666))
66 log.Println("OpenFile:", pth, err)
67 time.Sleep(time.Second)
70 ts := make([]*torrent.Torrent, 0, len(Torrents))
72 for _, h := range Torrents {
74 if t == nil || t.Info() == nil {
75 fmt.Fprintf(fd, "%s not ready\n", t.Name())
81 for _, t := range ts {
83 done := t.BytesCompleted() * 100 / t.Length()
88 tx := stats.BytesWrittenData.Int64()
89 tx += TxStats[t.InfoHash()]
90 ratio := float64(tx) / float64(t.Length())
92 prev := TorrentStats[t.InfoHash()]
93 TorrentStatsM.RUnlock()
95 if done < 100 && prev.rxSpeed > 0 {
96 etaRaw := time.Duration((t.Length() - t.BytesCompleted()) / prev.rxSpeed)
101 "%s%s%s %s%40s%s %8s %s%3d%%%s %4.1f %s%d%s/%s%d%s %d/%d/%d/%d %s\n",
102 Blue, t.InfoHash().HexString(), Reset,
103 Green, shortenName(t.Name()), Reset,
104 humanize.IBytes(uint64(t.Length())),
105 percColour, done, Reset,
107 Green, prev.rxSpeed/1024, Reset,
108 Magenta, prev.txSpeed/1024, Reset,
112 stats.ConnectedSeeders,
117 time.Sleep(time.Second)
121 func mustParseInt(s string) int {
122 i, err := strconv.Atoi(s)
129 func fifoPeerList(t *torrent.Torrent) {
130 pth := path.Join(FIFOsDir, PeersDir, t.InfoHash().HexString())
133 fd, err := os.OpenFile(pth, os.O_WRONLY|os.O_APPEND, os.FileMode(0666))
135 if os.IsNotExist(err) {
138 log.Println("OpenFile:", pth, err)
139 time.Sleep(time.Second)
143 sort.Sort(ByPeerID(pcs))
144 for _, pc := range pcs {
145 cols := strings.Split(pc.CompletedString(), "/")
146 done := (mustParseInt(cols[0]) * 100) / mustParseInt(cols[1])
151 stats := pc.Peer.Stats()
153 "%s%s%s %10s %s%3d%%%s %s%d%s/%s%d%s %s / %s | %s%s%s %q\n",
154 Blue, hex.EncodeToString(pc.PeerID[:]), Reset,
156 doneColour, done, Reset,
157 Green, int(pc.DownloadRate()/1024), Reset,
158 Magenta, int(pc.UploadRate()/1024), Reset,
159 humanize.IBytes(uint64(stats.BytesReadData.Int64())),
160 humanize.IBytes(uint64(stats.BytesWrittenData.Int64())),
161 Green, pc.RemoteAddr, Reset,
166 time.Sleep(time.Second)
170 func fifoFileList(t *torrent.Torrent) {
171 pth := path.Join(FIFOsDir, FilesDir, t.InfoHash().HexString())
174 fd, err := os.OpenFile(pth, os.O_WRONLY|os.O_APPEND, os.FileMode(0666))
176 if os.IsNotExist(err) {
179 log.Println("OpenFile:", pth, err)
180 time.Sleep(time.Second)
183 for n, f := range t.Files() {
186 done = (f.BytesCompleted() * 100) / f.Length()
193 "%5d %8s %3d%% | %s%s%s\n",
194 n, humanize.IBytes(uint64(f.Length())), done,
195 percColour, f.Path(), Reset,
199 time.Sleep(time.Second)
203 func fifoDHTList(c *torrent.Client) {
204 pth := path.Join(FIFOsDir, "dht")
207 fd, err := os.OpenFile(pth, os.O_WRONLY|os.O_APPEND, os.FileMode(0666))
209 if os.IsNotExist(err) {
212 log.Println("OpenFile:", pth, err)
213 time.Sleep(time.Second)
216 for _, s := range c.DhtServers() {
217 stats := s.Stats().(dht.ServerStats)
219 fd, "%s%s%s all:%d good:%d await:%d succ:%d bad:%d\n",
220 Green, s.Addr().String(), Reset,
223 stats.OutstandingTransactions,
224 stats.SuccessfulOutboundAnnouncePeerQueries,
229 time.Sleep(time.Second)
233 type topTorrent struct {
234 infoHash metainfo.Hash
239 func fifoTopSeed(c *torrent.Client) {
240 pth := path.Join(FIFOsDir, "top-seed")
243 fd, err := os.OpenFile(pth, os.O_WRONLY|os.O_APPEND, os.FileMode(0666))
245 log.Println("OpenFile:", pth, err)
246 time.Sleep(time.Second)
250 for _, t := range c.Torrents() {
255 ts = append(ts, &topTorrent{
256 infoHash: t.InfoHash(),
258 tx: stats.BytesWrittenData.Int64() + TxStats[t.InfoHash()],
261 sort.Sort(ByTxTraffic(ts))
262 for _, t := range ts {
264 "%s%s%s %s%40s%s %s\n",
265 Blue, t.infoHash.HexString(), Reset,
266 Green, shortenName(t.name), Reset,
267 humanize.IBytes(uint64(t.tx)),
271 time.Sleep(time.Second)
275 type stringAddr string
277 func (stringAddr) Network() string { return "" }
278 func (me stringAddr) String() string { return string(me) }
280 func resolveTestPeers(addrs []string) (ret []torrent.PeerInfo) {
281 for _, ta := range addrs {
282 ret = append(ret, torrent.PeerInfo{Addr: stringAddr(ta)})
287 func readLinesFromFIFO(pth string) []string {
288 fd, err := os.OpenFile(pth, os.O_RDONLY, os.FileMode(0666))
290 log.Println("OpenFile:", pth, err)
291 time.Sleep(time.Second)
295 scanner := bufio.NewScanner(fd)
299 lines = append(lines, t)
306 func saveTorrent(t *torrent.Torrent) error {
307 pth := t.Name() + TorrentExt
308 if _, err := os.Stat(pth); err == nil {
312 t.Metainfo().Write(&b)
313 return os.WriteFile(pth, b.Bytes(), 0666)
316 func fifoAdd(c *torrent.Client) {
317 pth := path.Join(FIFOsDir, "add")
320 for _, what := range readLinesFromFIFO(pth) {
321 cols := strings.Fields(what)
323 var t *torrent.Torrent
325 if strings.HasPrefix(what, "magnet:") {
326 t, err = c.AddMagnet(what)
328 log.Println("AddMagnet:", what, err)
332 metaInfo, err := metainfo.LoadFromFile(what)
334 log.Println("LoadFromFile:", what, err)
337 t, err = c.AddTorrent(metaInfo)
339 log.Println("AddTorrent:", what, err)
344 t.AddPeers(resolveTestPeers(cols[1:]))
347 for _, h := range Torrents {
348 if h.HexString() == t.InfoHash().HexString() {
352 Torrents = append(Torrents, t.InfoHash())
357 log.Println("added:", t.InfoHash().HexString(), t.Name())
360 if err = saveTorrent(t); err != nil {
361 log.Println("saveTorrent:", err)
363 txStatsLoad(t.InfoHash())
367 time.Sleep(time.Second)
371 func fifoDel(c *torrent.Client) {
372 pth := path.Join(FIFOsDir, "del")
375 for _, what := range readLinesFromFIFO(pth) {
376 raw, err := hex.DecodeString(what)
381 if len(raw) != infohash.Size {
382 log.Println("bad length")
388 for n, h := range Torrents {
389 if h.HexString() == i.HexString() {
390 Torrents = append(Torrents[:n], Torrents[n+1:]...)
395 t, ok := c.Torrent(i)
397 log.Println("no such torrent", what)
401 txStatsDel(t.InfoHash())
403 for _, where := range []string{"files", "peers"} {
404 pth := path.Join(where, t.InfoHash().HexString())
406 fd, err := os.Open(pth)
411 log.Println("deleted:", what, t.Name())
413 time.Sleep(time.Second)
417 func fifosPrepare() {
418 os.MkdirAll(path.Join(FIFOsDir, PeersDir), 0777)
419 os.MkdirAll(path.Join(FIFOsDir, FilesDir), 0777)
422 func fifosCleanup() {
423 os.Remove(path.Join(FIFOsDir, "list"))
424 os.Remove(path.Join(FIFOsDir, "dht"))
425 os.Remove(path.Join(FIFOsDir, "add"))
426 os.Remove(path.Join(FIFOsDir, "del"))
427 os.Remove(path.Join(FIFOsDir, "top-seed"))
428 os.RemoveAll(path.Join(FIFOsDir, PeersDir))
429 os.RemoveAll(path.Join(FIFOsDir, FilesDir))