aboutsummaryrefslogtreecommitdiff
path: root/meta/info.go
diff options
context:
space:
mode:
Diffstat (limited to 'meta/info.go')
-rw-r--r--meta/info.go156
1 files changed, 156 insertions, 0 deletions
diff --git a/meta/info.go b/meta/info.go
new file mode 100644
index 0000000..8f76c72
--- /dev/null
+++ b/meta/info.go
@@ -0,0 +1,156 @@
+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
+}