download_bt1.py 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877
  1. # Written by Bram Cohen
  2. # see LICENSE.txt for license information
  3. from zurllib import urlopen
  4. from urlparse import urlparse
  5. from BT1.btformats import check_message
  6. from BT1.Choker import Choker
  7. from BT1.Storage import Storage
  8. from BT1.StorageWrapper import StorageWrapper
  9. from BT1.FileSelector import FileSelector
  10. from BT1.Uploader import Upload
  11. from BT1.Downloader import Downloader
  12. from BT1.HTTPDownloader import HTTPDownloader
  13. from BT1.Connecter import Connecter
  14. from RateLimiter import RateLimiter
  15. from BT1.Encrypter import Encoder
  16. from RawServer import RawServer, autodetect_ipv6, autodetect_socket_style
  17. from BT1.Rerequester import Rerequester
  18. from BT1.DownloaderFeedback import DownloaderFeedback
  19. from RateMeasure import RateMeasure
  20. from CurrentRateMeasure import Measure
  21. from BT1.PiecePicker import PiecePicker
  22. from BT1.Statistics import Statistics
  23. from ConfigDir import ConfigDir
  24. from bencode import bencode, bdecode
  25. from natpunch import UPnP_test
  26. from sha import sha
  27. from os import path, makedirs, listdir
  28. from parseargs import parseargs, formatDefinitions, defaultargs
  29. from socket import error as socketerror
  30. from random import seed
  31. from threading import Thread, Event
  32. from clock import clock
  33. from BTcrypto import CRYPTO_OK
  34. from __init__ import createPeerID
  35. try:
  36. True
  37. except:
  38. True = 1
  39. False = 0
  40. defaults = [
  41. ('max_uploads', 7,
  42. "the maximum number of uploads to allow at once."),
  43. ('keepalive_interval', 120.0,
  44. 'number of seconds to pause between sending keepalives'),
  45. ('download_slice_size', 2 ** 14,
  46. "How many bytes to query for per request."),
  47. ('upload_unit_size', 1460,
  48. "when limiting upload rate, how many bytes to send at a time"),
  49. ('request_backlog', 10,
  50. "maximum number of requests to keep in a single pipe at once."),
  51. ('max_message_length', 2 ** 23,
  52. "maximum length prefix encoding you'll accept over the wire - larger values get the connection dropped."),
  53. ('ip', '',
  54. "ip to report you have to the tracker."),
  55. ('minport', 10000, 'minimum port to listen on, counts up if unavailable'),
  56. ('maxport', 60000, 'maximum port to listen on'),
  57. ('random_port', 1, 'whether to choose randomly inside the port range ' +
  58. 'instead of counting up linearly'),
  59. ('responsefile', '',
  60. 'file the server response was stored in, alternative to url'),
  61. ('url', '',
  62. 'url to get file from, alternative to responsefile'),
  63. ('crypto_allowed', int(CRYPTO_OK),
  64. 'whether to allow the client to accept encrypted connections'),
  65. ('crypto_only', 0,
  66. 'whether to only create or allow encrypted connections'),
  67. ('crypto_stealth', 0,
  68. 'whether to prevent all non-encrypted connection attempts; ' +
  69. 'will result in an effectively firewalled state on older trackers'),
  70. ('selector_enabled', 1,
  71. 'whether to enable the file selector and fast resume function'),
  72. ('expire_cache_data', 10,
  73. 'the number of days after which you wish to expire old cache data ' +
  74. '(0 = disabled)'),
  75. ('priority', '',
  76. 'a list of file priorities separated by commas, must be one per file, ' +
  77. '0 = highest, 1 = normal, 2 = lowest, -1 = download disabled'),
  78. ('saveas', '',
  79. 'local file name to save the file as, null indicates query user'),
  80. ('timeout', 300.0,
  81. 'time to wait between closing sockets which nothing has been received on'),
  82. ('timeout_check_interval', 60.0,
  83. 'time to wait between checking if any connections have timed out'),
  84. ('max_slice_length', 2 ** 17,
  85. "maximum length slice to send to peers, larger requests are ignored"),
  86. ('max_rate_period', 20.0,
  87. "maximum amount of time to guess the current rate estimate represents"),
  88. ('bind', '',
  89. 'comma-separated list of ips/hostnames to bind to locally'),
  90. # ('ipv6_enabled', autodetect_ipv6(),
  91. ('ipv6_enabled', 0,
  92. 'allow the client to connect to peers via IPv6'),
  93. ('ipv6_binds_v4', autodetect_socket_style(),
  94. "set if an IPv6 server socket won't also field IPv4 connections"),
  95. ('upnp_nat_access', 1,
  96. 'attempt to autoconfigure a UPnP router to forward a server port ' +
  97. '(0 = disabled, 1 = mode 1 [fast], 2 = mode 2 [slow])'),
  98. ('upload_rate_fudge', 5.0,
  99. 'time equivalent of writing to kernel-level TCP buffer, for rate adjustment'),
  100. ('tcp_ack_fudge', 0.03,
  101. 'how much TCP ACK download overhead to add to upload rate calculations ' +
  102. '(0 = disabled)'),
  103. ('display_interval', .5,
  104. 'time between updates of displayed information'),
  105. ('rerequest_interval', 5 * 60,
  106. 'time to wait between requesting more peers'),
  107. ('min_peers', 20,
  108. 'minimum number of peers to not do rerequesting'),
  109. ('http_timeout', 60,
  110. 'number of seconds to wait before assuming that an http connection has timed out'),
  111. ('max_initiate', 40,
  112. 'number of peers at which to stop initiating new connections'),
  113. ('check_hashes', 1,
  114. 'whether to check hashes on disk'),
  115. ('max_upload_rate', 0,
  116. 'maximum kB/s to upload at (0 = no limit, -1 = automatic)'),
  117. ('max_download_rate', 0,
  118. 'maximum kB/s to download at (0 = no limit)'),
  119. ('alloc_type', 'normal',
  120. 'allocation type (may be normal, background, pre-allocate or sparse)'),
  121. ('alloc_rate', 2.0,
  122. 'rate (in MiB/s) to allocate space at using background allocation'),
  123. ('buffer_reads', 1,
  124. 'whether to buffer disk reads'),
  125. ('write_buffer_size', 4,
  126. 'the maximum amount of space to use for buffering disk writes ' +
  127. '(in megabytes, 0 = disabled)'),
  128. ('breakup_seed_bitfield', 1,
  129. 'sends an incomplete bitfield and then fills with have messages, '
  130. 'in order to get around stupid ISP manipulation'),
  131. ('snub_time', 30.0,
  132. "seconds to wait for data to come in over a connection before assuming it's semi-permanently choked"),
  133. ('spew', 0,
  134. "whether to display diagnostic info to stdout"),
  135. ('rarest_first_cutoff', 2,
  136. "number of downloads at which to switch from random to rarest first"),
  137. ('rarest_first_priority_cutoff', 5,
  138. 'the number of peers which need to have a piece before other partials take priority over rarest first'),
  139. ('min_uploads', 4,
  140. "the number of uploads to fill out to with extra optimistic unchokes"),
  141. ('max_files_open', 50,
  142. 'the maximum number of files to keep open at a time, 0 means no limit'),
  143. ('round_robin_period', 30,
  144. "the number of seconds between the client's switching upload targets"),
  145. ('super_seeder', 0,
  146. "whether to use special upload-efficiency-maximizing routines (only for dedicated seeds)"),
  147. ('security', 1,
  148. "whether to enable extra security features intended to prevent abuse"),
  149. ('max_connections', 0,
  150. "the absolute maximum number of peers to connect with (0 = no limit)"),
  151. ('auto_kick', 1,
  152. "whether to allow the client to automatically kick/ban peers that send bad data"),
  153. ('double_check', 1,
  154. "whether to double-check data being written to the disk for errors (may increase CPU load)"),
  155. ('triple_check', 0,
  156. "whether to thoroughly check data being written to the disk (may slow disk access)"),
  157. ('lock_files', 1,
  158. "whether to lock files the client is working with"),
  159. ('lock_while_reading', 0,
  160. "whether to lock access to files being read"),
  161. ('auto_flush', 0,
  162. "minutes between automatic flushes to disk (0 = disabled)"),
  163. ('dedicated_seed_id', '',
  164. "code to send to tracker identifying as a dedicated seed"),
  165. ]
  166. argslistheader = 'Arguments are:\n\n'
  167. def _failfunc(x):
  168. print x
  169. # old-style downloader
  170. def download(params, filefunc, statusfunc, finfunc, errorfunc, doneflag, cols,
  171. pathFunc = None, presets = {}, exchandler = None,
  172. failed = _failfunc, paramfunc = None):
  173. try:
  174. config = parse_params(params, presets)
  175. except ValueError, e:
  176. failed('error: ' + str(e) + '\nrun with no args for parameter explanations')
  177. return
  178. if not config:
  179. errorfunc(get_usage())
  180. return
  181. myid = createPeerID()
  182. seed(myid)
  183. rawserver = RawServer(doneflag, config['timeout_check_interval'],
  184. config['timeout'], ipv6_enable = config['ipv6_enabled'],
  185. failfunc = failed, errorfunc = exchandler)
  186. upnp_type = UPnP_test(config['upnp_nat_access'])
  187. try:
  188. listen_port = rawserver.find_and_bind(config['minport'], config['maxport'],
  189. config['bind'], ipv6_socket_style = config['ipv6_binds_v4'],
  190. upnp = upnp_type, randomizer = config['random_port'])
  191. except socketerror, e:
  192. failed("Couldn't listen - " + str(e))
  193. return
  194. response = get_response(config['responsefile'], config['url'], failed)
  195. if not response:
  196. return
  197. infohash = sha(bencode(response['info'])).digest()
  198. d = BT1Download(statusfunc, finfunc, errorfunc, exchandler, doneflag,
  199. config, response, infohash, myid, rawserver, listen_port)
  200. if not d.saveAs(filefunc):
  201. return
  202. if pathFunc:
  203. pathFunc(d.getFilename())
  204. hashcheck = d.initFiles(old_style = True)
  205. if not hashcheck:
  206. return
  207. if not hashcheck():
  208. return
  209. if not d.startEngine():
  210. return
  211. d.startRerequester()
  212. d.autoStats()
  213. statusfunc(activity = 'connecting to peers')
  214. if paramfunc:
  215. paramfunc({ 'max_upload_rate' : d.setUploadRate, # change_max_upload_rate(<int KiB/sec>)
  216. 'max_uploads': d.setConns, # change_max_uploads(<int max uploads>)
  217. 'listen_port' : listen_port, # int
  218. 'peer_id' : myid, # string
  219. 'info_hash' : infohash, # string
  220. 'start_connection' : d._startConnection, # start_connection((<string ip>, <int port>), <peer id>)
  221. })
  222. rawserver.listen_forever(d.getPortHandler())
  223. d.shutdown()
  224. def parse_params(params, presets = {}):
  225. if len(params) == 0:
  226. return None
  227. config, args = parseargs(params, defaults, 0, 1, presets = presets)
  228. if args:
  229. if config['responsefile'] or config['url']:
  230. raise ValueError,'must have responsefile or url as arg or parameter, not both'
  231. if path.isfile(args[0]):
  232. config['responsefile'] = args[0]
  233. else:
  234. try:
  235. urlparse(args[0])
  236. except:
  237. raise ValueError, 'bad filename or url'
  238. config['url'] = args[0]
  239. elif (config['responsefile'] == '') == (config['url'] == ''):
  240. raise ValueError, 'need responsefile or url, must have one, cannot have both'
  241. return config
  242. def get_usage(defaults = defaults, cols = 100, presets = {}):
  243. return (argslistheader + formatDefinitions(defaults, cols, presets))
  244. def get_response(file, url, errorfunc):
  245. try:
  246. if file:
  247. h = open(file, 'rb')
  248. try:
  249. line = h.read(10) # quick test to see if responsefile contains a dict
  250. front,garbage = line.split(':',1)
  251. assert front[0] == 'd'
  252. int(front[1:])
  253. except:
  254. errorfunc(file+' is not a valid responsefile')
  255. return None
  256. try:
  257. h.seek(0)
  258. except:
  259. try:
  260. h.close()
  261. except:
  262. pass
  263. h = open(file, 'rb')
  264. else:
  265. try:
  266. h = urlopen(url)
  267. except:
  268. errorfunc(url+' bad url')
  269. return None
  270. response = h.read()
  271. except IOError, e:
  272. errorfunc('problem getting response info - ' + str(e))
  273. return None
  274. try:
  275. h.close()
  276. except:
  277. pass
  278. try:
  279. try:
  280. response = bdecode(response)
  281. except:
  282. errorfunc("warning: bad data in responsefile")
  283. response = bdecode(response, sloppy=1)
  284. check_message(response)
  285. except ValueError, e:
  286. errorfunc("got bad file info - " + str(e))
  287. return None
  288. return response
  289. class BT1Download:
  290. def __init__(self, statusfunc, finfunc, errorfunc, excfunc, doneflag,
  291. config, response, infohash, id, rawserver, port,
  292. appdataobj = None):
  293. self.statusfunc = statusfunc
  294. self.finfunc = finfunc
  295. self.errorfunc = errorfunc
  296. self.excfunc = excfunc
  297. self.doneflag = doneflag
  298. self.config = config
  299. self.response = response
  300. self.infohash = infohash
  301. self.myid = id
  302. self.rawserver = rawserver
  303. self.port = port
  304. self.info = self.response['info']
  305. self.pieces = [self.info['pieces'][x:x+20]
  306. for x in xrange(0, len(self.info['pieces']), 20)]
  307. self.len_pieces = len(self.pieces)
  308. self.argslistheader = argslistheader
  309. self.unpauseflag = Event()
  310. self.unpauseflag.set()
  311. self.downloader = None
  312. self.storagewrapper = None
  313. self.fileselector = None
  314. self.super_seeding_active = False
  315. self.filedatflag = Event()
  316. self.spewflag = Event()
  317. self.superseedflag = Event()
  318. self.whenpaused = None
  319. self.finflag = Event()
  320. self.rerequest = None
  321. self.tcp_ack_fudge = config['tcp_ack_fudge']
  322. self.selector_enabled = config['selector_enabled']
  323. if appdataobj:
  324. self.appdataobj = appdataobj
  325. elif self.selector_enabled:
  326. self.appdataobj = ConfigDir()
  327. self.appdataobj.deleteOldCacheData( config['expire_cache_data'],
  328. [self.infohash] )
  329. self.excflag = self.rawserver.get_exception_flag()
  330. self.failed = False
  331. self.checking = False
  332. self.started = False
  333. self.picker = PiecePicker(self.len_pieces, config['rarest_first_cutoff'],
  334. config['rarest_first_priority_cutoff'])
  335. self.choker = Choker(config, rawserver.add_task,
  336. self.picker, self.finflag.isSet)
  337. def checkSaveLocation(self, loc):
  338. if self.info.has_key('length'):
  339. return path.exists(loc)
  340. for x in self.info['files']:
  341. if path.exists(path.join(loc, x['path'][0])):
  342. return True
  343. return False
  344. def saveAs(self, filefunc, pathfunc = None):
  345. try:
  346. def make(f, forcedir = False):
  347. if not forcedir:
  348. f = path.split(f)[0]
  349. if f != '' and not path.exists(f):
  350. makedirs(f)
  351. if self.info.has_key('length'):
  352. file_length = self.info['length']
  353. file = filefunc(self.info['name'], file_length,
  354. self.config['saveas'], False)
  355. if file is None:
  356. return None
  357. make(file)
  358. files = [(file, file_length)]
  359. else:
  360. file_length = 0L
  361. for x in self.info['files']:
  362. file_length += x['length']
  363. file = filefunc(self.info['name'], file_length,
  364. self.config['saveas'], True)
  365. if file is None:
  366. return None
  367. # if this path exists, and no files from the info dict exist, we assume it's a new download and
  368. # the user wants to create a new directory with the default name
  369. existing = 0
  370. if path.exists(file):
  371. if not path.isdir(file):
  372. self.errorfunc(file + 'is not a dir')
  373. return None
  374. if len(listdir(file)) > 0: # if it's not empty
  375. for x in self.info['files']:
  376. if path.exists(path.join(file, x['path'][0])):
  377. existing = 1
  378. if not existing:
  379. file = path.join(file, self.info['name'])
  380. if path.exists(file) and not path.isdir(file):
  381. if file[-8:] == '.torrent':
  382. file = file[:-8]
  383. if path.exists(file) and not path.isdir(file):
  384. self.errorfunc("Can't create dir - " + self.info['name'])
  385. return None
  386. make(file, True)
  387. # alert the UI to any possible change in path
  388. if pathfunc != None:
  389. pathfunc(file)
  390. files = []
  391. for x in self.info['files']:
  392. n = file
  393. for i in x['path']:
  394. n = path.join(n, i)
  395. files.append((n, x['length']))
  396. make(n)
  397. except OSError, e:
  398. self.errorfunc("Couldn't allocate dir - " + str(e))
  399. return None
  400. self.filename = file
  401. self.files = files
  402. self.datalength = file_length
  403. return file
  404. def getFilename(self):
  405. return self.filename
  406. def _finished(self):
  407. self.finflag.set()
  408. try:
  409. self.storage.set_readonly()
  410. except (IOError, OSError), e:
  411. self.errorfunc('trouble setting readonly at end - ' + str(e))
  412. if self.superseedflag.isSet():
  413. self._set_super_seed()
  414. self.choker.set_round_robin_period(
  415. max( self.config['round_robin_period'],
  416. self.config['round_robin_period'] *
  417. self.info['piece length'] / 200000 ) )
  418. self.rerequest_complete()
  419. self.finfunc()
  420. def _data_flunked(self, amount, index):
  421. self.ratemeasure_datarejected(amount)
  422. if not self.doneflag.isSet():
  423. self.errorfunc('piece %d failed hash check, re-downloading it' % index)
  424. def _failed(self, reason):
  425. self.failed = True
  426. self.doneflag.set()
  427. if reason is not None:
  428. self.errorfunc(reason)
  429. def initFiles(self, old_style = False, statusfunc = None):
  430. if self.doneflag.isSet():
  431. return None
  432. if not statusfunc:
  433. statusfunc = self.statusfunc
  434. disabled_files = None
  435. if self.selector_enabled:
  436. self.priority = self.config['priority']
  437. if self.priority:
  438. try:
  439. self.priority = self.priority.split(',')
  440. assert len(self.priority) == len(self.files)
  441. self.priority = [int(p) for p in self.priority]
  442. for p in self.priority:
  443. assert p >= -1
  444. assert p <= 2
  445. except:
  446. self.errorfunc('bad priority list given, ignored')
  447. self.priority = None
  448. data = self.appdataobj.getTorrentData(self.infohash)
  449. try:
  450. d = data['resume data']['priority']
  451. assert len(d) == len(self.files)
  452. disabled_files = [x == -1 for x in d]
  453. except:
  454. try:
  455. disabled_files = [x == -1 for x in self.priority]
  456. except:
  457. pass
  458. try:
  459. try:
  460. self.storage = Storage(self.files, self.info['piece length'],
  461. self.doneflag, self.config, disabled_files)
  462. except IOError, e:
  463. self.errorfunc('trouble accessing files - ' + str(e))
  464. return None
  465. if self.doneflag.isSet():
  466. return None
  467. self.storagewrapper = StorageWrapper(self.storage, self.config['download_slice_size'],
  468. self.pieces, self.info['piece length'], self._finished, self._failed,
  469. statusfunc, self.doneflag, self.config['check_hashes'],
  470. self._data_flunked, self.rawserver.add_task,
  471. self.config, self.unpauseflag)
  472. except ValueError, e:
  473. self._failed('bad data - ' + str(e))
  474. except IOError, e:
  475. self._failed('IOError - ' + str(e))
  476. if self.doneflag.isSet():
  477. return None
  478. if self.selector_enabled:
  479. self.fileselector = FileSelector(self.files, self.info['piece length'],
  480. self.appdataobj.getPieceDir(self.infohash),
  481. self.storage, self.storagewrapper,
  482. self.rawserver.add_task,
  483. self._failed)
  484. if data:
  485. data = data.get('resume data')
  486. if data:
  487. self.fileselector.unpickle(data)
  488. self.checking = True
  489. if old_style:
  490. return self.storagewrapper.old_style_init()
  491. return self.storagewrapper.initialize
  492. def getCachedTorrentData(self):
  493. return self.appdataobj.getTorrentData(self.infohash)
  494. def _make_upload(self, connection, ratelimiter, totalup):
  495. return Upload(connection, ratelimiter, totalup,
  496. self.choker, self.storagewrapper, self.picker,
  497. self.config)
  498. def _kick_peer(self, connection):
  499. def k(connection = connection):
  500. connection.close()
  501. self.rawserver.add_task(k,0)
  502. def _ban_peer(self, ip):
  503. self.encoder_ban(ip)
  504. def _received_raw_data(self, x):
  505. if self.tcp_ack_fudge:
  506. x = int(x*self.tcp_ack_fudge)
  507. self.ratelimiter.adjust_sent(x)
  508. def _received_data(self, x):
  509. self.downmeasure.update_rate(x)
  510. self.ratemeasure.data_came_in(x)
  511. def _received_http_data(self, x):
  512. self.downmeasure.update_rate(x)
  513. self.ratemeasure.data_came_in(x)
  514. self.downloader.external_data_received(x)
  515. def _cancelfunc(self, pieces):
  516. self.downloader.cancel_piece_download(pieces)
  517. self.httpdownloader.cancel_piece_download(pieces)
  518. def _reqmorefunc(self, pieces):
  519. self.downloader.requeue_piece_download(pieces)
  520. def startEngine(self, ratelimiter = None, statusfunc = None):
  521. if self.doneflag.isSet():
  522. return False
  523. if not statusfunc:
  524. statusfunc = self.statusfunc
  525. self.checking = False
  526. if not CRYPTO_OK:
  527. if self.config['crypto_allowed']:
  528. self.errorfunc('warning - crypto library not installed')
  529. self.config['crypto_allowed'] = 0
  530. self.config['crypto_only'] = 0
  531. self.config['crypto_stealth'] = 0
  532. for i in xrange(self.len_pieces):
  533. if self.storagewrapper.do_I_have(i):
  534. self.picker.complete(i)
  535. self.upmeasure = Measure(self.config['max_rate_period'],
  536. self.config['upload_rate_fudge'])
  537. self.downmeasure = Measure(self.config['max_rate_period'])
  538. if ratelimiter:
  539. self.ratelimiter = ratelimiter
  540. else:
  541. self.ratelimiter = RateLimiter(self.rawserver.add_task,
  542. self.config['upload_unit_size'],
  543. self.setConns)
  544. self.ratelimiter.set_upload_rate(self.config['max_upload_rate'])
  545. self.ratemeasure = RateMeasure()
  546. self.ratemeasure_datarejected = self.ratemeasure.data_rejected
  547. self.downloader = Downloader(self.storagewrapper, self.picker,
  548. self.config['request_backlog'], self.config['max_rate_period'],
  549. self.len_pieces, self.config['download_slice_size'],
  550. self._received_data, self.config['snub_time'], self.config['auto_kick'],
  551. self._kick_peer, self._ban_peer)
  552. self.downloader.set_download_rate(self.config['max_download_rate'])
  553. self.connecter = Connecter(self._make_upload, self.downloader, self.choker,
  554. self.len_pieces, self.upmeasure, self.config,
  555. self.ratelimiter, self.rawserver.add_task)
  556. self.encoder = Encoder(self.connecter, self.rawserver,
  557. self.myid, self.config['max_message_length'], self.rawserver.add_task,
  558. self.config['keepalive_interval'], self.infohash,
  559. self._received_raw_data, self.config)
  560. self.encoder_ban = self.encoder.ban
  561. self.httpdownloader = HTTPDownloader(self.storagewrapper, self.picker,
  562. self.rawserver, self.finflag, self.errorfunc, self.downloader,
  563. self.config['max_rate_period'], self.infohash, self._received_http_data,
  564. self.connecter.got_piece)
  565. if self.response.has_key('httpseeds') and not self.finflag.isSet():
  566. for u in self.response['httpseeds']:
  567. self.httpdownloader.make_download(u)
  568. if self.selector_enabled:
  569. self.fileselector.tie_in(self.picker, self._cancelfunc,
  570. self._reqmorefunc, self.rerequest_ondownloadmore)
  571. if self.priority:
  572. self.fileselector.set_priorities_now(self.priority)
  573. self.appdataobj.deleteTorrentData(self.infohash)
  574. # erase old data once you've started modifying it
  575. if self.config['super_seeder']:
  576. self.set_super_seed()
  577. self.started = True
  578. return True
  579. def rerequest_complete(self):
  580. if self.rerequest:
  581. self.rerequest.announce(1)
  582. def rerequest_stopped(self):
  583. if self.rerequest:
  584. self.rerequest.announce(2)
  585. def rerequest_lastfailed(self):
  586. if self.rerequest:
  587. return self.rerequest.last_failed
  588. return False
  589. def rerequest_ondownloadmore(self):
  590. if self.rerequest:
  591. self.rerequest.hit()
  592. def startRerequester(self, seededfunc = None, force_rapid_update = False):
  593. if self.response.has_key('announce-list'):
  594. trackerlist = self.response['announce-list']
  595. else:
  596. trackerlist = [[self.response['announce']]]
  597. self.rerequest = Rerequester(self.port, self.myid, self.infohash,
  598. trackerlist, self.config,
  599. self.rawserver.add_task, self.rawserver.add_task,
  600. self.errorfunc, self.excfunc,
  601. self.encoder.start_connections,
  602. self.connecter.how_many_connections,
  603. self.storagewrapper.get_amount_left,
  604. self.upmeasure.get_total, self.downmeasure.get_total,
  605. self.upmeasure.get_rate, self.downmeasure.get_rate,
  606. self.doneflag, self.unpauseflag, seededfunc, force_rapid_update )
  607. self.rerequest.start()
  608. def _init_stats(self):
  609. self.statistics = Statistics(self.upmeasure, self.downmeasure,
  610. self.connecter, self.httpdownloader, self.ratelimiter,
  611. self.rerequest_lastfailed, self.filedatflag)
  612. if self.info.has_key('files'):
  613. self.statistics.set_dirstats(self.files, self.info['piece length'])
  614. if self.config['spew']:
  615. self.spewflag.set()
  616. def autoStats(self, displayfunc = None):
  617. if not displayfunc:
  618. displayfunc = self.statusfunc
  619. self._init_stats()
  620. DownloaderFeedback(self.choker, self.httpdownloader, self.rawserver.add_task,
  621. self.upmeasure.get_rate, self.downmeasure.get_rate,
  622. self.ratemeasure, self.storagewrapper.get_stats,
  623. self.datalength, self.finflag, self.spewflag, self.statistics,
  624. displayfunc, self.config['display_interval'])
  625. def startStats(self):
  626. self._init_stats()
  627. d = DownloaderFeedback(self.choker, self.httpdownloader, self.rawserver.add_task,
  628. self.upmeasure.get_rate, self.downmeasure.get_rate,
  629. self.ratemeasure, self.storagewrapper.get_stats,
  630. self.datalength, self.finflag, self.spewflag, self.statistics)
  631. return d.gather
  632. def getPortHandler(self):
  633. return self.encoder
  634. def shutdown(self, torrentdata = {}):
  635. if self.checking or self.started:
  636. self.storagewrapper.sync()
  637. self.storage.close()
  638. self.rerequest_stopped()
  639. if self.fileselector and self.started:
  640. if not self.failed:
  641. self.fileselector.finish()
  642. torrentdata['resume data'] = self.fileselector.pickle()
  643. try:
  644. self.appdataobj.writeTorrentData(self.infohash,torrentdata)
  645. except:
  646. self.appdataobj.deleteTorrentData(self.infohash) # clear it
  647. return not self.failed and not self.excflag.isSet()
  648. # if returns false, you may wish to auto-restart the torrent
  649. def setUploadRate(self, rate):
  650. try:
  651. def s(self = self, rate = rate):
  652. self.config['max_upload_rate'] = rate
  653. self.ratelimiter.set_upload_rate(rate)
  654. self.rawserver.add_task(s)
  655. except AttributeError:
  656. pass
  657. def setConns(self, conns, conns2 = None):
  658. if not conns2:
  659. conns2 = conns
  660. try:
  661. def s(self = self, conns = conns, conns2 = conns2):
  662. self.config['min_uploads'] = conns
  663. self.config['max_uploads'] = conns2
  664. if (conns > 30):
  665. self.config['max_initiate'] = conns + 10
  666. self.rawserver.add_task(s)
  667. except AttributeError:
  668. pass
  669. def setDownloadRate(self, rate):
  670. try:
  671. def s(self = self, rate = rate):
  672. self.config['max_download_rate'] = rate
  673. self.downloader.set_download_rate(rate)
  674. self.rawserver.add_task(s)
  675. except AttributeError:
  676. pass
  677. def startConnection(self, ip, port, id):
  678. self.encoder._start_connection((ip, port), id)
  679. def _startConnection(self, ipandport, id):
  680. self.encoder._start_connection(ipandport, id)
  681. def setInitiate(self, initiate):
  682. try:
  683. def s(self = self, initiate = initiate):
  684. self.config['max_initiate'] = initiate
  685. self.rawserver.add_task(s)
  686. except AttributeError:
  687. pass
  688. def getConfig(self):
  689. return self.config
  690. def getDefaults(self):
  691. return defaultargs(defaults)
  692. def getUsageText(self):
  693. return self.argslistheader
  694. def reannounce(self, special = None):
  695. try:
  696. def r(self = self, special = special):
  697. if special is None:
  698. self.rerequest.announce()
  699. else:
  700. self.rerequest.announce(specialurl = special)
  701. self.rawserver.add_task(r)
  702. except AttributeError:
  703. pass
  704. def getResponse(self):
  705. try:
  706. return self.response
  707. except:
  708. return None
  709. def Pause(self):
  710. if not self.storagewrapper:
  711. return False
  712. self.unpauseflag.clear()
  713. self.rawserver.add_task(self.onPause)
  714. return True
  715. def onPause(self):
  716. self.whenpaused = clock()
  717. if not self.downloader:
  718. return
  719. self.downloader.pause(True)
  720. self.encoder.pause(True)
  721. self.choker.pause(True)
  722. def Unpause(self):
  723. self.unpauseflag.set()
  724. self.rawserver.add_task(self.onUnpause)
  725. def onUnpause(self):
  726. if not self.downloader:
  727. return
  728. self.downloader.pause(False)
  729. self.encoder.pause(False)
  730. self.choker.pause(False)
  731. if self.rerequest and self.whenpaused and clock()-self.whenpaused > 60:
  732. self.rerequest.announce(3) # rerequest automatically if paused for >60 seconds
  733. def set_super_seed(self):
  734. try:
  735. self.superseedflag.set()
  736. def s(self = self):
  737. if self.finflag.isSet():
  738. self._set_super_seed()
  739. self.rawserver.add_task(s)
  740. except AttributeError:
  741. pass
  742. def _set_super_seed(self):
  743. if not self.super_seeding_active:
  744. self.super_seeding_active = True
  745. self.errorfunc(' ** SUPER-SEED OPERATION ACTIVE **\n' +
  746. ' please set Max uploads so each peer gets 6-8 kB/s')
  747. def s(self = self):
  748. self.downloader.set_super_seed()
  749. self.choker.set_super_seed()
  750. self.rawserver.add_task(s)
  751. if self.finflag.isSet(): # mode started when already finished
  752. def r(self = self):
  753. self.rerequest.announce(3) # so after kicking everyone off, reannounce
  754. self.rawserver.add_task(r)
  755. def am_I_finished(self):
  756. return self.finflag.isSet()
  757. def get_transfer_stats(self):
  758. return self.upmeasure.get_total(), self.downmeasure.get_total()