3
# Written by Bram Cohen
4
# see LICENSE.txt for license information
6
from BitTornado import PSYCO
10
assert psyco.__version__ >= 0x010100f0
15
from BitTornado.download_bt1 import BT1Download, defaults, parse_params, get_usage, get_response
16
from BitTornado.RawServer import RawServer, UPnP_ERROR
17
from random import seed
18
from socket import error as socketerror
19
from BitTornado.bencode import bencode
20
from BitTornado.natpunch import UPnP_test
21
from threading import Event
22
from os.path import abspath
23
from sys import argv, stdout
26
from time import strftime
27
from BitTornado.clock import clock
28
from BitTornado import createPeerID, version
29
from BitTornado.ConfigDir import ConfigDir
31
assert sys.version >= '2', "Install Python 2.0 or greater"
45
assert n >= 0 and n < 5184000 # 60 days
51
return '%d hour %02d min %02d sec' % (h, m, s)
53
return '%d min %02d sec' % (m, s)
55
class HeadlessDisplayer:
68
self.last_update_time = -1
72
self.percentDone = '100'
73
self.timeEst = 'Download Succeeded!'
79
self.percentDone = '0'
80
self.timeEst = 'Download Failed!'
84
def error(self, errormsg):
85
self.errors.append(errormsg)
88
def display(self, dpflag = Event(), fractionDone = None, timeEst = None,
89
downRate = None, upRate = None, activity = None,
90
statistics = None, **kws):
91
if self.last_update_time + 0.1 > clock() and fractionDone not in (0.0, 1.0) and activity is not None:
93
self.last_update_time = clock()
94
if fractionDone is not None:
95
self.percentDone = str(float(int(fractionDone * 1000)) / 10)
96
if timeEst is not None:
97
self.timeEst = hours(timeEst)
98
if activity is not None and not self.done:
99
self.timeEst = activity
100
if downRate is not None:
101
self.downRate = '%.1f kB/s' % (float(downRate) / (1 << 10))
102
if upRate is not None:
103
self.upRate = '%.1f kB/s' % (float(upRate) / (1 << 10))
104
if statistics is not None:
105
if (statistics.shareRating < 0) or (statistics.shareRating > 100):
106
self.shareRating = 'oo (%.1f MB up / %.1f MB down)' % (float(statistics.upTotal) / (1<<20), float(statistics.downTotal) / (1<<20))
108
self.shareRating = '%.3f (%.1f MB up / %.1f MB down)' % (statistics.shareRating, float(statistics.upTotal) / (1<<20), float(statistics.downTotal) / (1<<20))
110
self.seedStatus = '%d seen now, plus %.3f distributed copies' % (statistics.numSeeds,0.001*int(1000*statistics.numCopies))
112
self.seedStatus = '%d seen recently, plus %.3f distributed copies' % (statistics.numOldSeeds,0.001*int(1000*statistics.numCopies))
113
self.peerStatus = '%d seen now, %.1f%% done at %.1f kB/s' % (statistics.numPeers,statistics.percentDone,float(statistics.torrentRate) / (1 << 10))
115
for err in self.errors:
116
print 'ERROR:\n' + err + '\n'
117
print 'saving: ', self.file
118
print 'percent done: ', self.percentDone
119
print 'time left: ', self.timeEst
120
print 'download to: ', self.downloadTo
121
print 'download rate: ', self.downRate
122
print 'upload rate: ', self.upRate
123
print 'share rating: ', self.shareRating
124
print 'seed status: ', self.seedStatus
125
print 'peer status: ', self.peerStatus
129
def chooseFile(self, default, size, saveas, dir):
130
self.file = '%s (%.1f MB)' % (default, float(size) / (1 << 20))
133
self.downloadTo = abspath(default)
136
def newpath(self, path):
137
self.downloadTo = path
148
h = HeadlessDisplayer()
150
configdir = ConfigDir('downloadheadless')
151
defaultsToIgnore = ['responsefile', 'url', 'priority']
152
configdir.setDefaults(defaults,defaultsToIgnore)
153
configdefaults = configdir.loadConfig()
154
defaults.append(('save_options',0,
155
"whether to save the current options as the new default configuration " +
156
"(only for btdownloadheadless.py)"))
158
config = parse_params(params, configdefaults)
159
except ValueError, e:
160
print 'error: ' + str(e) + '\nrun with no args for parameter explanations'
163
print get_usage(defaults, 80, configdefaults)
165
if config['save_options']:
166
configdir.saveConfig(config)
167
configdir.deleteOldCacheData(config['expire_cache_data'])
169
myid = createPeerID()
173
def disp_exception(text):
175
rawserver = RawServer(doneflag, config['timeout_check_interval'],
176
config['timeout'], ipv6_enable = config['ipv6_enabled'],
177
failfunc = h.failed, errorfunc = disp_exception)
181
listen_port = rawserver.find_and_bind(config['minport'], config['maxport'],
182
config['bind'], ipv6_socket_style = config['ipv6_binds_v4'],
183
upnp = upnp_type, randomizer = config['random_port'])
185
except socketerror, e:
186
if upnp_type and e == UPnP_ERROR:
187
print 'WARNING: COULD NOT FORWARD VIA UPnP'
190
print "error: Couldn't listen - " + str(e)
194
response = get_response(config['responsefile'], config['url'], h.error)
198
infohash = sha(bencode(response['info'])).digest()
200
dow = BT1Download(h.display, h.finished, h.error, disp_exception, doneflag,
201
config, response, infohash, myid, rawserver, listen_port,
204
if not dow.saveAs(h.chooseFile, h.newpath):
207
if not dow.initFiles(old_style = True):
209
if not dow.startEngine():
212
dow.startRerequester()
215
if not dow.am_I_finished():
216
h.display(activity = 'connecting to peers')
217
rawserver.listen_forever(dow.getPortHandler())
218
h.display(activity = 'shutting down')
228
if __name__ == '__main__':
229
if argv[1:] == ['--version']:
234
import profile, pstats
235
p = profile.Profile()
236
p.runcall(run, argv[1:])
237
log = open('profile_data.'+strftime('%y%m%d%H%M%S')+'.txt','a')
238
normalstdout = sys.stdout
240
# pstats.Stats(p).strip_dirs().sort_stats('cumulative').print_stats()
241
pstats.Stats(p).strip_dirs().sort_stats('time').print_stats()
242
sys.stdout = normalstdout