📄 ui.py
字号:
# 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.# written by Matt Chisholmfrom __future__ import divisionimport osimport os.pathimport mathimport atexitimport itertoolsimport webbrowserfrom copy import copyimport loggingimport logging.handlersfrom BitTorrent.translation import _import BitTorrent.stackthreading as threadingfrom BitTorrent import GetTorrentfrom BitTorrent import LaunchPathfrom BitTorrent.MultiTorrent import UnknownInfohash, TorrentAlreadyInQueue, TorrentAlreadyRunning, TorrentNotRunningfrom BitTorrent.obsoletepythonsupport import setfrom BitTorrent.yielddefer import launch_coroutinefrom BitTorrent.defer import ThreadedDeferredfrom BitTorrent.platform import desktop, bttimefrom BitTorrent.Torrent import *from BitTorrent.ThreadProxy import ThreadProxystate_dict = {("created", "stop", False): _("Paused"), ("created", "stop", True): _("Paused"), ("created", "start", False): _("Starting"), ("created", "start", True): _("Starting"), ("created", "auto", False): _("Starting"), ("created", "auto", True): _("Starting"), ("initializing", "stop", False): _("Paused"), ("initializing", "stop", True): _("Paused"), ("initializing", "start", False): _("Starting"), ("initializing", "start", True): _("Starting"), ("initializing", "auto", False): _("Starting"), ("initializing", "auto", True): _("Starting"), ("initialized", "stop", False): _("Paused"), ("initialized", "stop", True): _("Paused"), ("initialized", "start", False): _("Starting"), ("initialized", "start", True): _("Starting"), ("initialized", "auto", False): _("Queued"), ("initialized", "auto", True): _("Complete"), ("running", "stop", False): _("Downloading"), ("running", "stop", True): _("Seeding"), ("running", "start", False): _("Downloading"), ("running", "start", True): _("Seeding"), ("running", "auto", False): _("Downloading"), ("running", "auto", True): _("Complete"), ("finishing", "stop", False): _("Finishing"), ("finishing", "stop", True): _("Finishing"), ("finishing", "start", False): _("Finishing"), ("finishing", "start", True): _("Finishing"), ("finishing", "auto", False): _("Finishing"), ("finishing", "auto", True): _("Finishing"), ("failed", "stop", False): _("Error"), ("failed", "stop", True): _("Error"), ("failed", "start", False): _("Error"), ("failed", "start", True): _("Error"), ("failed", "auto", False): _("Error"), ("failed", "auto", True): _("Error"),}def percentify(fraction, completed): if fraction is None: return None if completed and fraction >= 1.0: percent = 100.0 else: percent = min(99.9, math.floor(fraction * 1000.0) / 10.0) return percentclass Size(long): """displays size in human-readable format""" size_labels = ['','K','M','G','T','P','E','Z','Y'] radix = 2**10 def __new__(cls, value=None, precision=None): if value is None: self = long.__new__(cls, 0) self.empty = True else: self = long.__new__(cls, value) self.empty = False return self def __init__(self, value, precision=0): long.__init__(self, value) self.precision = precision def __str__(self, precision=None): if self.empty: return '' if precision is None: precision = self.precision value = self for unitname in self.size_labels: if value < self.radix and precision < self.radix: break value /= self.radix precision /= self.radix if unitname and value < 10 and precision < 1: return '%.1f %sB' % (value, unitname) else: return '%.0f %sB' % (value, unitname)class Rate(Size): """displays rate in human-readable format""" def __init__(self, value=None, precision=2**10): Size.__init__(self, value, precision) def __str__(self, precision=2**10): if self.empty: return '' return '%s/s'% Size.__str__(self, precision=precision)class Duration(float): """displays duration in human-readable format""" def __new__(cls, value=None): if value == None: self = float.__new__(cls, 0) self.empty = True else: self = float.__new__(cls, value) self.empty = False return self def __str__(self): if self.empty or self > 365 * 24 * 60 * 60: return '' elif self >= 172800: return _("%d days") % round(self/86400) # 2 days or longer elif self >= 86400: return _("1 day %d hours") % ((self-86400)//3600) # 1-2 days elif self >= 3600: return _("%d:%02d hours") % (self//3600, (self%3600)//60) # 1 h - 1 day elif self >= 60: return _("%d:%02d minutes") % (self//60, self%60) # 1 minute to 1 hour elif self >= 0: return _("%d seconds") % int(self) else: return _("0 seconds")def ip_sort(a_str,b_str): """Fast IP address sorting function""" for a,b in itertools.izip(a_str.split('.'), b_str.split('.')): if a == b: continue if len(a) == len(b): return cmp(a,b) return cmp(int(a), int(b)) return 0def find_dir(path): if os.path.isdir(path): return path directory, garbage = os.path.split(path) while directory: if os.access(directory, os.F_OK) and os.access(directory, os.W_OK): return directory directory, garbage = os.path.split(directory) if garbage == '': break return Nonedef smart_dir(path): path = find_dir(path) if path is None: path = desktop return pathif os.name == 'nt': disk_term = _("drive")elif os.name == 'posix' and os.uname()[0] == 'Darwin': disk_term = _("volume")else: disk_term = _("disk")class BasicTorrentObject(object): """Object for holding all information about a torrent""" def __init__(self, torrent): self.torrent = torrent self.pending = None self.infohash = torrent.metainfo.infohash self.metainfo = torrent.metainfo self.destination_path = torrent.destination_path self.working_path = torrent.working_path self.state = torrent.state self.policy = torrent.policy self.completed = torrent.completed self.priority = torrent.priority self.completion = None self.piece_states = None self.uptotal = 0 self.downtotal = 0 self.up_down_ratio = 0 self.peers = 0 self.statistics = {} self.handler = logging.handlers.MemoryHandler(0) # capacity is ignored logging.getLogger("core.MultiTorrent." + repr(self.infohash)).addHandler(self.handler) def update(self, torrent, statistics): self.torrent = torrent self.statistics = statistics self.destination_path = torrent.destination_path self.working_path = torrent.working_path self.state = torrent.state self.policy = torrent.policy self.completed = torrent.completed self.priority = statistics['priority'] self.completion = statistics['fractionDone'] self.piece_states = statistics['pieceStates'] self.uptotal += statistics.get('upTotal' , 0) self.downtotal += statistics.get('downTotal', 0) try: self.up_down_ratio = self.uptotal / self.torrent.metainfo.total_bytes except ZeroDivisionError: self.up_down_ratio = 0 self.peers = statistics.get('numPeers', 0) def wants_peers(self): return True def wants_files(self): return self.metainfo.is_batch def clean_up(self): logging.getLogger("core.MultiTorrent." + repr(self.infohash)).removeHandler(self.handler)class BasicApp(object): torrent_object_class = BasicTorrentObject def __init__(self, config): self.started = 0 self.multitorrent = None self.config = config self.torrents = {} self.external_torrents = [] self.installer_to_launch_at_exit = None self.logger = logging.getLogger('UI') self.next_autoupdate_nag = bttime() def gui_wrap(_f, *args, **kwargs): f(*args, **kwargs) self.gui_wrap = gui_wrap self.open_external_torrents_deferred = None def quit(self): if self.doneflag: self.doneflag.set() def visit_url(self, url, callback=None): """Visit a URL in the user's browser""" t = threading.Thread(target=self._visit_url, args=(url, callback)) t.start() def _visit_url(self, url, callback=None): """Visit a URL in the user's browser non-blockingly""" webbrowser.open(url) if callback: self.gui_wrap(callback) def open_torrent_arg(self, path): """Open a torrent from path (URL, file) non-blockingly""" df = ThreadedDeferred(self.gui_wrap, GetTorrent.get_quietly, path) return df def publish_torrent(self, torrent, publish_path): df = self.open_torrent_arg(torrent) yield df try: metainfo = df.getResult() except GetTorrent.GetTorrentException: self.logger.exception("publish_torrent failed") return df = self.multitorrent.create_torrent(metainfo, publish_path, publish_path) yield df df.getResult() def open_torrent_arg_with_callbacks(self, path): """Open a torrent from path (URL, file) non-blockingly, and call the appropriate GUI callback when necessary.""" def errback(e): exc_type, value, tb = e if issubclass(exc_type, GetTorrent.GetTorrentException): self.logger.critical(unicode(value.args[0])) else: self.logger.error("open_torrent_arg_with_callbacks failed", exc_info=e) def callback(metainfo): def open(metainfo): df = self.multitorrent.torrent_known(metainfo.infohash) yield df known = df.getResult() if known: self.torrent_already_open(metainfo) else: df = self.open_torrent_metainfo(metainfo) if df is not None: yield df try: df.getResult() except TorrentAlreadyInQueue: pass except TorrentAlreadyRunning: pass launch_coroutine(self.gui_wrap, open, metainfo) df = self.open_torrent_arg(path) df.addCallback(callback) df.addErrback(errback) return df def append_external_torrents(self, *a): """Append external torrents (such as those specified on the command line) so that they can be processed (for save paths, error reporting, etc.) once the GUI has started up.""" self.external_torrents.extend(a) def _open_external_torrents(self): """Open torrents added externally (on the command line before startup) in a non-blocking yet serial way.""" while self.external_torrents: arg = self.external_torrents.pop(0) df = self.open_torrent_arg(arg) yield df try: metainfo = df.getResult() except GetTorrent.GetTorrentException: self.logger.exception("Failed to get torrent") continue if metainfo is not None: # metainfo may be none if IE passes us a path to a # file in its cache that has already been deleted
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -