class Client(object): def __init__(self, client_config): self._config = client_config self._logger = logging.getLogger(self._get_name()) self._local_reporter = None if self._config.get_logfile() is not None: logfile_directory = FileUtils.get_path_from_full_filename(self._config.get_logfile()) if self._config.get_logfile().find(os.path.normcase('/')) > -1 and \ not os.access(logfile_directory, os.F_OK): os.mkdir(logfile_directory) torrent_file = self._config.get_single_torrent() if torrent_file is not None: if not os.access(torrent_file, os.F_OK): self._logger.error("The specified torrent file %s does not exist." % torrent_file) self._on_exit() def _get_name(self): return "%s.%i" % (self.__class__.__name__, self._config.get_id()) def _cleanup(self, signal=None, func=None): if self._config.get_delete_directories(): FileUtils.remove_directory_recursively(self._config.get_directory()) FileUtils.remove_directory_recursively(self._config.get_state_directory()) self._logger.warn("Removed directories %s and %s" % (self._config.get_directory(), self._config.get_state_directory())) def start_client(self): self._setup_client() self._start_downloads() set_exit_handler(self._on_exit) try: while True: time.sleep(5) time.sleep(self._config.get_report_interval()) except KeyboardInterrupt: pass finally: self._on_exit() def _setup_client(self): def setup_directories(): if not os.access(self._config.get_directory(), os.F_OK): os.mkdir(self._config.get_directory()) if self._config.get_state_directory(): if not os.access(self._config.get_state_directory(), os.F_OK): os.mkdir(self._config.get_state_directory()) else: state_dir = tempfile.mkdtemp() self._config.set_state_directory(state_dir) def setup_scfg(): scfg = SessionStartupConfig() scfg.set_state_dir(self._config.get_state_directory()) scfg.set_listen_port(self._config.get_port()) scfg.set_overlay(False) scfg.set_megacache(False) scfg.set_upnp_mode(UPNPMODE_DISABLED) return scfg def setup_callback_handlers(session, scfg): self._handlers = PeerCallbackHandlers(self._get_name(), self._config.get_report_interval()) self._handlers.register_handler(PeerReporter(self._get_name())) self._handlers.register_handler(ClientStatistics(self._config.get_directory(), self._config.get_id())) if self._config.is_hap_enabled(): self._logger.info("HAP support enabled") self._handlers.register_handler(ClientHAPHandler(self._config)) if self._config.get_sis_url() != None: ip_addr = net_utils.get_own_ip_addr() self._handlers.register_handler(PeerActivityReportEmitter( (ip_addr, self._config.get_port()), self._config.get_activity_report_interval(), sis_iop_endpoint_url=self._config.get_sis_iop_url())) if self._config.get_exit_on() == constants.EXIT_ON_ALL_FINISHED: self._handlers.register_handler(ClientUptimeHandler(session, method=constants.EXIT_ON_ALL_FINISHED, callback=self._on_exit)) elif self._config.get_exit_on() == constants.EXIT_ON_PLAYBACK_DONE: self._handlers.register_handler(ClientUptimeHandler(session, method=constants.EXIT_ON_PLAYBACK_DONE, callback=self._on_exit)) elif self._config.get_exit_on() == constants.EXIT_ON_SEEDING_TIME: self._handlers.register_handler(ClientUptimeHandler(session, method=constants.EXIT_ON_SEEDING_TIME, max_seeding_time=self._config.get_seeding_time(), callback=self._on_exit)) if self._config.get_report_to() is not None: if self._config.get_report_to() == 'local_report': self._local_reporter = PeerLocalReporter(self._get_name(), self._config.get_id(), session, self._config.get_directory()) self._handlers.register_handler(self._local_reporter) else: self._handlers.register_handler(PeerHTTPReporter(self._get_name(), self._config.get_id(), self._config.get_report_to(), scfg, self._config.get_compress_xml_reports(), self._config.get_serialization_method(), report_interval=self._config.get_report_interval())) setup_directories() self._logger.info("Client directory is at %s" % self._config.get_directory()) self._logger.info("Client state directory is at %s" % self._config.get_state_directory()) scfg = setup_scfg() self._session = Session(scfg) self._session.set_supporter_seed(self._config.is_supporter_seed())#TODO: parameterize!!! self._logger.error("Supporter IPs are: "+self._config.get_supporter_ip()) self._session.set_supporter_ips(self._config.get_supporter_ip()) setup_callback_handlers(self._session, scfg) source = self._config.get_ranking_source() endpoint = self._config.get_sis_client_endpoint() ranking = RankingPolicy.selectRankingSource(source, conf=self._config) NeighborSelection.createMechanism(self._config.get_ns_mode(), ranking, locality_pref=self._config.get_locality_preference()) BiasedUnchoking.BiasedUnchoking(self._config.get_peer_selection_mode(), ranking) def _start_downloads(self): torrent_files = [] if self._config.get_single_torrent() is not None: torrent_files.append(self._config.get_single_torrent()) else: torrent_files = files_list(self._config.get_directory(), [constants.TORRENT_DOWNLOAD_EXT, constants.TORRENT_VOD_EXT]) if len(torrent_files) == 0: self._logger.error("No torrents found.") self._on_exit() os._exit(1) for torrent in torrent_files: self._start_download(torrent) def _start_download(self, torrent): tdef = TorrentDef.load(torrent) dscfg = DownloadStartupConfig() #disable PEX protocol, otherwise it will crash if two clients are running on the same machine! #dscfg.set_ut_pex_max_addrs_from_peer(0) dscfg.set_dest_dir(self._config.get_directory()) if common_utils.has_torrent_video_files(tdef) and not self._config.is_supporter_seed(): dscfg.set_video_event_callback(self._handlers.video_event_callback) self._logger.warn("Download directory: " + dscfg.get_dest_dir()) dscfg.set_max_speed(UPLOAD, self._config.get_upload_limit()) dscfg.set_max_speed(DOWNLOAD, self._config.get_download_limit()) con_lim = self._config.get_connection_limit() dscfg.set_max_conns(con_lim) dscfg.set_max_conns_to_initiate((con_lim+1)/2) dscfg.set_min_peers((con_lim+2)/3) dscfg.set_max_uploads(self._config.get_max_upload_slots_per_download()) dscfg.set_peer_type("G") self._logger.warn("Files available: %s" % tdef.get_files()) if dscfg.get_mode() == DLMODE_VOD: self._logger.warn("RUN in streaming mode") if tdef.is_multifile_torrent(): for file in tdef.get_files(): if file.endswith(".avi"): dscfg.set_selected_files([file]) break else: self._logger.warn("RUN in file sharing mode") d = self._session.start_download(tdef, dscfg) d.set_state_callback(self._handlers.state_callback, getpeerlist=True) def _on_exit(self, signal=None, func=None): if self._local_reporter is not None: self._local_reporter.write_stats() # stop all active downloads for download in self._session.get_downloads(): download.stop() # shutting down session self._session.shutdown(checkpoint=False, gracetime=2.0) # call the provided method (cleanup purposes) self._cleanup(signal, func) os._exit(0)
class Client(object): def __init__(self, client_config): self._config = client_config self._logger = logging.getLogger(self._get_name()) self._local_reporter = None if self._config.get_logfile() is not None: logfile_directory = FileUtils.get_path_from_full_filename( self._config.get_logfile()) if self._config.get_logfile().find(os.path.normcase('/')) > -1 and \ not os.access(logfile_directory, os.F_OK): os.mkdir(logfile_directory) torrent_file = self._config.get_single_torrent() if torrent_file is not None: if not os.access(torrent_file, os.F_OK): self._logger.error( "The specified torrent file %s does not exist." % torrent_file) self._on_exit() def _get_name(self): return "%s.%i" % (self.__class__.__name__, self._config.get_id()) def _cleanup(self, signal=None, func=None): if self._config.get_delete_directories(): FileUtils.remove_directory_recursively( self._config.get_directory()) FileUtils.remove_directory_recursively( self._config.get_state_directory()) self._logger.warn("Removed directories %s and %s" % (self._config.get_directory(), self._config.get_state_directory())) def start_client(self): self._setup_client() self._start_downloads() set_exit_handler(self._on_exit) try: while True: time.sleep(5) time.sleep(self._config.get_report_interval()) except KeyboardInterrupt: pass finally: self._on_exit() def _setup_client(self): def setup_directories(): if not os.access(self._config.get_directory(), os.F_OK): os.mkdir(self._config.get_directory()) if self._config.get_state_directory(): if not os.access(self._config.get_state_directory(), os.F_OK): os.mkdir(self._config.get_state_directory()) else: state_dir = tempfile.mkdtemp() self._config.set_state_directory(state_dir) def setup_scfg(): scfg = SessionStartupConfig() scfg.set_state_dir(self._config.get_state_directory()) scfg.set_listen_port(self._config.get_port()) scfg.set_overlay(False) scfg.set_megacache(False) scfg.set_upnp_mode(UPNPMODE_DISABLED) return scfg def setup_callback_handlers(session, scfg): self._handlers = PeerCallbackHandlers( self._get_name(), self._config.get_report_interval()) self._handlers.register_handler(PeerReporter(self._get_name())) self._handlers.register_handler( ClientStatistics(self._config.get_directory(), self._config.get_id())) if self._config.is_hap_enabled(): self._logger.info("HAP support enabled") self._handlers.register_handler(ClientHAPHandler(self._config)) if self._config.get_sis_url() != None: ip_addr = net_utils.get_own_ip_addr() self._handlers.register_handler( PeerActivityReportEmitter( (ip_addr, self._config.get_port()), self._config.get_activity_report_interval(), sis_iop_endpoint_url=self._config.get_sis_iop_url())) if self._config.get_exit_on() == constants.EXIT_ON_ALL_FINISHED: self._handlers.register_handler( ClientUptimeHandler(session, method=constants.EXIT_ON_ALL_FINISHED, callback=self._on_exit)) elif self._config.get_exit_on() == constants.EXIT_ON_PLAYBACK_DONE: self._handlers.register_handler( ClientUptimeHandler(session, method=constants.EXIT_ON_PLAYBACK_DONE, callback=self._on_exit)) elif self._config.get_exit_on() == constants.EXIT_ON_SEEDING_TIME: self._handlers.register_handler( ClientUptimeHandler( session, method=constants.EXIT_ON_SEEDING_TIME, max_seeding_time=self._config.get_seeding_time(), callback=self._on_exit)) if self._config.get_report_to() is not None: if self._config.get_report_to() == 'local_report': self._local_reporter = PeerLocalReporter( self._get_name(), self._config.get_id(), session, self._config.get_directory()) self._handlers.register_handler(self._local_reporter) else: self._handlers.register_handler( PeerHTTPReporter( self._get_name(), self._config.get_id(), self._config.get_report_to(), scfg, self._config.get_compress_xml_reports(), self._config.get_serialization_method(), report_interval=self._config.get_report_interval()) ) setup_directories() self._logger.info("Client directory is at %s" % self._config.get_directory()) self._logger.info("Client state directory is at %s" % self._config.get_state_directory()) scfg = setup_scfg() self._session = Session(scfg) self._session.set_supporter_seed( self._config.is_supporter_seed()) #TODO: parameterize!!! self._logger.error("Supporter IPs are: " + self._config.get_supporter_ip()) self._session.set_supporter_ips(self._config.get_supporter_ip()) setup_callback_handlers(self._session, scfg) source = self._config.get_ranking_source() endpoint = self._config.get_sis_client_endpoint() ranking = RankingPolicy.selectRankingSource(source, conf=self._config) NeighborSelection.createMechanism( self._config.get_ns_mode(), ranking, locality_pref=self._config.get_locality_preference()) BiasedUnchoking.BiasedUnchoking(self._config.get_peer_selection_mode(), ranking) def _start_downloads(self): torrent_files = [] if self._config.get_single_torrent() is not None: torrent_files.append(self._config.get_single_torrent()) else: torrent_files = files_list( self._config.get_directory(), [constants.TORRENT_DOWNLOAD_EXT, constants.TORRENT_VOD_EXT]) if len(torrent_files) == 0: self._logger.error("No torrents found.") self._on_exit() os._exit(1) for torrent in torrent_files: self._start_download(torrent) def _start_download(self, torrent): tdef = TorrentDef.load(torrent) dscfg = DownloadStartupConfig() #disable PEX protocol, otherwise it will crash if two clients are running on the same machine! #dscfg.set_ut_pex_max_addrs_from_peer(0) dscfg.set_dest_dir(self._config.get_directory()) if common_utils.has_torrent_video_files( tdef) and not self._config.is_supporter_seed(): dscfg.set_video_event_callback(self._handlers.video_event_callback) self._logger.warn("Download directory: " + dscfg.get_dest_dir()) dscfg.set_max_speed(UPLOAD, self._config.get_upload_limit()) dscfg.set_max_speed(DOWNLOAD, self._config.get_download_limit()) con_lim = self._config.get_connection_limit() dscfg.set_max_conns(con_lim) dscfg.set_max_conns_to_initiate((con_lim + 1) / 2) dscfg.set_min_peers((con_lim + 2) / 3) dscfg.set_max_uploads(self._config.get_max_upload_slots_per_download()) dscfg.set_peer_type("G") self._logger.warn("Files available: %s" % tdef.get_files()) if dscfg.get_mode() == DLMODE_VOD: self._logger.warn("RUN in streaming mode") if tdef.is_multifile_torrent(): for file in tdef.get_files(): if file.endswith(".avi"): dscfg.set_selected_files([file]) break else: self._logger.warn("RUN in file sharing mode") d = self._session.start_download(tdef, dscfg) d.set_state_callback(self._handlers.state_callback, getpeerlist=True) def _on_exit(self, signal=None, func=None): if self._local_reporter is not None: self._local_reporter.write_stats() # stop all active downloads for download in self._session.get_downloads(): download.stop() # shutting down session self._session.shutdown(checkpoint=False, gracetime=2.0) # call the provided method (cleanup purposes) self._cleanup(signal, func) os._exit(0)
class Cache: ''' This class is used to start the cache applictaion. ''' def __init__(self, cache_config): self._cache_config = cache_config self.active_downloads = {} self.stopped_downloads = {} self.save_dir = cache_config.get_directory() self.disc_space = cache_config.get_space_limit() * 1024 * 1024 self.available_disc_space = self.disc_space self.dl_lock = RLock() self._logger = None self.ranking = None self.filtering = None self.replacement_strategy = None self.ratemanager = None self.handlers = None self._init_logger() self._init_cache_console() self._init_before_session() self.start_session(self._cache_config.get_port()) self._init_after_session() self._init_callback_handler() self._init_selector() def _get_name(self): return "%s.%i" % (self.__class__.__name__, self._cache_config.get_id()) def _init_logger(self): self._logger = logging.getLogger(self._get_name()) self._logger.info("Initialized logger") global logger logger = self._logger def _init_cache_console(self): if self._cache_config.has_cache_console(): CacheConsole.start_cache_console(self) def _init_before_session(self): self.ranking = RankingPolicy.selectRankingSource(self._cache_config.get_ranking_source(), conf=self._cache_config) self._logger.warning("Ranking source is %s" % self.ranking) exclude_below = 1# allow only non-remote connections! self.filtering = NeighborSelection.createMechanism(self._cache_config.get_ns_mode(), self.ranking, exclude_below=exclude_below, locality_pref=self._cache_config.get_locality_preference()) self._logger.warning("Neighbor selection is %s" % self.filtering) self.max_downloads = self._cache_config.get_max_downloads() def _init_after_session(self): policy_name = self._cache_config.get_replacement_strategy() self.replacement_strategy = ReplacementStrategy.getInstance(self, policy_name) self.ratemanager = Ratemanager.getInstance(self, self._cache_config) self.ratemanager.set_dl_limit(self._cache_config.get_download_limit()) self.ratemanager.set_ul_limit(self._cache_config.get_upload_limit()) self.ratemanager.set_connection_limit(self._cache_config.get_connection_limit()) def _init_callback_handler(self): self.handlers = PeerCallbackHandlers(self._get_name(), self._cache_config.get_report_interval()) self.handlers.register_handler(PeerReporter(self._get_name())) self.handlers.register_handler(CacheActiveDownloadTracker()) if self._cache_config.get_sis_url() is not None: ip_addr = net_utils.get_own_ip_addr() iop_url = self._cache_config.get_sis_iop_url() self.handlers.register_handler(PeerActivityReportEmitter( (ip_addr, self._cache_config.get_port()), self._cache_config.get_report_interval(), sis_iop_endpoint_url=iop_url)) if self._cache_config.get_report_to() is not None: if self._cache_config.get_report_to() == 'local_report': global local_reporter local_reporter = PeerLocalReporter(self._get_name(), self._cache_config.get_id(), self.session, self._cache_config.get_directory()) self.handlers.register_handler(local_reporter) else: self.handlers.register_handler(PeerHTTPReporter(self._get_name(), self._cache_config.get_id(), self._cache_config.get_report_to(), self.sconfig, self._cache_config.get_compress_xml_reports(), self._cache_config.get_serialization_method(), is_iop=True)) def _init_selector(self): selector = None mode = self._cache_config.get_torrent_selection_mechanism() if mode == 'local_folder': selector = LocalFolderSelector.LocalFolderSelector(self._cache_config.get_torrent_directory()) self._logger.info("Run in local folder mode") elif mode == 'SIS': #selector = self._cache_config.get_ws_client() selector = IoP_WSClientImpl(self._cache_config.get_sis_iop_url()) selector.set_torrent_dir(self._cache_config.get_torrent_directory()) #FIXME: we now create these instances twice!!! once in cache config and then here! Should rather use a wrapper here! self._logger.info("Run in WS torrent selection mode") else: raise ConfigurationException("Unsupported torrent selection mechanism!"+str()) assert selector is not None self._logger.info("Use torrent selection method "+str(selector)) interval = self._cache_config.get_torrent_selection_interval() max_torrents = self._cache_config.get_max_torrents() assert max_torrents >= self.max_downloads stay_in_torrents = self._cache_config.get_stay_in_torrents() self.torrent_selection = TorrentSelection.start_torrent_selection( self, selector, selection_interval=interval, max_torrents=max_torrents, stay_in_torrents=stay_in_torrents) def start_session(self, port): ''' Sets the values of the SessionStartupConfig an creates a new session. ''' self.sconfig = API.SessionStartupConfig() self.sconfig.set_state_dir(self._cache_config.get_directory()) self.sconfig.set_listen_port(port) self.sconfig.set_overlay(False) self.sconfig.set_megacache(False) self.sconfig.set_upnp_mode(simpledefs.UPNPMODE_DISABLED) self.session = API.Session(self.sconfig) global session session = self.session def join_swarm(self, tdef, id): ''' Starts a new download with the given torrent definition. This method tries to allocate disc space and to get a download slot before the new download can be started. If the cache already contains the specified download and it is stopped, this method can be used to restart the download. The id is used to identify the download. ''' self.dl_lock.acquire() try: dl = self.get_download(id, stopped=True, pop=True) if not dl == None: if self.get_download_slot(): self.restart_download(id, dl) return True length = tdef.get_length(tdef.get_files()) if self.allocate_disc_space(length): try: dlcfg = self.get_startup_config() dl = self.session.start_download(tdef, dlcfg) self._logger.info("Join new swarm %s" % dl.get_def().get_name()) dl.set_state_callback(self.handlers.state_callback, getpeerlist=True) if self.get_download_slot(): self.active_downloads[id] = dl else: time.sleep(5) dl.stop() self.stopped_downloads[id]=dl return True except: self.available_disc_space += length self._logger.warn("Failed to start download: "+tdef.get_name(), exc_info=True) return False else: self._logger.warn("Failed to allocate disc space for "+tdef.get_name()) return False except : self._logger.warn("Failed to join swarm "+tdef.get_name(), exc_info=True) return False finally: #print >> sys.stderr,"After join: active %s and stopped %s" % (self.active_downloads, self.stopped_downloads) self.dl_lock.release() def print_active(self): list = [] for dl in self.active_downloads.values(): list.append(dl.get_def().get_name()) self._logger.error("active: %s" % list) def leave_swarm(self, id, delete=False): ''' This method stops a currently running download. In addition the files of a running or stopped download can be deleted. ''' assert id in self.active_downloads.keys() , id self.dl_lock.acquire() try: if delete: dl = self.get_download(id, stopped=True, pop=True) length = dl.get_def().get_length(dl.get_def().get_files()) self.available_disc_space += length else: dl = self.get_download(id, pop=True) self._logger.info("Leave swarm %s" % dl.get_def().get_name()) self.stopped_downloads[id] = dl dl.stop_remove(delete, delete) return True except: traceback.print_exc() return False finally: self.dl_lock.release() def restart_download(self, id, dl): ''' Restarts a stopped download. ''' self._logger.info("Re-join swarm %s" % dl.get_def().get_name()) dl.set_max_speed(simpledefs.DOWNLOAD, 1)#TODO: reuse config!!! dl.set_max_speed(simpledefs.UPLOAD, 1) dl.restart() dl.set_state_callback(self.handlers.state_callback, getpeerlist=True) self.active_downloads[id] = dl time.sleep(3) self.update_download_settings() def get_download_slot(self): ''' This method checks if a download slot is available. If no download slot is available the replacement strategy is used to stop a running download. ''' if len(self.active_downloads) < self.max_downloads: return True else: return self.replacement_strategy.get_download_slot() def get_startup_config(self): ''' Sets values of the DownloadStartupConfig. ''' dlcfg = API.DownloadStartupConfig() dlcfg.set_dest_dir(self.save_dir) dlcfg.set_max_speed(simpledefs.DOWNLOAD, 1) dlcfg.set_max_speed(simpledefs.UPLOAD, 1) dlcfg.set_max_uploads(self._cache_config.get_max_upload_slots_per_download()) min_uploads = min(dlcfg.get_min_uploads(), self._cache_config.get_max_upload_slots_per_download()) dlcfg.set_min_uploads(min_uploads) return dlcfg def get_download(self, id, stopped=False, pop=False): ''' Returns the download specified by the id. ''' #print >> sys.stderr, "Check torrent: "+id #print >> sys.stderr, "Stopped: ", self.stopped_downloads #print >> sys.stderr, "Active: ", self.active_downloads if stopped: downloads = self.stopped_downloads else: downloads = self.active_downloads for (dl_id, dl) in downloads.items(): if dl_id == id: if pop: del downloads[dl_id] return dl return None def is_running(self, id): ''' Returns if the download specified by the id is currently running. ''' return self.active_downloads.has_key(id) #return not(self.get_download(id) == None) def is_stopped(self, id): ''' Returns if the download specified by the id is currently stopped. ''' return not(self.get_download(id, stopped=True) == None) def update_download_settings(self): ''' Updates the transmission rates of all running downloads. ''' self.dl_lock.acquire() try: self.ratemanager.adjust_speeds(self.active_downloads) finally: self.dl_lock.release() def allocate_disc_space(self, file_size): ''' Checks if enough disc space for the download is available and tries to free up the needed disc space. ''' #print >> sys.stderr, "Available space: %d, required space: %d " % (self.available_disc_space, file_size) if file_size <= self.available_disc_space: self.available_disc_space -= file_size return True if self.replacement_strategy.free_up_disc_space(file_size): self.available_disc_space -= file_size return True return False def start_connections(self, id, peer_dict): ''' Starts new connections. ''' # select active or passive download? assert id in self.active_downloads.keys() or id in self.stopped_downloads.keys() dl = self.active_downloads[id] #print>>sys.stderr, "download: %s session %s" % (dl, dl.sd) #TODO: acquire locks before starting connections? if dl.sd is None: # race condition? connect to them later! return bt1dl = dl.sd.get_bt1download() stats = dl.sd.get_stats(False) if stats[0] != None: return for torrent_id, list_of_peers in peer_dict.items(): for peer in list_of_peers: ip, port = peer try: bt1dl.startConnection(ip, port, False) # False means -> immediate connection except: self._logger.warn("Failed to connect to %s for torrent id %s" % (str(peer), str(torrent_id)), exc_info = True) def set_dl_limit(self, limit): ''' Sets the download limit of the cache and updates the rates for all running downloads. ''' self.ratemanager.set_dl_limit(limit) self.update_download_settings() def set_ul_limit(self, limit): ''' Sets the download limit of the cache and updates the rates for all running downloads. ''' self.ratemanager.set_ul_limit(limit) self.update_download_settings() def set_hostname(self): ''' This method is used to get the hostname of the cache, which is used by the cache_local mode of the NeighborSelection. ''' own_ip = self.session.get_external_ip() hostname = socket.gethostbyaddr(own_ip) NeighborSelection.getInstance().set_cache_hostname(hostname) def get_status(self): ''' Returns the current status of the cache and its running downloads. ''' return CacheStatus(self.active_downloads, self.stopped_downloads, self) def get_active_downloads_ids(self): ''' Return list of active torrent ids. ''' return list(self.active_downloads.keys()) def get_up_rated_downloads(self): ''' Return list of active torrent ids sorted by the descending upload rate. ''' rated = [] # download id to rate! status = self.get_status() for ds in status.downloadstats: id = ds['id'] rate = ds['up_rate'] if id in self.active_downloads: rated.append( (rate, id) ) return [id for (rate, id) in sorted(rated, reverse=True)] def run_forever(self): try: while True: time.sleep(30) except KeyboardInterrupt: on_exit()