#!/usr/bin/env python2 # written by Shawn C. Dodd # based on Bram Cohen's btdownloadheadless.py script # thanks to Alexey Vyskubov's ASPN Cookbook for the daemon code # thanks to Vinay Sajip's Red Dove Python Logging docs for the logging code # see LICENSE.txt for license information # Nov 12, 2003 # Modified by Rick Huang to make it compatable with Bit Torrent Version 3.3 from BitTorrent.download import download from threading import Event from os.path import abspath from sys import argv, version, stdout, stdin from time import time import logging import os import sys assert version >= '2', "Install Python 2.0 or greater" true = 1 false = 0 def hours(n): if n == -1: return '' if n == 0: return 'complete!' n = int(n) h, r = divmod(n, 60 * 60) m, sec = divmod(r, 60) if h > 1000000: return '' if h > 0: return '%d hour %02d min %02d sec' % (h, m, sec) else: return '%d min %02d sec' % (m, sec) class DaemonDisplayer: def __init__(self): # TODO refactoring notes: separate this out and have this and HeadlessDisplayer derive from a common ancestor self.done = false self.file = '' self.percentDone = '' self.timeEst = '' self.downloadTo = '' self.downRate = '' self.upRate = '' self.errors = [] self.errorCount = 0 self.lastErrorMessage = '' self.lastErrorTime = 0.0 def finished(self): self.done = true self.percentDone = '100' self.timeEst = 'Download Succeeded!' self.downRate = '' self.display({}) def failed(self): self.done = true self.percentDone = '0' self.timeEst = 'Download Failed!' self.downRate = '' self.display({}) def error(self, errormsg): # determine whether we've transitioned from one sequence of duplicate errors to another if errormsg == self.lastErrorMessage: self.errorCount = self.errorCount + 1 else: if self.lastErrorMessage != '': self.errors.append(str(self.errorCount) + ' repetitions of the error: ' + self.lastErrorMessage) self.lastErrorMessage = errormsg self.errorCount = 0 # determine whether enough time has passed to display the accumulated errors if time() - self.lastErrorTime > 1.0: if self.errorCount > 0: self.errors.append(str(self.errorCount) + ' repetitions of the error: ' + self.lastErrorMessage) else: self.errors.append(errormsg) self.errorCount = 0 self.lastErrorMessage = '' self.lastErrorTime = time() self.display({}) def display(self, dict): if dict.has_key('spew'): print_spew(dict['spew']) if dict.has_key('fractionDone'): self.percentDone = str(float(int(dict['fractionDone'] * 1000)) / 10) + '%' if dict.has_key('timeEst'): self.timeEst = hours(dict['timeEst']) if dict.has_key('activity') and not self.done: self.timeEst = dict['activity'] if dict.has_key('downRate'): self.downRate = '%.2f kB/s' % (float(dict['downRate']) / (1 << 10)) if dict.has_key('upRate'): self.upRate = '%.2f kB/s' % (float(dict['upRate']) / (1 << 10)) if dict.has_key('upTotal'): self.upTotal = '%.1f MiB' % (dict['upTotal']) if dict.has_key('downTotal'): self.downTotal = '%.1f MiB' % (dict['downTotal']) for err in self.errors: logging.error(str(os.getpid()) + ' | ' + err) self.errors = [] # SCD don't want to report same errors over and over again when logging to file if (dict.has_key('upTotal') and dict.has_key('downTotal')): logging.info(str(os.getpid()) + ' ' + self.file + ' ' + self.percentDone + ' ' + self.downRate + ' ' + self.upRate + ' ' + self.downloadTo + ' "' + self.timeEst + '"' + ' ' + self.downTotal + ' ' + self.upTotal) else: if dict.has_key('upTotal'): logging.info(str(os.getpid()) + ' ' + self.file + ' ' + self.percentDone + ' ' + self.downRate + ' ' + self.upRate + ' ' + self.downloadTo + ' "' + self.timeEst + '"' + ' ' + '---' + ' ' + self.upTotal) logging.info(str(os.getpid()) + ' ' + self.file + ' ' + self.percentDone + ' ' + self.downRate + ' ' + self.upRate + ' ' + self.downloadTo + ' "' + self.timeEst + '"') def chooseFile(self, default, size, saveas, dir): self.file = '%s (%.1f MB)' % (default, float(size) / (1 << 20)) if saveas != '': default = saveas self.downloadTo = abspath(default) return default def run(params): if os.fork()==0: # here's where we become a daemon (note: this only works under Unix) os.setsid() sys.stdout=open("/dev/null", 'w') sys.stdin=open("/dev/null", 'r') # configure the logging # TODO test this with the standard logging in Python 2.3 when it becomes available fmt = logging.Formatter("%(asctime)s %(levelname)-5s %(message)s") hdlr = logging.FileHandler("cronlog") hdlr.setFormatter(fmt) logging.getLogger("").addHandler(hdlr) logging.getLogger("").setLevel(logging.INFO) logging.warn(str(os.getpid()) + ' | Logging initialized, column headers: PID, FILE, PERCENT_DONE, DOWN_RATE, UP_RATE, DL_DESTINATION, TIME_EST/STATUS, UP_TOTAL, DOWN_TOTAL') # start the download h = DaemonDisplayer() download(params, h.chooseFile, h.display, h.finished, h.error, Event(), 80) if not h.done: h.failed() sys.exit(0) if __name__ == '__main__': run(argv[1:])