| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152 |
- package metainfo
- import (
- "sort"
- g "github.com/anacrolix/generics"
- "golang.org/x/exp/maps"
- "github.com/anacrolix/torrent/bencode"
- )
- const FileTreePropertiesKey = ""
- type FileTreeFile struct {
- Length int64 `bencode:"length"`
- PiecesRoot string `bencode:"pieces root"`
- }
- // The fields here don't need bencode tags as the marshalling is done manually.
- type FileTree struct {
- File FileTreeFile
- Dir map[string]FileTree
- }
- func (ft *FileTree) UnmarshalBencode(bytes []byte) (err error) {
- var dir map[string]bencode.Bytes
- err = bencode.Unmarshal(bytes, &dir)
- if err != nil {
- return
- }
- if propBytes, ok := dir[""]; ok {
- err = bencode.Unmarshal(propBytes, &ft.File)
- if err != nil {
- return
- }
- }
- delete(dir, "")
- g.MakeMapWithCap(&ft.Dir, len(dir))
- for key, bytes := range dir {
- var sub FileTree
- err = sub.UnmarshalBencode(bytes)
- if err != nil {
- return
- }
- ft.Dir[key] = sub
- }
- return
- }
- var _ bencode.Unmarshaler = (*FileTree)(nil)
- func (ft *FileTree) MarshalBencode() (bytes []byte, err error) {
- if ft.IsDir() {
- dir := make(map[string]bencode.Bytes, len(ft.Dir))
- for _, key := range ft.orderedKeys() {
- if key == FileTreePropertiesKey {
- continue
- }
- sub := g.MapMustGet(ft.Dir, key)
- subBytes, err := sub.MarshalBencode()
- if err != nil {
- return nil, err
- }
- dir[key] = subBytes
- }
- return bencode.Marshal(dir)
- } else {
- fileBytes, err := bencode.Marshal(ft.File)
- if err != nil {
- return nil, err
- }
- res := map[string]bencode.Bytes{
- "": fileBytes,
- }
- return bencode.Marshal(res)
- }
- }
- var _ bencode.Marshaler = (*FileTree)(nil)
- func (ft *FileTree) NumEntries() (num int) {
- num = len(ft.Dir)
- if g.MapContains(ft.Dir, FileTreePropertiesKey) {
- num--
- }
- return
- }
- func (ft *FileTree) IsDir() bool {
- return ft.NumEntries() != 0
- }
- func (ft *FileTree) orderedKeys() []string {
- keys := maps.Keys(ft.Dir)
- sort.Strings(keys)
- return keys
- }
- func (ft *FileTree) upvertedFiles(pieceLength int64, out func(fi FileInfo)) {
- var offset int64
- ft.upvertedFilesInner(pieceLength, nil, &offset, out)
- }
- func (ft *FileTree) upvertedFilesInner(
- pieceLength int64,
- path []string,
- offset *int64,
- out func(fi FileInfo),
- ) {
- if ft.IsDir() {
- for _, key := range ft.orderedKeys() {
- if key == FileTreePropertiesKey {
- continue
- }
- sub := g.MapMustGet(ft.Dir, key)
- sub.upvertedFilesInner(pieceLength, append(path, key), offset, out)
- }
- } else {
- out(FileInfo{
- Length: ft.File.Length,
- Path: append([]string(nil), path...),
- // BEP 52 requires paths be UTF-8 if possible.
- PathUtf8: append([]string(nil), path...),
- PiecesRoot: ft.PiecesRootAsByteArray(),
- TorrentOffset: *offset,
- })
- *offset += (ft.File.Length + pieceLength - 1) / pieceLength * pieceLength
- }
- }
- func (ft *FileTree) Walk(path []string, f func(path []string, ft *FileTree)) {
- f(path, ft)
- for key, sub := range ft.Dir {
- if key == FileTreePropertiesKey {
- continue
- }
- sub.Walk(append(path, key), f)
- }
- }
- func (ft *FileTree) PiecesRootAsByteArray() (ret g.Option[[32]byte]) {
- if ft.File.PiecesRoot == "" {
- return
- }
- n := copy(ret.Value[:], ft.File.PiecesRoot)
- if n != 32 {
- // Must be 32 bytes for meta version 2 and non-empty files. See BEP 52.
- panic(n)
- }
- ret.Ok = true
- return
- }
|