18 "github.com/anacrolix/dht/v2"
19 "github.com/anacrolix/torrent"
20 "github.com/anacrolix/torrent/metainfo"
21 "github.com/anacrolix/torrent/storage"
22 "github.com/anacrolix/torrent/types/infohash"
23 "github.com/dustin/go-humanize"
32 type TorrentStat struct {
33 stats torrent.ConnStats
40 TorrentStats = map[metainfo.Hash]TorrentStat{}
41 TorrentStatsM sync.RWMutex
42 Torrents []metainfo.Hash
43 TorrentsM sync.RWMutex
46 func recreateFIFO(pth string) {
48 if err := syscall.Mkfifo(pth, 0666); err != nil {
53 func shortenName(name string) string {
55 if len(s) > MaxListNameWidth {
56 s = s[:MaxListNameWidth]
61 func fifoList(c *torrent.Client) {
62 pth := path.Join(FIFOsDir, "list")
65 fd, err := os.OpenFile(pth, os.O_WRONLY|os.O_APPEND, os.FileMode(0666))
67 log.Println("OpenFile:", pth, err)
68 time.Sleep(time.Second)
71 ts := make([]*torrent.Torrent, 0, len(Torrents))
73 for _, h := range Torrents {
75 if t == nil || t.Info() == nil {
76 fmt.Fprintf(fd, "%s not ready\n", t.Name())
82 for _, t := range ts {
84 done := t.BytesCompleted() * 100 / t.Length()
89 tx := stats.BytesWrittenData.Int64()
90 tx += TxStats[t.InfoHash()]
91 ratio := float64(tx) / float64(t.Length())
93 prev := TorrentStats[t.InfoHash()]
94 TorrentStatsM.RUnlock()
96 if done < 100 && prev.rxSpeed > 0 {
97 etaRaw := time.Duration((t.Length() - t.BytesCompleted()) / prev.rxSpeed)
102 "%s%s%s %s%40s%s %8s %s%3d%%%s %4.1f %s%d%s/%s%d%s %d/%d/%d/%d %s\n",
103 Blue, t.InfoHash().HexString(), Reset,
104 Green, shortenName(t.Name()), Reset,
105 humanize.IBytes(uint64(t.Length())),
106 percColour, done, Reset,
108 Green, prev.rxSpeed/1024, Reset,
109 Magenta, prev.txSpeed/1024, Reset,
113 stats.ConnectedSeeders,
118 time.Sleep(time.Second)
122 func mustParseInt(s string) int {
123 i, err := strconv.Atoi(s)
130 func fifoPeerList(t *torrent.Torrent) {
131 pth := path.Join(FIFOsDir, PeersDir, t.InfoHash().HexString())
134 fd, err := os.OpenFile(pth, os.O_WRONLY|os.O_APPEND, os.FileMode(0666))
136 if os.IsNotExist(err) {
139 log.Println("OpenFile:", pth, err)
140 time.Sleep(time.Second)
144 sort.Sort(ByPeerID(pcs))
145 for _, pc := range pcs {
146 cols := strings.Split(pc.CompletedString(), "/")
147 done := (mustParseInt(cols[0]) * 100) / mustParseInt(cols[1])
152 stats := pc.Peer.Stats()
154 "%s%s%s %10s %s%3d%%%s %s%d%s/%s%d%s | %s%s%s / %s%s%s | %s%s%s %q\n",
155 Blue, hex.EncodeToString(pc.PeerID[:]), Reset,
157 doneColour, done, Reset,
158 Green, int(pc.DownloadRate()/1024), Reset,
159 Magenta, int(pc.UploadRate()/1024), Reset,
160 Green, humanize.IBytes(uint64(stats.BytesReadData.Int64())), Reset,
161 Magenta, humanize.IBytes(uint64(stats.BytesWrittenData.Int64())), Reset,
162 Green, pc.RemoteAddr, Reset,
167 time.Sleep(time.Second)
171 func fifoFileList(t *torrent.Torrent) {
172 pth := path.Join(FIFOsDir, FilesDir, t.InfoHash().HexString())
175 fd, err := os.OpenFile(pth, os.O_WRONLY|os.O_APPEND, os.FileMode(0666))
177 if os.IsNotExist(err) {
180 log.Println("OpenFile:", pth, err)
181 time.Sleep(time.Second)
184 for n, f := range t.Files() {
187 done = (f.BytesCompleted() * 100) / f.Length()
194 "%5d %8s %3d%% | %s%s%s\n",
195 n, humanize.IBytes(uint64(f.Length())), done,
196 percColour, f.Path(), Reset,
200 time.Sleep(time.Second)
204 func fifoDHTList(c *torrent.Client) {
205 pth := path.Join(FIFOsDir, "dht")
208 fd, err := os.OpenFile(pth, os.O_WRONLY|os.O_APPEND, os.FileMode(0666))
210 if os.IsNotExist(err) {
213 log.Println("OpenFile:", pth, err)
214 time.Sleep(time.Second)
217 for _, s := range c.DhtServers() {
218 stats := s.Stats().(dht.ServerStats)
220 fd, "%s%s%s all:%d good:%d await:%d succ:%d bad:%d\n",
221 Green, s.Addr().String(), Reset,
224 stats.OutstandingTransactions,
225 stats.SuccessfulOutboundAnnouncePeerQueries,
230 time.Sleep(time.Second)
234 type topTorrent struct {
235 infoHash metainfo.Hash
241 func fifoTopSeed(c *torrent.Client) {
242 pth := path.Join(FIFOsDir, "top-seed")
245 fd, err := os.OpenFile(pth, os.O_WRONLY|os.O_APPEND, os.FileMode(0666))
247 log.Println("OpenFile:", pth, err)
248 time.Sleep(time.Second)
252 for _, t := range c.Torrents() {
258 infoHash: t.InfoHash(),
260 tx: stats.BytesWrittenData.Int64() + TxStats[t.InfoHash()],
262 top.ratio = float64(top.tx) / float64(t.Length())
263 ts = append(ts, &top)
265 sort.Sort(ByTxTraffic(ts))
266 for _, t := range ts {
268 "%s%s%s %s%40s%s %s%4.1f%s %s\n",
269 Blue, t.infoHash.HexString(), Reset,
270 Green, shortenName(t.name), Reset,
271 Magenta, t.ratio, Reset,
272 humanize.IBytes(uint64(t.tx)),
276 time.Sleep(time.Second)
280 type stringAddr string
282 func (stringAddr) Network() string { return "" }
283 func (me stringAddr) String() string { return string(me) }
285 func resolveTestPeers(addrs []string) (ret []torrent.PeerInfo) {
286 for _, ta := range addrs {
287 ret = append(ret, torrent.PeerInfo{Addr: stringAddr(ta)})
292 func readLinesFromFIFO(pth string) []string {
293 fd, err := os.OpenFile(pth, os.O_RDONLY, os.FileMode(0666))
295 log.Println("OpenFile:", pth, err)
296 time.Sleep(time.Second)
300 scanner := bufio.NewScanner(fd)
304 lines = append(lines, t)
311 func saveTorrent(t *torrent.Torrent) error {
312 pth := storage.PathShortener(t.Name()) + TorrentExt
313 if _, err := os.Stat(pth); err == nil {
317 t.Metainfo().Write(&b)
318 return os.WriteFile(pth, b.Bytes(), 0666)
321 func fifoAdd(c *torrent.Client) {
322 pth := path.Join(FIFOsDir, "add")
325 for _, what := range readLinesFromFIFO(pth) {
326 cols := strings.Fields(what)
328 var t *torrent.Torrent
330 if strings.HasPrefix(what, "magnet:") {
331 t, err = c.AddMagnet(what)
333 log.Println("AddMagnet:", what, err)
337 metaInfo, err := metainfo.LoadFromFile(what)
339 log.Println("LoadFromFile:", what, err)
342 t, err = c.AddTorrent(metaInfo)
344 log.Println("AddTorrent:", what, err)
349 t.AddPeers(resolveTestPeers(cols[1:]))
352 for _, h := range Torrents {
353 if h.HexString() == t.InfoHash().HexString() {
357 Torrents = append(Torrents, t.InfoHash())
362 log.Println("added:", t.InfoHash().HexString(), t.Name())
365 if err = saveTorrent(t); err != nil {
366 log.Println("saveTorrent:", err)
368 txStatsLoad(t.InfoHash())
372 time.Sleep(time.Second)
376 func fifoDel(c *torrent.Client) {
377 pth := path.Join(FIFOsDir, "del")
380 for _, what := range readLinesFromFIFO(pth) {
381 raw, err := hex.DecodeString(what)
386 if len(raw) != infohash.Size {
387 log.Println("bad length")
393 for n, h := range Torrents {
394 if h.HexString() == i.HexString() {
395 Torrents = append(Torrents[:n], Torrents[n+1:]...)
400 t, ok := c.Torrent(i)
402 log.Println("no such torrent", what)
406 txStatsDel(t.InfoHash())
408 for _, where := range []string{"files", "peers"} {
409 pth := path.Join(where, t.InfoHash().HexString())
411 fd, err := os.Open(pth)
416 log.Println("deleted:", what, t.Name())
418 time.Sleep(time.Second)
422 func fifosPrepare() {
423 os.MkdirAll(path.Join(FIFOsDir, PeersDir), 0777)
424 os.MkdirAll(path.Join(FIFOsDir, FilesDir), 0777)
427 func fifosCleanup() {
428 os.Remove(path.Join(FIFOsDir, "list"))
429 os.Remove(path.Join(FIFOsDir, "dht"))
430 os.Remove(path.Join(FIFOsDir, "add"))
431 os.Remove(path.Join(FIFOsDir, "del"))
432 os.Remove(path.Join(FIFOsDir, "top-seed"))
433 os.RemoveAll(path.Join(FIFOsDir, PeersDir))
434 os.RemoveAll(path.Join(FIFOsDir, FilesDir))