client.go | 9 +++++++++ cmd/torrentfs/main.go | 9 ++++++--- connection.go | 43 +++++++++++++++++++++++++++++++++++++++++++ torrent.go | 36 ++++++++++++++++++++++++++++++++++++ diff --git a/client.go b/client.go index fc4d48ff84bab1cbec02448f94b9a286516209a7..f99d6af164f3dfe2fb9a137f9e4b3a84fb482cb5 100644 --- a/client.go +++ b/client.go @@ -109,6 +109,15 @@ torrents map[InfoHash]*torrent dataWaiter chan struct{} } +func (cl *Client) WriteStatus(w io.Writer) { + cl.mu.Lock() + defer cl.mu.Unlock() + for _, t := range cl.torrents { + fmt.Fprintf(w, "%s: %f%%\n", t.MetaInfo.Name, 100*(1-float32(t.BytesLeft())/float32(t.Length()))) + t.WriteStatus(w) + } +} + // Read torrent data at the given offset. Returns ErrDataNotReady if the data // isn't available. func (cl *Client) TorrentReadAt(ih InfoHash, off int64, p []byte) (n int, err error) { diff --git a/cmd/torrentfs/main.go b/cmd/torrentfs/main.go index 3fe50bcd02048578e17253c7818e92459cb6ac46..97f916b315d464a7b0de829d2493e1486d932239 100644 --- a/cmd/torrentfs/main.go +++ b/cmd/torrentfs/main.go @@ -26,7 +26,7 @@ torrentPath string mountDir string disableTrackers = flag.Bool("disableTrackers", false, "disables trackers") testPeer = flag.String("testPeer", "", "the address for a test peer") - pprofAddr = flag.String("pprofAddr", "", "pprof HTTP server bind address") + httpAddr = flag.String("httpAddr", "", "HTTP server bind address") readaheadBytes = flag.Int("readaheadBytes", 10*1024*1024, "bytes to readahead in each torrent from the last read piece") testPeerAddr *net.TCPAddr ) @@ -120,8 +120,8 @@ os.Stderr.WriteString("y u no specify mountpoint?\n") os.Exit(2) } log.SetFlags(log.LstdFlags | log.Lshortfile) - if *pprofAddr != "" { - go http.ListenAndServe(*pprofAddr, nil) + if *httpAddr != "" { + go http.ListenAndServe(*httpAddr, nil) } conn, err := fuse.Mount(mountDir) if err != nil { @@ -136,6 +136,9 @@ DataDir: downloadDir, DisableTrackers: *disableTrackers, DownloadStrategy: &torrent.ResponsiveDownloadStrategy{*readaheadBytes}, } + http.DefaultServeMux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { + client.WriteStatus(w) + }) client.Start() addTorrentDir(client, torrentPath) resolveTestPeerAddr() diff --git a/connection.go b/connection.go index df774451888f8981a5d56a51fdf97eee41d3b250..a0e45ec36fe22dcef0ede2a2017a56f7a0d66140 100644 --- a/connection.go +++ b/connection.go @@ -3,6 +3,8 @@ import ( "container/list" "encoding" + "fmt" + "io" "log" "net" "sync" @@ -32,6 +34,47 @@ PeerRequests map[request]struct{} PeerExtensions [8]byte PeerPieces []bool PeerMaxRequests int // Maximum pending requests the peer allows. +} + +func (cn *connection) completedString() string { + if cn.PeerPieces == nil { + return "?" + } + f := float32(cn.piecesPeerHasCount()) / float32(cn.totalPiecesCount()) + return fmt.Sprintf("%d%%", int(f*100)) +} + +func (cn *connection) totalPiecesCount() int { + return len(cn.PeerPieces) +} + +func (cn *connection) piecesPeerHasCount() (count int) { + for _, has := range cn.PeerPieces { + if has { + count++ + } + } + return +} + +func (cn *connection) WriteStatus(w io.Writer) { + fmt.Fprintf(w, "%q: %s-%s: %s completed: ", cn.PeerId, cn.Socket.LocalAddr(), cn.Socket.RemoteAddr(), cn.completedString()) + c := func(b byte) { + fmt.Fprintf(w, "%c", b) + } + // https://trac.transmissionbt.com/wiki/PeerStatusText + if len(cn.Requests) != 0 { + c('D') + } else if cn.Interested { + c('d') + } + if !cn.PeerChoked && !cn.Interested { + c('K') + } + if !cn.Choked && !cn.PeerInterested { + c('?') + } + fmt.Fprintln(w) } func (c *connection) Close() { diff --git a/torrent.go b/torrent.go index cbb4cd3221cf9494eb158f57dfbb9fa70e6d067c..1e3cf91bbb79e419dfa02e3d2e017fc603bf234f 100644 --- a/torrent.go +++ b/torrent.go @@ -3,6 +3,7 @@ import ( "container/list" "fmt" + "io" "net" "sort" @@ -38,6 +39,37 @@ Trackers [][]tracker.Client lastReadPiece int } +func (t *torrent) pieceStatusChar(index int) byte { + p := t.Pieces[index] + switch { + case p.Complete(): + return 'C' + case p.QueuedForHash: + return 'Q' + case p.Hashing: + return 'H' + case t.PiecePartiallyDownloaded(index): + return 'P' + default: + return '.' + } +} + +func (t *torrent) WriteStatus(w io.Writer) { + fmt.Fprint(w, "Pieces: ") + for index := range t.Pieces { + fmt.Fprintf(w, "%c", t.pieceStatusChar(index)) + } + fmt.Fprintln(w) + fmt.Fprintln(w, "Priorities: ") + for e := t.Priorities.Front(); e != nil; e = e.Next() { + fmt.Fprintf(w, "\t%v\n", e.Value) + } + for _, c := range t.Conns { + c.WriteStatus(w) + } +} + func (t *torrent) String() string { return t.MetaInfo.Name } @@ -47,6 +79,10 @@ for i := pp.Integer(0); i < pp.Integer(t.NumPieces()); i++ { left += int64(t.PieceNumPendingBytes(i)) } return +} + +func (t *torrent) PiecePartiallyDownloaded(index int) bool { + return t.PieceNumPendingBytes(pp.Integer(index)) != t.PieceLength(pp.Integer(index)) } func NumChunksForPiece(chunkSize int, pieceSize int) int {