diff --git a/README.md b/README.md index e69de29..56cddfb 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,12 @@ +# go-bt + +bittorrent related things + +## ./bencode/ + +fork from code.google.com/p/bencode-go + +## ./torrent/ + +Decoder and struct for the torrent file format + diff --git a/example_loader.go b/example_loader.go new file mode 100644 index 0000000..d790761 --- /dev/null +++ b/example_loader.go @@ -0,0 +1,59 @@ +package main + +import ( + "github.com/vbatts/go-bt/bencode" + "github.com/vbatts/go-bt/torrent" + "flag" + "fmt" + "os" +) + +var ( + flOutput = flag.String("o", "", "output the re-encoded torrent to file at this path") +) + +func main() { + flag.Parse() + + if len(*flOutput) > 0 && flag.NArg() > 1 { + fmt.Fprintf(os.Stderr, "-o and multiple input files can not be used together") + os.Exit(1) + } + + for _, arg := range flag.Args() { + fh, err := os.Open(arg) + if err != nil { + fmt.Fprintf(os.Stderr, "%s\n", err) + continue + } + + data, err := bencode.Decode(fh) + if err != nil { + fmt.Fprintf(os.Stderr, "%s\n", err) + fh.Close() + continue + } + fh.Close() + + tf, err := torrent.DecocdeTorrentData(data) + if err != nil { + fmt.Fprintf(os.Stderr, "%s\n", err) + continue + } + fmt.Printf("Loaded: %s (%d files)\n", tf.Info.Name, len(tf.Info.Files)) + + if len(*flOutput) > 0 { + fhOutput, err := os.Create(*flOutput) + if err != nil { + fmt.Fprintf(os.Stderr, "%s\n", err) + continue + } + err = bencode.Marshal(fhOutput, *tf) + if err != nil { + fmt.Fprintf(os.Stderr, "%s\n", err) + } + fmt.Printf("wrote: %s\n", fhOutput.Name()) + fhOutput.Close() + } + } +} diff --git a/torrent/file.go b/torrent/file.go new file mode 100644 index 0000000..b4c6846 --- /dev/null +++ b/torrent/file.go @@ -0,0 +1,114 @@ +package torrent + +import ( + //"github.com/vbatts/go-bt/bencode" + "crypto/sha1" +) + +/* +map[string]interface {}{"announce":"http://torrent.fedoraproject.org:6969/announce", "creation date":1387244350, "info":map[string]interface {}{"files":[]interface {}{map[string]interface {}{"length":1125, "path":[]interface {}{"Fedora-20-x86_64-CHECKSUM"}}, map[string]interface {}{"length":4603248640, "path":[]interface {}{"Fedora-20-x86_64-DVD.iso"}}}, "name":"Fedora-20-x86_64-DVD", "piece length":262144, "pieces":"m\x +*/ +type File struct { + // URL of a main tracker + Announce string "announce" + + // Epoch of the creation of this torrent + CreationDate int64 "creation date" + + // Dictionary about this torrent, including files to be tracked + Info TorrentFileInfo "info" +} + +type TorrentFileInfo struct { + // suggested file/directory name where the file(s) are to be saved + Name string "name" + + // hash list of joined SHA1 sums (160-bit length) + Pieces string "pieces" + + // number of bytes per piece + PieceLength int64 "piece length" + + // size of the file in bytes (only if this torrent is for a single file) + Length int64 "length" + + // list of information about the files + Files []FileInfo "files" +} + +func (tfi TorrentFileInfo) PiecesList() []string { + pieces := []string{} + for i := 0; i < (len(tfi.Pieces) / sha1.Size); i++ { + pieces = append(pieces, tfi.Pieces[i*sha1.Size:(i+1)*sha1.Size]) + } + return pieces +} + +type FileInfo struct { + // size of file in bytes + Length int64 "length" + + // list of strings corresponding to subdirectory names, the last of which is the actual file name + Path []string "path" +} + +type torrentError struct { + Msg string +} +func (te torrentError) Error() string { + return te.Msg +} + +var ( + ErrNotProperDataInterface = torrentError{"data does not look like map[string]interface{}"} +) + +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, + Info: TorrentFileInfo{ + Name: infoName, + Length: infoLength, + Pieces: pieces, + PieceLength: pieceLength, + Files: files, + }, + }, nil +}