package meta import ( "bytes" "crypto/sha1" "errors" "fmt" "log" "os" "path" "time" ) var ErrNotImplemented = errors.New("not implemented") type File struct { Length int `bencode:"length"` MD5Sum []byte `bencode:"md5sum,optional"` Path []string `bencode:"path"` } type Info struct { Files []File `bencode:"files"` Length int `bencode:"length"` MD5Sum []byte `bencode:"md5sum,optional"` Name string `bencode:"name"` PieceLength int `bencode:"piece length"` Pieces []byte `bencode:"pieces"` Private bool `bencode:"private"` } func (i Info) NPieces() int { return len(i.Pieces) / sha1.Size } func (i Info) Check(b []byte, n int) bool { sum := sha1.Sum(b) off := n * sha1.Size return bytes.Equal(i.Pieces[off:off+sha1.Size], sum[:]) } func (i Info) CheckSum(n int) []byte { off := n * sha1.Size return i.Pieces[off : off+sha1.Size] } func (i Info) CheckSums() [][]byte { cs := make([][]byte, i.NPieces()) for n := 0; n < i.NPieces(); n++ { cs[n] = i.CheckSum(n) } return cs } 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") } p := append([]string{i.Name}, i.Files[n].Path...) return path.Join(p...), nil } // piece-to-file mapping: // piece # -> [ { file #, off, len }, ... ] type pieceToFile map[int][]struct { fd os.File offset int length int } func (i Info) FindFile(chunk, offset int) int { if i.isSingleFile() { return 0 } return 0 } func (i Info) Offset(piece int) int { return i.PieceLength * piece } func (i Info) FileOffset(piece int) (int, int) { off := i.PieceLength * piece for n, l := range i.Files { if l.Length < off { off -= l.Length } else { log.Println(n, off, i.PieceLength-off) return n, off } } 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) WriteAt(b []byte, off int64) (n int, err error) { return 0, ErrNotImplemented } // 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 { } return 0, ErrNotImplemented } /* func (i Info) WriteAt(data []byte, chunk, offcet int) error { return nil } func (i Info) ReadAt(chunk, offset, length int) ([]byte, error) { if length == 0 { length = i.PieceLength offset = 0 } data := make([]byte, length) start := chunk * i.PieceLength var n, off int for n = range i.Files { off += i.Files[n].Length if off > start { break } } // ... return data, nil } */ func (i Info) isSingleFile() bool { return len(i.Files) == 0 } // PieceCount returns count of full pieces and size of last piece func (i Info) PieceCount() (int, int) { return i.Length / i.PieceLength, i.Length % i.PieceLength } func (i Info) String() string { var s string if l := len(i.Files); l > 0 { s += fmt.Sprintln("files:", l) 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) i.Length += f.Length } } s += fmt.Sprintf("%s (%d) ", i.Name, i.Length) full, last := i.PieceCount() s += fmt.Sprintf("%d × %d + %d\n", full, i.PieceLength, last) return s } type Torrent struct { Announce string `bencode:"announce"` AnnounceList [][]string `bencode:"announce-list,optional"` Comment string `bencode:"comment,optional"` CreatedBy string `bencode:"created by,optional"` CreationDate time.Time `bencode:"creation date,optional"` Encoding string `bencode:"encoding,optional"` HTTPSeeds []string `bencode:"httpseeds,optional"` Info Info `bencode:"info"` URLList string `bencode:"url-list,optional"` InfoHash []byte `bencode:"-"` } func (t Torrent) String() string { s := fmt.Sprintf("announce: %s\n", t.Announce) s += fmt.Sprintf("comment: %s\n", t.Comment) s += fmt.Sprintf("date: %s\n", t.CreationDate) s += fmt.Sprintf("%v", t.Info) s += fmt.Sprintf("info hash: %x", t.InfoHash) return s } /* metainfo file.: OpenBSD_songs_ogg-2016-03-25-0127.torrent info hash.....: 1aa5af9f6f533961a65169bdeeb801e611724d32 directory name: OpenBSD_songs_ogg files.........: 38 OpenBSD_songs_ogg/song30.ogg (2636244) 2.51 MB OpenBSD_songs_ogg/song31.ogg (2381018) 2.27 MB OpenBSD_songs_ogg/song32.ogg (2253880) 2.15 MB OpenBSD_songs_ogg/song33.ogg (3238559) 3.09 MB OpenBSD_songs_ogg/song34.ogg (5062603) 4.83 MB OpenBSD_songs_ogg/song35.ogg (7191264) 6.86 MB OpenBSD_songs_ogg/song36.ogg (5197630) 4.96 MB OpenBSD_songs_ogg/song37.ogg (13937255) 13.29 MB OpenBSD_songs_ogg/song38.ogg (5860681) 5.59 MB OpenBSD_songs_ogg/song38b.ogg (5759105) 5.49 MB OpenBSD_songs_ogg/song39.ogg (5954997) 5.68 MB OpenBSD_songs_ogg/song40.ogg (3610740) 3.44 MB OpenBSD_songs_ogg/song41.ogg (8382781) 7.99 MB OpenBSD_songs_ogg/song42.ogg (6713047) 6.40 MB OpenBSD_songs_ogg/song43.ogg (6834505) 6.52 MB OpenBSD_songs_ogg/song44.ogg (4628329) 4.41 MB OpenBSD_songs_ogg/song45.ogg (4737475) 4.52 MB OpenBSD_songs_ogg/song46.ogg (3766867) 3.59 MB OpenBSD_songs_ogg/song47.ogg (6586251) 6.28 MB OpenBSD_songs_ogg/song48.ogg (3143847) 3.00 MB OpenBSD_songs_ogg/song49.ogg (6006834) 5.73 MB OpenBSD_songs_ogg/song50.ogg (4184951) 3.99 MB OpenBSD_songs_ogg/song51.ogg (4216946) 4.02 MB OpenBSD_songs_ogg/song52.ogg (4283099) 4.08 MB OpenBSD_songs_ogg/song53.ogg (4635216) 4.42 MB OpenBSD_songs_ogg/song54.ogg (3195265) 3.05 MB OpenBSD_songs_ogg/song55.ogg (6160151) 5.87 MB OpenBSD_songs_ogg/song56.ogg (5471902) 5.22 MB OpenBSD_songs_ogg/song57.ogg (4057801) 3.87 MB OpenBSD_songs_ogg/song58a.ogg (3252520) 3.10 MB OpenBSD_songs_ogg/song58b.ogg (4376163) 4.17 MB OpenBSD_songs_ogg/song58c.ogg (3595317) 3.43 MB OpenBSD_songs_ogg/song58d.ogg (7013091) 6.69 MB OpenBSD_songs_ogg/song59a.ogg (5766764) 5.50 MB OpenBSD_songs_ogg/song59b.ogg (5317415) 5.07 MB OpenBSD_songs_ogg/songsh.ogg (4890381) 4.66 MB OpenBSD_songs_ogg/songsi.ogg (5694670) 5.43 MB OpenBSD_songs_ogg/songty.ogg (6045645) 5.77 MB archive size..: 196041209 (747 * 262144 + 219641) 186.96 MB announce url..: http://OpenBSD.somedomain.net:6969/announce creation date.: Fri Mar 25 02:27:56 2016 created by....: mktorrent 1.0 comment.......: ogg files from OpenBSD/songs Created by andrew fresh (andrew@afresh1.com) http://OpenBSD.somedomain.net/ */ /* metainfo file.: OpenBSD_5.9_amd64_install59.iso-2016-03-29-0449.torrent info hash.....: e840038dea1998c39614dcd28594501df02bd32d file name.....: OpenBSD_5.9_amd64_install59.iso file size.....: 233662464 (891 * 262144 + 92160) 222.84 MB announce url..: http://OpenBSD.somedomain.net:6969/announce creation date.: Tue Mar 29 06:49:18 2016 created by....: mktorrent 1.0 comment.......: OpenBSD/5.9/amd64/install59.iso Created by andrew fresh (andrew@afresh1.com) http://OpenBSD.somedomain.net/ */