tfmainline.py 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789
  1. #!/usr/bin/env python
  2. ################################################################################
  3. # $Id: tfmainline.py 2535 2007-02-06 22:20:57Z b4rt $
  4. # $Revision: 2535 $
  5. # $Date: 2007-02-06 16:20:57 -0600 (Tue, 06 Feb 2007) $
  6. ################################################################################
  7. #
  8. # The contents of this file are subject to the BitTorrent Open Source License
  9. # Version 1.1 (the License). You may not copy or use this file, in either
  10. # source code or executable form, except in compliance with the License. You
  11. # may obtain a copy of the License at http://www.bittorrent.com/license/.
  12. #
  13. # Software distributed under the License is distributed on an AS IS basis,
  14. # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  15. # for the specific language governing rights and limitations under the
  16. # License.
  17. #
  18. # Written by Bram Cohen, Uoti Urpala, John Hoffman, and David Harrison
  19. #
  20. ################################################################################
  21. #
  22. # tfmainline.py - use BitTorrent ("mainline") with torrentflux-b4rt
  23. # http://tf-b4rt.berlios.de/
  24. #
  25. ################################################################################
  26. from __future__ import division
  27. app_name = "BitTorrent"
  28. from BitTorrent.translation import _
  29. import sys
  30. import os
  31. from os import getpid, remove
  32. from os.path import isfile
  33. from cStringIO import StringIO
  34. import logging
  35. from logging import ERROR, WARNING
  36. from time import strftime, sleep
  37. import traceback
  38. import BTL.stackthreading as threading
  39. from BTL.platform import decode_from_filesystem, encode_for_filesystem
  40. from BitTorrent.platform import get_dot_dir
  41. from BTL.defer import DeferredEvent
  42. from BitTorrent import inject_main_logfile
  43. from BitTorrent.MultiTorrent import Feedback, MultiTorrent
  44. from BitTorrent.defaultargs import get_defaults
  45. from BitTorrent.parseargs import printHelp
  46. from BitTorrent.prefs import Preferences
  47. from BitTorrent import configfile
  48. from BitTorrent import BTFailure, UserFailure
  49. from BitTorrent import version
  50. from BitTorrent import GetTorrent
  51. from BTL.ConvertedMetainfo import ConvertedMetainfo
  52. from BitTorrent.MultiTorrent import TorrentNotInitialized
  53. from BitTorrent.RawServer_twisted import RawServer
  54. from twisted.internet import task
  55. from BitTorrent.UI import Size, Duration
  56. inject_main_logfile()
  57. from BitTorrent import console
  58. from BitTorrent import stderr_console
  59. def wrap_log(context_string, logger):
  60. """Useful when passing a logger to a deferred's errback. The context
  61. specifies what was being done when the exception was raised."""
  62. return lambda e, *args, **kwargs : logger.error(context_string, exc_info=e)
  63. def fmtsize(n):
  64. s = str(n)
  65. size = s[-3:]
  66. while len(s) > 3:
  67. s = s[:-3]
  68. size = '%s,%s' % (s[-3:], size)
  69. size = '%s (%s)' % (size, str(Size(n)))
  70. return size
  71. #------------------------------------------------------------------------------#
  72. # tfb static methods #
  73. #------------------------------------------------------------------------------#
  74. def fmttime(n):
  75. """ fmttime """
  76. # short format :
  77. return fmttimeshort(n)
  78. # long format :
  79. # return fmttimelong(n)
  80. def fmttimeshort(n):
  81. """ fmttimeshort """
  82. if n == 0:
  83. return 'complete!'
  84. try:
  85. n = int(n)
  86. assert n >= 0 and n < 5184000 # 60 days
  87. except:
  88. return '<unknown>'
  89. m, s = divmod(n, 60)
  90. h, m = divmod(m, 60)
  91. d, h = divmod(h, 24)
  92. if d >= 7:
  93. return '-'
  94. elif d > 0:
  95. return '%dd %02d:%02d:%02d' % (d, h, m, s)
  96. else:
  97. return '%02d:%02d:%02d' % (h, m, s)
  98. def fmttimelong(n):
  99. """ fmttimelong """
  100. if n == 0:
  101. return 'complete!'
  102. try:
  103. n = int(n)
  104. assert n >= 0 and n < 5184000 # 60 days
  105. except:
  106. return '<unknown>'
  107. m, s = divmod(n, 60)
  108. h, m = divmod(m, 60)
  109. d, h = divmod(h, 24)
  110. y, d = divmod(d, 365)
  111. dec, y = divmod(y, 10)
  112. cent, dec = divmod(dec, 10)
  113. if cent > 0:
  114. return '%dcent %ddec %dy %dd %02d:%02d:%02d' % (cent, dec, y, d, h, m, s)
  115. elif dec > 0:
  116. return '%ddec %dy %dd %02d:%02d:%02d' % (dec, y, d, h, m, s)
  117. elif y > 0:
  118. return '%dy %dd %02d:%02d:%02d' % (y, d, h, m, s)
  119. elif d > 0:
  120. return '%dd %02d:%02d:%02d' % (d, h, m, s)
  121. else:
  122. return '%02d:%02d:%02d' % (h, m, s)
  123. def transferLog(message, ts):
  124. """ transferLog """
  125. try:
  126. FILE = open(transferLogFile,"a+")
  127. if not ts:
  128. FILE.write(message)
  129. else:
  130. FILE.write(strftime('[%Y/%m/%d - %H:%M:%S]') + " " + message)
  131. FILE.flush()
  132. FILE.close()
  133. except Exception, e:
  134. sys.stderr.write("Failed to write log-file : " + transferLogFile + "\n")
  135. #------------------------------------------------------------------------------#
  136. # HeadlessDisplayer #
  137. #------------------------------------------------------------------------------#
  138. class HeadlessDisplayer(object):
  139. def __init__(self):
  140. self.done = False
  141. self.state = 1
  142. self.percentDone = ''
  143. self.timeEst = ''
  144. self.downRate = '---'
  145. self.upRate = '---'
  146. self.shareRating = ''
  147. self.seedStatus = ''
  148. self.peerStatus = ''
  149. self.errors = []
  150. self.file = ''
  151. self.downloadTo = ''
  152. self.fileSize = ''
  153. self.fileSize_stat = ''
  154. self.numpieces = 0
  155. self.tfOwner = config['tf_owner']
  156. self.seedLimit = config['seed_limit']
  157. self.dieWhenDone = config['die_when_done']
  158. self.isInShutDown = 0
  159. self.running = '1'
  160. self.displayCounter = 0
  161. def set_torrent_values(self, name, path, size, numpieces):
  162. self.file = name
  163. self.downloadTo = path
  164. self.fileSize = fmtsize(size)
  165. self.fileSize_stat = int(size)
  166. self.numpieces = numpieces
  167. def finished(self):
  168. """ this method is never called """
  169. self.done = True
  170. self.downRate = '---'
  171. self.display({'activity':_("download succeeded"), 'fractionDone':1})
  172. def error(self, errormsg):
  173. self.errors.append(errormsg)
  174. # log error
  175. transferLog("error: " + errormsg + "\n", True)
  176. def display(self, statistics):
  177. # only process when not in shutdown-sequence
  178. if self.isInShutDown == 0:
  179. # eta
  180. activity = statistics.get('activity')
  181. timeEst = statistics.get('timeEst')
  182. if timeEst is not None:
  183. self.timeEst = fmttime(timeEst)
  184. elif activity is not None:
  185. self.timeEst = activity
  186. # fractionDone
  187. fractionDone = statistics.get('fractionDone')
  188. if fractionDone is not None:
  189. self.percentDone = str(int(fractionDone * 1000) / 10)
  190. # downRate
  191. downRate = statistics.get('downRate')
  192. if downRate is not None:
  193. self.downRate = '%.1f kB/s' % (downRate / (1 << 10))
  194. # upRate
  195. upRate = statistics.get('upRate')
  196. if upRate is not None:
  197. self.upRate = '%.1f kB/s' % (upRate / (1 << 10))
  198. # totals
  199. downTotal = statistics.get('downTotal')
  200. upTotal = statistics.get('upTotal')
  201. if upTotal is None:
  202. upTotal = 0
  203. if downTotal is None:
  204. downTotal = 0
  205. # share-rating
  206. if downTotal > 0:
  207. if upTotal > 0:
  208. self.shareRating = _("%.3f") % ((upTotal * 100) / downTotal)
  209. else:
  210. self.shareRating = "0"
  211. else:
  212. self.shareRating = "oo"
  213. # seeds
  214. seeds = statistics.get('numSeeds')
  215. if seeds is None:
  216. seeds = 0
  217. # dht :
  218. numCopies = statistics.get('numCopies')
  219. if numCopies is not None:
  220. seeds += numCopies
  221. # set status
  222. self.seedStatus = _("%d") % seeds
  223. # peers
  224. peers = statistics.get('numPeers')
  225. if peers is not None:
  226. self.peerStatus = _("%d") % peers
  227. else:
  228. self.peerStatus = "0"
  229. # set some fields in app which we need in shutdown
  230. app.percentDone = self.percentDone
  231. app.shareRating = self.shareRating
  232. app.upTotal = upTotal
  233. app.downTotal = downTotal
  234. # process command-stack
  235. die = app.processCommandStack()
  236. if die:
  237. # shutdown
  238. self.execShutdown()
  239. return;
  240. # die-on-seed-limit / die-when-done
  241. if app.multitorrent.isDone:
  242. if self.dieWhenDone == 'True':
  243. transferLog("die-when-done set, setting shutdown-flag...\n", True)
  244. die = True
  245. else:
  246. seedLimitMax = int(self.seedLimit)
  247. if seedLimitMax > 0:
  248. totalShareRating = int(((upTotal * 100) / self.fileSize_stat))
  249. if totalShareRating >= seedLimitMax:
  250. transferLog("seed-limit "+str(self.seedLimit)+" reached, setting shutdown-flag...\n", True)
  251. die = True
  252. # shutdown / stat
  253. if die:
  254. # shutdown
  255. self.execShutdown()
  256. else:
  257. # write every 5 secs
  258. if self.displayCounter < 5:
  259. self.displayCounter += 1
  260. else:
  261. self.displayCounter = 0
  262. # write stat-file
  263. try:
  264. FILE = open(transferStatFile, "w")
  265. FILE.write(repr(self.state)+"\n")
  266. FILE.write(self.percentDone+"\n")
  267. FILE.write(self.timeEst+"\n")
  268. FILE.write(self.downRate+"\n")
  269. FILE.write(self.upRate+"\n")
  270. FILE.write(self.tfOwner+"\n")
  271. FILE.write(self.seedStatus+"\n")
  272. FILE.write(self.peerStatus+"\n")
  273. FILE.write(self.shareRating+"\n")
  274. FILE.write(self.seedLimit+"\n")
  275. FILE.write(repr(upTotal)+"\n")
  276. FILE.write(repr(downTotal)+"\n")
  277. FILE.write(repr(self.fileSize_stat))
  278. FILE.flush()
  279. FILE.close()
  280. except Exception, e:
  281. transferLog("Failed to write stat-file : " + transferStatFile + "\n", True)
  282. def execShutdown(self):
  283. """ execShutdown """
  284. # log
  285. transferLog("mainline shutting down...\n", True)
  286. # set flags
  287. self.state = 0
  288. self.isInShutDown = 1
  289. self.running = '0'
  290. # shutdown
  291. df = app.multitorrent.shutdown()
  292. stop_rawserver = lambda *a : app.multitorrent.rawserver.stop()
  293. df.addCallbacks(stop_rawserver, stop_rawserver)
  294. def print_spew(self, spew):
  295. s = StringIO()
  296. s.write('\n\n\n')
  297. for c in spew:
  298. s.write('%20s ' % c['ip'])
  299. if c['initiation'] == 'L':
  300. s.write('l')
  301. else:
  302. s.write('r')
  303. total, rate, interested, choked = c['upload']
  304. s.write(' %10s %10s ' % (str(int(total/10485.76)/100),
  305. str(int(rate))))
  306. if c['is_optimistic_unchoke']:
  307. s.write('*')
  308. else:
  309. s.write(' ')
  310. if interested:
  311. s.write('i')
  312. else:
  313. s.write(' ')
  314. if choked:
  315. s.write('c')
  316. else:
  317. s.write(' ')
  318. total, rate, interested, choked, snubbed = c['download']
  319. s.write(' %10s %10s ' % (str(int(total/10485.76)/100),
  320. str(int(rate))))
  321. if interested:
  322. s.write('i')
  323. else:
  324. s.write(' ')
  325. if choked:
  326. s.write('c')
  327. else:
  328. s.write(' ')
  329. if snubbed:
  330. s.write('s')
  331. else:
  332. s.write(' ')
  333. s.write('\n')
  334. print s.getvalue()
  335. #------------------------------------------------------------------------------#
  336. # TorrentApp #
  337. #------------------------------------------------------------------------------#
  338. class TorrentApp(object):
  339. class LogHandler(logging.Handler):
  340. def __init__(self, app, level=logging.NOTSET):
  341. logging.Handler.__init__(self,level)
  342. self.app = app
  343. def emit(self, record):
  344. self.app.display_error(record.getMessage() )
  345. if record.exc_info is not None:
  346. self.app.display_error( " %s: %s" %
  347. ( str(record.exc_info[0]), str(record.exc_info[1])))
  348. tb = record.exc_info[2]
  349. stack = traceback.extract_tb(tb)
  350. l = traceback.format_list(stack)
  351. for s in l:
  352. self.app.display_error( " %s" % s )
  353. class LogFilter(logging.Filter):
  354. def filter( self, record):
  355. if record.name == "NatTraversal":
  356. return 0
  357. return 1 # allow.
  358. def __init__(self, metainfo, config):
  359. assert isinstance(metainfo, ConvertedMetainfo )
  360. self.metainfo = metainfo
  361. self.config = Preferences().initWithDict(config)
  362. self.torrent = None
  363. self.multitorrent = None
  364. self.logger = logging.getLogger("bittorrent-console")
  365. log_handler = TorrentApp.LogHandler(self)
  366. log_handler.setLevel(WARNING)
  367. logger = logging.getLogger()
  368. logger.addHandler(log_handler)
  369. # some fields we need in shutdown
  370. self.percentDone = "0"
  371. self.shareRating = "0"
  372. self.upTotal = "0"
  373. self.downTotal = "0"
  374. # disable stdout and stderr error reporting to stderr.
  375. global stderr_console
  376. logging.getLogger('').removeHandler(console)
  377. if stderr_console is not None:
  378. logging.getLogger('').removeHandler(stderr_console)
  379. logging.getLogger().setLevel(WARNING)
  380. def start_torrent(self,metainfo,save_incomplete_as,save_as):
  381. """Tells the MultiTorrent to begin downloading."""
  382. try:
  383. self.d.display({'activity':_("initializing"), 'fractionDone':0})
  384. multitorrent = self.multitorrent
  385. df = multitorrent.create_torrent(metainfo, save_incomplete_as,
  386. save_as)
  387. df.addErrback( wrap_log('Failed to start torrent', self.logger))
  388. def create_finished(torrent):
  389. self.torrent = torrent
  390. if self.torrent.is_initialized():
  391. multitorrent.start_torrent(self.torrent.infohash)
  392. else:
  393. # HEREDAVE: why should this set the doneflag?
  394. self.core_doneflag.set() # e.g., if already downloading...
  395. df.addCallback( create_finished )
  396. except KeyboardInterrupt:
  397. raise
  398. except UserFailure, e:
  399. self.logger.error( "Failed to create torrent: " + unicode(e.args[0]) )
  400. except Exception, e:
  401. self.logger.error( "Failed to create torrent", exc_info = e )
  402. return
  403. def run(self):
  404. self.core_doneflag = DeferredEvent()
  405. rawserver = RawServer(self.config)
  406. self.d = HeadlessDisplayer()
  407. # set up shut-down procedure before we begin doing things that
  408. # can throw exceptions.
  409. def shutdown():
  410. print "shutdown."
  411. self.d.display({'activity':_("shutting down"), 'fractionDone':0})
  412. if self.multitorrent:
  413. df = self.multitorrent.shutdown()
  414. stop_rawserver = lambda *a : rawserver.stop()
  415. df.addCallbacks(stop_rawserver, stop_rawserver)
  416. else:
  417. rawserver.stop()
  418. # write pid-file
  419. currentPid = (str(getpid())).strip()
  420. transferLog("writing pid-file : " + transferPidFile + " (" + currentPid + ")\n", True)
  421. try:
  422. pidFile = open(transferPidFile, 'w')
  423. pidFile.write(currentPid + "\n")
  424. pidFile.flush()
  425. pidFile.close()
  426. except Exception, e:
  427. transferLog("Failed to write pid-file : " + transferPidFile + " (" + currentPid + ")" + "\n", True)
  428. raise BTFailure(_("Failed to write pid-file."))
  429. # It is safe to addCallback here, because there is only one thread,
  430. # but even if the code were multi-threaded, core_doneflag has not
  431. # been passed to anyone. There is no chance of a race condition
  432. # between core_doneflag's callback and addCallback.
  433. self.core_doneflag.addCallback(
  434. lambda r: rawserver.external_add_task(0, shutdown))
  435. rawserver.install_sigint_handler(self.core_doneflag)
  436. # semantics for --save_in vs --save_as:
  437. # save_in specifies the directory in which torrent is written.
  438. # If the torrent is a batch torrent then the files in the batch
  439. # go in save_in/metainfo.name_fs/.
  440. # save_as specifies the filename for the torrent in the case of
  441. # a non-batch torrent, and specifies the directory name
  442. # in the case of a batch torrent. Thus the files in a batch
  443. # torrent go in save_as/.
  444. metainfo = self.metainfo
  445. torrent_name = metainfo.name_fs # if batch then this contains
  446. # directory name.
  447. if config['save_as']:
  448. if config['save_in']:
  449. raise BTFailure(_("You cannot specify both --save_as and "
  450. "--save_in."))
  451. saveas,bad = encode_for_filesystem(config['save_as'])
  452. if bad:
  453. raise BTFailure(_("Invalid path encoding."))
  454. savein = os.path.dirname(os.path.abspath(saveas))
  455. elif config['save_in']:
  456. savein,bad = encode_for_filesystem(config['save_in'])
  457. if bad:
  458. raise BTFailure(_("Invalid path encoding."))
  459. saveas = os.path.join(savein,torrent_name)
  460. else:
  461. saveas = torrent_name
  462. if config['save_incomplete_in']:
  463. save_incomplete_in,bad = \
  464. encode_for_filesystem(config['save_incomplete_in'])
  465. if bad:
  466. raise BTFailure(_("Invalid path encoding."))
  467. save_incomplete_as = os.path.join(save_incomplete_in,torrent_name)
  468. else:
  469. save_incomplete_as = os.path.join(savein,torrent_name)
  470. data_dir,bad = encode_for_filesystem(config['data_dir'])
  471. if bad:
  472. raise BTFailure(_("Invalid path encoding."))
  473. try:
  474. self.multitorrent = \
  475. MultiTorrent(self.config, rawserver, data_dir,
  476. is_single_torrent = True,
  477. resume_from_torrent_config = False)
  478. self.d.set_torrent_values(metainfo.name, os.path.abspath(saveas),
  479. metainfo.total_bytes, len(metainfo.hashes))
  480. self.start_torrent(self.metainfo, save_incomplete_as, saveas)
  481. self.get_status()
  482. except UserFailure, e:
  483. self.logger.error( unicode(e.args[0]) )
  484. rawserver.add_task(0, self.core_doneflag.set)
  485. except Exception, e:
  486. self.logger.error( "", exc_info = e )
  487. rawserver.add_task(0, self.core_doneflag.set)
  488. # log that we are done with startup
  489. transferLog("mainline up and running.\n", True)
  490. # always make sure events get processed even if only for
  491. # shutting down.
  492. rawserver.listen_forever()
  493. # overwrite stat-file in "Torrent Stopped"/"Download Succeeded!" format.
  494. try:
  495. FILE = open(transferStatFile, "w")
  496. FILE.write("0\n")
  497. pcts = "-"+self.percentDone
  498. pctf = float(pcts)
  499. pctf -= 100
  500. FILE.write(str(pctf))
  501. FILE.write("\n")
  502. if self.multitorrent.isDone:
  503. FILE.write("Download Succeeded!\n")
  504. else:
  505. FILE.write("Torrent Stopped\n")
  506. FILE.write("\n")
  507. FILE.write("\n")
  508. FILE.write(self.d.tfOwner+"\n")
  509. FILE.write("\n")
  510. FILE.write("\n")
  511. FILE.write(self.shareRating+"\n")
  512. FILE.write(self.d.seedLimit+"\n")
  513. FILE.write(repr(self.upTotal)+"\n")
  514. FILE.write(repr(self.downTotal)+"\n")
  515. FILE.write(repr(self.d.fileSize_stat))
  516. FILE.flush()
  517. FILE.close()
  518. except Exception, e:
  519. transferLog("Failed to write stat-file : " + transferStatFile + "\n", True)
  520. def get_status(self):
  521. self.multitorrent.rawserver.add_task(self.config['display_interval'],
  522. self.get_status)
  523. if self.torrent is not None:
  524. status = self.torrent.get_status(self.config['spew'])
  525. self.d.display(status)
  526. def display_error(self, text):
  527. """Called by the logger via LogHandler to display error messages in the
  528. curses window."""
  529. self.d.error(text)
  530. def processCommandStack(self):
  531. """ processCommandStack """
  532. if isfile(transferCommandFile):
  533. # process file
  534. transferLog("Processing command-file " + transferCommandFile + "...\n", True)
  535. try:
  536. # read file to mem
  537. f = open(transferCommandFile, 'r')
  538. commands = f.readlines()
  539. f.close
  540. # remove file
  541. try:
  542. remove(transferCommandFile)
  543. except:
  544. transferLog("Failed to remove command-file : " + transferCommandFile + "\n", True)
  545. pass
  546. # exec commands
  547. if len(commands) > 0:
  548. for command in commands:
  549. command = command.replace("\n", "")
  550. if len(command) > 0:
  551. # exec, early out when reading a quit-command
  552. if self.execCommand(command):
  553. return True
  554. else:
  555. transferLog("No commands found.\n", True)
  556. except:
  557. transferLog("Failed to read command-file : " + transferCommandFile + "\n", True)
  558. pass
  559. return False
  560. def execCommand(self, command):
  561. """ execCommand """
  562. opCode = command[0]
  563. # q
  564. if opCode == 'q':
  565. transferLog("command: stop-request, setting shutdown-flag...\n", True)
  566. return True
  567. # u
  568. elif opCode == 'u':
  569. if len(command) < 2:
  570. transferLog("invalid rate.\n", True)
  571. return False
  572. rateNew = command[1:]
  573. transferLog("command: setting upload-rate to " + rateNew + "...\n", True)
  574. self.multitorrent.set_option('max_upload_rate', int(rateNew), None, False)
  575. return False
  576. # d
  577. elif opCode == 'd':
  578. if len(command) < 2:
  579. transferLog("invalid rate.\n", True)
  580. return False
  581. rateNew = command[1:]
  582. transferLog("command: setting download-rate to " + rateNew + "...\n", True)
  583. self.multitorrent.set_option('max_download_rate', int(rateNew), None, False)
  584. return False
  585. # r
  586. elif opCode == 'r':
  587. if len(command) < 2:
  588. transferLog("invalid runtime-code.\n", True)
  589. return False
  590. runtimeNew = command[1]
  591. rt = ''
  592. if runtimeNew == '0':
  593. rt = 'False'
  594. elif runtimeNew == '1':
  595. rt = 'True'
  596. else:
  597. transferLog("runtime-code unknown: " + runtimeNew + "\n", True)
  598. return False
  599. transferLog("command: setting die-when-done to " + rt + "...\n", True)
  600. self.d.dieWhenDone = rt
  601. return False
  602. # s
  603. elif opCode == 's':
  604. if len(command) < 2:
  605. transferLog("invalid sharekill.\n", True)
  606. return False
  607. sharekillNew = command[1:]
  608. transferLog("command: setting sharekill to " + sharekillNew + "...\n", True)
  609. self.d.seedLimit = sharekillNew
  610. return False
  611. # default
  612. else:
  613. transferLog("op-code unknown: " + opCode + "\n", True)
  614. return False
  615. #------------------------------------------------------------------------------#
  616. # __main__ #
  617. #------------------------------------------------------------------------------#
  618. if __name__ == '__main__':
  619. uiname = 'bittorrent-console'
  620. # args
  621. defaults = get_defaults(uiname)
  622. metainfo = None
  623. if len(sys.argv) <= 1:
  624. printHelp(uiname, defaults)
  625. sys.exit(1)
  626. try:
  627. # Modifying default values from get_defaults is annoying...
  628. # Implementing specific default values for each uiname in
  629. # defaultargs.py is even more annoying. --Dave
  630. data_dir = [[name, value,doc] for (name, value, doc) in defaults
  631. if name == "data_dir"][0]
  632. defaults = [(name, value,doc) for (name, value, doc) in defaults
  633. if not name == "data_dir"]
  634. ddir = os.path.join( get_dot_dir(), "console" )
  635. data_dir[1] = decode_from_filesystem(ddir)
  636. defaults.append( tuple(data_dir) )
  637. config, args = configfile.parse_configuration_and_args(defaults,
  638. uiname, sys.argv[1:], 0, 1)
  639. torrentfile = None
  640. if len(args):
  641. torrentfile = args[0]
  642. if torrentfile is not None:
  643. try:
  644. metainfo = GetTorrent.get(torrentfile)
  645. except GetTorrent.GetTorrentException, e:
  646. raise UserFailure(_("Error reading .torrent file: ") + '\n' + unicode(e.args[0]))
  647. else:
  648. raise UserFailure(_("you must specify a .torrent file"))
  649. except BTFailure, e:
  650. print unicode(e.args[0])
  651. sys.exit(1)
  652. except KeyboardInterrupt:
  653. sys.exit(1)
  654. # get/set cmd-file
  655. transferCommandFile = torrentfile + ".cmd"
  656. # get/set stats-file
  657. transferStatFile = torrentfile + ".stat"
  658. # get/set log-file
  659. transferLogFile = torrentfile + ".log"
  660. # get/set pid-file
  661. transferPidFile = torrentfile + ".pid"
  662. # log what we are starting up
  663. transferLog("mainline starting up :\n", True)
  664. transferLog(" - torrentfile : " + torrentfile + "\n", True)
  665. transferLog(" - save_in : " + config['save_in'] + "\n", True)
  666. transferLog(" - tf_owner : " + config['tf_owner'] + "\n", True)
  667. transferLog(" - transferStatFile : " + transferStatFile + "\n", True)
  668. transferLog(" - transferCommandFile : " + transferCommandFile + "\n", True)
  669. transferLog(" - transferLogFile : " + transferLogFile + "\n", True)
  670. transferLog(" - transferPidFile : " + transferPidFile + "\n", True)
  671. transferLog(" - die_when_done : " + str(config['die_when_done']) + "\n", True)
  672. transferLog(" - seed_limit : " + str(config['seed_limit']) + "\n", True)
  673. transferLog(" - minport : " + str(config['minport']) + "\n", True)
  674. transferLog(" - maxport : " + str(config['maxport']) + "\n", True)
  675. transferLog(" - max_upload_rate : " + str(config['max_upload_rate']) + "\n", True)
  676. transferLog(" - max_download_rate : " + str(config['max_download_rate']) + "\n", True)
  677. transferLog(" - min_uploads : " + str(config['min_uploads']) + "\n", True)
  678. transferLog(" - max_uploads : " + str(config['max_uploads']) + "\n", True)
  679. transferLog(" - min_peers : " + str(config['min_peers']) + "\n", True)
  680. transferLog(" - max_initiate : " + str(config['max_initiate']) + "\n", True)
  681. transferLog(" - max_incomplete : " + str(config['max_incomplete']) + "\n", True)
  682. transferLog(" - max_allow_in : " + str(config['max_allow_in']) + "\n", True)
  683. transferLog(" - rerequest_interval : " + str(config['rerequest_interval']) + "\n", True)
  684. transferLog(" - start_trackerless_client : " + str(config['start_trackerless_client']) + "\n", True)
  685. transferLog(" - check_hashes : " + str(config['check_hashes']) + "\n", True)
  686. transferLog(" - max_files_open : " + str(config['max_files_open']) + "\n", True)
  687. transferLog(" - upnp : " + str(config['upnp']) + "\n", True)
  688. # remove command-file if exists
  689. if isfile(transferCommandFile):
  690. try:
  691. transferLog("removing command-file " + transferCommandFile + "...\n", True)
  692. remove(transferCommandFile)
  693. except:
  694. pass
  695. # app
  696. app = TorrentApp(metainfo, config)
  697. try:
  698. app.run()
  699. except KeyboardInterrupt:
  700. pass
  701. except BTFailure, e:
  702. print unicode(e.args[0])
  703. except Exception, e:
  704. logging.getLogger().exception(e)
  705. # if after a reasonable amount of time there are still
  706. # non-daemon threads hanging around then print them.
  707. nondaemons = [d for d in threading.enumerate() if not d.isDaemon()]
  708. if len(nondaemons) > 1:
  709. sleep(4)
  710. nondaemons = [d for d in threading.enumerate() if not d.isDaemon()]
  711. if len(nondaemons) > 1:
  712. print "non-daemon threads not shutting down:"
  713. for th in nondaemons:
  714. print " ", th
  715. # remove pid-file
  716. transferLog("removing pid-file : " + transferPidFile + "\n", True)
  717. try:
  718. remove(transferPidFile)
  719. except Exception, e:
  720. transferLog("Failed to remove pid-file : " + transferPidFile + "\n", True)
  721. # log exit
  722. transferLog("mainline exit.\n", True)