1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
|
# Copyright 2008 The Tor Project, Inc. See LICENSE for licensing information.
import os.path
import time
import threading
import thandy.master_keys
no_bt = None
try:
import BitTorrent.bencode
import BitTorrent.btformats
import BitTorrent.download
except ImportError:
no_bt = True
from sha import sha # XXX Use PyCrypto here?
class BtCompat:
"""Interface for different bittorrent implementations"""
usingBt = False
def __init__(self):
self.tUrl = thandy.master_keys.DEFAULT_TRACKER
if not no_bt:
assert(self.tUrl is not None and self.tUrl != "")
self.pieceLength = 2 ** 18 # Piece length of 262144 bytes
# XXX Do we need to be thread-safe here and below?
@staticmethod
def shouldUseBt():
return BtCompat.usingBt
@staticmethod
def setUseBt(useBt):
if no_bt:
return
BtCompat.usingBt = useBt
@staticmethod
def getBtMetadataLocation(packagepath, filepath, pathprefix=""):
"""Given a path for the package, the path for a file of that
package, and an optional prefix, return the path for the
.torrent metadata file. Always return Unix-like paths, to
ensure compatibility with fetching the path from a
webserver.
"""
return (os.path.join(pathprefix, os.path.dirname(packagepath),
os.path.basename(filepath)) + ".torrent"
).replace("\\", "/")
def makeMetaFile(self, file):
"""Given a path to a file, create the contents of a .torrent
metadata file and return them.
"""
size = os.path.getsize(file)
filename = os.path.basename(file)
pieces = []
p = 0
h = open(file, 'rb')
while p < size:
x = h.read(min(self.pieceLength, size - p))
pieces.append(sha(x).digest())
p += self.pieceLength
if p > size:
p = size
h.close()
info = {'pieces': ''.join(pieces),
'piece length': self.pieceLength, 'length': size,
'name': filename}
# Check we didn't screw up with the info
BitTorrent.btformats.check_info(info)
data = {'info': info, 'announce': self.tUrl,
'creation date': long(time.time())}
return BitTorrent.bencode.bencode(data)
def getFileLength(self, file):
"""Parse the .torrent metainfo file and return the length of the
file it refers to.
"""
f = open(file, 'rb')
metainfo = BitTorrent.bencode.bdecode(f.read())['info']
f.close()
assert(metainfo['length'])
return metainfo['length']
def getFileHash(self, file):
"""Parse the .torrent metainfo file and return the hash of the
file it refers to.
"""
f = open(file, 'rb')
metainfo = BitTorrent.bencode.bdecode(f.read())['info']
f.close()
return sha(BitTorrent.bencode.bencode(metainfo)).hexdigest()
def download(self, metaFile, saveTo ):
"""Initiate a download via bittorrent."""
event = threading.Event()
params = ['--responsefile', metaFile, '--saveas', saveTo]
def filefunc(default, size, saveas, dir):
return saveas
def statusfunc(dict):
# XXX we should see how fast we upload/download here.
# If we don't get a connection for quite a while, or we are
# _very_ slow, we should cancel bt, disable it, and start fetching
# via http.
pass
def finfunc():
# XXX here we can set a timer for how long to seed, or
# wait for statusfunc to have shared some data, or something.
# Not the real solution, though, because installation will be
# delayed by the time we sleep...
# time.sleep(60)
event.set()
pass
def errorfunc(msg):
# XXX Not really sure how to encounter an error here. Our best bet
# is to cancel the download, stop bittorrent, and move on.
BtCompat.setUseBt(False)
event.set()
BitTorrent.download.download(params, filefunc, statusfunc, finfunc,
errorfunc, event, 80)
|