Browse Source
add torrent support
add torrent support
This change adds an option to download files with BitTorrent. A webseed is provided in the torrent file to bootstrap the swarm.pull/13/head
mutantmonkey
9 years ago
5 changed files with 160 additions and 0 deletions
@ -0,0 +1,96 @@ |
|||
package main |
|||
|
|||
import ( |
|||
"bytes" |
|||
"crypto/sha1" |
|||
"fmt" |
|||
"io" |
|||
"net/http" |
|||
"os" |
|||
"path" |
|||
"time" |
|||
|
|||
"github.com/zeebo/bencode" |
|||
"github.com/zenazn/goji/web" |
|||
) |
|||
|
|||
const ( |
|||
TORRENT_PIECE_LENGTH = 262144 |
|||
) |
|||
|
|||
func check(e error) { |
|||
if e != nil { |
|||
panic(e) |
|||
} |
|||
} |
|||
|
|||
type TorrentInfo struct { |
|||
PieceLength int `bencode:"piece length"` |
|||
Pieces []byte `bencode:"pieces"` |
|||
Name string `bencode:"name"` |
|||
Length int `bencode:"length"` |
|||
} |
|||
|
|||
type Torrent struct { |
|||
Encoding string `bencode:"encoding"` |
|||
Info TorrentInfo `bencode:"info"` |
|||
UrlList []string `bencode:"url-list"` |
|||
} |
|||
|
|||
func CreateTorrent(fileName string, filePath string) []byte { |
|||
chunk := make([]byte, TORRENT_PIECE_LENGTH) |
|||
var pieces []byte |
|||
length := 0 |
|||
|
|||
f, err := os.Open(filePath) |
|||
check(err) |
|||
|
|||
for { |
|||
n, err := f.Read(chunk) |
|||
if err == io.EOF { |
|||
break |
|||
} |
|||
check(err) |
|||
|
|||
length += n |
|||
|
|||
h := sha1.New() |
|||
h.Write(chunk) |
|||
pieces = append(pieces, h.Sum(nil)...) |
|||
} |
|||
|
|||
f.Close() |
|||
|
|||
torrent := &Torrent{ |
|||
Encoding: "UTF-8", |
|||
Info: TorrentInfo{ |
|||
PieceLength: TORRENT_PIECE_LENGTH, |
|||
Pieces: pieces, |
|||
Name: fileName, |
|||
Length: length, |
|||
}, |
|||
UrlList: []string{fmt.Sprintf("%sselif/%s", Config.siteURL, fileName)}, |
|||
} |
|||
|
|||
data, err := bencode.EncodeBytes(torrent) |
|||
check(err) |
|||
|
|||
return data |
|||
} |
|||
|
|||
func fileTorrentHandler(c web.C, w http.ResponseWriter, r *http.Request) { |
|||
fileName := c.URLParams["name"] |
|||
filePath := path.Join(Config.filesDir, fileName) |
|||
|
|||
if !fileExistsAndNotExpired(fileName) { |
|||
notFoundHandler(c, w, r) |
|||
return |
|||
} |
|||
|
|||
encoded := CreateTorrent(fileName, filePath) |
|||
|
|||
w.Header().Set(`Content-Disposition`, fmt.Sprintf(`attachment; filename="%s.torrent"`, fileName)) |
|||
http.ServeContent(w, r, "", time.Now(), bytes.NewReader(encoded)) |
|||
} |
|||
|
|||
// vim:set ts=8 sw=8 noet:
|
@ -0,0 +1,43 @@ |
|||
package main |
|||
|
|||
import ( |
|||
"fmt" |
|||
"testing" |
|||
|
|||
"github.com/zeebo/bencode" |
|||
) |
|||
|
|||
func TestCreateTorrent(t *testing.T) { |
|||
fileName := "server.go" |
|||
encoded := CreateTorrent(fileName, fileName) |
|||
var decoded Torrent |
|||
|
|||
bencode.DecodeBytes(encoded, &decoded) |
|||
|
|||
if decoded.Encoding != "UTF-8" { |
|||
t.Fatalf("Encoding was %s, expected UTF-8", decoded.Encoding) |
|||
} |
|||
|
|||
if decoded.Info.Name != "server.go" { |
|||
t.Fatalf("Name was %s, expected server.go", decoded.Info.Name) |
|||
} |
|||
|
|||
if decoded.Info.PieceLength <= 0 { |
|||
t.Fatal("Expected a piece length, got none") |
|||
} |
|||
|
|||
if len(decoded.Info.Pieces) <= 0 { |
|||
t.Fatal("Expected at least one piece, got none") |
|||
} |
|||
|
|||
if decoded.Info.Length <= 0 { |
|||
t.Fatal("Length was less than or equal to 0, expected more") |
|||
} |
|||
|
|||
tracker := fmt.Sprintf("%sselif/%s", Config.siteURL, fileName) |
|||
if decoded.UrlList[0] != tracker { |
|||
t.Fatal("First entry in URL list was %s, expected %s", decoded.UrlList[0], tracker) |
|||
} |
|||
} |
|||
|
|||
// vim:set ts=8 sw=8 noet:
|
Write
Preview
Loading…
Cancel
Save
Reference in new issue