From: Matt Joiner Date: Tue, 7 Sep 2021 02:08:31 +0000 (+1000) Subject: cmd/torrent: Use anacrolix/args and merge several other cmds in as subcommands X-Git-Tag: v1.32.0~53 X-Git-Url: http://www.git.stargrave.org/?a=commitdiff_plain;h=baddf0e5284d690e20bd8e71933ba1431640616f;p=btrtrc.git cmd/torrent: Use anacrolix/args and merge several other cmds in as subcommands --- diff --git a/README.md b/README.md index 4bda1b91..e821755e 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,8 @@ Here I'll describe what some of the packages in `./cmd` do. See [installation](# ### torrent +#### torrent download + Downloads torrents from the command-line. $ torrent download 'magnet:?xt=urn:btih:KRWPCX3SJUM4IMM4YF5RPHL6ANPYTQPU' @@ -66,6 +68,15 @@ Downloads torrents from the command-line. $ echo such amaze wow +#### torrent metainfo magnet + +Creates a magnet link from a torrent file. Note the extracted trackers, display name, and info hash. + + $ torrent metainfo testdata/debian-10.8.0-amd64-netinst.iso.torrent magnet + magnet:?xt=urn:btih:4090c3c2a394a49974dfbbf2ce7ad0db3cdeddd7&dn=debian-10.8.0-amd64-netinst.iso&tr=http%3A%2F%2Fbttracker.debian.org%3A6969%2Fannounce + +See `torrent metainfo --help` for other metainfo related commands. + ### torrentfs torrentfs mounts a FUSE filesystem at `-mountDir`. The contents are the torrents described by the torrent files and magnet links at `-metainfoDir`. Data for read requests is fetched only as required from the torrent network, and stored at `-downloadDir`. @@ -81,9 +92,3 @@ torrentfs mounts a FUSE filesystem at `-mountDir`. The contents are the torrents 996MB 0:04:40 [3.55MB/s] [========================================>] 100% 1b305d585b1918f297164add46784116 - -### torrent-magnet - -Creates a magnet link from a torrent file. Note the extracted trackers, display name, and info hash. - - $ torrent-magnet < torrents/ubuntu-14.04.2-desktop-amd64.iso.torrent - magnet:?xt=urn:btih:546cf15f724d19c4319cc17b179d7e035f89c1f4&dn=ubuntu-14.04.2-desktop-amd64.iso&tr=http%3A%2F%2Ftorrent.ubuntu.com%3A6969%2Fannounce&tr=http%3A%2F%2Fipv6.torrent.ubuntu.com%3A6969%2Fannounce diff --git a/cmd/torrent-infohash/main.go b/cmd/torrent-infohash/main.go deleted file mode 100644 index 81319c99..00000000 --- a/cmd/torrent-infohash/main.go +++ /dev/null @@ -1,25 +0,0 @@ -package main - -import ( - "fmt" - "log" - - "github.com/anacrolix/tagflag" - - "github.com/anacrolix/torrent/metainfo" -) - -func main() { - var args struct { - tagflag.StartPos - Files []string `arity:"+" type:"pos"` - } - tagflag.Parse(&args) - for _, arg := range args.Files { - mi, err := metainfo.LoadFromFile(arg) - if err != nil { - log.Fatal(err) - } - fmt.Printf("%s: %s\n", mi.HashInfoBytes().HexString(), arg) - } -} diff --git a/cmd/torrent-magnet/main.go b/cmd/torrent-magnet/main.go deleted file mode 100644 index 8f04a37d..00000000 --- a/cmd/torrent-magnet/main.go +++ /dev/null @@ -1,27 +0,0 @@ -package main - -import ( - "fmt" - "os" - - "github.com/anacrolix/tagflag" - - "github.com/anacrolix/torrent/metainfo" -) - -func main() { - tagflag.Parse(nil, tagflag.Description("reads a torrent file from stdin and writes out its magnet link to stdout")) - - mi, err := metainfo.Load(os.Stdin) - if err != nil { - fmt.Fprintf(os.Stderr, "error reading metainfo from stdin: %s", err) - os.Exit(1) - } - info, err := mi.UnmarshalInfo() - if err != nil { - fmt.Fprintf(os.Stderr, "error unmarshalling info: %s", err) - os.Exit(1) - } - - fmt.Fprintf(os.Stdout, "%s\n", mi.Magnet(nil, &info).String()) -} diff --git a/cmd/torrent-metainfo-pprint/main.go b/cmd/torrent-metainfo-pprint/main.go deleted file mode 100644 index cfecc224..00000000 --- a/cmd/torrent-metainfo-pprint/main.go +++ /dev/null @@ -1,78 +0,0 @@ -package main - -import ( - "bufio" - "encoding/hex" - "encoding/json" - "fmt" - "io" - "log" - "os" - - "github.com/anacrolix/envpprof" - "github.com/anacrolix/tagflag" - - "github.com/anacrolix/torrent/metainfo" -) - -var flags struct { - JustName bool - PieceHashes bool - Files bool - tagflag.StartPos -} - -func processReader(r io.Reader) error { - metainfo, err := metainfo.Load(r) - if err != nil { - return err - } - info, err := metainfo.UnmarshalInfo() - if err != nil { - return fmt.Errorf("error unmarshalling info: %s", err) - } - if flags.JustName { - fmt.Printf("%s\n", info.Name) - return nil - } - d := map[string]interface{}{ - "Name": info.Name, - "NumPieces": info.NumPieces(), - "PieceLength": info.PieceLength, - "InfoHash": metainfo.HashInfoBytes().HexString(), - "NumFiles": len(info.UpvertedFiles()), - "TotalLength": info.TotalLength(), - "Announce": metainfo.Announce, - "AnnounceList": metainfo.AnnounceList, - "UrlList": metainfo.UrlList, - } - if len(metainfo.Nodes) > 0 { - d["Nodes"] = metainfo.Nodes - } - if flags.Files { - d["Files"] = info.UpvertedFiles() - } - if flags.PieceHashes { - d["PieceHashes"] = func() (ret []string) { - for i, numPieces := 0, info.NumPieces(); i < numPieces; i += 1 { - ret = append(ret, hex.EncodeToString(info.Pieces[i*20:(i+1)*20])) - } - return - }() - } - b, _ := json.MarshalIndent(d, "", " ") - _, err = os.Stdout.Write(b) - return err -} - -func main() { - defer envpprof.Stop() - tagflag.Parse(&flags) - err := processReader(bufio.NewReader(os.Stdin)) - if err != nil { - log.Fatal(err) - } - if !flags.JustName { - os.Stdout.WriteString("\n") - } -} diff --git a/cmd/torrent/announce.go b/cmd/torrent/announce.go index 990688a2..5ba51be4 100644 --- a/cmd/torrent/announce.go +++ b/cmd/torrent/announce.go @@ -14,7 +14,7 @@ type AnnounceCmd struct { InfoHash torrent.InfoHash } -func announceErr() error { +func announceErr(flags AnnounceCmd) error { response, err := tracker.Announce{ TrackerUrl: flags.Tracker, Request: tracker.AnnounceRequest{ diff --git a/cmd/torrent/main.go b/cmd/torrent/main.go index 95b99e5c..624c2a4e 100644 --- a/cmd/torrent/main.go +++ b/cmd/torrent/main.go @@ -16,17 +16,15 @@ import ( "syscall" "time" - "github.com/alexflint/go-arg" + "github.com/anacrolix/args" "github.com/anacrolix/envpprof" + "github.com/anacrolix/log" "github.com/anacrolix/missinggo/v2" + "github.com/anacrolix/tagflag" "github.com/anacrolix/torrent/bencode" "github.com/anacrolix/torrent/version" "github.com/davecgh/go-spew/spew" "github.com/dustin/go-humanize" - - "github.com/anacrolix/log" - - "github.com/anacrolix/tagflag" "golang.org/x/time/rate" "github.com/anacrolix/torrent" @@ -85,7 +83,7 @@ func (stringAddr) Network() string { return "" } func (me stringAddr) String() string { return string(me) } func resolveTestPeers(addrs []string) (ret []torrent.PeerInfo) { - for _, ta := range flags.TestPeer { + for _, ta := range addrs { ret = append(ret, torrent.PeerInfo{ Addr: stringAddr(ta), }) @@ -93,7 +91,7 @@ func resolveTestPeers(addrs []string) (ret []torrent.PeerInfo) { return } -func addTorrents(client *torrent.Client) error { +func addTorrents(client *torrent.Client, flags downloadFlags) error { testPeers := resolveTestPeers(flags.TestPeer) for _, arg := range flags.Torrent { t, err := func() (*torrent.Torrent, error) { @@ -159,20 +157,11 @@ func addTorrents(client *torrent.Client) error { return nil } -var flags struct { +type downloadFlags struct { Debug bool - - *DownloadCmd `arg:"subcommand:download"` - *ListFilesCmd `arg:"subcommand:list-files"` - *SpewBencodingCmd `arg:"subcommand:spew-bencoding"` - *AnnounceCmd `arg:"subcommand:announce"` - *VersionCmd `arg:"subcommand:version"` + DownloadCmd } -type VersionCmd struct{} - -type SpewBencodingCmd struct{} - type DownloadCmd struct { Mmap bool `help:"memory-map torrent data"` TestPeer []string `help:"addresses of some starting peers"` @@ -202,17 +191,13 @@ type DownloadCmd struct { Torrent []string `arity:"+" help:"torrent file path or magnet uri" arg:"positional"` } -type ListFilesCmd struct { - TorrentPath string `arg:"positional"` -} - func stdoutAndStderrAreSameFile() bool { fi1, _ := os.Stdout.Stat() fi2, _ := os.Stderr.Stat() return os.SameFile(fi1, fi2) } -func statsEnabled() bool { +func statsEnabled(flags downloadFlags) bool { if flags.Stats == nil { return flags.Debug } @@ -238,53 +223,73 @@ func main() { func mainErr() error { stdLog.SetFlags(stdLog.Flags() | stdLog.Lshortfile) - p := arg.MustParse(&flags) - switch { - case flags.AnnounceCmd != nil: - return announceErr() - //case : - // return announceErr(flags.Args, parser) - case flags.DownloadCmd != nil: - return downloadErr() - case flags.ListFilesCmd != nil: - mi, err := metainfo.LoadFromFile(flags.ListFilesCmd.TorrentPath) - if err != nil { - return fmt.Errorf("loading from file %q: %v", flags.ListFilesCmd.TorrentPath, err) - } - info, err := mi.UnmarshalInfo() - if err != nil { - return fmt.Errorf("unmarshalling info from metainfo at %q: %v", flags.ListFilesCmd.TorrentPath, err) - } - for _, f := range info.UpvertedFiles() { - fmt.Println(f.DisplayPath(&info)) - } - return nil - case flags.SpewBencodingCmd != nil: - d := bencode.NewDecoder(os.Stdin) - for i := 0; ; i++ { - var v interface{} - err := d.Decode(&v) - if err == io.EOF { - break + debug := args.Flag(args.FlagOpt{Long: "debug"}) + p := args.ParseMain( + debug, + args.Subcommand("metainfo", metainfoCmd), + args.Subcommand("announce", func(p args.SubCmdCtx) error { + var cmd AnnounceCmd + err := p.NewParser().AddParams( + args.Pos("tracker", &cmd.Tracker), + args.Pos("infohash", &cmd.InfoHash)).Parse() + if err != nil { + return err } + return announceErr(cmd) + }), + args.Subcommand("download", func(p args.SubCmdCtx) error { + var dlf DownloadCmd + err := p.NewParser().AddParams( + append(args.FromStruct(&dlf), debug)..., + ).Parse() if err != nil { - return fmt.Errorf("decoding message index %d: %w", i, err) + return err } - spew.Dump(v) + return downloadErr(downloadFlags{ + Debug: debug.Bool(), + DownloadCmd: dlf, + }) + }), + args.Subcommand( + "spew-bencoding", + func(p args.SubCmdCtx) error { + d := bencode.NewDecoder(os.Stdin) + for i := 0; ; i++ { + var v interface{} + err := d.Decode(&v) + if err == io.EOF { + break + } + if err != nil { + return fmt.Errorf("decoding message index %d: %w", i, err) + } + spew.Dump(v) + } + return nil + }, + args.Help("reads bencoding from stdin into Go native types and spews the result"), + ), + args.Subcommand("version", func(p args.SubCmdCtx) error { + fmt.Printf("HTTP User-Agent: %q\n", version.DefaultHttpUserAgent) + fmt.Printf("Torrent client version: %q\n", version.DefaultExtendedHandshakeClientVersion) + fmt.Printf("Torrent version prefix: %q\n", version.DefaultBep20Prefix) + return nil + }), + ) + if p.Err != nil { + if errors.Is(p.Err, args.ErrHelped) { + return nil } - return nil - case flags.VersionCmd != nil: - fmt.Printf("HTTP User-Agent: %q\n", version.DefaultHttpUserAgent) - fmt.Printf("Torrent client version: %q\n", version.DefaultExtendedHandshakeClientVersion) - fmt.Printf("Torrent version prefix: %q\n", version.DefaultBep20Prefix) - return nil - default: - p.Fail(fmt.Sprintf("unexpected subcommand: %v", p.Subcommand())) - panic("unreachable") + return p.Err } + if !p.RanSubCmd { + p.PrintChoices(os.Stderr) + args.FatalUsage() + } + return nil } -func downloadErr() error { +func downloadErr(flags downloadFlags) error { clientConfig := torrent.NewDefaultClientConfig() clientConfig.DisableWebseeds = flags.DisableWebseeds clientConfig.DisableTCP = !flags.TcpPeers @@ -331,7 +336,7 @@ func downloadErr() error { client, err := torrent.NewClient(clientConfig) if err != nil { - return fmt.Errorf("creating client: %v", err) + return fmt.Errorf("creating client: %w", err) } var clientClose sync.Once //In certain situations, close was being called more than once. defer clientClose.Do(client.Close) @@ -346,11 +351,11 @@ func downloadErr() error { http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { client.WriteStatus(w) }) - err = addTorrents(client) + err = addTorrents(client, flags) if err != nil { return fmt.Errorf("adding torrents: %w", err) } - defer outputStats(client) + defer outputStats(client, flags) if client.WaitAll() { log.Print("downloaded ALL the torrents") } else { @@ -360,15 +365,15 @@ func downloadErr() error { if len(client.Torrents()) == 0 { log.Print("no torrents to seed") } else { - outputStats(client) + outputStats(client, flags) <-stop.C() } } return nil } -func outputStats(cl *torrent.Client) { - if !statsEnabled() { +func outputStats(cl *torrent.Client, args downloadFlags) { + if !statsEnabled(args) { return } expvar.Do(func(kv expvar.KeyValue) { diff --git a/cmd/torrent/metainfo.go b/cmd/torrent/metainfo.go new file mode 100644 index 00000000..720ac333 --- /dev/null +++ b/cmd/torrent/metainfo.go @@ -0,0 +1,105 @@ +package main + +import ( + "encoding/hex" + "encoding/json" + "fmt" + "os" + + "github.com/anacrolix/args" + "github.com/anacrolix/torrent/metainfo" + "github.com/bradfitz/iter" +) + +type pprintMetainfoFlags struct { + JustName bool + PieceHashes bool + Files bool +} + +func metainfoCmd(ctx args.SubCmdCtx) (err error) { + var metainfoPath string + var mi *metainfo.MetaInfo + return ctx.NewParser().AddParams( + args.Pos("torrent file", &metainfoPath, args.AfterParse(func() (err error) { + mi, err = metainfo.LoadFromFile(metainfoPath) + return + })), + args.Subcommand("magnet", func(ctx args.SubCmdCtx) (err error) { + info, err := mi.UnmarshalInfo() + if err != nil { + return + } + fmt.Fprintf(os.Stdout, "%s\n", mi.Magnet(nil, &info).String()) + return nil + }), + args.Subcommand("pprint", func(ctx args.SubCmdCtx) (err error) { + var flags pprintMetainfoFlags + err = ctx.NewParser().AddParams(args.FromStruct(&flags)...).Parse() + if err != nil { + return + } + err = pprintMetainfo(mi, flags) + if err != nil { + return + } + if !flags.JustName { + os.Stdout.WriteString("\n") + } + return + }), + args.Subcommand("infohash", func(ctx args.SubCmdCtx) (err error) { + fmt.Printf("%s: %s\n", mi.HashInfoBytes().HexString(), metainfoPath) + return nil + }), + args.Subcommand("list-files", func(ctx args.SubCmdCtx) (err error) { + info, err := mi.UnmarshalInfo() + if err != nil { + return fmt.Errorf("unmarshalling info from metainfo at %q: %v", metainfoPath, err) + } + for _, f := range info.UpvertedFiles() { + fmt.Println(f.DisplayPath(&info)) + } + return nil + }), + ).Parse() +} + +func pprintMetainfo(metainfo *metainfo.MetaInfo, flags pprintMetainfoFlags) error { + info, err := metainfo.UnmarshalInfo() + if err != nil { + return fmt.Errorf("error unmarshalling info: %s", err) + } + if flags.JustName { + fmt.Printf("%s\n", info.Name) + return nil + } + d := map[string]interface{}{ + "Name": info.Name, + "NumPieces": info.NumPieces(), + "PieceLength": info.PieceLength, + "InfoHash": metainfo.HashInfoBytes().HexString(), + "NumFiles": len(info.UpvertedFiles()), + "TotalLength": info.TotalLength(), + "Announce": metainfo.Announce, + "AnnounceList": metainfo.AnnounceList, + "UrlList": metainfo.UrlList, + } + if len(metainfo.Nodes) > 0 { + d["Nodes"] = metainfo.Nodes + } + if flags.Files { + d["Files"] = info.UpvertedFiles() + } + if flags.PieceHashes { + d["PieceHashes"] = func() (ret []string) { + for i := range iter.N(info.NumPieces()) { + ret = append(ret, hex.EncodeToString(info.Pieces[i*20:(i+1)*20])) + } + return + }() + } + b, _ := json.MarshalIndent(d, "", " ") + _, err = os.Stdout.Write(b) + return err +} diff --git a/cmd/tracker-announce/main.go b/cmd/tracker-announce/main.go deleted file mode 100644 index 511c783c..00000000 --- a/cmd/tracker-announce/main.go +++ /dev/null @@ -1,129 +0,0 @@ -package main - -import ( - "fmt" - "log" - "net/url" - "os" - "strings" - "sync" - "sync/atomic" - - "github.com/anacrolix/tagflag" - "github.com/anacrolix/torrent" - "github.com/anacrolix/torrent/metainfo" - "github.com/anacrolix/torrent/tracker" - "github.com/davecgh/go-spew/spew" -) - -func argSpec(arg string) (ts *torrent.TorrentSpec, _ error) { - if strings.HasPrefix(arg, "magnet:") { - return torrent.TorrentSpecFromMagnetUri(arg) - } - mi, fileErr := metainfo.LoadFromFile(arg) - if fileErr == nil { - ts = torrent.TorrentSpecFromMetaInfo(mi) - return - } - var ih torrent.InfoHash - ihErr := ih.FromHexString(arg) - if ihErr == nil { - ts = &torrent.TorrentSpec{ - InfoHash: ih, - } - return - } - if len(arg) == 40 { - return nil, ihErr - } else { - return nil, fileErr - } -} - -func main() { - flags := struct { - Port uint16 - Tracker []string - tagflag.StartPos - Torrents []string `arity:"+"` - }{ - Port: 50007, - } - tagflag.Parse(&flags) - var exitCode int32 - var wg sync.WaitGroup - startAnnounce := func(ih torrent.InfoHash, tURI string) { - ar := tracker.AnnounceRequest{ - NumWant: -1, - Left: -1, - Port: flags.Port, - InfoHash: ih, - } - wg.Add(1) - go func(tURI string) { - defer wg.Done() - if doTracker(tURI, ar) { - atomic.StoreInt32(&exitCode, 1) - } - }(tURI) - } - for _, arg := range flags.Torrents { - ts, err := argSpec(arg) - if err != nil { - log.Fatal(err) - } - for _, tier := range ts.Trackers { - for _, tURI := range tier { - startAnnounce(ts.InfoHash, tURI) - } - } - for _, tUri := range flags.Tracker { - startAnnounce(ts.InfoHash, tUri) - } - } - wg.Wait() - os.Exit(int(exitCode)) -} - -func doTracker(tURI string, ar tracker.AnnounceRequest) (hadError bool) { - for _, res := range announces(tURI, ar) { - err := res.error - resp := res.AnnounceResponse - if err != nil { - hadError = true - log.Printf("error announcing to %q: %s", tURI, err) - continue - } - fmt.Printf("from %q for %x:\n%s", tURI, ar.InfoHash, spew.Sdump(resp)) - } - return -} - -type announceResult struct { - tracker.AnnounceResponse - error -} - -func announces(uri string, ar tracker.AnnounceRequest) (ret []announceResult) { - u, err := url.Parse(uri) - if err != nil { - return []announceResult{{error: err}} - } - a := tracker.Announce{ - Request: ar, - TrackerUrl: uri, - } - if u.Scheme == "udp" { - a.UdpNetwork = "udp4" - ret = append(ret, announce(a)) - a.UdpNetwork = "udp6" - ret = append(ret, announce(a)) - return - } - return []announceResult{announce(a)} -} - -func announce(a tracker.Announce) announceResult { - resp, err := a.Do() - return announceResult{resp, err} -} diff --git a/go.mod b/go.mod index dcbe602c..843bee23 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ require ( crawshaw.io/sqlite v0.3.3-0.20210127221821-98b1f83c5508 github.com/RoaringBitmap/roaring v0.9.4 github.com/alexflint/go-arg v1.4.2 + github.com/anacrolix/args v0.1.0 github.com/anacrolix/chansync v0.2.1-0.20210910114620-14955c95ded9 github.com/anacrolix/confluence v1.8.0 // indirect github.com/anacrolix/dht/v2 v2.10.5-0.20210902001729-06cc4fe90e53 diff --git a/go.sum b/go.sum index 4dc4fcef..507f5040 100644 --- a/go.sum +++ b/go.sum @@ -44,8 +44,9 @@ github.com/alexflint/go-arg v1.4.2/go.mod h1:9iRbDxne7LcR/GSvEr7ma++GLpdIU1zrghf github.com/alexflint/go-scalar v1.0.0/go.mod h1:GpHzbCOZXEKMEcygYQ5n/aa4Aq84zbxjy3MxYW0gjYw= github.com/alexflint/go-scalar v1.1.0 h1:aaAouLLzI9TChcPXotr6gUhq+Scr8rl0P9P4PnltbhM= github.com/alexflint/go-scalar v1.1.0/go.mod h1:LoFvNMqS1CPrMVltza4LvnGKhaSpc3oyLEBUZVhhS2o= +github.com/anacrolix/args v0.1.0 h1:gcDj4DgjLhe+f311XqQ8eC6wYyKhZocxXUTeDvezAJ0= +github.com/anacrolix/args v0.1.0/go.mod h1:RCPBt2vU1GJn4gG9rL+fuYu7ivnE9tmK2pHm63t3yO0= github.com/anacrolix/chansync v0.0.0-20210524073341-a336ebc2de92/go.mod h1:DZsatdsdXxD0WiwcGl0nJVwyjCKMDv+knl1q2iBjA2k= -github.com/anacrolix/chansync v0.1.0 h1:4cIfJmEV8sYkSEMW2AXnPjX6iQT4plqELJ65pLna6OA= github.com/anacrolix/chansync v0.1.0/go.mod h1:DZsatdsdXxD0WiwcGl0nJVwyjCKMDv+knl1q2iBjA2k= github.com/anacrolix/chansync v0.1.1-0.20210904130811-9cd7139c8dd9 h1:Jk3Mdr+XbO1uvf/+nUXjb/M1dPDNPQThxKmS5MLGE+w= github.com/anacrolix/chansync v0.1.1-0.20210904130811-9cd7139c8dd9/go.mod h1:DZsatdsdXxD0WiwcGl0nJVwyjCKMDv+knl1q2iBjA2k=