| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262 |
- # -*- coding: utf-8 -*-
- #
- # author: oldj
- # blog: http://oldj.net
- # email: [email protected]
- #
- import os
- import sys
- import glob
- import simplejson as json
- import wx
- from wx import stc
- import ui
- import urllib
- import re
- import traceback
- import random
- import Queue
- import time
- from Hosts import Hosts
- from TaskbarIcon import TaskBarIcon
- from BackThreads import BackThreads
- import common_operations as co
- import lang
- sys_type = co.getSystemType()
- if sys_type == "linux":
- # Linux
- try:
- import pynotify
- pynotify.init("SwitchHosts!")
- except ImportError:
- pynotify = None
- elif sys_type == "mac":
- # Mac
- import gntp.notifier
- growl = gntp.notifier.GrowlNotifier(
- applicationName="SwitchHosts!",
- notifications=["New Updates", "New Messages"],
- defaultNotifications=["New Messages"],
- hostname="127.0.0.1", # Defaults to localhost
- # password="" # Defaults to a blank password
- )
- try:
- growl.register()
- has_growl = True
- except Exception:
- has_growl = False
- class MainFrame(ui.Frame):
- def __init__(self, mainjob, instance_name,
- parent=None, id=wx.ID_ANY, title=None, pos=wx.DefaultPosition,
- size=wx.DefaultSize,
- style=wx.DEFAULT_FRAME_STYLE,
- version=None, working_path=None,
- taskbar_icon=None,
- ):
- u""""""
- self.mainjob = mainjob
- self.instance_name = instance_name
- self.version = version
- self.default_title = "SwitchHosts! %s" % self.version
- self.sudo_password = ""
- self.is_running = True
- ui.Frame.__init__(self, parent, id,
- title or self.default_title, pos, size, style)
- self.taskbar_icon = taskbar_icon or TaskBarIcon(self)
- if taskbar_icon:
- self.taskbar_icon.setMainFrame(self)
- self.latest_stable_version = "0"
- self.__sys_hosts_path = None
- self.local_encoding = co.getLocalEncoding()
- self.sys_type = co.getSystemType()
- if working_path:
- working_path = working_path.decode(self.local_encoding)
- self.working_path = working_path
- self.configs_path = os.path.join(self.working_path, "configs.json")
- self.hosts_path = os.path.join(self.working_path, "hosts")
- if not os.path.isdir(self.hosts_path):
- os.makedirs(self.hosts_path)
- self.active_fn = os.path.join(self.working_path, ".active")
- self.task_qu = Queue.Queue(4096)
- self.startBackThreads(2)
- self.makeHostsContextMenu()
- self.init2()
- self.initBind()
- # self.task_qu.put(self.chkActive)
- def init2(self):
- self.showing_rnd_id = random.random()
- self.is_switching_text = False
- self.current_using_hosts = None
- self.current_showing_hosts = None
- self.current_tree_hosts = None
- self.current_dragging_hosts = None
- self.current_tree_item = None # 当前选中的树无素
- self.origin_hostses = []
- self.common_hostses = []
- self.hostses = []
- self.fn_common_hosts = "COMMON.hosts"
- self.configs = {}
- self.loadConfigs()
- common_host_file_path = os.path.join(self.hosts_path, self.fn_common_hosts)
- if not os.path.isfile(common_host_file_path):
- common_file = open(common_host_file_path, "w")
- common_file.write("# common")
- common_file.close()
- hosts = Hosts(path=common_host_file_path, is_common=True)
- self.addHosts(hosts)
- self.getSystemHosts()
- self.scanSavedHosts()
- if not os.path.isdir(self.hosts_path):
- os.makedirs(self.hosts_path)
- def initBind(self):
- u"""初始化时绑定事件"""
- self.Bind(wx.EVT_CLOSE, self.OnClose)
- self.Bind(wx.EVT_MENU, self.OnExit, id=wx.ID_EXIT)
- self.Bind(wx.EVT_MENU, self.OnAbout, id=wx.ID_ABOUT)
- self.Bind(wx.EVT_MENU, self.OnHomepage, self.m_menuItem_homepage)
- self.Bind(wx.EVT_MENU, self.OnFeedback, self.m_menuItem_feedback)
- self.Bind(wx.EVT_MENU, self.OnChkUpdate, self.m_menuItem_chkUpdate)
- self.Bind(wx.EVT_MENU, self.OnNew, self.m_menuItem_new)
- self.Bind(wx.EVT_MENU, self.OnDel, id=wx.ID_DELETE)
- self.Bind(wx.EVT_MENU, self.OnApply, id=wx.ID_APPLY)
- self.Bind(wx.EVT_MENU, self.OnEdit, id=wx.ID_EDIT)
- self.Bind(wx.EVT_MENU, self.OnRefresh, id=wx.ID_REFRESH)
- self.Bind(wx.EVT_MENU, self.OnExport, self.m_menuItem_export)
- self.Bind(wx.EVT_MENU, self.OnImport, self.m_menuItem_import)
- self.Bind(wx.EVT_MENU, self.OnDonate, self.m_menuItem_donate)
- self.Bind(wx.EVT_BUTTON, self.OnNew, self.m_btn_add)
- self.Bind(wx.EVT_BUTTON, self.OnApply, id=wx.ID_APPLY)
- self.Bind(wx.EVT_BUTTON, self.OnDel, id=wx.ID_DELETE)
- self.Bind(wx.EVT_BUTTON, self.OnRefresh, id=wx.ID_REFRESH)
- self.Bind(wx.EVT_BUTTON, self.OnEdit, id=wx.ID_EDIT)
- self.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnTreeSelectionChange, self.m_tree)
- self.Bind(wx.EVT_TREE_ITEM_RIGHT_CLICK, self.OnTreeRClick, self.m_tree)
- self.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.OnTreeActive, self.m_tree)
- self.Bind(wx.EVT_TREE_END_LABEL_EDIT, self.OnRenameEnd, self.m_tree)
- self.Bind(wx.EVT_TREE_BEGIN_DRAG, self.OnTreeBeginDrag, self.m_tree)
- self.Bind(wx.EVT_TREE_END_DRAG, self.OnTreeEndDrag, self.m_tree)
- self.Bind(stc.EVT_STC_CHANGE, self.OnHostsChange, id=self.ID_HOSTS_TEXT)
- def startBackThreads(self, count=1):
- self.back_threads = []
- for i in xrange(count):
- t = BackThreads(task_qu=self.task_qu)
- t.start()
- self.back_threads.append(t)
- def stopBackThreads(self):
- for t in self.back_threads:
- t.stop()
- def makeHostsContextMenu(self):
- self.hosts_item_menu = wx.Menu()
- self.hosts_item_menu.Append(wx.ID_APPLY, u"切换到当前hosts")
- self.hosts_item_menu.Append(wx.ID_EDIT, u"编辑")
- self.hosts_item_menu.AppendMenu(-1, u"图标", self.makeSubIconMenu())
- self.hosts_item_menu.AppendSeparator()
- self.hosts_item_menu.Append(wx.ID_REFRESH, u"刷新")
- self.hosts_item_menu.Append(wx.ID_DELETE, u"删除")
- def makeSubIconMenu(self):
- u"""生成图标子菜单"""
- menu = wx.Menu()
- def _f(i):
- return lambda e: self.setHostsIcon(e, i)
- icons_length = len(co.ICONS)
- for i in range(icons_length):
- item_id = wx.NewId()
- mitem = wx.MenuItem(menu, item_id, u"图标#%d" % (i + 1))
- mitem.SetBitmap(co.GetMondrianBitmap(i))
- menu.AppendItem(mitem)
- self.Bind(wx.EVT_MENU, _f(i), id=item_id)
- return menu
- def setHostsIcon(self, event=None, i=0):
- u"""图标子菜单,点击动作的响应函数"""
- hosts = self.current_showing_hosts
- if not hosts:
- return
- hosts.icon_idx = i
- self.updateHostsIcon(hosts)
- hosts.save()
- def scanSavedHosts(self):
- u"""扫描目前保存的各个hosts"""
- fns = glob.glob(os.path.join(self.hosts_path, "*.hosts"))
- fns = [os.path.split(fn)[1] for fn in fns]
- if self.fn_common_hosts in fns:
- fns.remove(self.fn_common_hosts)
- cfg_hostses = self.configs.get("hostses", [])
- # 移除不存在的 hosts
- tmp_hosts = []
- for fn in cfg_hostses:
- if fn in fns:
- tmp_hosts.append(fn)
- cfg_hostses = tmp_hosts
- # 添加新的 hosts
- for fn in fns:
- if fn not in cfg_hostses:
- cfg_hostses.append(fn)
- self.configs["hostses"] = cfg_hostses
- self.saveConfigs()
- for fn in self.configs["hostses"]:
- path = os.path.join(self.hosts_path, fn)
- hosts = Hosts(path)
- if hosts.content:
- pass
- self.addHosts(hosts)
- def setHostsDir(self):
- pass
- @property
- def sys_hosts_path(self):
- u"""取得系统 hosts 文件的路径"""
- if not self.__sys_hosts_path:
- if os.name == "nt":
- systemroot = os.environ.get("SYSTEMROOT", "C:\\Windows")
- path = "%s\\System32\\drivers\\etc\\hosts" % systemroot
- else:
- path = "/etc/hosts"
- self.__sys_hosts_path = path if os.path.isfile(path) else None
- return self.__sys_hosts_path
- def getSystemHosts(self):
- path = self.sys_hosts_path
- if path:
- hosts = Hosts(path=path, title=lang.trans("origin_hosts"), is_origin=True)
- self.origin_hostses = [hosts]
- self.addHosts(hosts)
- self.highLightHosts(hosts)
- self.updateBtnStatus(hosts)
- def showHosts(self, hosts):
- self.showing_rnd_id = random.random()
- content = hosts.content if not hosts.is_loading else "loading..."
- self.is_switching_text = True
- self.m_textCtrl_content.SetReadOnly(False)
- self.m_textCtrl_content.SetValue(content)
- self.m_textCtrl_content.SetReadOnly(not self.getHostsAttr(hosts, "is_content_edit_able"))
- self.is_switching_text = False
- if self.current_showing_hosts:
- self.m_tree.SetItemBackgroundColour(self.current_showing_hosts.tree_item_id, None)
- self.m_tree.SetItemBackgroundColour(hosts.tree_item_id, "#ccccff")
- self.current_showing_hosts = hosts
- def tryToShowHosts(self, hosts):
- if hosts == self.current_showing_hosts:
- self.showHosts(hosts)
- def tryToSaveBySudoPassword(self, hosts, common_hosts):
- if not self.sudo_password:
- # 尝试获取sudo密码
- pswd = None
- dlg = wx.PasswordEntryDialog(None, u"请输入sudo密码:", u"需要管理员权限",
- style=wx.OK|wx.CANCEL
- )
- if dlg.ShowModal() == wx.ID_OK:
- pswd = dlg.GetValue().strip()
- dlg.Destroy()
- if not pswd:
- return False
- self.sudo_password = pswd
- #尝试通过sudo密码保存
- try:
- hosts.save(path=self.sys_hosts_path, common=common_hosts,
- sudo_password=self.sudo_password)
- return True
- except Exception:
- print(traceback.format_exc())
- return False
- def useHosts(self, hosts):
- if hosts.is_loading:
- wx.MessageBox(u"当前 hosts 内容正在下载中,请稍后再试...")
- return
- msg = None
- is_success = False
- common_hosts = None
- try:
- for common_hosts in self.common_hostses:
- if common_hosts.is_common:
- break
- hosts.save(path=self.sys_hosts_path, common=common_hosts)
- is_success = True
- except Exception:
- err = traceback.format_exc()
- co.log(err)
- if "Permission denied:" in err:
- if sys_type in ("linux", "mac") and self.tryToSaveBySudoPassword(
- hosts, common_hosts
- ):
- is_success = True
- else:
- msg = u"切换 hosts 失败!\n没有修改 '%s' 的权限!" % self.sys_hosts_path
- else:
- msg = u"切换 hosts 失败!\n\n%s" % err
- if msg and self.current_showing_hosts:
- wx.MessageBox(msg, caption=u"出错啦!")
- return
- if is_success:
- if len(self.origin_hostses) > 0:
- self.origin_hostses[0].icon_idx = hosts.icon_idx
- self.notify(msg=u"hosts 已切换为「%s」。" % hosts.title, title=u"hosts 切换成功")
- self.tryToFlushDNS()
- self.highLightHosts(hosts)
- def tryToFlushDNS(self):
- u"""尝试更新 DNS 缓存
- @see http://cnzhx.net/blog/how-to-flush-dns-cache-in-linux-windows-mac/
- """
- try:
- if self.sys_type == "mac":
- cmd = "dscacheutil -flushcache"
- os.popen(cmd)
- elif self.sys_type == "win":
- cmd = "ipconfig /flushdns"
- os.popen(cmd)
- elif self.sys_type == "linux":
- cmd = "service nscd restart"
- os.popen(cmd)
- except Exception:
- pass
- def highLightHosts(self, hosts):
- u"""将切换的host文件高亮显示"""
- self.m_tree.SelectItem(hosts.tree_item_id)
- if self.current_using_hosts:
- self.m_tree.SetItemBold(self.current_using_hosts.tree_item_id, bold=False)
- self.m_tree.SetItemBold(hosts.tree_item_id)
- self.showHosts(hosts)
- self.current_using_hosts = hosts
- self.updateIcon()
- def updateIcon(self):
- co.log("update icon")
- if self.current_using_hosts:
- if len(self.origin_hostses) > 0:
- self.updateHostsIcon(self.origin_hostses[0])
- self.SetIcon(co.GetMondrianIcon(self.current_using_hosts.icon_idx))
- self.taskbar_icon.updateIcon()
- def addHosts(self, hosts, show_after_add=False):
- if hosts.is_origin:
- tree = self.m_tree_origin
- list_hosts = self.origin_hostses
- elif hosts.is_online:
- tree = self.m_tree_online
- list_hosts = self.hostses
- elif hosts.is_common:
- tree = self.m_tree_common
- list_hosts = self.common_hostses
- else:
- tree = self.m_tree_local
- list_hosts = self.hostses
- if hosts.is_origin:
- hosts.tree_item_id = self.m_tree_origin
- elif hosts.is_common:
- hosts.tree_item_id = self.m_tree_common
- list_hosts.append(hosts)
- else:
- list_hosts.append(hosts)
- hosts.tree_item_id = self.m_tree.AppendItem(tree, hosts.title)
- self.updateHostsIcon(hosts)
- self.m_tree.Expand(tree)
- if show_after_add:
- self.m_tree.SelectItem(hosts.tree_item_id)
- def updateHostsIcon(self, hosts):
- icon_idx = hosts.icon_idx
- if type(icon_idx) not in (int, long) or icon_idx < 0:
- icon_idx = 0
- elif icon_idx >= len(self.ico_colors_idx):
- icon_idx = len(self.ico_colors_idx) - 1
- self.m_tree.SetItemImage(
- hosts.tree_item_id, self.ico_colors_idx[icon_idx], wx.TreeItemIcon_Normal
- )
- # if hosts == self.current_using_hosts:
- # self.updateIcon()
- def delHosts(self, hosts):
- if not hosts:
- return False
- if hosts.is_origin:
- wx.MessageBox(u"初始 hosts 不能删除哦~", caption=u"出错啦!")
- return False
- if hosts == self.current_using_hosts:
- wx.MessageBox(u"这个 hosts 方案正在使用,不能删除哦~", caption=u"出错啦!")
- return False
- dlg = wx.MessageDialog(None, u"确定要删除 hosts '%s'?" % hosts.title, u"删除 hosts",
- wx.YES_NO | wx.ICON_QUESTION
- )
- ret_code = dlg.ShowModal()
- if ret_code != wx.ID_YES:
- dlg.Destroy()
- return False
- dlg.Destroy()
- try:
- hosts.remove()
- except Exception:
- err = traceback.format_exc()
- wx.MessageBox(err, caption=u"出错啦!")
- return False
- self.m_tree.Delete(hosts.tree_item_id)
- self.hostses.remove(hosts)
- cfg_hostses = self.configs.get("hostses")
- if cfg_hostses and hosts.title in cfg_hostses:
- cfg_hostses.remove(hosts.title)
- return True
- def export(self, path):
- u"""将当前所有设置以及方案导出为一个文件"""
- data = {
- "version": self.version,
- "configs": self.configs,
- }
- hosts_files = []
- for hosts in self.hostses:
- hosts_files.append({
- "filename": hosts.filename,
- "content": hosts.full_content,
- })
- data["hosts_files"] = hosts_files
- try:
- self.writeFile(path, json.dumps(data))
- except Exception:
- wx.MessageBox(u"导出失败!\n\n%s" % traceback.format_exc(), caption=u"出错啦!")
- return
- wx.MessageBox(u"导出完成!")
- def importHosts(self, content):
- u"""导入"""
- try:
- data = json.loads(content)
- except Exception:
- wx.MessageBox(u"档案解析出错了!", caption=u"导入失败")
- return
- if type(data) != dict:
- wx.MessageBox(u"档案格式有误!", caption=u"导入失败")
- return
- configs = data.get("configs")
- hosts_files = data.get("hosts_files")
- if type(configs) != dict or type(hosts_files) not in (list, tuple):
- wx.MessageBox(u"档案数据有误!", caption=u"导入失败")
- return
- # 删除现有 hosts 文件
- current_files = glob.glob(os.path.join(self.hosts_path, "*.hosts"))
- for fn in current_files:
- try:
- os.remove(fn)
- except Exception:
- wx.MessageBox(u"删除 '%s' 时失败!\n\n%s" % (fn, traceback.format_exc()),
- caption=u"导入失败")
- return
- # 写入新 hosts 文件
- for hf in hosts_files:
- if type(hf) != dict or "filename" not in hf or "content" not in hf:
- continue
- fn = hf["filename"].strip()
- if not fn or not fn.lower().endswith(".hosts"):
- continue
- try:
- self.writeFile(os.path.join(self.hosts_path, fn), hf["content"].strip().encode("utf-8"))
- except Exception:
- wx.MessageBox(u"写入 '%s' 时失败!\n\n%s" % (fn, traceback.format_exc()),
- caption=u"导入失败")
- return
- # 更新 configs
- # self.configs = {}
- try:
- self.writeFile(self.configs_path, json.dumps(configs).encode("utf-8"))
- except Exception:
- wx.MessageBox(u"写入 '%s' 时失败!\n\n%s" % (self.configs_path, traceback.format_exc()),
- caption=u"导入失败")
- return
- # self.clearTree()
- # self.init2()
- wx.MessageBox(u"导入成功!")
- self.restart()
- def restart(self):
- u"""重启主界面程序"""
- self.mainjob.toRestart(None)
- # self.mainjob.toRestart(self.taskbar_icon)
- self.stopBackThreads()
- self.taskbar_icon.Destroy()
- self.Destroy()
- def clearTree(self):
- for hosts in self.all_hostses:
- self.m_tree.Delete(hosts.tree_item_id)
- def notify(self, msg="", title=u"消息"):
- def macGrowlNotify(msg, title):
- try:
- growl.notify(
- noteType="New Messages",
- title=title,
- description=msg,
- sticky=False,
- priority=1,
- )
- except Exception:
- pass
- if self.sys_type == "mac":
- # Mac 系统
- if has_growl:
- macGrowlNotify(msg, title)
- elif self.sys_type == "linux":
- # linux 系统
- pynotify.Notification(title, msg).show()
- else:
- try:
- import ToasterBox as TB
- except ImportError:
- TB = None
- sw, sh = wx.GetDisplaySize()
- width, height = 210, 50
- px = sw - 230
- py = sh - 100
- tb = TB.ToasterBox(self)
- tb.SetPopupText(msg)
- tb.SetPopupSize((width, height))
- tb.SetPopupPosition((px, py))
- tb.Play()
- self.SetFocus()
- def updateConfigs(self, configs):
- keys = ("hostses",)
- for k in keys:
- if k in configs:
- self.configs[k] = configs[k]
- # 校验配置有效性
- if type(self.configs.get("hostses")) != list:
- self.configs["hostses"] = []
- def loadConfigs(self):
- if os.path.isfile(self.configs_path):
- try:
- configs = json.loads(open(self.configs_path, "rb").read())
- except Exception:
- wx.MessageBox("读取配置信息失败!", caption=u"出错啦!")
- return
- if type(configs) != dict:
- wx.MessageBox("配置信息格式有误!", caption=u"出错啦!")
- return
- self.updateConfigs(configs)
- self.saveConfigs()
- def saveConfigs(self):
- try:
- self.writeFile(self.configs_path, json.dumps(self.configs))
- except Exception:
- wx.MessageBox("保存配置信息失败!\n\n%s" % traceback.format_exc(), caption=u"出错啦!")
- def eachHosts(self, func):
- for hosts in self.hostses:
- func(hosts)
- @property
- def all_hostses(self):
- return self.origin_hostses + self.hostses
- @property
- def local_hostses(self):
- return [hosts for hosts in self.hostses if not hosts.is_online]
- @property
- def online_hostses(self):
- return [hosts for hosts in self.hostses if hosts.is_online]
- def makeNewHostsFileName(self):
- u"""生成一个新的 hosts 文件名"""
- fns = glob.glob(os.path.join(self.hosts_path, "*.hosts"))
- fns = [os.path.split(fn)[1] for fn in fns]
- for i in xrange(1024):
- fn = "%d.hosts" % i
- if fn not in fns:
- break
- else:
- return None
- return fn
- def saveHosts(self, hosts):
- try:
- if hosts.save():
- co.log("saved.")
- return True
- except Exception:
- err = traceback.format_exc()
- if "Permission denied:" in err:
- msg = u"没有修改 '%s' 的权限!" % hosts.path
- else:
- msg = u"保存 hosts 失败!\n\n%s" % err
- wx.MessageBox(msg, caption=u"出错啦!")
- return False
- def showDetailEditor(self, hosts=None, default_is_online=False):
- u"""显示详情编辑窗口"""
- dlg = ui.Dlg_addHosts(self)
- if hosts:
- # 初始化值
- dlg.m_radioBtn_local.SetValue(not hosts.is_online)
- dlg.m_radioBtn_online.SetValue(hosts.is_online)
- dlg.m_radioBtn_local.Enable(False)
- dlg.m_radioBtn_online.Enable(False)
- dlg.m_textCtrl_title.SetValue(hosts.title)
- if hosts.url:
- dlg.m_textCtrl_url.SetValue(hosts.url)
- dlg.m_textCtrl_url.Enable(True)
- else:
- dlg.m_radioBtn_local.SetValue(not default_is_online)
- dlg.m_radioBtn_online.SetValue(default_is_online)
- dlg.m_textCtrl_url.Enabled = default_is_online
- if dlg.ShowModal() != wx.ID_OK:
- dlg.Destroy()
- return
- dlg.Destroy()
- is_online = dlg.m_radioBtn_online.GetValue()
- title = dlg.m_textCtrl_title.GetValue().strip()
- url = dlg.m_textCtrl_url.GetValue().strip()
- if not title:
- wx.MessageBox(u"方案名不能为空!", caption=u"出错啦!")
- return
- for h in self.hostses:
- if h != hosts and h.title == title:
- wx.MessageBox(u"已经有名为 '%s' 的方案了!" % title, caption=u"出错啦!")
- return
- if not hosts:
- # 新建 hosts
- fn = self.makeNewHostsFileName()
- if not fn:
- wx.MessageBox(u"hosts 文件数超出限制,无法再创建新 hosts 了!", caption=u"出错啦!")
- return
- path = os.path.join(self.hosts_path, fn)
- hosts = Hosts(path, title=title, url=url if is_online else None)
- hosts.content = u"# %s" % title
- if hosts.is_online:
- self.getHostsContent(hosts)
- self.addHosts(hosts, show_after_add=True)
- else:
- # 修改 hosts
- hosts.is_online = is_online
- hosts.title = title
- hosts.url = url if is_online else None
- self.updateHostsTitle(hosts)
- self.saveHosts(hosts)
- def getHostsContent(self, hosts):
- hosts.is_loading = True
- def tryToDestroy(obj):
- # mac 下,progress_dlg 销毁时总是会异常退出...
- if sys_type != "mac":
- try:
- obj.Destroy()
- except Exception:
- print(traceback.format_exc())
- if hosts.is_online:
- progress_dlg = wx.ProgressDialog(u"加载中",
- u"正在加载「%s」...\nURL: %s" % (hosts.title, hosts.url), 100,
- style=wx.PD_AUTO_HIDE
- )
- self.task_qu.put(lambda : [
- wx.CallAfter(progress_dlg.Update, 10),
- hosts.getContent(force=True, progress_dlg=progress_dlg),
- wx.CallAfter(progress_dlg.Update, 80),
- wx.CallAfter(self.tryToShowHosts, hosts),
- wx.CallAfter(progress_dlg.Update, 90),
- wx.CallAfter(self.saveHosts, hosts),
- wx.CallAfter(progress_dlg.Update, 100),
- # wx.CallAfter(lambda : progress_dlg.Destroy() and self.SetFocus()),
- wx.CallAfter(lambda : tryToDestroy(progress_dlg)),
- wx.CallAfter(self.SetFocus),
- ])
- else:
- self.task_qu.put(lambda : [
- hosts.getContent(force=True),
- wx.CallAfter(self.tryToShowHosts, hosts),
- wx.CallAfter(self.saveHosts, hosts),
- ])
- self.tryToShowHosts(hosts)
- def updateHostsTitle(self, hosts):
- u"""更新hosts的名称"""
- self.m_tree.SetItemText(hosts.tree_item_id, hosts.title)
- def getHostsFromTreeByEvent(self, event):
- item = event.GetItem()
- self.current_tree_item = item
- if item in (self.m_tree_online, self.m_tree_local, self.m_tree_root):
- pass
- elif self.current_using_hosts and item == self.current_using_hosts.tree_item_id:
- return self.current_using_hosts
- else:
- for hosts in self.all_hostses:
- if item == hosts.tree_item_id:
- return hosts
- for hosts in self.common_hostses:
- if item == hosts.tree_item_id:
- return hosts
- return None
- def getLatestStableVersion(self, alert=False):
- url = "https://github.com/oldj/SwitchHosts/blob/master/README.md"
- ver = None
- try:
- c = urllib.urlopen(url).read()
- # wx.CallAfter(progress_dlg.Update, 50)
- v = re.search(r"\bLatest Stable:\s?(?P<version>[\d\.]+)\b", c)
- if v:
- ver = v.group("version")
- self.latest_stable_version = ver
- co.log("last_stable_version: %s" % ver)
- except Exception:
- pass
- if not alert:
- return
- def _msg():
- if not ver:
- wx.MessageBox(u"未能取得最新版本号!", caption=u"出错啦!")
- else:
- cmpv = co.compareVersion(self.version, self.latest_stable_version)
- try:
- if cmpv >= 0:
- wx.MessageBox(u"当前已是最新版本!")
- else:
- if wx.MessageBox(
- u"更新的稳定版 %s 已经发布,现在立刻查看吗?" % self.latest_stable_version,
- u"发现新版本!",
- wx.YES_NO | wx.ICON_INFORMATION
- ) == wx.YES:
- self.openHomepage()
- except Exception:
- co.debugErr()
- pass
- wx.CallAfter(_msg)
- def getHostsAttr(self, hosts, key=None):
- attrs = {
- "is_refresh_able": hosts and hosts in self.all_hostses or hosts in self.common_hostses,
- "is_delete_able": hosts and hosts in self.hostses,
- "is_info_edit_able": hosts and not hosts.is_loading and hosts in self.hostses,
- "is_content_edit_able": hosts and not hosts.is_loading and
- (hosts in self.hostses or hosts in self.common_hostses),
- "is_apply_able": not hosts.is_common and not hosts.is_origin,
- }
- for k in attrs:
- attrs[k] = True if attrs[k] else False
- return attrs.get(key, False) if key else attrs
- def updateBtnStatus(self, hosts):
- hosts_attrs = self.getHostsAttr(hosts)
- # 更新下方按钮状态
- self.m_btn_refresh.Enable(hosts_attrs["is_refresh_able"])
- self.m_btn_del.Enable(hosts_attrs["is_delete_able"])
- self.m_btn_edit_info.Enable(hosts_attrs["is_info_edit_able"])
- self.m_btn_apply.Enable(hosts_attrs["is_apply_able"])
- # 更新右键菜单项状态
- self.hosts_item_menu.Enable(wx.ID_EDIT, hosts_attrs["is_info_edit_able"])
- self.hosts_item_menu.Enable(wx.ID_DELETE, hosts_attrs["is_delete_able"])
- self.hosts_item_menu.Enable(wx.ID_REFRESH, hosts_attrs["is_refresh_able"])
- self.hosts_item_menu.Enable(wx.ID_APPLY, hosts_attrs["is_apply_able"])
- def writeFile(self, path, content, mode="w"):
- try:
- path = path.encode(self.local_encoding)
- except Exception:
- co.debugErr()
- open(path, mode).write(content)
- def openHomepage(self):
- u"""打开项目主页"""
- url= "http://oldj.github.io/SwitchHosts/"
- wx.LaunchDefaultBrowser(url)
- def OnHomepage(self, event):
- self.openHomepage()
- def openFeedbackPage(self):
- u"""打开反馈主页"""
- url = "https://github.com/oldj/SwitchHosts/issues?direction=desc&sort=created&state=open"
- wx.LaunchDefaultBrowser(url)
- def OnFeedback(self, event):
- self.openFeedbackPage()
- def OnHostsChange(self, event):
- if self.is_switching_text:
- return
- self.current_showing_hosts.content = self.m_textCtrl_content.GetText().strip()
- self.saveHosts(self.current_showing_hosts)
- def OnChkUpdate(self, event):
- self.task_qu.put(lambda : [
- self.getLatestStableVersion(alert=True),
- ])
- def OnExit(self, event):
- self.is_running = False
- self.stopBackThreads()
- self.taskbar_icon.Destroy()
- self.Destroy()
- # 退出时删除进程锁文件
- lock_fn = os.path.join(self.working_path, self.instance_name) \
- if self.instance_name else None
- if lock_fn and os.path.isfile(lock_fn):
- os.remove(lock_fn)
- # sys.exit()
- def OnAbout(self, event):
- dlg = ui.AboutBox(version=self.version, latest_stable_version=self.latest_stable_version)
- dlg.ShowModal()
- dlg.Destroy()
- def OnTreeSelectionChange(self, event):
- u"""当点击左边树状结构的节点的时候触发"""
- hosts = self.getHostsFromTreeByEvent(event)
- if not hosts:
- return
- self.current_tree_hosts = hosts
- self.updateBtnStatus(hosts)
- if not hosts or (hosts not in self.hostses and hosts not in self.origin_hostses and hosts not in self.common_hostses):
- return event.Veto()
- if hosts and hosts != self.current_showing_hosts:
- if hosts.is_origin:
- # 重新读取系统 hosts 值
- hosts.getContent()
- self.showHosts(hosts)
- def OnTreeRClick(self, event):
- u"""在树节点上单击右键,展示右键菜单"""
- hosts = self.getHostsFromTreeByEvent(event)
- if hosts:
- self.OnTreeSelectionChange(event)
- self.m_tree.PopupMenu(self.hosts_item_menu, event.GetPoint())
- def OnTreeMenu(self, event):
- co.log("tree menu...")
- def OnTreeActive(self, event):
- u"""双击树的节点时候触发"""
- hosts = self.getHostsFromTreeByEvent(event)
- if hosts:
- if hosts.is_common or hosts.is_origin:
- return
- self.useHosts(hosts)
- def OnApply(self, event):
- u"""点击切换Hosts时候,触发该函数"""
- if self.current_showing_hosts and self.current_showing_hosts.is_common:
- return
- if self.current_showing_hosts:
- self.useHosts(self.current_showing_hosts)
- def OnDel(self, event):
- if self.delHosts(self.current_tree_hosts):
- self.current_showing_hosts = None
- def OnNew(self, event):
- is_online = False
- hosts = self.current_showing_hosts
- if hosts.is_online or self.current_tree_item == self.m_tree_online:
- is_online = True
- self.showDetailEditor(default_is_online=is_online)
- def OnEdit(self, event):
- self.showDetailEditor(hosts=self.current_showing_hosts)
- def OnRename(self, event):
- hosts = self.current_showing_hosts
- if not hosts:
- return
- if hosts in self.origin_hostses:
- wx.MessageBox(u"%s不能改名!" % lang.trans("origin_hosts"), caption=u"出错啦!")
- return
- self.m_tree.EditLabel(hosts.tree_item_id)
- def OnRenameEnd(self, event):
- hosts = self.current_showing_hosts
- if not hosts:
- return
- title = event.GetLabel().strip()
- if title and hosts.title != title:
- hosts.title = title
- hosts.save()
- else:
- event.Veto()
- def OnRefresh(self, event):
- hosts = self.current_showing_hosts
- self.getHostsContent(hosts)
- def OnExport(self, event):
- if wx.MessageBox(
- u"您可以将现在的 hosts 档案导出并共享给其他 SwitchHosts! 用户。\n\n" +
- u"注意,只有“%s”和“%s”中的 hosts 会被导出!" % (
- lang.trans("local_hosts"), lang.trans("online_hosts")),
- caption=u"导出档案",
- style=wx.OK | wx.CANCEL,
- ) != wx.OK:
- return
- wildcard = u"SwicthHosts! 档案 (*.swh)|*.swh"
- dlg = wx.FileDialog(self, u"导出为...", os.getcwd(), "hosts.swh", wildcard, wx.SAVE)
- if dlg.ShowModal() == wx.ID_OK:
- self.export(dlg.GetPath())
- dlg.Destroy()
- def OnImport(self, event):
- dlg = ui.Dlg_Import(self)
- if dlg.ShowModal() == wx.ID_OK:
- path = dlg.m_filePicker.GetPath()
- url = dlg.m_textCtrl_url.GetValue()
- content = None
- if dlg.m_notebook.GetSelection() != 1:
- # 本地
- if os.path.isfile(path):
- content = open(path).read()
- else:
- wx.MessageBox(u"%s 不是有效的文件路径!" % path, caption=u"出错啦!")
- else:
- # 在线
- if co.httpExists(url):
- content = urllib.urlopen(url).read()
- else:
- wx.MessageBox(u"URL %s 无法访问!" % url, caption=u"出错啦!")
- if content and wx.MessageBox(u"导入档案会替换现有设置及数据,确定要导入吗?",
- caption=u"警告",
- style=wx.OK | wx.CANCEL) == wx.OK:
- self.importHosts(content)
- dlg.Destroy()
- def OnDonate(self, event):
- wx.LaunchDefaultBrowser("https://me.alipay.com/oldj")
- def OnTreeBeginDrag(self, event):
- item = event.GetItem()
- hosts = self.getHostsFromTreeByEvent(event)
- if not hosts or hosts.is_origin or hosts.is_common:
- event.Veto()
- return
- co.log("drag start..")
- self.current_dragging_hosts = hosts
- self.__dragging_item = item
- event.Allow()
- self.m_tree.Bind(wx.EVT_MOTION, self._drag_OnMotion)
- self.m_tree.Bind(wx.EVT_LEFT_UP, self._drag_OnMouseLeftUp)
- def _drag_OnMotion(self, event):
- event.Skip()
- def _drag_OnMouseLeftUp(self, event):
- co.log("mouse left up..")
- self.m_tree.Unbind(wx.EVT_MOTION)
- self.m_tree.Unbind(wx.EVT_LEFT_UP)
- event.Skip()
- def OnTreeEndDrag(self, event):
- co.log("drag end..")
- target_item = event.GetItem()
- target_hosts = self.getHostsFromTreeByEvent(event)
- source_item = self.__dragging_item
- source_hosts = self.current_dragging_hosts
- self.__dragging_item = None
- self.current_dragging_hosts = None
- def getHostsIdx(hosts):
- idx = 0
- for h in self.hostses:
- if h == hosts:
- break
- if h.is_online == hosts.is_online:
- idx += 1
- return idx
- is_dragged = False
- if target_hosts and target_hosts != source_hosts and \
- source_hosts.is_online == target_hosts.is_online:
- # 拖到目标 hosts 上了
- parent = self.m_tree.GetItemParent(target_item)
- added_item_id = self.m_tree.InsertItemBefore(parent, getHostsIdx(target_hosts),
- source_hosts.title
- )
- source_hosts.tree_item_id = added_item_id
- # self.updateHostsTitle(source_hosts)
- self.updateHostsIcon(source_hosts)
- if source_hosts == self.current_using_hosts:
- self.highLightHosts(source_hosts)
- self.hostses.remove(source_hosts)
- self.hostses.insert(self.hostses.index(target_hosts), source_hosts)
- is_dragged = True
- elif target_item == self.m_tree_local and not source_hosts.is_online:
- # 拖到本地树上了
- pass
- elif target_item == self.m_tree_online and source_hosts.is_online:
- # 拖到在线树上了
- pass
- if is_dragged:
- self.updateConfigs({
- "hostses": [hosts.filename for hosts in self.hostses],
- })
- self.saveConfigs()
- self.m_tree.Delete(source_item)
- self.m_tree.SelectItem(source_hosts.tree_item_id)
- def OnActiveApp(self, event):
- """Called when the doc icon is clicked, and ???"""
- print("---")
- # self.GetTopWindow().Raise()
- self.Raise()
- def chkActive(self):
- u"""循环查看工作目录下是否有 .active 文件,有则激活主窗口"""
- if self.is_running and os.path.isfile(self.active_fn):
- print("active..")
- os.remove(self.active_fn)
- # print(dir(self.mainjob.app))
- self.Raise()
- wx.TopLevelWindow.RequestUserAttention(self)
- # self.mainjob.app.SetTopWindow(self)
- time.sleep(0.5)
- # wx.CallAfter(self.chkActive)
- if self.is_running:
- self.task_qu.put(self.chkActive)
|