package meta import ( "crypto/sha1" "errors" "fmt" "os" "path" ) var ErrNotImplemented = errors.New("not implemented") type Info struct { Files []File `bencode:"files"` Length int `bencode:"length"` MD5Sum []byte `bencode:"md5sum,optional"` // never seen in wildlife Name string `bencode:"name"` PieceLength int `bencode:"piece length"` Pieces []byte `bencode:"pieces"` // compact mode Private bool `bencode:"private"` // BEP-0027 RootHash []byte `bencode:"root hash"` // BEP-0030 } func (i Info) TotalLength() int { if i.Length > 0 { return i.Length } var l int for _, f := range i.Files { l += f.Length } return l } func (i Info) GetPieces() []Piece { length := i.TotalLength() n := length / i.PieceLength last := length % i.PieceLength if last > 0 { n++ } p := make([]Piece, n) for k := 0; k < n; k++ { p[k].Length = i.PieceLength if k+1 == n { p[k].Length = length } off := k * sha1.Size copy(p[k].Sum[:], i.Pieces[off:off+sha1.Size]) p[k].Offset = k * i.PieceLength } return p } func (i Info) FullPath(n int) (string, error) { if i.isSingleFile() { return i.Name, nil } if n >= len(i.Files) || n < 0 { return "", errors.New("out of range") } return path.Join(i.Name, i.Files[n].Name()), nil } func (i Info) Offset(piece int) int { return i.PieceLength * piece } func (i Info) FindFile(off int) (int, int) { for n, l := range i.Files { if l.Length > off { return n, off } off -= l.Length } return 0, off } /* * 0 1 2 3 4 5 6 7 #piece * |----|----|----|----|----|----|----|--| pieces * |-|----|--|----|----------|-|----|----| files * 0 1 2 3 4 5 6 7 #file */ func (i Info) isSingleFile() bool { return len(i.Files) == 0 } // FIXME WriteAt ... func (i Info) WriteAt(b []byte, off int64) (n int, err error) { return 0, ErrNotImplemented } // FIXME ReadAt ... func (i Info) ReadAt(b []byte, off int64) (n int, err error) { if i.isSingleFile() { fd, err := os.OpenFile(i.Name, os.O_RDWR|os.O_CREATE, 0644) if err != nil { return 0, err } defer fd.Close() return fd.ReadAt(b, int64(i.PieceLength)*off) } else { n, off := i.FindFile(int(off)) start := off end := i.PieceLength for k := n; k < len(i.Files); k++ { f := i.Files[n] p, err := i.FullPath(k) if err != nil { return 0, err } fd, err := os.OpenFile(p, os.O_RDWR|os.O_CREATE, 0644) if err != nil { return 0, err } defer fd.Close() if end > f.Length { end = f.Length } fd.ReadAt(b[start:end], int64(off)) start = end end = i.PieceLength off += f.Length if start == end { return cap(b), nil } } } return 0, ErrNotImplemented } // Last returns size of last truncated piece func (i Info) Last() int { return i.TotalLength() % i.PieceLength } // Full returns count of full pieces func (i Info) Full() int { return i.TotalLength() / i.PieceLength } func (i Info) String() string { var s string for n, f := range i.Files { p, err := i.FullPath(n) if err != nil { panic(err) } s += fmt.Sprintf(" %s (%d)\n", p, f.Length) } s += fmt.Sprintf("%s (%d) ", i.Name, i.TotalLength()) s += fmt.Sprintf("%d × %d + %d\n", i.Full(), i.PieceLength, i.Last()) return s }