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 } // Full returns count of full pieces func (i Info) Full() int { return i.TotalLength() / i.PieceLength } // Last returns size of last truncated piece func (i Info) Last() int { return i.TotalLength() % i.PieceLength } // TotalLength calculates and retuns the length of all files func (i Info) TotalLength() int { if i.Length == 0 { for _, f := range i.Files { i.Length += f.Length } } return i.Length } func (i Info) GetPieces() []Piece { n, last := i.Full(), i.Last() 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 = last } off := k * sha1.Size copy(p[k].Sum[:], i.Pieces[off:off+sha1.Size]) p[k].Offset = k * i.PieceLength } return p } // Open N-th file and allocate required path on demand func (i Info) Open(n int) (*os.File, error) { var p string var sz int if len(i.Files) == 0 { p = i.Name sz = i.Length } else { p = path.Join(i.Name, i.Files[n].Name()) if err := os.MkdirAll(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(int64(sz)) } // Offset of piece in gross buffer func (i Info) Offset(piece int) int { return i.PieceLength * piece } // FindFile looks up first file # corresponding to offset in gross buffer // If piece is bigger the next file(s) with offset 0 shall be used to // fit a piece into 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 * * 0 2 3 4 #piece * |--------|--------|--------|-----| pieces * |-|--|--|-|--|--|-------------|--| files * 0 1 2 3 4 5 6 7 #file */ // 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 len(i.Files) == 0 { fd, err := i.Open(0) 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] fd, err := i.Open(k) 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 } 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 }