1 // Downloads torrents from the command-line.
17 _ "github.com/anacrolix/envpprof"
18 "github.com/anacrolix/torrent"
19 "github.com/anacrolix/torrent/metainfo"
20 "github.com/dustin/go-humanize"
21 "github.com/jessevdk/go-flags"
24 // fmt.Fprintf(os.Stderr, "Usage: %s \n", os.Args[0])
26 func resolvedPeerAddrs(ss []string) (ret []torrent.Peer, err error) {
27 for _, s := range ss {
29 addr, err = net.ResolveTCPAddr("tcp", s)
33 ret = append(ret, torrent.Peer{
41 func bytesCompleted(tc *torrent.Client) (ret int64) {
42 for _, t := range tc.Torrents() {
44 ret += t.BytesCompleted()
50 // Returns an estimate of the total bytes for all torrents.
51 func totalBytesEstimate(tc *torrent.Client) (ret int64) {
52 var noInfo, hadInfo int64
53 for _, t := range tc.Torrents() {
59 ret += info.TotalLength()
63 // Treat each torrent without info as the average of those with,
65 ret += (noInfo*ret + hadInfo - 1) / hadInfo
70 func progressLine(tc *torrent.Client) string {
71 return fmt.Sprintf("\033[K%s / %s\r", humanize.Bytes(uint64(bytesCompleted(tc))), humanize.Bytes(uint64(totalBytesEstimate(tc))))
74 func dstFileName(picked string) string {
75 parts := strings.Split(picked, "/")
76 return parts[len(parts)-1]
80 log.SetFlags(log.LstdFlags | log.Lshortfile)
81 var rootGroup = struct {
82 Client *torrent.ClientConfig `group:"Client Options"`
83 TestPeers []string `long:"test-peer" description:"address of peer to inject to every torrent"`
84 Pick string `long:"pick" description:"filename to pick"`
86 Client: torrent.NewDefaultClientConfig(),
88 // Don't pass flags.PrintError because it's inconsistent with printing.
89 // https://github.com/jessevdk/go-flags/issues/132
90 parser := flags.NewParser(&rootGroup, flags.HelpFlag|flags.PassDoubleDash)
91 parser.Usage = "[OPTIONS] (magnet URI or .torrent file path)..."
92 posArgs, err := parser.Parse()
94 fmt.Fprintf(os.Stderr, "%s", "Download from the BitTorrent network.\n\n")
98 log.Printf("File to pick: %s", rootGroup.Pick)
100 testPeers, err := resolvedPeerAddrs(rootGroup.TestPeers)
105 if len(posArgs) == 0 {
106 fmt.Fprintln(os.Stderr, "no torrents specified")
110 tmpdir, err := ioutil.TempDir("", "torrent-pick-")
115 defer os.RemoveAll(tmpdir)
117 rootGroup.Client.DataDir = tmpdir
119 client, err := torrent.NewClient(rootGroup.Client)
121 log.Fatalf("error creating client: %s", err)
123 http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
124 client.WriteStatus(w)
128 dstName := dstFileName(rootGroup.Pick)
130 f, err := os.Create(dstName)
134 dstWriter := bufio.NewWriter(f)
136 done := make(chan struct{})
137 for _, arg := range posArgs {
138 t := func() *torrent.Torrent {
139 if strings.HasPrefix(arg, "magnet:") {
140 t, err := client.AddMagnet(arg)
142 log.Fatalf("error adding magnet: %s", err)
146 metaInfo, err := metainfo.LoadFromFile(arg)
150 t, err := client.AddTorrent(metaInfo)
157 t.AddPeers(testPeers)
162 for _, file := range t.Files() {
163 if file.DisplayPath() != rootGroup.Pick {
167 srcReader := file.NewReader()
168 defer srcReader.Close()
169 io.Copy(dstWriter, srcReader)
172 log.Print("file not found")
176 ticker := time.NewTicker(time.Second)
183 os.Stdout.WriteString(progressLine(client))
186 if rootGroup.Client.Seed {