]> Sergey Matveev's repositories - btrtrc.git/blobdiff - metainfo/metainfo.go
Buffer metainfo loads from files
[btrtrc.git] / metainfo / metainfo.go
index 3b2a04a2ff43b20a68d360f9c79ebca692be2a61..93f9103b968b09f661cb3aa9a9a6618a8102cdb6 100644 (file)
@@ -1,25 +1,28 @@
 package metainfo
 
 import (
-       "crypto/sha1"
-       "errors"
-       "fmt"
+       "bufio"
        "io"
-       "log"
+       "net/url"
        "os"
-       "path/filepath"
-       "strings"
        "time"
 
-       "github.com/anacrolix/missinggo"
-
        "github.com/anacrolix/torrent/bencode"
 )
 
-// Information specific to a single file inside the MetaInfo structure.
-type FileInfo struct {
-       Length int64    `bencode:"length"`
-       Path   []string `bencode:"path"`
+type MetaInfo struct {
+       InfoBytes    bencode.Bytes `bencode:"info,omitempty"`                              // BEP 3
+       Announce     string        `bencode:"announce,omitempty"`                          // BEP 3
+       AnnounceList AnnounceList  `bencode:"announce-list,omitempty"`                     // BEP 12
+       Nodes        []Node        `bencode:"nodes,omitempty,ignore_unmarshal_type_error"` // BEP 5
+       // Where's this specified? Mentioned at
+       // https://wiki.theory.org/index.php/BitTorrentSpecification: (optional) the creation time of
+       // the torrent, in standard UNIX epoch format (integer, seconds since 1-Jan-1970 00:00:00 UTC)
+       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"` // BEP 19 WebSeeds
 }
 
 // Load a MetaInfo from an io.Reader. Returns a non-nil error in case of
@@ -41,209 +44,55 @@ func LoadFromFile(filename string) (*MetaInfo, error) {
                return nil, err
        }
        defer f.Close()
-       return Load(f)
-}
-
-// 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"`
-       Files       []FileInfo `bencode:"files,omitempty"`
-}
-
-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 {
-               log.Println(path, root, 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)
-               log.Println(relPath, err)
-               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
-       }
-       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
-}
-
-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 || err != nil {
-                       return fmt.Errorf("error hashing %v: %s", fi, err)
-               }
-       }
-       return nil
-}
-
-// Set info.Pieces by hashing info.Files.
-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
+       var buf bufio.Reader
+       buf.Reset(f)
+       return Load(&buf)
 }
 
-func (info *Info) TotalLength() (ret int64) {
-       if info.IsDir() {
-               for _, fi := range info.Files {
-                       ret += fi.Length
-               }
-       } else {
-               ret = info.Length
-       }
+func (mi MetaInfo) UnmarshalInfo() (info Info, err error) {
+       err = bencode.Unmarshal(mi.InfoBytes, &info)
        return
 }
 
-func (info *Info) NumPieces() int {
-       if len(info.Pieces)%20 != 0 {
-               panic(len(info.Pieces))
-       }
-       return len(info.Pieces) / 20
-}
-
-func (info *InfoEx) Piece(i int) Piece {
-       return Piece{info, i}
-}
-
-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
-}
-
-// The info dictionary with its hash and raw bytes exposed, as these are
-// important to Bittorrent.
-type InfoEx struct {
-       Info
-       Hash  *Hash
-       Bytes []byte
-}
-
-var (
-       _ bencode.Marshaler   = InfoEx{}
-       _ bencode.Unmarshaler = &InfoEx{}
-)
-
-func (ie *InfoEx) UnmarshalBencode(data []byte) error {
-       ie.Bytes = append(make([]byte, 0, len(data)), data...)
-       h := sha1.New()
-       _, err := h.Write(ie.Bytes)
-       if err != nil {
-               panic(err)
-       }
-       ie.Hash = new(Hash)
-       missinggo.CopyExact(ie.Hash, h.Sum(nil))
-       return bencode.Unmarshal(data, &ie.Info)
-}
-
-func (ie InfoEx) MarshalBencode() ([]byte, error) {
-       if ie.Bytes != nil {
-               return ie.Bytes, nil
-       }
-       return bencode.Marshal(&ie.Info)
-}
-
-type MetaInfo struct {
-       Info         InfoEx      `bencode:"info"`
-       Announce     string      `bencode:"announce,omitempty"`
-       AnnounceList [][]string  `bencode:"announce-list,omitempty"`
-       Nodes        []Node      `bencode:"nodes,omitempty"`
-       CreationDate int64       `bencode:"creation date,omitempty"`
-       Comment      string      `bencode:"comment,omitempty"`
-       CreatedBy    string      `bencode:"created by,omitempty"`
-       Encoding     string      `bencode:"encoding,omitempty"`
-       URLList      interface{} `bencode:"url-list,omitempty"`
+func (mi MetaInfo) HashInfoBytes() (infoHash Hash) {
+       return HashBytes(mi.InfoBytes)
 }
 
 // Encode to bencoded form.
-func (mi *MetaInfo) Write(w io.Writer) error {
+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
 }
 
-// Magnetize creates a Magnet from a MetaInfo
-func (mi *MetaInfo) Magnet() (m Magnet) {
-       for _, tier := range mi.AnnounceList {
-               for _, tracker := range tier {
-                       m.Trackers = append(m.Trackers, tracker)
-               }
+// Creates a Magnet from a MetaInfo. Optional infohash and parsed info can be provided.
+func (mi MetaInfo) Magnet(infoHash *Hash, info *Info) (m Magnet) {
+       m.Trackers = append(m.Trackers, mi.UpvertedAnnounceList().DistinctValues()...)
+       if info != nil {
+               m.DisplayName = info.BestName()
+       }
+       if infoHash != nil {
+               m.InfoHash = *infoHash
+       } else {
+               m.InfoHash = mi.HashInfoBytes()
        }
-       m.DisplayName = mi.Info.Name
-       m.InfoHash = *mi.Info.Hash
+       m.Params = make(url.Values)
+       m.Params["ws"] = mi.UrlList
        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{{mi.Announce}}
+       }
+       return nil
+}