go-bt/torrent/file.go

139 lines
3.8 KiB
Go
Raw Normal View History

2014-08-14 18:48:33 +00:00
package torrent
import (
"crypto/sha1"
2014-08-14 20:52:27 +00:00
"time"
2014-08-14 18:48:33 +00:00
)
// File is a representation of https://wiki.theory.org/BitTorrentSpecification#Metainfo_File_Structure
2014-08-14 18:48:33 +00:00
type File struct {
// Announce is the URL of a main tracker
2014-08-14 19:19:42 +00:00
Announce string `bencode:"announce"`
2014-08-14 18:48:33 +00:00
// AnnounceList lists additional trackers
2014-08-14 20:47:34 +00:00
AnnounceList []string `bencode:"announce-list"`
// CreationDate is epoch of the creation of this torrent
CreationDate int64 `bencode:"creation date,omitempty"`
2014-08-14 18:48:33 +00:00
// Info is the dictionary about this torrent, including files to be tracked
Info InfoSection `bencode:"info,omitempty"`
2014-08-14 20:47:34 +00:00
// Comment is free-form textual comments of the author
Comment string `bencode:"comment,omitempty"`
2014-08-14 20:47:34 +00:00
// CreatedBy is the name and version of the program used to create the .torrent
CreatedBy string `bencode:"created by,omitempty"`
2014-08-14 20:47:34 +00:00
// Encoding string encoding used to generate the `pieces` and `info` fields
2014-08-14 20:47:34 +00:00
Encoding string `bencode:"encoding"`
2014-08-14 18:48:33 +00:00
}
// CreationDateTime returns a time from the File's CreationDate
2014-08-14 20:52:27 +00:00
func (f File) CreationDateTime() time.Time {
return time.Unix(f.CreationDate, 0)
}
2014-08-14 20:47:34 +00:00
type InfoSection struct {
2014-08-14 18:48:33 +00:00
// suggested file/directory name where the file(s) are to be saved
Name string `bencode:"name,omitempty"`
2014-08-14 18:48:33 +00:00
// hash list of joined SHA1 sums (160-bit length)
2014-08-14 19:19:42 +00:00
Pieces string `bencode:"pieces"`
2014-08-14 18:48:33 +00:00
// number of bytes per piece
2014-08-14 19:19:42 +00:00
PieceLength int64 `bencode:"piece length"`
2014-08-14 18:48:33 +00:00
// size of the file in bytes (only if this torrent is for a single file)
Length int64 `bencode:"length,omitempty"`
2014-08-14 20:47:34 +00:00
// 32-char hexadecimal string corresponding to the MD5 sum of the file (only if this torrent is for a single file)
MD5 string `bencode:"md5sum,omitempty"`
2014-08-14 18:48:33 +00:00
// list of information about the files
2014-08-14 19:19:42 +00:00
Files []FileInfo `bencode:"files"`
2014-08-14 18:48:33 +00:00
}
2014-08-14 20:47:34 +00:00
func (is InfoSection) PiecesList() []string {
2014-08-14 18:48:33 +00:00
pieces := []string{}
2014-08-14 20:47:34 +00:00
for i := 0; i < (len(is.Pieces) / sha1.Size); i++ {
pieces = append(pieces, is.Pieces[i*sha1.Size:(i+1)*sha1.Size])
2014-08-14 18:48:33 +00:00
}
return pieces
}
type FileInfo struct {
// size of file in bytes
2014-08-14 19:19:42 +00:00
Length int64 `bencode:"length"`
2014-08-14 18:48:33 +00:00
// list of strings corresponding to subdirectory names, the last of which is the actual file name
2014-08-14 19:19:42 +00:00
Path []string `bencode:"path"`
2014-08-14 20:47:34 +00:00
// 32-char hexadecimal string corresponding to the MD5 sum of the file (only if this torrent is for a single file)
MD5 string `bencode:"md5sum,omitempty"`
2014-08-14 18:48:33 +00:00
}
type torrentError struct {
2014-08-14 19:19:42 +00:00
Msg string
2014-08-14 18:48:33 +00:00
}
2014-08-14 19:19:42 +00:00
2014-08-14 18:48:33 +00:00
func (te torrentError) Error() string {
2014-08-14 19:19:42 +00:00
return te.Msg
2014-08-14 18:48:33 +00:00
}
var (
2014-08-14 19:19:42 +00:00
ErrNotProperDataInterface = torrentError{"data does not look like map[string]interface{}"}
2014-08-14 18:48:33 +00:00
)
// DecocdeTorrentData translates a bencode decoded message from a *.torrent file.
// Assuming a generic map[string]interface{} data.
2014-08-14 18:48:33 +00:00
func DecocdeTorrentData(data interface{}) (*File, error) {
m, ok := data.(map[string]interface{})
if !ok {
return nil, ErrNotProperDataInterface
}
announce := m["announce"].(string)
creationDate := m["creation date"].(int64)
info := m["info"].(map[string]interface{})
pieceLength := info["piece length"].(int64)
pieces := info["pieces"].(string)
infoName := info["name"].(string)
isSingleFileTorrent := true
infoFiles, ok := info["files"].([]interface{})
if ok {
isSingleFileTorrent = false
}
infoLength := int64(0)
if isSingleFileTorrent {
infoLength = info["length"].(int64)
}
files := []FileInfo{}
if !isSingleFileTorrent {
for _, fileInterface := range infoFiles {
fileInfo := fileInterface.(map[string]interface{})
paths := []string{}
for _, path := range fileInfo["path"].([]interface{}) {
paths = append(paths, path.(string))
}
files = append(files, FileInfo{
Length: fileInfo["length"].(int64),
Path: paths,
})
}
}
return &File{
Announce: announce,
CreationDate: creationDate,
2014-08-14 20:47:34 +00:00
Info: InfoSection{
2014-08-14 18:48:33 +00:00
Name: infoName,
Length: infoLength,
Pieces: pieces,
PieceLength: pieceLength,
Files: files,
},
}, nil
}