init
This commit is contained in:
1
vendor/github.com/anacrolix/torrent/metainfo/README
generated
vendored
Normal file
1
vendor/github.com/anacrolix/torrent/metainfo/README
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
A library for manipulating ".torrent" files.
|
27
vendor/github.com/anacrolix/torrent/metainfo/announcelist.go
generated
vendored
Normal file
27
vendor/github.com/anacrolix/torrent/metainfo/announcelist.go
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
package metainfo
|
||||
|
||||
type AnnounceList [][]string
|
||||
|
||||
// Whether the AnnounceList should be preferred over a single URL announce.
|
||||
func (al AnnounceList) OverridesAnnounce(announce string) bool {
|
||||
for _, tier := range al {
|
||||
for _, url := range tier {
|
||||
if url != "" || announce == "" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (al AnnounceList) DistinctValues() (ret map[string]struct{}) {
|
||||
for _, tier := range al {
|
||||
for _, v := range tier {
|
||||
if ret == nil {
|
||||
ret = make(map[string]struct{})
|
||||
}
|
||||
ret[v] = struct{}{}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
27
vendor/github.com/anacrolix/torrent/metainfo/fileinfo.go
generated
vendored
Normal file
27
vendor/github.com/anacrolix/torrent/metainfo/fileinfo.go
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
package metainfo
|
||||
|
||||
import "strings"
|
||||
|
||||
// Information specific to a single file inside the MetaInfo structure.
|
||||
type FileInfo struct {
|
||||
Length int64 `bencode:"length"`
|
||||
Path []string `bencode:"path"`
|
||||
}
|
||||
|
||||
func (fi *FileInfo) DisplayPath(info *Info) string {
|
||||
if info.IsDir() {
|
||||
return strings.Join(fi.Path, "/")
|
||||
} else {
|
||||
return info.Name
|
||||
}
|
||||
}
|
||||
|
||||
func (me FileInfo) Offset(info *Info) (ret int64) {
|
||||
for _, fi := range info.UpvertedFiles() {
|
||||
if me.DisplayPath(info) == fi.DisplayPath(info) {
|
||||
return
|
||||
}
|
||||
ret += fi.Length
|
||||
}
|
||||
panic("not found")
|
||||
}
|
58
vendor/github.com/anacrolix/torrent/metainfo/hash.go
generated
vendored
Normal file
58
vendor/github.com/anacrolix/torrent/metainfo/hash.go
generated
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
package metainfo
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const HashSize = 20
|
||||
|
||||
// 20-byte SHA1 hash used for info and pieces.
|
||||
type Hash [HashSize]byte
|
||||
|
||||
func (h Hash) Bytes() []byte {
|
||||
return h[:]
|
||||
}
|
||||
|
||||
func (h Hash) AsString() string {
|
||||
return string(h[:])
|
||||
}
|
||||
|
||||
func (h Hash) String() string {
|
||||
return h.HexString()
|
||||
}
|
||||
|
||||
func (h Hash) HexString() string {
|
||||
return fmt.Sprintf("%x", h[:])
|
||||
}
|
||||
|
||||
func (h *Hash) FromHexString(s string) (err error) {
|
||||
if len(s) != 2*HashSize {
|
||||
err = fmt.Errorf("hash hex string has bad length: %d", len(s))
|
||||
return
|
||||
}
|
||||
n, err := hex.Decode(h[:], []byte(s))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if n != HashSize {
|
||||
panic(n)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func NewHashFromHex(s string) (h Hash) {
|
||||
err := h.FromHexString(s)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func HashBytes(b []byte) (ret Hash) {
|
||||
hasher := sha1.New()
|
||||
hasher.Write(b)
|
||||
copy(ret[:], hasher.Sum(nil))
|
||||
return
|
||||
}
|
156
vendor/github.com/anacrolix/torrent/metainfo/info.go
generated
vendored
Normal file
156
vendor/github.com/anacrolix/torrent/metainfo/info.go
generated
vendored
Normal file
@@ -0,0 +1,156 @@
|
||||
package metainfo
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/anacrolix/missinggo/slices"
|
||||
)
|
||||
|
||||
// The info dictionary.
|
||||
type Info struct {
|
||||
PieceLength int64 `bencode:"piece length"`
|
||||
Pieces []byte `bencode:"pieces"`
|
||||
Name string `bencode:"name"`
|
||||
Length int64 `bencode:"length,omitempty"`
|
||||
Private *bool `bencode:"private,omitempty"`
|
||||
// TODO: Document this field.
|
||||
Source string `bencode:"source,omitempty"`
|
||||
Files []FileInfo `bencode:"files,omitempty"`
|
||||
}
|
||||
|
||||
// This is a helper that sets Files and Pieces from a root path and its
|
||||
// children.
|
||||
func (info *Info) BuildFromFilePath(root string) (err error) {
|
||||
info.Name = filepath.Base(root)
|
||||
info.Files = nil
|
||||
err = filepath.Walk(root, func(path string, fi os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if fi.IsDir() {
|
||||
// Directories are implicit in torrent files.
|
||||
return nil
|
||||
} else if path == root {
|
||||
// The root is a file.
|
||||
info.Length = fi.Size()
|
||||
return nil
|
||||
}
|
||||
relPath, err := filepath.Rel(root, path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting relative path: %s", err)
|
||||
}
|
||||
info.Files = append(info.Files, FileInfo{
|
||||
Path: strings.Split(relPath, string(filepath.Separator)),
|
||||
Length: fi.Size(),
|
||||
})
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
slices.Sort(info.Files, func(l, r FileInfo) bool {
|
||||
return strings.Join(l.Path, "/") < strings.Join(r.Path, "/")
|
||||
})
|
||||
err = info.GeneratePieces(func(fi FileInfo) (io.ReadCloser, error) {
|
||||
return os.Open(filepath.Join(root, strings.Join(fi.Path, string(filepath.Separator))))
|
||||
})
|
||||
if err != nil {
|
||||
err = fmt.Errorf("error generating pieces: %s", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Concatenates all the files in the torrent into w. open is a function that
|
||||
// gets at the contents of the given file.
|
||||
func (info *Info) writeFiles(w io.Writer, open func(fi FileInfo) (io.ReadCloser, error)) error {
|
||||
for _, fi := range info.UpvertedFiles() {
|
||||
r, err := open(fi)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error opening %v: %s", fi, err)
|
||||
}
|
||||
wn, err := io.CopyN(w, r, fi.Length)
|
||||
r.Close()
|
||||
if wn != fi.Length {
|
||||
return fmt.Errorf("error copying %v: %s", fi, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Sets Pieces (the block of piece hashes in the Info) by using the passed
|
||||
// function to get at the torrent data.
|
||||
func (info *Info) GeneratePieces(open func(fi FileInfo) (io.ReadCloser, error)) error {
|
||||
if info.PieceLength == 0 {
|
||||
return errors.New("piece length must be non-zero")
|
||||
}
|
||||
pr, pw := io.Pipe()
|
||||
go func() {
|
||||
err := info.writeFiles(pw, open)
|
||||
pw.CloseWithError(err)
|
||||
}()
|
||||
defer pr.Close()
|
||||
var pieces []byte
|
||||
for {
|
||||
hasher := sha1.New()
|
||||
wn, err := io.CopyN(hasher, pr, info.PieceLength)
|
||||
if err == io.EOF {
|
||||
err = nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if wn == 0 {
|
||||
break
|
||||
}
|
||||
pieces = hasher.Sum(pieces)
|
||||
if wn < info.PieceLength {
|
||||
break
|
||||
}
|
||||
}
|
||||
info.Pieces = pieces
|
||||
return nil
|
||||
}
|
||||
|
||||
func (info *Info) TotalLength() (ret int64) {
|
||||
if info.IsDir() {
|
||||
for _, fi := range info.Files {
|
||||
ret += fi.Length
|
||||
}
|
||||
} else {
|
||||
ret = info.Length
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (info *Info) NumPieces() int {
|
||||
return len(info.Pieces) / 20
|
||||
}
|
||||
|
||||
func (info *Info) IsDir() bool {
|
||||
return len(info.Files) != 0
|
||||
}
|
||||
|
||||
// The files field, converted up from the old single-file in the parent info
|
||||
// dict if necessary. This is a helper to avoid having to conditionally handle
|
||||
// single and multi-file torrent infos.
|
||||
func (info *Info) UpvertedFiles() []FileInfo {
|
||||
if len(info.Files) == 0 {
|
||||
return []FileInfo{{
|
||||
Length: info.Length,
|
||||
// Callers should determine that Info.Name is the basename, and
|
||||
// thus a regular file.
|
||||
Path: nil,
|
||||
}}
|
||||
}
|
||||
return info.Files
|
||||
}
|
||||
|
||||
func (info *Info) Piece(index int) Piece {
|
||||
return Piece{info, pieceIndex(index)}
|
||||
}
|
77
vendor/github.com/anacrolix/torrent/metainfo/magnet.go
generated
vendored
Normal file
77
vendor/github.com/anacrolix/torrent/metainfo/magnet.go
generated
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
package metainfo
|
||||
|
||||
import (
|
||||
"encoding/base32"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Magnet link components.
|
||||
type Magnet struct {
|
||||
InfoHash Hash
|
||||
Trackers []string
|
||||
DisplayName string
|
||||
}
|
||||
|
||||
const xtPrefix = "urn:btih:"
|
||||
|
||||
func (m Magnet) String() string {
|
||||
// net.URL likes to assume //, and encodes ':' on us, so we do most of
|
||||
// this manually.
|
||||
ret := "magnet:?xt="
|
||||
ret += xtPrefix + hex.EncodeToString(m.InfoHash[:])
|
||||
if m.DisplayName != "" {
|
||||
ret += "&dn=" + url.QueryEscape(m.DisplayName)
|
||||
}
|
||||
for _, tr := range m.Trackers {
|
||||
ret += "&tr=" + url.QueryEscape(tr)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// ParseMagnetURI parses Magnet-formatted URIs into a Magnet instance
|
||||
func ParseMagnetURI(uri string) (m Magnet, err error) {
|
||||
u, err := url.Parse(uri)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("error parsing uri: %s", err)
|
||||
return
|
||||
}
|
||||
if u.Scheme != "magnet" {
|
||||
err = fmt.Errorf("unexpected scheme: %q", u.Scheme)
|
||||
return
|
||||
}
|
||||
xt := u.Query().Get("xt")
|
||||
if !strings.HasPrefix(xt, xtPrefix) {
|
||||
err = fmt.Errorf("bad xt parameter")
|
||||
return
|
||||
}
|
||||
infoHash := xt[len(xtPrefix):]
|
||||
|
||||
// BTIH hash can be in HEX or BASE32 encoding
|
||||
// will assign appropriate func judging from symbol length
|
||||
var decode func(dst, src []byte) (int, error)
|
||||
switch len(infoHash) {
|
||||
case 40:
|
||||
decode = hex.Decode
|
||||
case 32:
|
||||
decode = base32.StdEncoding.Decode
|
||||
}
|
||||
|
||||
if decode == nil {
|
||||
err = fmt.Errorf("unhandled xt parameter encoding: encoded length %d", len(infoHash))
|
||||
return
|
||||
}
|
||||
n, err := decode(m.InfoHash[:], []byte(infoHash))
|
||||
if err != nil {
|
||||
err = fmt.Errorf("error decoding xt: %s", err)
|
||||
return
|
||||
}
|
||||
if n != 20 {
|
||||
panic(n)
|
||||
}
|
||||
m.DisplayName = u.Query().Get("dn")
|
||||
m.Trackers = u.Query()["tr"]
|
||||
return
|
||||
}
|
87
vendor/github.com/anacrolix/torrent/metainfo/metainfo.go
generated
vendored
Normal file
87
vendor/github.com/anacrolix/torrent/metainfo/metainfo.go
generated
vendored
Normal file
@@ -0,0 +1,87 @@
|
||||
package metainfo
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/anacrolix/torrent/bencode"
|
||||
)
|
||||
|
||||
type MetaInfo struct {
|
||||
InfoBytes bencode.Bytes `bencode:"info,omitempty"`
|
||||
Announce string `bencode:"announce,omitempty"`
|
||||
AnnounceList AnnounceList `bencode:"announce-list,omitempty"`
|
||||
Nodes []Node `bencode:"nodes,omitempty"`
|
||||
CreationDate int64 `bencode:"creation date,omitempty,ignore_unmarshal_type_error"`
|
||||
Comment string `bencode:"comment,omitempty"`
|
||||
CreatedBy string `bencode:"created by,omitempty"`
|
||||
Encoding string `bencode:"encoding,omitempty"`
|
||||
UrlList UrlList `bencode:"url-list,omitempty"`
|
||||
}
|
||||
|
||||
// Load a MetaInfo from an io.Reader. Returns a non-nil error in case of
|
||||
// failure.
|
||||
func Load(r io.Reader) (*MetaInfo, error) {
|
||||
var mi MetaInfo
|
||||
d := bencode.NewDecoder(r)
|
||||
err := d.Decode(&mi)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &mi, nil
|
||||
}
|
||||
|
||||
// Convenience function for loading a MetaInfo from a file.
|
||||
func LoadFromFile(filename string) (*MetaInfo, error) {
|
||||
f, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
return Load(f)
|
||||
}
|
||||
|
||||
func (mi MetaInfo) UnmarshalInfo() (info Info, err error) {
|
||||
err = bencode.Unmarshal(mi.InfoBytes, &info)
|
||||
return
|
||||
}
|
||||
|
||||
func (mi MetaInfo) HashInfoBytes() (infoHash Hash) {
|
||||
return HashBytes(mi.InfoBytes)
|
||||
}
|
||||
|
||||
// Encode to bencoded form.
|
||||
func (mi MetaInfo) Write(w io.Writer) error {
|
||||
return bencode.NewEncoder(w).Encode(mi)
|
||||
}
|
||||
|
||||
// Set good default values in preparation for creating a new MetaInfo file.
|
||||
func (mi *MetaInfo) SetDefaults() {
|
||||
mi.Comment = "yoloham"
|
||||
mi.CreatedBy = "github.com/anacrolix/torrent"
|
||||
mi.CreationDate = time.Now().Unix()
|
||||
// mi.Info.PieceLength = 256 * 1024
|
||||
}
|
||||
|
||||
// Creates a Magnet from a MetaInfo.
|
||||
func (mi *MetaInfo) Magnet(displayName string, infoHash Hash) (m Magnet) {
|
||||
for t := range mi.UpvertedAnnounceList().DistinctValues() {
|
||||
m.Trackers = append(m.Trackers, t)
|
||||
}
|
||||
m.DisplayName = displayName
|
||||
m.InfoHash = infoHash
|
||||
return
|
||||
}
|
||||
|
||||
// Returns the announce list converted from the old single announce field if
|
||||
// necessary.
|
||||
func (mi *MetaInfo) UpvertedAnnounceList() AnnounceList {
|
||||
if mi.AnnounceList.OverridesAnnounce(mi.Announce) {
|
||||
return mi.AnnounceList
|
||||
}
|
||||
if mi.Announce != "" {
|
||||
return [][]string{[]string{mi.Announce}}
|
||||
}
|
||||
return nil
|
||||
}
|
40
vendor/github.com/anacrolix/torrent/metainfo/nodes.go
generated
vendored
Normal file
40
vendor/github.com/anacrolix/torrent/metainfo/nodes.go
generated
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
package metainfo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
|
||||
"github.com/anacrolix/torrent/bencode"
|
||||
)
|
||||
|
||||
type Node string
|
||||
|
||||
var (
|
||||
_ bencode.Unmarshaler = new(Node)
|
||||
)
|
||||
|
||||
func (n *Node) UnmarshalBencode(b []byte) (err error) {
|
||||
var iface interface{}
|
||||
err = bencode.Unmarshal(b, &iface)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
switch v := iface.(type) {
|
||||
case string:
|
||||
*n = Node(v)
|
||||
case []interface{}:
|
||||
func() {
|
||||
defer func() {
|
||||
r := recover()
|
||||
if r != nil {
|
||||
err = r.(error)
|
||||
}
|
||||
}()
|
||||
*n = Node(net.JoinHostPort(v[0].(string), strconv.FormatInt(v[1].(int64), 10)))
|
||||
}()
|
||||
default:
|
||||
err = fmt.Errorf("unsupported type: %T", iface)
|
||||
}
|
||||
return
|
||||
}
|
32
vendor/github.com/anacrolix/torrent/metainfo/piece.go
generated
vendored
Normal file
32
vendor/github.com/anacrolix/torrent/metainfo/piece.go
generated
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
package metainfo
|
||||
|
||||
import (
|
||||
"github.com/anacrolix/missinggo"
|
||||
)
|
||||
|
||||
type Piece struct {
|
||||
Info *Info
|
||||
i pieceIndex
|
||||
}
|
||||
|
||||
type pieceIndex = int
|
||||
|
||||
func (p Piece) Length() int64 {
|
||||
if int(p.i) == p.Info.NumPieces()-1 {
|
||||
return p.Info.TotalLength() - int64(p.i)*p.Info.PieceLength
|
||||
}
|
||||
return p.Info.PieceLength
|
||||
}
|
||||
|
||||
func (p Piece) Offset() int64 {
|
||||
return int64(p.i) * p.Info.PieceLength
|
||||
}
|
||||
|
||||
func (p Piece) Hash() (ret Hash) {
|
||||
missinggo.CopyExact(&ret, p.Info.Pieces[p.i*HashSize:(p.i+1)*HashSize])
|
||||
return
|
||||
}
|
||||
|
||||
func (p Piece) Index() pieceIndex {
|
||||
return p.i
|
||||
}
|
7
vendor/github.com/anacrolix/torrent/metainfo/piece_key.go
generated
vendored
Normal file
7
vendor/github.com/anacrolix/torrent/metainfo/piece_key.go
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
package metainfo
|
||||
|
||||
// Uniquely identifies a piece.
|
||||
type PieceKey struct {
|
||||
InfoHash Hash
|
||||
Index pieceIndex
|
||||
}
|
27
vendor/github.com/anacrolix/torrent/metainfo/urllist.go
generated
vendored
Normal file
27
vendor/github.com/anacrolix/torrent/metainfo/urllist.go
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
package metainfo
|
||||
|
||||
import (
|
||||
"github.com/anacrolix/torrent/bencode"
|
||||
)
|
||||
|
||||
type UrlList []string
|
||||
|
||||
var (
|
||||
_ bencode.Unmarshaler = (*UrlList)(nil)
|
||||
)
|
||||
|
||||
func (me *UrlList) UnmarshalBencode(b []byte) error {
|
||||
if len(b) == 0 {
|
||||
return nil
|
||||
}
|
||||
if b[0] == 'l' {
|
||||
var l []string
|
||||
err := bencode.Unmarshal(b, &l)
|
||||
*me = l
|
||||
return err
|
||||
}
|
||||
var s string
|
||||
err := bencode.Unmarshal(b, &s)
|
||||
*me = []string{s}
|
||||
return err
|
||||
}
|
Reference in New Issue
Block a user