#!/usr/bin/env python # The contents of this file are subject to the BitTorrent Open Source License # Version 1.1 (the License). You may not copy or use this file, in either # source code or executable form, except in compliance with the License. You # may obtain a copy of the License at http://www.bittorrent.com/license/. # # Software distributed under the License is distributed on an AS IS basis, # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License # for the specific language governing rights and limitations under the # License. # Original version written by John Hoffman, heavily modified for different # multitorrent architecture by Uoti Urpala (over 40% shorter than original) import os from cStringIO import StringIO from traceback import print_exc from BitTorrent import configfile from BitTorrent.parsedir import parsedir from BitTorrent.download import Multitorrent, Feedback from BitTorrent.ConvertedMetainfo import ConvertedMetainfo from BitTorrent import BTFailure from threading import Event from time import time class LaunchMany(Feedback): def __init__(self, config, output, configfile_key): try: self.config = config self.output = output self.configfile_key = configfile_key self.torrent_dir = config['torrent_dir'] self.torrent_cache = {} self.file_cache = {} self.blocked_files = {} self.torrent_list = [] self.downloads = {} self.doneflag = Event() self.hashcheck_queue = [] self.hashcheck_store = {} self.hashcheck_current = None self.multitorrent = Multitorrent(config, self.doneflag, self.global_error) self.rawserver = self.multitorrent.rawserver self.rawserver.add_task(self.scan, 0) self.rawserver.add_task(self.stats, 0) try: import signal def handler(signum, frame): self.rawserver.external_add_task(self.read_config, 0) signal.signal(signal.SIGHUP, handler) self.rawserver.install_sigint_handler() except Exception, e: self.output.message(_("Could not set signal handler: ") + str(e)) self.rawserver.listen_forever() self.output.message(_("shutting down")) for infohash in self.torrent_list: self.output.message(_('dropped "%s"') % self.torrent_cache[infohash]['path']) torrent = self.downloads[infohash] if torrent is not None: torrent.shutdown() except: data = StringIO() print_exc(file = data) output.exception(data.getvalue()) def scan(self): self.rawserver.add_task(self.scan, self.config['parse_dir_interval']) r = parsedir(self.torrent_dir, self.torrent_cache, self.file_cache, self.blocked_files, self.output.message) ( self.torrent_cache, self.file_cache, self.blocked_files, added, removed ) = r for infohash, data in removed.items(): self.output.message(_('dropped "%s"') % data['path']) self.remove(infohash) for infohash, data in added.items(): self.output.message(_('added "%s"' ) % data['path']) if self.config['launch_delay'] > 0: self.rawserver.add_task(self.add, self.config['launch_delay'], (infohash, data)) else: self.add(infohash, data) def stats(self): self.rawserver.add_task(self.stats, self.config['display_interval']) data = [] for infohash in self.torrent_list: cache = self.torrent_cache[infohash] if self.config['display_path']: name = cache['path'] else: name = cache['name'] size = cache['length'] d = self.downloads[infohash] progress = '0.0%' peers = 0 seeds = 0 seedsmsg = "S" dist = 0.0 uprate = 0.0 dnrate = 0.0 upamt = 0 dnamt = 0 t = 0 msg = '' if d is None: status = _("waiting for hash check") else: stats = d.get_status() status = stats['activity'] progress = '%.1f%%' % (int(stats['fractionDone']*1000)/10.0) if d.started and not d.closed: s = stats dist = s['numCopies'] if d.is_seed: seeds = 0 # s['numOldSeeds'] seedsmsg = "s" else: if s['numSeeds'] + s['numPeers']: t = stats['timeEst'] if t is None: t = -1 if t == 0: # unlikely t = 0.01 status = _("downloading") else: t = -1 status = _("connecting to peers") seeds = s['numSeeds'] dnrate = stats['downRate'] peers = s['numPeers'] uprate = stats['upRate'] upamt = s['upTotal'] dnamt = s['downTotal'] if d.errors and (d.closed or d.errors[-1][0] + 300 > time()): msg = d.errors[-1][2] data.append(( name, status, progress, peers, seeds, seedsmsg, dist, uprate, dnrate, upamt, dnamt, size, t, msg )) stop = self.output.display(data) if stop: self.doneflag.set() def remove(self, infohash): self.torrent_list.remove(infohash) if self.downloads[infohash] is not None: self.downloads[infohash].shutdown() self.was_stopped(infohash) del self.downloads[infohash] def add(self, infohash, data): self.torrent_list.append(infohash) self.downloads[infohash] = None self.hashcheck_queue.append(infohash) self.hashcheck_store[infohash] = data['metainfo'] self.check_hashcheck_queue() def check_hashcheck_queue(self): if self.hashcheck_current is not None or not self.hashcheck_queue: return self.hashcheck_current = self.hashcheck_queue.pop(0) metainfo = self.hashcheck_store[self.hashcheck_current] del self.hashcheck_store[self.hashcheck_current] filename = self.determine_filename(self.hashcheck_current) self.downloads[self.hashcheck_current] = self.multitorrent. \ start_torrent(ConvertedMetainfo(metainfo), self.config, self, filename) def determine_filename(self, infohash): x = self.torrent_cache[infohash] name = x['name'] savein = self.config['save_in'] isdir = not x['metainfo']['info'].has_key('length') style = self.config['saveas_style'] if style == 4: torrentname = os.path.split(x['path'][:-8])[1] suggestedname = name if torrentname == suggestedname: style = 1 else: style = 3 if style == 1 or style == 3: if savein: saveas = os.path.join(savein,x['file'][:-8]) # strip '.torrent' else: saveas = x['path'][:-8] # strip '.torrent' if style == 3 and not isdir: saveas = os.path.join(saveas, name) else: if savein: saveas = os.path.join(savein, name) else: saveas = os.path.join(os.path.split(x['path'])[0], name) return saveas def was_stopped(self, infohash): try: self.hashcheck_queue.remove(infohash) except: pass else: del self.hashcheck_store[infohash] if self.hashcheck_current == infohash: self.hashcheck_current = None self.check_hashcheck_queue() def global_error(self, level, text): self.output.message(text) def exchandler(self, s): self.output.exception(s) def read_config(self): try: newvalues = configfile.get_config(self.config, self.configfile_key) except Exception, e: self.output.message(_("Error reading config: ") + str(e)) return self.output.message(_("Rereading config file")) self.config.update(newvalues) # The set_option call can potentially trigger something that kills # the torrent (when writing this the only possibility is a change in # max_files_open causing an IOError while closing files), and so # the self.failed() callback can run during this loop. for option, value in newvalues.iteritems(): self.multitorrent.set_option(option, value) for torrent in self.downloads.values(): if torrent is not None: for option, value in newvalues.iteritems(): torrent.set_option(option, value) # rest are callbacks from torrent instances def started(self, torrent): self.hashcheck_current = None self.check_hashcheck_queue() def failed(self, torrent, is_external): infohash = torrent.infohash self.was_stopped(infohash) if self.torrent_cache.has_key(infohash): self.output.message('DIED: "'+self.torrent_cache[infohash]['path']+'"') def exception(self, torrent, text): self.exchandler(text)