### torrent
+#### torrent download
+
Downloads torrents from the command-line.
$ torrent download 'magnet:?xt=urn:btih:KRWPCX3SJUM4IMM4YF5RPHL6ANPYTQPU'
$ 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`.
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
+++ /dev/null
-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)
- }
-}
+++ /dev/null
-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())
-}
+++ /dev/null
-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")
- }
-}
InfoHash torrent.InfoHash
}
-func announceErr() error {
+func announceErr(flags AnnounceCmd) error {
response, err := tracker.Announce{
TrackerUrl: flags.Tracker,
Request: tracker.AnnounceRequest{
"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"
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),
})
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) {
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"`
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
}
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
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)
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 {
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) {
--- /dev/null
+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
+}
+++ /dev/null
-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}
-}
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
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=