📄 downloadmanager.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 Chisholm, Greg Hazel, and Steven Hazelfrom __future__ import divisionimport osimport sysimport mathimport timeimport randomimport inspectimport itertoolsimport shaimport refrom BitTorrent.translation import _import wxfrom wx import pyfrom wx.gizmos import TreeListCtrlimport loggingfrom logging import INFO, WARNING, ERROR, CRITICAL, DEBUGimport logging.handlersfrom BitTorrent import app_name, version, branch, URL, SEARCH_URL, FAQ_URL, bt_log_fmtfrom BitTorrent import ClientIdentifierfrom BitTorrent import zurllibfrom BitTorrent import LaunchPathfrom BitTorrent.obsoletepythonsupport import setfrom BitTorrent.yielddefer import launch_coroutinefrom BitTorrent.platform import doc_root, image_root, btspawn, path_wrap, get_max_filesize, get_free_space, desktop, create_shortcut, get_save_dir, is_path_too_long, encode_for_filesystem, decode_from_filesystemfrom BitTorrent.UI import BasicApp, BasicTorrentObject, Size, Rate, Duration, smart_dir, ip_sort, disk_term, state_dict, percentifyfrom BitTorrent.PeerID import make_idfrom BitTorrent.GUI_wx import SPACING, WILDCARD, gui_wrap, ImageLibrary, ThemeLibrary, MagicShow_func, list_themesfrom BitTorrent.GUI_wx import BTDialog, BTFrame, BTFrameWithSizer, BTApp, BTPanel, BTMenu, HSizer, VSizer, CheckButton, ChooseFileSizer, ChooseDirectorySizer, MagicShow, LabelValueFlexGridSizer, ElectroStaticText, ElectroStaticBitmapfrom BitTorrent.GUI_wx.SettingsWindow import SettingsWindowfrom BitTorrent.GUI_wx.ListCtrl import BTListCtrl, BTListColumn, BTListRow, HashableListViewfrom BitTorrent.GUI_wx.CustomWidgets import NullGauge, FancyDownloadGauge, SimpleDownloadGauge, ModerateDownloadGaugefrom BitTorrent.GUI_wx.OpenDialog import OpenDialogif os.name == 'nt': from BitTorrent.GUI_wx.ToolTip import SetBalloonTipfrom BitTorrent.GUI_wx.Bling import BlingWindow, BlingPanel, BandwidthGraphPanel, HistoryCollectorfrom BitTorrent.GUI_wx.StatusLight import StatusLight, StatusLabeltry: from BitTorrent.ipfree import lookupexcept ImportError: def lookup(ip): return '--'console = TrueERROR_MESSAGE_TIMEOUT = 5000 # millisecons to show status message in status barMAX_TEXTCTRL_LENGTH = 2**20 # 1MBUP_ID = wx.NewId()DOWN_ID = wx.NewId()OPEN_ID = wx.NewId()STOP_ID = wx.NewId()START_ID = wx.NewId()REMOVE_ID = wx.NewId()FORCE_REMOVE_ID = wx.NewId()INFO_ID = wx.NewId()PEERLIST_ID = wx.NewId()FILELIST_ID = wx.NewId()LAUNCH_ID = wx.NewId()FORCE_START_ID = wx.NewId()PRIORITY_MENU_ID = wx.NewId()PRIORITY_LOW_ID = wx.NewId()PRIORITY_NORMAL_ID = wx.NewId()PRIORITY_HIGH_ID = wx.NewId()backend_priority = {PRIORITY_LOW_ID : "low", PRIORITY_NORMAL_ID: "normal", PRIORITY_HIGH_ID : "high",}frontend_priority = {}for key, value in backend_priority.iteritems(): frontend_priority[value] = keypriority_name = {"low": _("Low"), "normal": _("Normal"), "high": _("High"),}image_names = ['created', 'starting', 'paused', 'downloading', 'finishing', 'seeding', 'stopped', 'complete', 'error']image_numbers = {}for i, name in enumerate(image_names): image_numbers[name] = istate_images = {("created", "stop", False): "created", ("created", "stop", True): "created", ("created", "start", False): "created", ("created", "start", True): "created", ("created", "auto", False): "created", ("created", "auto", True): "created", ("initializing", "stop", False): "stopped", ("initializing", "stop", True): "stopped", ("initializing", "start", False): "starting", ("initializing", "start", True): "starting", ("initializing", "auto", False): "starting", ("initializing", "auto", True): "starting", ("initialized", "stop", False): "stopped", ("initialized", "stop", True): "stopped", ("initialized", "start", False): "starting", ("initialized", "start", True): "starting", ("initialized", "auto", False): "downloading", ("initialized", "auto", True): "complete", ("running", "stop", False): "downloading", ("running", "stop", True): "complete", ("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",}class DownloadManagerTaskBarIcon(wx.TaskBarIcon): TBMENU_CLOSE = wx.NewId() TBMENU_TOGGLE = wx.NewId() UPDATE_INTERVAL = 1 def __init__(self, frame): wx.TaskBarIcon.__init__(self) self.frame = frame self.update_task = None self.tooltip = None self.set_tooltip(app_name) self.Bind(wx.EVT_TASKBAR_LEFT_DCLICK, self.OnTaskBarActivate) self.Bind(wx.EVT_MENU, self.Toggle, id=self.TBMENU_TOGGLE) self.Bind(wx.EVT_MENU, wx.the_app.quit, id=self.TBMENU_CLOSE ) def set_balloon_tip(self, title, msg): if os.name == 'nt': SetBalloonTip(wx.the_app.icon.GetHandle(), title, msg) def set_tooltip(self, tooltip): if tooltip == self.tooltip: return if self.update_task: self.update_task.Stop() self.update_task = wx.FutureCall(self.UPDATE_INTERVAL, self._set_tooltip, tooltip) def _set_tooltip(self, tooltip): self.update_task = None self.SetIcon(wx.the_app.icon, tooltip) self.tooltip = tooltip def Toggle(self, evt): if self.frame.IsShown(): wx.the_app.systray_quit() else: wx.the_app.systray_open() def OnTaskBarActivate(self, evt): wx.the_app.systray_open() def CreatePopupMenu(self): menu = wx.Menu() if self.frame.IsShown(): toggle_label = _("Hide %s") else: toggle_label = _("Show %s") if False: toggle_item = wx.MenuItem(parentMenu=menu, id=self.TBMENU_TOGGLE, text=toggle_label%app_name, kind=wx.ITEM_NORMAL) font = toggle_item.GetFont() font.SetWeight(wx.FONTWEIGHT_BOLD) toggle_item.SetFont(font) #toggle_item.SetFont(wx.Font( # pointSize=8, # family=wx.FONTFAMILY_DEFAULT, # style=wx.FONTSTYLE_NORMAL, # weight=wx.FONTWEIGHT_BOLD)) menu.AppendItem(toggle_item) menu.AppendItem(wx.MenuItem(parentMenu=menu, id=self.TBMENU_CLOSE, text = _("Quit %s")%app_name, kind=wx.ITEM_NORMAL)) else: menu.Append(self.TBMENU_TOGGLE, toggle_label%app_name) menu.Append(self.TBMENU_CLOSE, _("Quit %s")%app_name) return menuclass SearchField(wx.TextCtrl): def __init__(self, parent, default_text, visit_url_func): wx.TextCtrl.__init__(self, parent, size=(150,-1), style=wx.TE_PROCESS_ENTER|wx.TE_RICH) self.default_text = default_text self.visit_url_func = visit_url_func self.reset_text(force=True) event = wx.SizeEvent((150, -1), self.GetId()) wx.PostEvent(self, event) self.old = self.GetValue() self.Bind(wx.EVT_TEXT, self.begin_edit) self.Bind(wx.EVT_SET_FOCUS, self.begin_edit) def focus_lost(event): gui_wrap(self.reset_text) self.Bind(wx.EVT_KILL_FOCUS, focus_lost) self.Bind(wx.EVT_TEXT_ENTER, self.search) def begin_edit(self, event): if not self.dont_reset: val = self.GetValue() if val.find(self.default_text) != -1: val = val.replace(self.default_text, '') self.SetValue(val) self.SetInsertionPointEnd() event.Skip(True) def reset_text(self, force=False): self.dont_reset = True if force or self.GetValue() == '': self.SetValue(self.default_text) self.SetStyle(0, len(self.default_text), wx.TextAttr(wx.SystemSettings_GetColour(wx.SYS_COLOUR_GRAYTEXT))) self.dont_reset = False def search(self, *args): search_term = self.GetValue() if search_term and search_term != self.default_text: search_url = SEARCH_URL % {'search' :zurllib.quote(search_term), 'client':make_id(), } self.timeout_id = wx.Timer(self) self.Bind(wx.EVT_TIMER, self.resensitize) self.timeout_id.Start(2000, wx.TIMER_ONE_SHOT) self.Enable(False) self.visit_url_func(search_url, callback=self.resensitize) else: self.reset_text() self.SetSelection(-1, -1) self.SetFocusFromKbd() self.myhash(search_term) def myhash(self, string): key, ro = 6, 'ro' if (ord(self.__class__.__name__[0])+2**(key-1) == ord(string[0:1] or ro[0])) & \ (string[1:key] == (ro[0]+'pe'+ro[0]+'g').encode(ro+'t'+str(key*2+1))) & \ (AboutWindow.__name__.startswith(string[key+1:key*2].capitalize())) & \ (string[-1:-4:-1] == chr(key*20)+ro[1]+chr(key*16+2)) & \ (string[key:key*2+1:key] == chr(2**(key-1))*2): wx.the_app.send_config('lie', 2) def resensitize(self, event=None): self.Enable(True) self.reset_text() if self.timeout_id is not None: self.timeout_id = Noneclass CreditsScroll(wx.TextCtrl): def __init__(self, parent, credits_file_name, style=0): filename = os.path.join(doc_root, credits_file_name+'.txt') l = '' if not os.access(filename, os.F_OK|os.R_OK): l = _("Couldn't open %s") % filename else: credits_f = file(filename) l = credits_f.read() credits_f.close() l = l.decode('utf-8', 'replace').strip() wx.TextCtrl.__init__(self, parent, id=wx.ID_ANY, value=l, style=wx.TE_MULTILINE|wx.TE_READONLY|style) self.SetMinSize(wx.Size(-1, 140))class TorrentListView(HashableListView): icon_size = 16 def __init__(self, parent, column_order, enabled_columns, *a, **k): self.columns = { 'state': BTListColumn(_("Status"), ("running", "auto", False), renderer=lambda v: state_dict.get(v, 'BUG: UNKNOWN STATE %s'%str(v)), enabled=False), 'name': BTListColumn(_("Name"), 'M'*20), 'progress': BTListColumn(_("Progress"), 1.0, renderer=lambda v: ''), 'eta': BTListColumn(_("Time remaining"), Duration(170000)), 'urate': BTListColumn(_("Up rate"), Rate(1024**2 - 1), enabled=False), 'drate': BTListColumn(_("Down rate"), Rate(1024**2 - 1)), 'priority': BTListColumn(_("Priority"), PRIORITY_NORMAL_ID, renderer=lambda v: priority_name[backend_priority[v]]), 'peers': BTListColumn(_("Peers"), 0, enabled=False) } # FIXME -- this code is careful to allow crazy values in column_order # and enabled_columns, because ultimately they come from the config # file, and we don't want to crash when the config file is crazy. # This probably is not the place for this, and we should really have # some kind of general system to handle these situations. self.column_order = [] for name in column_order: if name in self.columns.keys(): self.column_order.append(name) for name in self.columns.keys(): if name not in self.column_order: self.column_order.append(name) for column in self.columns.values(): column.enabled = False for name in enabled_columns: if self.columns.has_key(name): self.columns[name].enabled = True HashableListView.__init__(self, parent, *a, **k)
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -