file-tree.go 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. package metainfo
  2. import (
  3. "sort"
  4. g "github.com/anacrolix/generics"
  5. "golang.org/x/exp/maps"
  6. "github.com/anacrolix/torrent/bencode"
  7. )
  8. const FileTreePropertiesKey = ""
  9. type FileTreeFile struct {
  10. Length int64 `bencode:"length"`
  11. PiecesRoot string `bencode:"pieces root"`
  12. }
  13. // The fields here don't need bencode tags as the marshalling is done manually.
  14. type FileTree struct {
  15. File FileTreeFile
  16. Dir map[string]FileTree
  17. }
  18. func (ft *FileTree) UnmarshalBencode(bytes []byte) (err error) {
  19. var dir map[string]bencode.Bytes
  20. err = bencode.Unmarshal(bytes, &dir)
  21. if err != nil {
  22. return
  23. }
  24. if propBytes, ok := dir[""]; ok {
  25. err = bencode.Unmarshal(propBytes, &ft.File)
  26. if err != nil {
  27. return
  28. }
  29. }
  30. delete(dir, "")
  31. g.MakeMapWithCap(&ft.Dir, len(dir))
  32. for key, bytes := range dir {
  33. var sub FileTree
  34. err = sub.UnmarshalBencode(bytes)
  35. if err != nil {
  36. return
  37. }
  38. ft.Dir[key] = sub
  39. }
  40. return
  41. }
  42. var _ bencode.Unmarshaler = (*FileTree)(nil)
  43. func (ft *FileTree) MarshalBencode() (bytes []byte, err error) {
  44. if ft.IsDir() {
  45. dir := make(map[string]bencode.Bytes, len(ft.Dir))
  46. for _, key := range ft.orderedKeys() {
  47. if key == FileTreePropertiesKey {
  48. continue
  49. }
  50. sub := g.MapMustGet(ft.Dir, key)
  51. subBytes, err := sub.MarshalBencode()
  52. if err != nil {
  53. return nil, err
  54. }
  55. dir[key] = subBytes
  56. }
  57. return bencode.Marshal(dir)
  58. } else {
  59. fileBytes, err := bencode.Marshal(ft.File)
  60. if err != nil {
  61. return nil, err
  62. }
  63. res := map[string]bencode.Bytes{
  64. "": fileBytes,
  65. }
  66. return bencode.Marshal(res)
  67. }
  68. }
  69. var _ bencode.Marshaler = (*FileTree)(nil)
  70. func (ft *FileTree) NumEntries() (num int) {
  71. num = len(ft.Dir)
  72. if g.MapContains(ft.Dir, FileTreePropertiesKey) {
  73. num--
  74. }
  75. return
  76. }
  77. func (ft *FileTree) IsDir() bool {
  78. return ft.NumEntries() != 0
  79. }
  80. func (ft *FileTree) orderedKeys() []string {
  81. keys := maps.Keys(ft.Dir)
  82. sort.Strings(keys)
  83. return keys
  84. }
  85. func (ft *FileTree) upvertedFiles(pieceLength int64, out func(fi FileInfo)) {
  86. var offset int64
  87. ft.upvertedFilesInner(pieceLength, nil, &offset, out)
  88. }
  89. func (ft *FileTree) upvertedFilesInner(
  90. pieceLength int64,
  91. path []string,
  92. offset *int64,
  93. out func(fi FileInfo),
  94. ) {
  95. if ft.IsDir() {
  96. for _, key := range ft.orderedKeys() {
  97. if key == FileTreePropertiesKey {
  98. continue
  99. }
  100. sub := g.MapMustGet(ft.Dir, key)
  101. sub.upvertedFilesInner(pieceLength, append(path, key), offset, out)
  102. }
  103. } else {
  104. out(FileInfo{
  105. Length: ft.File.Length,
  106. Path: append([]string(nil), path...),
  107. // BEP 52 requires paths be UTF-8 if possible.
  108. PathUtf8: append([]string(nil), path...),
  109. PiecesRoot: ft.PiecesRootAsByteArray(),
  110. TorrentOffset: *offset,
  111. })
  112. *offset += (ft.File.Length + pieceLength - 1) / pieceLength * pieceLength
  113. }
  114. }
  115. func (ft *FileTree) Walk(path []string, f func(path []string, ft *FileTree)) {
  116. f(path, ft)
  117. for key, sub := range ft.Dir {
  118. if key == FileTreePropertiesKey {
  119. continue
  120. }
  121. sub.Walk(append(path, key), f)
  122. }
  123. }
  124. func (ft *FileTree) PiecesRootAsByteArray() (ret g.Option[[32]byte]) {
  125. if ft.File.PiecesRoot == "" {
  126. return
  127. }
  128. n := copy(ret.Value[:], ft.File.PiecesRoot)
  129. if n != 32 {
  130. // Must be 32 bytes for meta version 2 and non-empty files. See BEP 52.
  131. panic(n)
  132. }
  133. ret.Ok = true
  134. return
  135. }