📄 torrentqueue.py
字号:
# The contents of this file are subject to the BitTorrent Open Source License# Version 1.0 (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.# Written by Uoti Urpalafrom __future__ import divisionimport osimport sysimport threadingfrom time import timefrom BitTorrent.download import Feedback, Multitorrentfrom BitTorrent.controlsocket import ControlSocketfrom BitTorrent.bencode import bdecodefrom BitTorrent.ConvertedMetainfo import ConvertedMetainfofrom BitTorrent import BTFailure, BTShutdown, INFO, WARNING, ERROR, CRITICALfrom BitTorrent import configfileimport BitTorrent# check if dns library from http://www.dnspython.org/ is either installed# or the dns subdirectory has been copied to BitTorrent/dnsHAVE_DNS = Falsetry: from BitTorrent import dns sys.modules['dns'] = dns import dns.resolver HAVE_DNS = Trueexcept: try: import dns.resolver HAVE_DNS = True except: passRUNNING = 0RUN_QUEUED = 1QUEUED = 2KNOWN = 3ASKING_LOCATION = 4class TorrentInfo(object): def __init__(self): self.metainfo = None self.dlpath = None self.dl = None self.state = None self.completion = None self.finishtime = None self.uptotal = 0 self.uptotal_old = 0 self.downtotal = 0 self.downtotal_old = 0def decode_position(l, pred, succ, default=None): if default is None: default = len(l) if pred is None and succ is None: return default if pred is None: return 0 if succ is None: return len(l) try: if l[0] == succ and pred not in l: return 0 if l[-1] == pred and succ not in l: return len(l) i = l.index(pred) if l[i+1] == succ: return i+1 except (ValueError, IndexError): pass return defaultclass TorrentQueue(Feedback): def __init__(self, config, ui_options, controlsocket): self.config = dict(config) self.ui_options = ui_options self.controlsocket = controlsocket self.config['def_running_torrents'] = 1 # !@# XXX self.config['max_running_torrents'] = 3 # !@# XXX self.doneflag = threading.Event() self.torrents = {} self.starting_torrent = None self.running_torrents = [] self.queue = [] self.other_torrents = [] self.last_save_time = 0 self.last_version_check = 0 def run(self, ui, ui_wrap, startflag): self.ui = ui self.run_ui_task = ui_wrap self.multitorrent = Multitorrent(self.config, self.doneflag, self.global_error, listen_fail_ok=True) self.rawserver = self.multitorrent.rawserver self.controlsocket.set_rawserver(self.rawserver) self.controlsocket.start_listening(self.external_command) try: self._restore_state() except BTFailure, e: self.queue = [] self.other_torrents = [] self.torrents = {} self.global_error(ERROR, "Could not load saved state: "+str(e)) else: for infohash in self.running_torrents + self.queue + \ self.other_torrents: t = self.torrents[infohash] if t.dlpath is not None: t.completion = self.multitorrent.get_completion( self.config, t.metainfo, t.dlpath) state = t.state if state == RUN_QUEUED: state = RUNNING self.run_ui_task(self.ui.new_displayed_torrent, infohash, t.metainfo, t.dlpath, state, t.completion, t.uptotal, t.downtotal) self._check_queue() startflag.set() self._queue_loop() self._check_version() self.multitorrent.rawserver.listen_forever() self.multitorrent.close_listening_socket() self.controlsocket.close_socket() for infohash in list(self.running_torrents): t = self.torrents[infohash] if t.state == RUN_QUEUED: continue t.dl.shutdown() if t.dl is not None: # possibly set to none by failed() totals = t.dl.get_total_transfer() t.uptotal = t.uptotal_old + totals[0] t.downtotal = t.downtotal_old + totals[1] self._dump_state() def _check_version(self): now = time() if self.last_version_check > now - 24*3600: return self.last_version_check = now if not HAVE_DNS: self.global_error(WARNING, "Version check failed: no DNS library") return threading.Thread(target=self._version_thread).start() def _version_thread(self): def error(level, text): def f(): self.global_error(level, text) self.rawserver.external_add_task(f, 0) def splitversion(text): return [int(t) for t in text.split('.')] try: try: a = dns.resolver.query('version.bittorrent.com', 'TXT') except: # the exceptions from the library have empty str(), # just different classes... raise BTFailure('DNS query failed') if len(a) != 1: raise BTFailure('number of received TXT fields is not 1') value = iter(a).next() # the object doesn't support a[0] if len(value.strings) != 1: raise BTFailure('number of strings in reply is not 1?') s = value.strings[0].split(None, 2) myversion = splitversion(BitTorrent.version) if myversion[1] % 2 and len(s) > 1: s = s[1] else: s = s[0] try: latest = splitversion(s) except ValueError: raise BTFailure("Could not parse new version string") for my, new in zip(myversion, latest): if my > new: break if my < new: download_url = 'http://bittorrent.com/download.html' if hasattr(self.ui, 'new_version'): self.run_ui_task(self.ui.new_version, s, download_url) else: error(ERROR, "A newer version of BitTorrent is " "available.\nYou can always get the latest " "version from\n%s." % download_url) except Exception, e: error(WARNING, "Version check failed: " + str(e)) def _dump_config(self): configfile.save_ui_config(self.config, 'btdownloadgui', self.ui_options, self.global_error) def _dump_state(self): self.last_save_time = time() r = [] def write_entry(infohash, t): if t.dlpath is None: assert t.state == ASKING_LOCATION r.append(infohash.encode('hex') + '\n') else: r.append(infohash.encode('hex') + ' ' + str(t.uptotal) + ' ' + str(t.downtotal)+' '+t.dlpath.encode('string_escape')+'\n') r.append('BitTorrent UI state file, version 3\n') r.append('Running torrents\n') for infohash in self.running_torrents: write_entry(infohash, self.torrents[infohash]) r.append('Queued torrents\n') for infohash in self.queue: write_entry(infohash, self.torrents[infohash]) r.append('Known torrents\n') for infohash in self.other_torrents: write_entry(infohash, self.torrents[infohash]) r.append('End\n') f = None try: f = file(os.path.join(self.config['data_dir'], 'ui_state'), 'wb') f.write(''.join(r)) f.close() except Exception, e: self.global_error(ERROR, 'Could not save UI state: ' + str(e)) if f is not None: f.close() def _restore_state(self): def decode_line(line): hashtext = line[:40] try: infohash = hashtext.decode('hex') except: raise BTFailure("Invalid state file contents") if len(infohash) != 20: raise BTFailure("Invalid state file contents") try: path = os.path.join(self.config['data_dir'], 'metainfo', hashtext) f = file(path, 'rb') data = f.read() f.close() except Exception, e: try: f.close() except: pass self.global_error(ERROR,"Error reading file "+path+" ("+str(e)+ "), cannot restore state completely") return None if infohash in self.torrents: raise BTFailure("Invalid state file (duplicate entry)") t = TorrentInfo() self.torrents[infohash] = t try: t.metainfo = ConvertedMetainfo(bdecode(data)) except Exception, e: self.global_error(ERROR, "Corrupt data in "+path+ " , cannot restore torrent ("+str(e)+")") return None t.metainfo.reported_errors = True # suppress redisplay on restart if infohash != t.metainfo.infohash: self.global_error(ERROR, "Corrupt data in "+path+ " , cannot restore torrent ("+str(e)+")") return None if len(line) == 41: t.dlpath = None return infohash, t try: if version < 2: t.dlpath = line[41:-1].decode('string_escape') else: up, down, dlpath = line[41:-1].split(' ', 2) t.uptotal = t.uptotal_old = int(up) t.downtotal = t.downtotal_old = int(down) t.dlpath = dlpath.decode('string_escape') except ValueError: # unpack, int(), decode() raise BTFailure('Invalid state file (bad entry)') return infohash, t filename = os.path.join(self.config['data_dir'], 'ui_state') if not os.path.exists(filename): return f = None try: f = file(filename, 'rb') lines = f.readlines() f.close() except Exception, e: if f is not None: f.close() raise BTFailure(str(e)) i = iter(lines) try: txt = 'BitTorrent UI state file, version ' version = i.next() if not version.startswith(txt): raise BTFailure('Bad UI state file') try: version = int(version[len(txt):-1]) except: raise BTFailure('Bad UI state file version') if version > 3: raise BTFailure('Unsupported UI state file version (from ' 'newer client version?') if version < 3: if i.next() != "Running/queued torrents\n": raise BTFailure("Invalid state file contents") else: if i.next() != "Running torrents\n": raise BTFailure("Invalid state file contents") while True: line = i.next() if line == 'Queued torrents\n': break t = decode_line(line) if t is None: continue infohash, t = t if t.dlpath is None: raise BTFailure("Invalid state file contents") t.state = RUN_QUEUED self.running_torrents.append(infohash) while True: line = i.next() if line == 'Known torrents\n': break t = decode_line(line) if t is None: continue infohash, t = t if t.dlpath is None: raise BTFailure("Invalid state file contents") t.state = QUEUED self.queue.append(infohash) while True: line = i.next() if line == 'End\n': break t = decode_line(line) if t is None: continue infohash, t = t if t.dlpath is None: t.state = ASKING_LOCATION else: t.state = KNOWN self.other_torrents.append(infohash) except StopIteration: raise BTFailure("Invalid state file contents") def _queue_loop(self): if self.doneflag.isSet(): return self.rawserver.add_task(self._queue_loop, 20) now = time() if self.queue and self.starting_torrent is None: mintime = now - self.config['next_torrent_time'] * 60 minratio = self.config['next_torrent_ratio'] / 100 else: mintime = 0 minratio = self.config['last_torrent_ratio'] / 100 if not minratio: return for infohash in self.running_torrents: t = self.torrents[infohash] if t.state == RUN_QUEUED: continue totals = t.dl.get_total_transfer() # not updated for remaining torrents if one is stopped, who cares
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -