metainfo.go 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. package metainfo
  2. import (
  3. "bufio"
  4. "fmt"
  5. "io"
  6. "net/url"
  7. "os"
  8. "time"
  9. "github.com/anacrolix/torrent/bencode"
  10. infohash_v2 "github.com/anacrolix/torrent/types/infohash-v2"
  11. )
  12. // Also known as a torrent file.
  13. type MetaInfo struct {
  14. InfoBytes bencode.Bytes `bencode:"info,omitempty"` // BEP 3
  15. Announce string `bencode:"announce,omitempty"` // BEP 3
  16. AnnounceList AnnounceList `bencode:"announce-list,omitempty"` // BEP 12
  17. Nodes []Node `bencode:"nodes,omitempty,ignore_unmarshal_type_error"` // BEP 5
  18. // Where's this specified? Mentioned at
  19. // https://wiki.theory.org/index.php/BitTorrentSpecification: (optional) the creation time of
  20. // the torrent, in standard UNIX epoch format (integer, seconds since 1-Jan-1970 00:00:00 UTC)
  21. CreationDate int64 `bencode:"creation date,omitempty,ignore_unmarshal_type_error"`
  22. Comment string `bencode:"comment,omitempty"`
  23. CreatedBy string `bencode:"created by,omitempty"`
  24. Encoding string `bencode:"encoding,omitempty"`
  25. UrlList UrlList `bencode:"url-list,omitempty"` // BEP 19 WebSeeds
  26. // BEP 52 (BitTorrent v2): Keys are file merkle roots ("pieces root"s), and the values are the
  27. // concatenated hashes of the merkle tree layer that corresponds to the piece length.
  28. PieceLayers map[string]string `bencode:"piece layers,omitempty"`
  29. }
  30. // Load a MetaInfo from an io.Reader. Returns a non-nil error in case of failure.
  31. func Load(r io.Reader) (*MetaInfo, error) {
  32. var mi MetaInfo
  33. d := bencode.NewDecoder(r)
  34. err := d.Decode(&mi)
  35. if err != nil {
  36. return nil, err
  37. }
  38. err = d.ReadEOF()
  39. if err != nil {
  40. err = fmt.Errorf("error after decoding metainfo: %w", err)
  41. }
  42. return &mi, err
  43. }
  44. // Convenience function for loading a MetaInfo from a file.
  45. func LoadFromFile(filename string) (*MetaInfo, error) {
  46. f, err := os.Open(filename)
  47. if err != nil {
  48. return nil, err
  49. }
  50. defer f.Close()
  51. var buf bufio.Reader
  52. buf.Reset(f)
  53. return Load(&buf)
  54. }
  55. func (mi *MetaInfo) UnmarshalInfo() (info Info, err error) {
  56. err = bencode.Unmarshal(mi.InfoBytes, &info)
  57. return
  58. }
  59. func (mi *MetaInfo) HashInfoBytes() (infoHash Hash) {
  60. return HashBytes(mi.InfoBytes)
  61. }
  62. // Encode to bencoded form.
  63. func (mi *MetaInfo) Write(w io.Writer) error {
  64. return bencode.NewEncoder(w).Encode(mi)
  65. }
  66. // Set good default values in preparation for creating a new MetaInfo file.
  67. func (mi *MetaInfo) SetDefaults() {
  68. mi.CreatedBy = "github.com/anacrolix/torrent"
  69. mi.CreationDate = time.Now().Unix()
  70. }
  71. // Deprecated: Use MagnetV2. Creates a Magnet from a MetaInfo. Optional infohash and parsed info can
  72. // be provided.
  73. func (mi MetaInfo) Magnet(infoHash *Hash, info *Info) (m Magnet) {
  74. m.Trackers = append(m.Trackers, mi.UpvertedAnnounceList().DistinctValues()...)
  75. if info != nil {
  76. m.DisplayName = info.BestName()
  77. }
  78. if infoHash != nil {
  79. m.InfoHash = *infoHash
  80. } else {
  81. m.InfoHash = mi.HashInfoBytes()
  82. }
  83. m.Params = make(url.Values)
  84. m.Params["ws"] = mi.UrlList
  85. return
  86. }
  87. // Creates a MagnetV2 from a MetaInfo. This supports v1, hybrid, and v2 magnet links.
  88. func (mi *MetaInfo) MagnetV2() (m MagnetV2, err error) {
  89. m.Trackers = append(m.Trackers, mi.UpvertedAnnounceList().DistinctValues()...)
  90. info, err := mi.UnmarshalInfo()
  91. if err != nil {
  92. return
  93. }
  94. m.DisplayName = info.BestName()
  95. if info.HasV1() {
  96. m.InfoHash.Set(mi.HashInfoBytes())
  97. }
  98. if info.HasV2() {
  99. m.V2InfoHash.Set(infohash_v2.HashBytes(mi.InfoBytes))
  100. }
  101. m.Params = make(url.Values)
  102. m.Params["ws"] = mi.UrlList
  103. return
  104. }
  105. // Returns the announce-list converted from the old single announce field if necessary.
  106. func (mi *MetaInfo) UpvertedAnnounceList() AnnounceList {
  107. if mi.AnnounceList.OverridesAnnounce(mi.Announce) {
  108. return mi.AnnounceList
  109. }
  110. if mi.Announce != "" {
  111. return [][]string{{mi.Announce}}
  112. }
  113. return nil
  114. }