package meta import ( "crypto/sha1" "errors" "fmt" "os" "path" ) var ErrNotImplemented = errors.New("not implemented") type Info struct { Files []File `bencode:"files"` Length int64 `bencode:"length"` MD5Sum []byte `bencode:"md5sum,optional"` // never seen in wildlife Name string `bencode:"name"` PieceLength int `bencode:"piece length"` Pieces Pieces `bencode:"pieces"` Private bool `bencode:"private"` // BEP-0027 RootHash []byte `bencode:"root hash"` // BEP-0030 Raw []byte `bencode:"-"` // raw representation of bencoded structure } func (i Info) Hash() [sha1.Size]byte { return sha1.Sum(i.Raw) } // Full returns count of full pieces func (i Info) Full() int { return int(i.TotalLength() / int64(i.PieceLength)) } // Last returns size of last truncated piece func (i Info) Last() int { return int(i.TotalLength() % int64(i.PieceLength)) } // TotalLength calculates and retuns the length of all files func (i Info) TotalLength() int64 { if i.Length == 0 { for _, f := range i.Files { i.Length += f.Length } } return i.Length } func (i Info) fileLength(n int) int64 { if len(i.Files) == 0 { return i.Length } return i.Files[n].Length } // Open N-th file and allocate required path on demand func (i Info) Open(n int) (*os.File, error) { p := i.Name sz := i.Length if len(i.Files) > 0 { p = path.Join(p, i.Files[n].Name()) if err := os.MkdirAll(path.Dir(p), 0755); err != nil { return nil, err } sz = i.Files[n].Length } fd, err := os.OpenFile(p, os.O_RDWR|os.O_CREATE, 0644) if err != nil { return nil, err } return fd, fd.Truncate(sz) } // Offset of piece in gross buffer func (i Info) Offset(piece int) int64 { return int64(piece) * int64(i.PieceLength) } // findFirstFile looks up first file # corresponding to offset into it // If piece is bigger then file, next file(s) with offset 0 shall be used // to fit a piece into func (i Info) findFirstFile(off int64) (int, int64) { 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 * * 0 2 3 4 #piece * |--------|--------|--------|-----| pieces * |-|--|--|-|--|--|-------------|--| files * 0 1 2 3 4 5 6 7 #file */ // WriteAt offset func (i Info) WriteAt(b []byte, off int64) (n int, err error) { if len(b) == 0 || off >= i.TotalLength() { return 0, nil } n, foff := i.findFirstFile(off) fd, err := i.Open(n) if err != nil { return 0, err } defer fd.Close() end := i.fileLength(n) - foff if blen := int64(len(b)); end > blen { end = blen } done, err := fd.WriteAt(b[:end], foff) if err != nil { return 0, err } next, err := i.WriteAt(b[end:], off+end) if err != nil { return 0, err } return done + next, nil } // ReadAt offset func (i Info) ReadAt(b []byte, off int64) (n int, err error) { if len(b) == 0 || off >= i.TotalLength() { return 0, nil } n, foff := i.findFirstFile(off) fd, err := i.Open(n) if err != nil { return 0, err } defer fd.Close() end := i.fileLength(n) - foff if blen := int64(len(b)); end > blen { end = blen } done, err := fd.ReadAt(b[:end], foff) if err != nil { return 0, err } next, err := i.ReadAt(b[end:], off+end) if err != nil { return 0, err } return done + next, nil } func (i Info) String() string { s := fmt.Sprintf("%s (%d) ", i.Name, i.TotalLength()) s += fmt.Sprintf("%d × %d + %d\n", i.Full(), i.PieceLength, i.Last()) for _, f := range i.Files { s += fmt.Sprintf(" %v\n", f) } return s }