// Downloads torrents from the command-line. package main import ( "bufio" "fmt" "io" "log" "net" "net/http" _ "net/http/pprof" "os" "strings" "time" _ "github.com/anacrolix/envpprof" "github.com/dustin/go-humanize" "github.com/jessevdk/go-flags" "github.com/anacrolix/torrent" "github.com/anacrolix/torrent/metainfo" ) // fmt.Fprintf(os.Stderr, "Usage: %s \n", os.Args[0]) func resolvedPeerAddrs(ss []string) (ret []torrent.PeerInfo, err error) { for _, s := range ss { var addr *net.TCPAddr addr, err = net.ResolveTCPAddr("tcp", s) if err != nil { return } ret = append(ret, torrent.PeerInfo{ Addr: addr, }) } return } func bytesCompleted(tc *torrent.Client) (ret int64) { for _, t := range tc.Torrents() { if t.Info() != nil { ret += t.BytesCompleted() } } return } // Returns an estimate of the total bytes for all torrents. func totalBytesEstimate(tc *torrent.Client) (ret int64) { var noInfo, hadInfo int64 for _, t := range tc.Torrents() { info := t.Info() if info == nil { noInfo++ continue } ret += info.TotalLength() hadInfo++ } if hadInfo != 0 { // Treat each torrent without info as the average of those with, // rounded up. ret += (noInfo*ret + hadInfo - 1) / hadInfo } return } func progressLine(tc *torrent.Client) string { return fmt.Sprintf("\033[K%s / %s\r", humanize.Bytes(uint64(bytesCompleted(tc))), humanize.Bytes(uint64(totalBytesEstimate(tc)))) } func dstFileName(picked string) string { parts := strings.Split(picked, "/") return parts[len(parts)-1] } func main() { log.SetFlags(log.LstdFlags | log.Lshortfile) rootGroup := struct { Client *torrent.ClientConfig `group:"Client Options"` TestPeers []string `long:"test-peer" description:"address of peer to inject to every torrent"` Pick string `long:"pick" description:"filename to pick"` }{ Client: torrent.NewDefaultClientConfig(), } // Don't pass flags.PrintError because it's inconsistent with printing. // https://github.com/jessevdk/go-flags/issues/132 parser := flags.NewParser(&rootGroup, flags.HelpFlag|flags.PassDoubleDash) parser.Usage = "[OPTIONS] (magnet URI or .torrent file path)..." posArgs, err := parser.Parse() if err != nil { fmt.Fprintf(os.Stderr, "%s", "Download from the BitTorrent network.\n\n") fmt.Println(err) os.Exit(2) } log.Printf("File to pick: %s", rootGroup.Pick) testPeers, err := resolvedPeerAddrs(rootGroup.TestPeers) if err != nil { log.Fatal(err) } if len(posArgs) == 0 { fmt.Fprintln(os.Stderr, "no torrents specified") return } tmpdir, err := os.MkdirTemp("", "torrent-pick-") if err != nil { log.Fatal(err) } defer os.RemoveAll(tmpdir) rootGroup.Client.DataDir = tmpdir client, err := torrent.NewClient(rootGroup.Client) if err != nil { log.Fatalf("error creating client: %s", err) } http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { client.WriteStatus(w) }) defer client.Close() dstName := dstFileName(rootGroup.Pick) f, err := os.Create(dstName) if err != nil { log.Fatal(err) } dstWriter := bufio.NewWriter(f) done := make(chan struct{}) for _, arg := range posArgs { t := func() *torrent.Torrent { if strings.HasPrefix(arg, "magnet:") { t, err := client.AddMagnet(arg) if err != nil { log.Fatalf("error adding magnet: %s", err) } return t } else { metaInfo, err := metainfo.LoadFromFile(arg) if err != nil { log.Fatal(err) } t, err := client.AddTorrent(metaInfo) if err != nil { log.Fatal(err) } return t } }() t.AddPeers(testPeers) go func() { defer close(done) <-t.GotInfo() for _, file := range t.Files() { if file.DisplayPath() != rootGroup.Pick { continue } file.Download() srcReader := file.NewReader() defer srcReader.Close() io.Copy(dstWriter, srcReader) return } log.Print("file not found") }() } ticker := time.NewTicker(time.Second) defer ticker.Stop() waitDone: for { select { case <-done: break waitDone case <-ticker.C: os.Stdout.WriteString(progressLine(client)) } } if rootGroup.Client.Seed { select {} } }