diff --git a/.gitattributes b/.gitattributes index 50f39b7..8191892 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,6 +1,8 @@ # Set default behaviour, in case users don't have core.autocrlf set. * text eol=lf *.py text eol=lf +*.rst text eol=lf +windows* text eol=crlf *.zip binary *.dmg binary diff --git a/.gitignore b/.gitignore index 6747499..1777019 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,12 @@ +downloads/tmp *.tmp *.pyc *.data + Thumbs.db hosts .DS_Store + +.idea/ +data/ipv4_mods/*.hosts +doc/build/ \ No newline at end of file diff --git a/README.md b/README.md deleted file mode 100644 index 0c7d39e..0000000 --- a/README.md +++ /dev/null @@ -1,141 +0,0 @@ -Hosts Setup Utility -=================== - - - -Introduction ------------- - -Due to the Chinese government is using the GFW blocking the web access to some -world famous sites like Googel+, twitter, Facebook, and Wikipedia etc., we -made this hosts tool to help people to get through the Great Firewall. - -Hosts Setup Utility provides basic tools to manage the hosts file on operating -systems with a desktop environment. Users could use these tool to modify the -hosts file in order to visit specified websites blocked by Chinese government. -Tools for users to backup/restore hosts files is also provided. - -The home page of this project is . - -You can also visit the project page on Google Code to get our latest news -. - - -License -------- - -Licensed under the GNU General Public License, version 3. You should -have received a copy of the GNU General Public License along with -this program. If not, see . - - -Usage ------ - -* Windows(x86/x64): run hostsutl.exe from the binary excutables package to get - started. - - - "Run as Administrator" is needed for operations to change the hosts - file on Windows Vista or newer. - -* Mac OS X: run HostsUtl application from the binary excutables package to get - started. - - - Because of the locale problem with py2app, the automatic language - selection may not work correctly on Mac OS with binary excutables. - You can just choose the language on your on choice. - -* Linux/X11(Source code): run command "python hostsutl.py" to get started. - - - All platforms with python and the PyQT4 extension could use this - - method to run the source code. - - A desktop environment with QT and python is needed only for - Linux/X11 users. - - "sudo" is needed for operations to change the hosts file. - -(Note if the programe is not running with privileges to modify the hosts file, -a warning message would be shown and you could only do operations like backup -hosts file and update the local data file.) - - -Requirements ------------- - -* Microsoft Windows 2000 or newer for Windows users. - -* Mac OS X 10.6 or newer for Macintosh users. - -* Linux/X11 desktop with QT for Linux/X11 users. - -* Python 2.6/2.7 with PyQT4 extension for develop. - -* py2exe or py2app would be required while making binary excutables for - specified platforms. - - -Available Modules ------------------ - -* hostsutl.py - contains main parts of Hosts Setup Utility. - -* qthostsui.py - contains UI class for the main dialog of Hosts Setup Utility. - -* qthosts_rc.py - contains images used by the main dialog. - -* retrievedata.py - contains tools to read data from the local hosts data - file. - -* utilities.py - contains basic utilities used by Hosts Setup Utility. - - -Tools for Developers --------------------- - -* _build.py - contains tools to make packages for different platforms. - - Usage: _build.py [type] - Options: - type define the platform to make package for. Optional choices - could be: py2exe, py2app, py2tar, py2source - py2exe - Make binary excutables for Windows. The - py2app - Make binary excutables for Mac OS X. The - operations of this option depends on the py2app - distutils extension. - py2app - Make source code packages for X11 users. - py2source - Make source code packages for developers. - -* _pylupdate4.py : contains tools to update the language files for UI. - -* _pyuic4.py : contains tools update the UI code from UI design. - - -The Rest of the Distribution ----------------------------- - -* lang/ - This directory contains language files for Hosts Setup Utility. - The *.qm files would be included in distribution packages. - -* img/ - This directory contains images and Icons used by Hosts Setup - Utility. - The files in this directory would on be included source code package - for developers. - -* mac_res/ - This directory contains resources to make excutables binaries for - Mac OS X. - The files in this directory would on be included source code package - for developers. - -* theme/ - This directory contains QT stylesheet files of the program. - The stylesheet files (*.qss) are used to create the user interface of - Hosts Setup Utility. - -* hostsutl.pro - Project file for QT. - -* qthosts.qrc - Resource file for main dialog designed by QT. - -* qthostsui.ui - UI project file for the main dialog. - - -Author/Maintainer ------------------ -huhamhire diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..92cebc0 --- /dev/null +++ b/README.rst @@ -0,0 +1,167 @@ +################### +Hosts Setup Utility +################### + +Introduction +============ +Since the governments of some countries are using technical methods blocking +internet access to some websites and web service providers which includes some +world famous sites like Google, YouTube, twitter, Facebook, and Wikipedia +etc., we designed this tiny utility in order to help people getting through +the Internet blockade. + +`Hosts Setup Utility` provides basic tools to manage the hosts file on current +operating systems. It also provides both support for Graphical Desktop +environment with Graphical User Interface (GUI) and CLI environment with +Text-based User Interface (TUI). + +Users could use these tool to modify the hosts to visit specified websites or +services blocked by ISP/government. Functions which help users to +backup/restore hosts files are also provided. + +`Hosts Setup Utility` is designed by `huhamhire-hosts team`. For further +information, please visit our `website `_. + + +Developer Documentation +======================= +Developers could find our project documentation by visiting +``_. + +This documentation contains descriptions of modules and functions which are +used in the current version of `Hosts Setup Utility`. These documents also +provides some optional methods for developers to help improve this tool. + + +License +======= +Licensed under the GNU General Public License, version 3. You should have +received a copy of the GNU General Public License along with this program. +If not, see ``_. + + +System Requirements +=================== +Here are the system requirements needed for using `Hosts Setup Utility`. + + +Graphical User Interface (GUI) +------------------------------ +System requirements to run `Hosts Setup Utility` on Graphical Desktop are +listed here: + +* Microsoft Windows 2000 or newer for Windows users. + +* Mac OS X 10.6 or newer for Macintosh users. + +* Linux/X11 desktop with Python 2 and PyQt4 for Linux/X11 users. + +* Internet access is required for retrieving the latest hosts data file. + + - On some linux distributions, pre-built packages of PyQt4 can be + found in software repositories. For example, you can install PyQt4 on + a debian distribution simplly using: + + .. code-block:: bash + + apt-get install python-qt4 + + +More requirements are needed for developers: + +* Python 2.6/2.7 with PyQt4 extension for developers. + +* py2exe or py2app would be required while making binary excutables for + specified platforms. + + +Text-based User Interface (TUI) +------------------------------- +Any devices with `Python 2` and `Python Standard Library` INSTALLED could run +`Hosts Setup Utility` in TUI mode from a 80x24 terminal. In addition to this, +TUI mode could also be operated via SSH on remote devices/machines/servers. +All you need is a system with Python 2 installed. + + +Get Started +=========== +Since `Hosts Setup Utility` supports both Graphical Desktop environment with +Graphical User Interface (GUI) and CLI/terminal environment with Text-based +User Interface (TUI), users could the way they would like to launch this tool. + +However, GUI mode is highly recommended because several features like +backup/restore hosts file are still not supported in TUI mode currently. + +* If the program is not running with privileges to modify the hosts + file, a warning message would be shown and you could only do operations + like backup hosts file and update the local data file. Plus, TUI mode + could not get started in this condition. + + +Graphical User Interface (GUI) Mode +----------------------------------- +* Windows(x86/x64): Run hoststool.exe from the binary excutables package to + get started. + + - "Run as Administrator" is needed for operations to change the + hosts file on Windows Vista or newer. + +* Mac OS X: Run HostsUtl application from the binary excutables package to get + started. + + - Because of the locale problem with py2app, the automatic language + selection may not work correctly on Mac OS with binary executable + files. You can just choose the language on your on choice. + +* Linux/X11(Source code): Run command "python hoststool.py" to get started. + + - All platforms with Python and the PyQt4 could use this method to run + with the source code. + - A desktop environment with PyQt4 and python is needed only for + Linux/X11 users to start a GUI Session. + + +Text-based User Interface (TUI) Mode +----------------------------------- +* Windows Excutable(x86/x64): + + #. Start a command line(could be `cmd` or `Power Shell`). + + - "Run as Administrator" is needed for operations to change the + hosts file on Windows Vista or newer. + + #. Change directory to the folder contains binary executable files. of + `Hosts Setup Utility`. + + #. Run ``hoststool_tui.exe`` with an argument ``-t`` from the directory to + get started. + +* Python Source Code: + + `Python Source Code` is very easy to be started through any terminals on any + operating systems. + + #. Change your directory to the source script. + + #. Run ``python hoststool.py -t`` in the terminal. Of course, wirte + privileges to access the hosts file on current system is required. If + not, a warning message box would show up and then terminate current + session. + + +User Customized Hosts +===================== + +Users are allowed to add customized hosts list as an independent module to +make a hosts file. All you need to do is create a simple text file named +``custom.hosts`` in the working directory, and put your own hosts entries +into this file. Then you would find a `Customized Hosts` option in the +function list. + + * Non-ASCII characters are not recommended to be put into the + customized hosts file. + + +Author/Maintainer +================= +huhamhire ``_ diff --git a/_pyuic4.py b/__version__.py similarity index 51% rename from _pyuic4.py rename to __version__.py index 97d6cbf..cf7cba5 100644 --- a/_pyuic4.py +++ b/__version__.py @@ -1,9 +1,9 @@ -#!/usr/bin/python +#!/usr/bin/env python # -*- coding: utf-8 -*- # -# _pyuic4.py : Tools update the UI code from UI design +# __version__.py : Version info for Hosts Setup Tool. # -# Copyleft (C) 2013 - huhamhire hosts team +# Copyleft (C) 2014 - huhamhire hosts team # ===================================================================== # Licensed under the GNU General Public License, version 3. You should # have received a copy of the GNU General Public License along with @@ -14,12 +14,6 @@ # PURPOSE. # ===================================================================== -import os -for root, dirs, files in os.walk('.'): - for file in files: - if file.endswith('.ui'): - os.system('pyuic4 -o %s.py -x %s' \ - % (file.rsplit('.', 1)[0], file)) - elif file.endswith('.qrc'): - os.system('pyrcc4 -o %s_rc.py %s' \ - % (file.rsplit('.', 1)[0], file)) +__version__ = "1.9.8" +__release__ = "beta" +__revision__ = "$Id$" \ No newline at end of file diff --git a/_build.py b/_build.py index 5ec3ee1..a9cf3ff 100644 --- a/_build.py +++ b/_build.py @@ -3,7 +3,7 @@ # # _build.py : Tools to make packages for different platforms # -# Copyleft (C) 2013 - huhamhire hosts team +# Copyleft (C) 2014 - huhamhire hosts team # ===================================================================== # Licensed under the GNU General Public License, version 3. You should # have received a copy of the GNU General Public License along with @@ -14,16 +14,15 @@ # PURPOSE. # ===================================================================== -__version__ = "0.9.0" __author__ = "huhamhire " import os import sys import shutil -from hostsutl import __version__ +from __version__ import __version__ -SCRIPT = "hostsutl.py" +SCRIPT = "hoststool.py" SCRIPT_DIR = os.getcwd() + '/' RELEASE_DIR = "../release/" @@ -32,10 +31,10 @@ VERSION = __version__ DESCRIPTION = "HostsUtl - Hosts Setup Utility" AUTHOR = "Hamhire Hu" -AUTHOR_EMAIL ="develop@huhamhire.com", +AUTHOR_EMAIL = "hosts@huhamhire.com", LICENSE = "Public Domain, Python, BSD, GPLv3 (see LICENSE)", URL = "https://hosts.huhamhire.com", -CLASSIFIERS = [ +CLASSIFIERS = [ "Development Status :: 4 - Beta", "Environment :: MacOS X", "Environment :: Win32 (MS Windows)", @@ -60,49 +59,70 @@ "Topic :: System :: Networking", "Topic :: Software Development :: Documentation", "Topic :: Text Processing", - "Topic :: Utilities", + "Topic :: CommonUtil", ] DATA_FILES = [ - ("lang", [ - "lang/en_US.qm", - "lang/zh_CN.qm", - "lang/zh_TW.qm", - ] - ), - ("theme", [ - "theme/darkdefault.qss", - ] - ), - "LICENSE", - "README.md", - "network.conf", + ("gui/lang", [ + "gui/lang/en_US.qm", + "gui/lang/zh_CN.qm", + "gui/lang/zh_TW.qm", + ]), + ("gui/theme", [ + "gui/theme/default.qss", + ]), + (".", [ + "LICENSE", + "README.rst", + "network.conf", + ]), ] if sys.argv > 1: tar_flag = 0 + includes = [] + excludes = [] + file_path = lambda rel_path: SCRIPT_DIR + rel_path if sys.argv[1] == "py2tar": # Pack up script package for Linux users - file_path = lambda rel_path: SCRIPT_DIR + rel_path includes = [ - "*.py", "lang/*.qm", "theme/*.qss", "LICENSE", "README.md", - "network.conf"] - excludes = ["_*.py", ".gitattributes"] + "*.py", + "gui/lang/*.qm", + "gui/theme/*.qss", + "*/*.py", + "LICENSE", + "README.rst", + "network.conf", + ] + excludes = [ + "_build.py", + "_pylupdate4.py", + "_pyuic4.py", + ".gitattributes", + ".gitignore", + ] ex_files = [] - prefix = "HostsUtl-x11-gpl-" + prefix = "HostsTool-x11-gpl-" tar_flag = 1 elif sys.argv[1] == "py2source": # Pack up source package for Linux users - file_path = lambda rel_path: SCRIPT_DIR + rel_path includes = ["*"] - excludes = [".gitattributes"] + excludes = [ + ".gitattributes", + ".gitignore", + "hostslist.data", + ] ex_files = [] - prefix = "HostsUtl-source-gpl-" + prefix = "HostsTool-source-gpl-" tar_flag = 1 + else: + prefix = "Error" + ex_files = [] if tar_flag: import glob import tarfile + TAR_NAME = prefix + VERSION + ".tar.gz" RELEASE_PATH = RELEASE_DIR + TAR_NAME if not os.path.exists(RELEASE_DIR): @@ -117,24 +137,26 @@ files = glob.glob(file_path(name_format)) for src_file in files: if src_file not in ex_files: - tar_path = os.path.join(prefix + VERSION, src_file[rel_len:]) + tar_path = os.path.join(prefix + VERSION, + src_file[rel_len:]) tar.add(src_file, tar_path) - print src_file + print "compressing: %s" % src_file tar.close() exit(1) -from utilities import Utilities -system = Utilities.check_platform()[0] +from util import CommonUtil + +system = CommonUtil.check_platform()[0] if system == "Windows": # Build binary executables for Windows import struct import zipfile from distutils.core import setup - import py2exe + # Set working directories WORK_DIR = SCRIPT_DIR + "work/" - DIR_NAME = "HostsUtl" + DIR_NAME = "HostsTool" DIST_DIR = WORK_DIR + DIR_NAME + '/' WIN_OPTIONS = { "includes": ["sip"], @@ -149,23 +171,32 @@ shutil.rmtree(DIST_DIR) # Build Executable print " Building Executable ".center(78, '=') + EXE_NAME = SCRIPT.split(".")[0] setup( - name = NAME, - version = VERSION, - options = {"py2exe": WIN_OPTIONS}, - windows = [ + name=NAME, + version=VERSION, + options={"py2exe": WIN_OPTIONS}, + console=[ + {"script": SCRIPT, + "dest_base": "hoststool_tui", + "uac_info": "highestAvailable", + }, + ], + windows=[ {"script": SCRIPT, - "icon_resources": [(1, "img/hosts_utl.ico")] + "icon_resources": [(1, "res/img/icons/hosts_utl.ico")], + "dest_base": EXE_NAME, + "uac_info": "highestAvailable", }, ], - description = DESCRIPTION, - author = AUTHOR, - author_email = AUTHOR_EMAIL, - license = LICENSE, - url = URL, - zipfile = None, - data_files = DATA_FILES, - classifiers = CLASSIFIERS, + description=DESCRIPTION, + author=AUTHOR, + author_email=AUTHOR_EMAIL, + license=LICENSE, + url=URL, + zipfile="lib/shared.lib", + data_files=DATA_FILES, + classifiers=CLASSIFIERS, ) # Clean work directory after build shutil.rmtree(SCRIPT_DIR + "build/") @@ -175,6 +206,8 @@ PLAT = "x64" elif struct.calcsize("P") * 8 == 32: PLAT = "x86" + else: + PLAT = "unknown" DIR_NAME = DIR_NAME + '-win-gpl-' + VERSION + '-' + PLAT ZIP_NAME = DIR_NAME + ".zip" ZIP_FILE = WORK_DIR + ZIP_NAME @@ -202,13 +235,13 @@ from setuptools import setup # Set working directories WORK_DIR = SCRIPT_DIR + "work/" - RES_DIR = SCRIPT_DIR + "mac_res/" - APP_NAME = "HostsUtl.app" + RES_DIR = SCRIPT_DIR + "res/mac/" + APP_NAME = "HostsTool.app" APP_PATH = WORK_DIR + APP_NAME DIST_DIR = APP_PATH + "/Contents/" # Set build configuration MAC_OPTIONS = { - "iconfile": "img/hosts_utl.icns", + "iconfile": "res/img/icons/hosts_utl.icns", "includes": ["sip", "PyQt4.QtCore", "PyQt4.QtGui"], "excludes": [ "PyQt4.QtDBus", @@ -233,45 +266,45 @@ "plist": { "CFBundleAllowMixedLocalizations": True, "CFBundleSignature": "hamh", - "CFBundleIdentifier": "org.pythonmac.huhamhire.HostsUtl", - "NSHumanReadableCopyright": "(C) 2013, Huhamhire hosts Team"} + "CFBundleIdentifier": "org.pythonmac.huhamhire.HostsTool", + "NSHumanReadableCopyright": "(C) 2014, huhamhire hosts Team"} } # Clean work space before build if os.path.exists(APP_PATH): shutil.rmtree(APP_PATH) if not os.path.exists(WORK_DIR): os.mkdir(WORK_DIR) - # Make daemon APP + # Make daemon APP OSAC_CMD = "osacompile -o %s %sHostsUtl.scpt" % (APP_PATH, RES_DIR) os.system(OSAC_CMD) # Build APP print " Building Application ".center(78, '=') setup( - app = [SCRIPT], - name = NAME, - version = VERSION, - options = {"py2app": MAC_OPTIONS}, - setup_requires = ["py2app"], - description = DESCRIPTION, - author = AUTHOR, - author_email = AUTHOR_EMAIL, - license = LICENSE, - url = URL, - data_files = DATA_FILES, - classifiers = CLASSIFIERS, + app=[SCRIPT], + name=NAME, + version=VERSION, + options={"py2app": MAC_OPTIONS}, + setup_requires=["py2app"], + description=DESCRIPTION, + author=AUTHOR, + author_email=AUTHOR_EMAIL, + license=LICENSE, + url=URL, + data_files=DATA_FILES, + classifiers=CLASSIFIERS, ) # Clean work directory after build os.remove(DIST_DIR + "Resources/applet.icns") shutil.copy2( - SCRIPT_DIR + "img/hosts_utl.icns", + SCRIPT_DIR + "res/img/icons/hosts_utl.icns", DIST_DIR + "Resources/applet.icns") shutil.copy2(RES_DIR + "Info.plist", DIST_DIR + "Info.plist") shutil.rmtree(SCRIPT_DIR + "build/") # Pack APP to DMG file VDMG_DIR = WORK_DIR + "package_vdmg/" DMG_TMP = WORK_DIR + "pack_tmp.dmg" - DMG_RES_DIR = RES_DIR + "dmg_resource/" - VOL_NAME = "HostsUtl" + DMG_RES_DIR = RES_DIR + "dmg/" + VOL_NAME = "HostsTool" DMG_NAME = VOL_NAME + "-mac-gpl-" + VERSION + ".dmg" DMG_PATH = WORK_DIR + DMG_NAME # Clean work space before pack up @@ -281,7 +314,7 @@ os.remove(DMG_TMP) if os.path.isfile(DMG_PATH): os.remove(DMG_PATH) - # Prepare files in DMG package + # Prepare files in DMG package os.mkdir(VDMG_DIR) shutil.move(APP_PATH, VDMG_DIR) os.symlink("/Applications", VDMG_DIR + " ") diff --git a/curseshosts.py b/curseshosts.py deleted file mode 100644 index fcfe278..0000000 --- a/curseshosts.py +++ /dev/null @@ -1,609 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# hcurses.py: -# -# Copyleft (C) 2013 - huhamhire -# ===================================================================== -# Licensed under the GNU General Public License, version 3. You should -# have received a copy of the GNU General Public License along with -# this program. If not, see . -# ===================================================================== - -__version__ = '1.9.7' -__revision__ = "$Id$" -__author__ = "huhamhire " - -__all__ = [ 'HostsCursesUI' ] - -import curses -import locale - -import os, sys -import socket - -import math -import urllib -import json - -from zipfile import BadZipfile - -from retrievedata import RetrieveData, make_hosts -from utilities import Utilities - -class HostsCursesUI(object): - __stdscr = '' - __title = "HOSTS SETUP UTILITY" - __copyleft = "v%s Copyleft 2011-2013, Huhamhire-hosts Team" % __version__ - - colorpairs = [(curses.COLOR_WHITE, curses.COLOR_BLUE), - (curses.COLOR_WHITE, curses.COLOR_RED), - (curses.COLOR_YELLOW, curses.COLOR_BLUE), - (curses.COLOR_BLUE, curses.COLOR_WHITE), - (curses.COLOR_WHITE, curses.COLOR_WHITE), - (curses.COLOR_BLACK, curses.COLOR_WHITE), - (curses.COLOR_GREEN, curses.COLOR_WHITE), - (curses.COLOR_WHITE, curses.COLOR_BLACK), - (curses.COLOR_RED, curses.COLOR_WHITE),] - ops_keys = [curses.KEY_F5, curses.KEY_F6, curses.KEY_F10] - hotkeys = [curses.KEY_UP, curses.KEY_DOWN, 10, 32] - func_items = [[], []] - func_selec = [[], []] - settings = [["Server", 0, []], - ["IP Version", 0, ["IPv4", "IPv6"]]] - funckeys = [["", "Select Item"], ["Tab", "Select Field"], - ["Enter", "Set Item"], ["F5", "Check Update"], - ["F6", "Get Update"], ["F10", "Apply Changes"], - ["Esc", "Exit"]] - subtitles = [["Configure Settings", (1, 2)], ["Status", (8, 2)], - ["Hosts File", (13, 2)], ["Select Functions", (1, 28)]] - statusinfo = [["Connection", "N/A", "GREEN"], ["OS", "N/A", "GREEN"]] - hostsinfo = {"Version": "N/A", "Release": "N/A", "Latest": "N/A"} - platform = [] - - filename = "hostslist.data" - infofile = "hostsinfo.json" - - item_sup = 0 - item_inf = 0 - - def __init__(self): - locale.setlocale(locale.LC_ALL, '') - self.__stdscr = curses.initscr() - curses.start_color() - curses.noecho() - curses.cbreak() - curses.curs_set(0) - # Set colors - curses.use_default_colors() - for i, color in enumerate(self.colorpairs): - curses.init_pair(i + 1, *color) - - def banner(self): - screen = self.__stdscr.subwin(2, 80, 0, 0) - screen.bkgd(' ', curses.color_pair(1)) - # Set local variable - title = curses.A_NORMAL - title += curses.A_BOLD - normal = curses.color_pair(4) - # Print title - screen.addstr(0, 0, self.__title.center(79), title) - screen.addstr(1, 0, "Setup".center(10), normal) - screen.refresh() - - def footer(self): - screen = self.__stdscr.subwin(1, 80, 23, 0) - screen.bkgd(' ', curses.color_pair(1)) - # Set local variable - normal = curses.A_NORMAL - # Copyright info - copyleft = self.__copyleft - screen.addstr(0, 0, copyleft.center(79), normal) - screen.refresh() - - def configure_settings(self, pos=None, key_in=None): - self.__stdscr.keypad(1) - screen = self.__stdscr.subwin(8, 25, 2, 0) - screen.bkgd(' ', curses.color_pair(4)) - # Set local variable - normal = curses.A_NORMAL - select = curses.color_pair(5) - select += curses.A_BOLD - - id_num = range(len(self.settings)) - if pos != None: - if key_in == curses.KEY_DOWN: - pos = list(id_num[1:] + id_num[:1])[pos] - elif key_in == curses.KEY_UP: - pos = list(id_num[-1:] + id_num[:-1])[pos] - elif key_in in [10, 32]: - self.sub_selection(pos) - self.info(pos, 0) - for p, item in enumerate(self.settings): - item_str = item[0].ljust(12) - screen.addstr(3 + p, 2, item_str, select if p == pos else normal) - if p: - choice = "[%s]" % item[2][item[1]] - else: - choice = "[%s]" % item[2][item[1]]["label"] - screen.addstr(3 + p, 15, ''.ljust(10), normal) - screen.addstr(3 + p, 15, choice, select if p == pos else normal) - screen.refresh() - return pos - - def status(self): - screen = self.__stdscr.subwin(11, 25, 10, 0) - screen.bkgd(' ', curses.color_pair(4)) - # Set local variable - normal = curses.A_NORMAL - green = curses.color_pair(7) - red = curses.color_pair(9) - # Status info - for i, stat in enumerate(self.statusinfo): - screen.addstr(2 + i, 2, stat[0], normal) - stat_str = ''.join(['[', stat[1], ']']).ljust(9) - screen.addstr(2 + i, 15, stat_str, - green if stat[2] == "GREEN" else red) - # Hosts file info - i = 0 - for key, info in self.hostsinfo.items(): - screen.addstr(7 + i, 2, key, normal) - screen.addstr(7 + i, 15, info, normal) - i += 1 - screen.refresh() - - def select_func(self, pos=None, key_in=None): - screen = self.__stdscr.subwin(18, 26, 2, 26) - screen.bkgd(' ', curses.color_pair(4)) - # Set local variable - normal = curses.A_NORMAL - select = curses.color_pair(5) - select += curses.A_BOLD - list_height = 15 - ip = self.settings[1][1] - # Key Press Operations - item_len = len(self.func_items[ip]) - item_sup, item_inf = self.item_sup, self.item_inf - if pos != None: - if item_len > list_height: - if pos <= 1: - item_sup = 0 - item_inf = list_height - 1 - elif pos >= item_len - 2: - item_sup = item_len - list_height + 1 - item_inf = item_len - else: - item_sup = 0 - item_inf = item_len - if key_in == curses.KEY_DOWN: - pos += 1 - if pos >= item_len: - pos = 0 - if pos not in range(item_sup, item_inf): - item_sup += 2 if item_sup == 0 else 1 - item_inf += 1 - elif key_in == curses.KEY_UP: - pos -= 1 - if pos < 0: - pos = item_len - 1 - if pos not in range(item_sup, item_inf): - item_inf -= 2 if item_inf == item_len else 1 - item_sup -= 1 - elif key_in in [10, 32]: - self.func_selec[ip][pos] = not self.func_selec[ip][pos] - mutex = RetrieveData.get_ids(self.func_items[ip][pos][2]) - for c_id, c in enumerate(self.func_items[ip]): - if c[0] == self.func_items[ip][pos][0]: - if c[1] in mutex and self.func_selec[ip][c_id] == 1: - self.func_selec[ip][c_id] = 0 - self.info(pos, 1) - else: - item_sup = 0 - if item_len > list_height: - item_inf = list_height - 1 - else: - item_inf = item_len - # Function list - items_show = self.func_items[ip][item_sup:item_inf] - items_selec = self.func_selec[ip][item_sup:item_inf] - for p, item in enumerate(items_show): - sel_ch = '+' if items_selec[p] else ' ' - item_str = ("[%s] %s" % (sel_ch, item[3])).ljust(23) - item_pos = pos - item_sup if pos != None else None - highlight = select if p == item_pos else normal - if item_len > list_height: - if item_inf - item_sup == list_height - 2: - screen.addstr(4 + p, 2, item_str, highlight) - elif item_inf == item_len: - screen.addstr(4 + p, 2, item_str, highlight) - elif item_sup == 0: - screen.addstr(3 + p, 2, item_str, highlight) - else: - screen.addstr(3 + p, 2, item_str, highlight) - if item_len > list_height: - if item_inf - item_sup == list_height - 2: - screen.addstr(3, 2, " More ".center(23, '.'), normal) - screen.addch(3, 15, curses.ACS_UARROW) - screen.addstr(17, 2, " More ".center(23, '.'), normal) - screen.addch(17, 15, curses.ACS_DARROW) - elif item_inf == item_len: - screen.addstr(3, 2, " More ".center(23, '.'), normal) - screen.addch(3, 15, curses.ACS_UARROW) - elif item_sup == 0: - screen.addstr(17, 2, " More ".center(23, '.'), normal) - screen.addch(17, 15, curses.ACS_DARROW) - else: - for line_i in range(list_height - item_len): - screen.addstr(17 - line_i, 2, ' ' * 23, normal) - screen.refresh() - - self.item_sup, self.item_inf = item_sup, item_inf - return pos - - def info(self, pos, tab): - screen = self.__stdscr.subwin(18, 24, 2, 52) - screen.bkgd(' ', curses.color_pair(4)) - normal = curses.A_NORMAL - if tab: - ip = self.settings[1][1] - info_str = self.func_items[ip][pos][3] - else: - info_str = self.settings[pos][0] - # Clear Expired Infomotion - for i in range(6): - screen.addstr(1 + i, 2, ''.ljust(22), normal) - screen.addstr(1, 2, info_str, normal) - # Key Info Offset - k_info_y = 10 - k_info_x_key = 2 - k_info_x_text = 10 - # Arrow Keys - screen.addch(k_info_y, k_info_x_key, curses.ACS_UARROW, normal) - screen.addch(k_info_y, k_info_x_key + 1, curses.ACS_DARROW, normal) - # Show Key Info - for i, keyinfo in enumerate(self.funckeys): - screen.addstr(k_info_y + i, k_info_x_key, keyinfo[0], normal) - screen.addstr(k_info_y + i, k_info_x_text, keyinfo[1], normal) - screen.refresh() - - def process_bar(self, done, block, total, mode=1): - screen = self.__stdscr.subwin(2, 80, 20, 0) - screen.bkgd(' ', curses.color_pair(4)) - normal = curses.A_NORMAL - line_width = 76 - prog_len = line_width - 20 - # Progress Bar - if mode: - done = done * block - prog = prog_len * done / total - progress = ''.join(['=' * int(prog), '-' * int(2 * prog % 2)]) - progress = progress.ljust(prog_len) - total = Utilities.convert_size(total).ljust(7) - done = Utilities.convert_size(done).rjust(7) - else: - progress = ' ' * prog_len - done = total = 'N/A'.center(7) - # Show Progress - prog_bar = "[%s] %s | %s" % (progress, done, total) - screen.addstr(1, 2, prog_bar, normal) - screen.refresh() - - def section_daemon(self): - screen = self.__stdscr.subwin(0, 0, 0, 0) - screen.keypad(1) - # Draw Menu - self.banner() - self.footer() - # Key Press Operations - key_in = None - tab = 0 - pos = 0 - hot_keys = self.hotkeys - tab_entry = [self.configure_settings, self.select_func] - while key_in != 27: - self.setup_menu() - self.status() - self.process_bar(0, 0, 0, 0) - for i, sec in enumerate(tab_entry): - tab_entry[i](pos if i == tab else None) - if key_in == None: - self.platform = self.check_platform() - test = self.settings[0][2][0]["test_url"] - self.check_connection(test) - key_in = screen.getch() - if key_in == 9: - if self.func_items == [[], []]: - tab = 0 - else: - tab = not tab - pos = 0 - elif key_in in hot_keys: - pos = tab_entry[tab](pos, key_in) - elif key_in in self.ops_keys: - i = self.ops_keys.index(key_in) - if i: - confirm = self.confirm_win(i) - else: - self.check_update() - - def sub_selection(self, pos): - i_len = len(self.settings[pos][2]) - i_pos = self.settings[pos][1] - # Draw Shadow - shadow = curses.newwin(i_len + 2, 18, 13 - i_len / 2, 31) - shadow.bkgd(' ', curses.color_pair(8)) - shadow.refresh() - # Draw Subwindow - screen = curses.newwin(i_len + 2, 18, 12 - i_len / 2, 30) - screen.box() - screen.bkgd(' ', curses.color_pair(1)) - screen.keypad(1) - # Set local variable - normal = curses.A_NORMAL - select = normal + curses.A_BOLD - # Title of Subwindow - screen.addstr(0, 3, self.settings[pos][0].center(12), normal) - # Key Press Operations - id_num = range(len(self.settings[pos][2])) - key_in = None - while key_in != 27: - for p, item in enumerate(self.settings[pos][2]): - item_str = item if pos else item["tag"] - screen.addstr(1 + p, 2, item_str, - select if p == i_pos else normal) - screen.refresh() - key_in = screen.getch() - if key_in == curses.KEY_DOWN: - i_pos = list(id_num[1:] + id_num[:1])[i_pos] - elif key_in == curses.KEY_UP: - i_pos = list(id_num[-1:] + id_num[:-1])[i_pos] - elif key_in in [10, 32]: - if pos == 0 and i_pos != self.settings[pos][1]: - test = self.settings[pos][2][i_pos]["test_url"] - self.check_connection(test) - self.settings[pos][1] = i_pos - return - - def setup_menu(self): - screen = self.__stdscr.subwin(21, 80, 2, 0) - screen.box() - screen.bkgd(' ', curses.color_pair(4)) - # Configuration Section - screen.addch(0, 26, curses.ACS_BSSS) - screen.vline(1, 26, curses.ACS_VLINE, 17) - # Status Section - screen.addch(7, 0, curses.ACS_SSSB) - screen.addch(7, 26, curses.ACS_SBSS) - screen.hline(7, 1, curses.ACS_HLINE, 25) - # Select Functions Section - screen.addch(0, 52, curses.ACS_BSSS) - screen.vline(1, 52, curses.ACS_VLINE, 17) - # Process Bar Section - screen.addch(18, 0, curses.ACS_SSSB) - screen.addch(18, 79, curses.ACS_SBSS) - screen.hline(18, 1, curses.ACS_HLINE, 78) - screen.addch(18, 26, curses.ACS_SSBS) - screen.addch(18, 52, curses.ACS_SSBS) - # Section Titles - title = curses.color_pair(6) - for s_title in self.subtitles: - cord = s_title[1] - screen.addstr(cord[0], cord[1], s_title[0], title) - screen.hline(cord[0] + 1, cord[1], curses.ACS_HLINE, 23) - screen.refresh() - - def confirm_win(self, op): - # Draw Shadow - shadow = curses.newwin(5, 40, 11, 21) - shadow.bkgd(' ', curses.color_pair(8)) - shadow.refresh() - # Draw Subwindow - screen = curses.newwin(5, 40, 10, 20) - screen.box() - screen.bkgd(' ', curses.color_pair(2)) - screen.keypad(1) - # Set local variable - normal = curses.A_NORMAL - select = curses.A_REVERSE - messages = ["Apply Changes to hosts file?", - "Backup current hosts file?", - "Restore hosts from a backup?"] - choices = ["Apply", "Cancel"] - # Draw subwindow frame - screen.addstr(1, 2, messages[op].center(36), normal) - screen.hline(2, 1, curses.ACS_HLINE, 38) - screen.addch(2, 0, curses.ACS_SSSB) - screen.addch(2, 39, curses.ACS_SBSS) - # Apply or Cancel the Operation - tab = 0 - key_in = None - while key_in != 27: - for i, item in enumerate(choices): - item_str = ''.join(['[', item, ']']) - screen.addstr(3, 6 + 20 * i, item_str, - select if i == tab else normal) - screen.refresh() - key_in = screen.getch() - if key_in in [9, curses.KEY_LEFT, curses.KEY_RIGHT]: - tab = [1, 0][tab] - if key_in in [ord('a'), ord('c')]: - key_in -= (ord('a') - ord('A')) - if key_in in [ord('A'), ord('C')]: - return [ord('A'), ord('C')].index(key_in) - if key_in in [10, 32]: - return tab - - def check_connection(self, url): - # Draw Shadow - shadow = curses.newwin(3, 30, 11, 21) - shadow.bkgd(' ', curses.color_pair(8)) - shadow.refresh() - # Draw Subwindow - screen = curses.newwin(3, 30, 10, 20) - screen.box() - screen.bkgd(' ', curses.color_pair(2)) - screen.keypad(1) - - normal = curses.A_NORMAL - screen.addstr(1, 3, "Checking Server Status...", normal) - screen.refresh() - - conn = Utilities.check_connection(url) - if conn: - self.statusinfo[0][1] = "OK" - self.statusinfo[0][2] = "GREEN" - else: - self.statusinfo[0][1] = "Error" - self.statusinfo[0][2] = "RED" - self.status() - return conn - - def check_platform(self): - plat = Utilities.check_platform() - self.statusinfo[1] = [self.statusinfo[1][0], plat[0], - "GREEN" if plat[4] else "RED"] - self.status() - return plat - - def check_update(self): - # Draw Shadow - shadow = curses.newwin(3, 30, 11, 21) - shadow.bkgd(' ', curses.color_pair(8)) - shadow.refresh() - # Draw Subwindow - screen = curses.newwin(3, 30, 10, 20) - screen.box() - screen.bkgd(' ', curses.color_pair(2)) - screen.keypad(1) - - normal = curses.A_NORMAL - screen.addstr(1, 7, "Checking Update...", normal) - screen.refresh() - - srv_id = self.settings[0][1] - url = self.settings[0][2][srv_id]["update"] + self.infofile - try: - socket.setdefaulttimeout(5) - urlobj = urllib.urlopen(url) - j_str = urlobj.read() - urlobj.close() - info = json.loads(j_str) - except: - info = {"version": "[Error]"} - self.hostsinfo["Latest"] = info["version"] - self.status() - return info - -class HostsCurses(object): - _ipv_id = 0 - _is_root = 0 - _down_flag = 0 - _funcs = [[], []] - _hostsinfo = [] - _make_cfg = {} - _make_mode = "" - _make_path = "./hosts" - _sys_eol = "" - _update = {} - hostsinfo = ["N/A", "N/A"] - - choice = [[], []] - slices = [[], []] - # OS related configuration - platform = '' - hostname = '' - hostspath = '' - # Mirror related configuration - _mirr_id = 0 - mirrors = [] - # Data file related configuration - filename = "hostslist.data" - infofile = "hostsinfo.json" - - def init_main(self): - # Set mirrors - self.mirrors = Utilities.set_network("network.conf") - self.set_platform() - # Read data file and set function list - try: - RetrieveData.unpack() - RetrieveData.connect_db() - self.set_func_list() - self.set_info() - except IOError: - pass - except BadZipfile: - pass - # Check if current session have root privileges - self.check_root() - - def opt_session(self): - window = HostsCursesUI() - window.func_items = self.choice - window.func_selec = self._funcs - window.hostsinfo["Version"] = self.hostsinfo[0] - window.hostsinfo["Release"] = self.hostsinfo[1] - window.settings[0][2] = self.mirrors - - window.section_daemon() - - def set_platform(self): - """Set OS info - Public Method - - Set the information of current operating system platform. - """ - system, hostname, path, encode, flag = Utilities.check_platform() - color = "GREEN" if flag else "RED" - self.platform = system - self.hostname = hostname - self.hostspath = path - if encode == "win_ansi": - self._sys_eol = "\r\n" - else: - self._sys_eol = "\n" - - def set_func_list(self): - for ip in range(2): - choice, defaults, slices = RetrieveData.get_choice(ip) - self.choice[ip] = choice - self.slices[ip] = slices - funcs = [] - for func in choice: - if func[1] in defaults[func[0]]: - funcs.append(1) - else: - funcs.append(0) - self._funcs[ip] = funcs - - def set_info(self): - """Set data file info - Public Method - - Set the information of the current local data file. - """ - info = RetrieveData.get_info() - ver = info["Version"] - build = info["Buildtime"] - build = Utilities.timestamp_to_date(build) - self.hostsinfo = [ver, build] - - def check_root(self): - """Check root privileges - Public Method - - Check if current session is ran with root privileges. - """ - is_root = Utilities.check_privileges()[1] - self._is_root = is_root - if not is_root: - #self.warning_permission() - pass - - -class HostsDownload(object): - def get_file(self, url, path, ui_class): - socket.setdefaulttimeout(10) - urllib.urlretrieve(url, path, ui_class.process_bar) - -if __name__ == "__main__": - main = HostsCurses() - main.init_main() - main.opt_session() diff --git a/doc/dev/Makefile b/doc/dev/Makefile new file mode 100644 index 0000000..14cc5b9 --- /dev/null +++ b/doc/dev/Makefile @@ -0,0 +1,178 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = ../../document +SOURCEDIR = ./ + +# User-friendly check for sphinx-build +ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) +$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) +endif + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) $(SOURCEDIR) +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) $(SOURCEDIR) + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " xml to make Docutils-native XML files" + @echo " pseudoxml to make pseudoxml-XML files for display purposes" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/huhamhire-hosts.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/huhamhire-hosts.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/huhamhire-hosts" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/huhamhire-hosts" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +latexpdfja: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through platex and dvipdfmx..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." + +xml: + $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml + @echo + @echo "Build finished. The XML files are in $(BUILDDIR)/xml." + +pseudoxml: + $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml + @echo + @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." diff --git a/doc/dev/conf.py b/doc/dev/conf.py new file mode 100644 index 0000000..91db424 --- /dev/null +++ b/doc/dev/conf.py @@ -0,0 +1,268 @@ +# -*- coding: utf-8 -*- +# +# huhamhire-hosts documentation build configuration file, created by +# sphinx-quickstart on Tue Jan 14 10:49:55 2014. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys +import os + +sys.path.insert(0, os.path.abspath('../../')) + +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.pngmath', + 'sphinx.ext.viewcode', + 'sphinx.ext.graphviz', + 'sphinx.ext.inheritance_diagram', +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'Hosts Setup Utility' +copyright = u'2011-2014, huhamhire-hosts team' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '1.9.8' +# The full version, including alpha/beta/rc tags. +release = '1.9.8 beta' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +#exclude_patterns = [] + +#unused_docs = ["gpl"] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + +# If true, keep warnings as "system message" paragraphs in the built documents. +#keep_warnings = False + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'default' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} +html_theme_options = { + "stickysidebar": True, + "collapsiblesidebar": False, +} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +#html_extra_path = [] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'huhamhire-hostsdoc' + + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { + 'inputenc': '', + 'utf8extra': '', + 'preamble': ''' +\\hypersetup{unicode=true} +\\usepackage{CJKutf8} +\\AtBeginDocument{\\begin{CJK}{UTF8}{}} +\\AtEndDocument{\\end{CJK}} +''', + 'papersize': 'a4paper', +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + ('index', 'huhamhire-hosts.tex', u'Hosts Setup Utility Documentation', + u'huhamhire-hosts team', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +latex_show_pagerefs = True + +# If true, show URL addresses after external links. +latex_show_urls = True + +# Documents to append as an appendix to all manuals. +latex_appendices = ['gpl'] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'huhamhire-hosts', u'Hosts Setup Utility Documentation', + [u'huhamhire-hosts team'], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ('index', 'huhamhire-hosts', u'Hosts Setup Utility Documentation', + u'huhamhire-hosts team', 'huhamhire-hosts', + 'Easy managing hosts file.', 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +#texinfo_no_detailmenu = False diff --git a/doc/dev/gpl.rst b/doc/dev/gpl.rst new file mode 100644 index 0000000..50dc790 --- /dev/null +++ b/doc/dev/gpl.rst @@ -0,0 +1,11 @@ +:orphan: + +GNU General Public License +========================== + +`Hosts Setup Utility` is designed by `huhamhire-hosts team` and licensed under +the `GNU General Public License, version 3(GPLv3)`. For further information, +please visit our `website `_. + +.. include:: ../../LICENSE + :literal: diff --git a/doc/dev/gui.rst b/doc/dev/gui.rst new file mode 100644 index 0000000..9a28a03 --- /dev/null +++ b/doc/dev/gui.rst @@ -0,0 +1,3 @@ +.. automodule:: gui.__doc__ + :members: + diff --git a/doc/dev/hoststool.rst b/doc/dev/hoststool.rst new file mode 100644 index 0000000..0f36226 --- /dev/null +++ b/doc/dev/hoststool.rst @@ -0,0 +1,69 @@ +Hosts Setup Utility +=================== + +This chapter contains discriptions for modules, scripts, and configurations +at the root working directory of `Hosts Setup Utility`. + +UtilLauncher +------------ +.. automodule:: hoststool + :members: + + +Packaging Tool +-------------- + +A simple script is also provided for developer to make new package of +`Hosts Setup Utility` distributions. This tool could create distributions for +`Windows`, `OS X`, `Linux`, and any `Unix-like` systems. + +* **_build.py** + +Here is the usage for running this script:: + + Usage: _build.py [type] + Options: + type Indicating the mode for making packages. Optional choices + could be: py2exe, py2app, py2tar, py2source + py2exe - Make binary excutables for Windows. The + operations of this option depends on the py2exe + distutils extension. + py2app - Make binary excutables for Mac OS X. The + operations of this option depends on the py2app + distutils extension. + py2app - Make source code packages for X11 users. + py2source - Make source code packages for developers. + +.. note:: + * ``py2exe`` option requires a Windows platform with `py2exe` installed. + * ``py2app`` option requires an OS X platform with `PyQt4` and `py2app`. + + +Configuration Files +------------------- + +There is only one configuration file stored in the root working directory +currently, which is used to configure the mirrors servers for +`Hosts Setup Utility`. + +* **network.conf** + +Here is an example for setting up a server in the configuration file:: + + [Github] + label = GITHUB + server = github.com + update = http://huhamhire.github.com/huhamhire-hosts/update/ + + +Hosts Data File +--------------- + +`hosts data file` is not included in any distributions. It is usually +downloaded from the mirror servers of this project. + +* **hostslist.data** + +In fact, the data file `hostslist.data` is just a zip package containing a +sqlite database file `hostslist.s3db`. The database file could be loaded by +any sqlite database managing tools. diff --git a/doc/dev/index.rst b/doc/dev/index.rst new file mode 100644 index 0000000..0da3203 --- /dev/null +++ b/doc/dev/index.rst @@ -0,0 +1,32 @@ +Hosts Setup Utility Developer Documentation +=========================================== + +Since the governments of some countries are using technical methods blocking +internet access to some websites and web service providers which includes some +world famous sites like Google, YouTube, twitter, Facebook, and Wikipedia +etc., we designed this tiny utility in order to help people getting through +the Internet blockade. + +Chapter one of this documentation contains a brief introduction of +`Hosts Setup Utility` and basic usage to start this tool. While the others +describing modules and functions that are used in the current version of +`Hosts Setup Utility`. These chapters also describes some optional methods +for developers to help improve this tool. + +This is the documentation for `Hosts Setup Utility` |release|, last updated +|today|. + +`Hosts Setup Utility` is designed by `huhamhire-hosts team` and licensed under +the `GNU General Public License, version 3(GPLv3)`. For further information, +please visit our `website `_. + + +.. toctree:: + :numbered: + :maxdepth: 2 + + intro + hoststool + gui + tui + util diff --git a/doc/dev/intro.rst b/doc/dev/intro.rst new file mode 100644 index 0000000..86f7758 --- /dev/null +++ b/doc/dev/intro.rst @@ -0,0 +1,160 @@ +Introduction +============ + +Since the governments of some countries are using the blocking the internet +access to several websites and web service providers which includes some world +famous sites like Google, YouTube, twitter, Facebook, and Wikipedia etc., we +designed this tiny utility in order to help people getting through the +Internet blockade. + +`Hosts Setup Utility` provides basic tools to manage the hosts file on current +operating systems. It also provides both support for Graphical Desktop +environment with Graphical User Interface (GUI) and CLI environment with +Text-based User Interface (TUI). + +Users could use these tool to modify the hosts to visit specified websites or +services blocked by ISP/government. Functions which help users to +backup/restore hosts files are also provided. + + +System Requirements +------------------- + +Here are the system requirements needed for using `Hosts Setup Utility`. + +Graphical User Interface (GUI) +`````````````````````````````` + +System requirements to run `Hosts Setup Utility` on Graphical Desktop are +listed here: + +* Microsoft Windows 2000 or newer for Windows users. + +* Mac OS X 10.6 or newer for Macintosh users. + +* Linux/X11 desktop with Python 2 and PyQt4 for Linux/X11 users. + +* Internet access is required for retrieving the latest hosts data file. + +.. note:: On some linux distributions, pre-built packages of PyQt4 can be + found in software repositories. For example, you can install PyQt4 on + a debian distribution simplly using: + + .. code-block:: bash + + apt-get install python-qt4 + + +More requirements are needed for developers: + +* Python 2.6/2.7 with PyQt4 extension for developers. + +* py2exe or py2app would be required while making binary excutables for + specified platforms. + + +Text-based User Interface (TUI) +``````````````````````````````` + +Any devices with `Python 2` and `Python Standard Library` INSTALLED could run +`Hosts Setup Utility` in TUI mode from a 80x24 terminal. In addition to this, +TUI mode could also be operated via SSH on remote devices/machines/servers. +All you need is a system with Python 2 installed. + + +.. _intro-get-started: + +Get Started +----------- + +Since `Hosts Setup Utility` supports both Graphical Desktop environment with +Graphical User Interface (GUI) and CLI/terminal environment with Text-based +User Interface (TUI), users could the way they would like to launch this tool. + +However, GUI mode is highly recommended because several features like +backup/restore hosts file are still not supported in TUI mode currently. + +.. note:: If the program is not running with privileges to modify the hosts + file, a warning message would be shown and you could only do operations + like backup hosts file and update the local data file. Plus, TUI mode + could not get started in this condition. + + +Graphical User Interface (GUI) Mode +``````````````````````````````````` + +* Windows(x86/x64): Run hoststool.exe from the binary excutables package to + get started. + + .. note:: + - "Run as Administrator" is needed for operations to change the + hosts file on Windows Vista or newer. + +* Mac OS X: Run HostsUtl application from the binary excutables package to get + started. + + .. note:: + - Because of the locale problem with py2app, the automatic language + selection may not work correctly on Mac OS with binary executable + files. You can just choose the language on your on choice. + +* Linux/X11(Source code): Run command "python hoststool.py" to get started. + + .. note:: + - All platforms with Python and the PyQt4 could use this method to run + with the source code. + - A desktop environment with PyQt4 and python is needed only for + Linux/X11 users to start a GUI Session. + + +Text-based User Interface (TUI) Mode +```````````````````````````````````` + +* Windows Excutable(x86/x64): + + #. Start a command line(could be `cmd` or `Power Shell`). + + .. note:: "Run as Administrator" is needed for operations to change the + hosts file on Windows Vista or newer. + + #. Change directory to the folder contains binary executable files. of + `Hosts Setup Utility`. + + #. Run ``hoststool_tui.exe`` with an argument ``-t`` from the directory to + get started. + +* Python Source Code: + + `Python Source Code` is very easy to be started through any terminals on any + operating systems. + + #. Change your directory to the source script. + + #. Run ``python hoststool.py -t`` in the terminal. Of course, wirte + privileges to access the hosts file on current system is required. If + not, a warning message box would show up and then terminate current + session. + +.. seealso:: :class:`~hoststool.UtilLauncher`. + + +.. _intro-customize: + +User Customized Hosts +--------------------- + +Users are allowed to add customized hosts list as an independent module to +make a hosts file. All you need to do is create a simple text file named +``custom.hosts`` in the working directory, and put your own hosts entries +into this file. Then you would find a `Customized Hosts` option in the +function list. + +.. warning:: Non-ASCII characters are not recommended to be put into the + customized hosts file. + +.. versionadded:: 1.9.8 + + +.. note:: Specific user manual is not included in this documentation. For + further information, please visit our + `website `_. diff --git a/doc/dev/make.bat b/doc/dev/make.bat new file mode 100644 index 0000000..b7869b9 --- /dev/null +++ b/doc/dev/make.bat @@ -0,0 +1,243 @@ +@ECHO OFF + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set BUILDDIR=../../document +set SOURCEDIR=./ +set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% %SOURCEDIR% +set I18NSPHINXOPTS=%SPHINXOPTS% %SOURCEDIR% +if NOT "%PAPER%" == "" ( + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% + set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% +) + +if "%1" == "" goto help + +if "%1" == "help" ( + :help + echo.Please use `make ^` where ^ is one of + echo. html to make standalone HTML files + echo. dirhtml to make HTML files named index.html in directories + echo. singlehtml to make a single large HTML file + echo. pickle to make pickle files + echo. json to make JSON files + echo. htmlhelp to make HTML files and a HTML help project + echo. qthelp to make HTML files and a qthelp project + echo. devhelp to make HTML files and a Devhelp project + echo. epub to make an epub + echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + echo. text to make text files + echo. man to make manual pages + echo. texinfo to make Texinfo files + echo. gettext to make PO message catalogs + echo. changes to make an overview over all changed/added/deprecated items + echo. xml to make Docutils-native XML files + echo. pseudoxml to make pseudoxml-XML files for display purposes + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + goto end +) + +if "%1" == "clean" ( + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i + del /q /s %BUILDDIR%\* + goto end +) + + +%SPHINXBUILD% 2> nul +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +if "%1" == "html" ( + %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/html. + goto end +) + +if "%1" == "dirhtml" ( + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. + goto end +) + +if "%1" == "singlehtml" ( + %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. + goto end +) + +if "%1" == "pickle" ( + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the pickle files. + goto end +) + +if "%1" == "json" ( + %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the JSON files. + goto end +) + +if "%1" == "htmlhelp" ( + %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run HTML Help Workshop with the ^ +.hhp project file in %BUILDDIR%/htmlhelp. + goto end +) + +if "%1" == "qthelp" ( + %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run "qcollectiongenerator" with the ^ +.qhcp project file in %BUILDDIR%/qthelp, like this: + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\huhamhire-hosts.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\huhamhire-hosts.ghc + goto end +) + +if "%1" == "devhelp" ( + %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. + goto end +) + +if "%1" == "epub" ( + %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The epub file is in %BUILDDIR%/epub. + goto end +) + +if "%1" == "latex" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdf" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf + cd %BUILDDIR%/.. + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdfja" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf-ja + cd %BUILDDIR%/.. + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "text" ( + %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The text files are in %BUILDDIR%/text. + goto end +) + +if "%1" == "man" ( + %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The manual pages are in %BUILDDIR%/man. + goto end +) + +if "%1" == "texinfo" ( + %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. + goto end +) + +if "%1" == "gettext" ( + %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The message catalogs are in %BUILDDIR%/locale. + goto end +) + +if "%1" == "changes" ( + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes + if errorlevel 1 exit /b 1 + echo. + echo.The overview file is in %BUILDDIR%/changes. + goto end +) + +if "%1" == "linkcheck" ( + %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck + if errorlevel 1 exit /b 1 + echo. + echo.Link check complete; look for any errors in the above output ^ +or in %BUILDDIR%/linkcheck/output.txt. + goto end +) + +if "%1" == "doctest" ( + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest + if errorlevel 1 exit /b 1 + echo. + echo.Testing of doctests in the sources finished, look at the ^ +results in %BUILDDIR%/doctest/output.txt. + goto end +) + +if "%1" == "xml" ( + %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The XML files are in %BUILDDIR%/xml. + goto end +) + +if "%1" == "pseudoxml" ( + %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. + goto end +) + +:end diff --git a/doc/dev/tui.rst b/doc/dev/tui.rst new file mode 100644 index 0000000..86b8b41 --- /dev/null +++ b/doc/dev/tui.rst @@ -0,0 +1,2 @@ +.. automodule:: tui.__doc__ + :members: diff --git a/doc/dev/util.rst b/doc/dev/util.rst new file mode 100644 index 0000000..3251d85 --- /dev/null +++ b/doc/dev/util.rst @@ -0,0 +1,2 @@ +.. automodule:: util.__doc__ + :members: diff --git a/gui/__doc__.py b/gui/__doc__.py new file mode 100644 index 0000000..cf9025e --- /dev/null +++ b/gui/__doc__.py @@ -0,0 +1,218 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# __doc__.py : Document in reST format of gui module. +# +# Copyleft (C) 2014 - huhamhire +# ===================================================================== +# Licensed under the GNU General Public License, version 3. You should +# have received a copy of the GNU General Public License along with +# this program. If not, see . +# ===================================================================== +""" +.. _gui-module: + +Graphical User Interface (GUI) +============================== + +The following sections describe the objects and methods from the Graphical +User Interface (GUI) module of huhamhire-hosts HostsUtil. The methods to make +GUI here are based on `PyQT4 `_. + + +HostsUtil(GUI) +-------------- +.. autoclass:: gui.hostsutil.HostsUtil + :members: + + .. automethod:: gui.hostsutil.HostsUtil.__del__ + + +QDialogSlots +------------ +.. autoclass:: gui.qdialog_slots.QDialogSlots + :members: + + .. automethod:: gui.qdialog_slots.QDialogSlots.__init__ + + +LangUtil +-------- +.. autoclass:: gui.language.LangUtil + :members: + + +QDialogDaemon +------------- +.. autoclass:: gui.qdialog_d.QDialogDaemon + :members: + + +QDialogUI +--------- +.. autoclass:: gui.qdialog_ui.QDialogUI + :members: + + .. automethod:: gui.qdialog_ui.QDialogUI.__init__ + + +QSubChkConnection +----------------- +.. autoclass:: gui._checkconn.QSubChkConnection + :members: + + .. automethod:: gui._checkconn.QSubChkConnection.__init__ + + +QSubChkUpdate +------------- +.. autoclass:: gui._checkupdate.QSubChkUpdate + :members: + + .. automethod:: gui._checkupdate.QSubChkUpdate.__init__ + + +QSubFetchUpdate +--------------- +.. autoclass:: gui._update.QSubFetchUpdate + :members: + + .. automethod:: gui._update.QSubFetchUpdate.__init__ + + +QSubMakeHosts +------------- +.. autoclass:: gui._make.QSubMakeHosts + :members: + + .. automethod:: gui._make.QSubMakeHosts.__init__ + + +.. _qt-resource-modules: + +Resource Modules +---------------- +All Qt Project files and resource files are convented into python Resource +Modules in order to be used by `Hosts Setup Utility`. Including: + +* util_ui.py: :class:`~gui.util_ui.Ui_Util` class organizing layout of the + main dialog for `Hosts Setup Utility`. This file is translated from the UI + project file `util_ui.ui`. + + .. seealso:: `util_ui.ui` in :ref:`QT Project Files `. + +* util_rc.py: Images resources used by the main dialog. + +* style_rc.py: Images resources used by the default `Qt Stylesheet`. + + .. seealso:: :ref:`QT Stylesheet `. + +.. seealso:: `_pyuic4.py` in :ref:`QT Project Scripts `. + + +.. _qt-project-files: + +QT Project Files +---------------- +Qt Project Files and Qt resources files used to design the Qt user interface +are provided in the "`gui/pyqt/`" directory, including: + +* util.pro: QT Project file containing configuration of current project. + +* util.qrc: Resource file for main dialog generated by `Qt Designer`. + +* style.qrc: Resource file for the default `Qt Style Sheet` generated by + `Qt Designer`. + + .. seealso:: :ref:`QT Stylesheet `. + +* util_ui.ui: UI project file for the main dialog designed with + `Qt designer`. + +.. seealso:: + `Qt Designer `_. + + +.. _qt-project-scripts: + +QT Project Scripts +------------------ +Scripts to help managing the Qt project are also provided in the "`gui/pyqt/`" +directory: + +* _pylupdate4.py: Contains tools to update the language files for UI. + + .. note:: This script would parse files declared as `SOURCES` in the Qt + project file (`gui/pyqt/util.pro`), and generate/update language files + declared as `TRANSLATIONS` in the Qt project file. + + .. seealso:: :ref:`Language Files `. + +* _pyuic4.py: Tools used to update the `UI module` and `Resource Modules` + from `UI file` and `Resources Files` designed with `Qt designer`. + + .. seealso:: :ref:`Resource Modules `. + + +.. _qt-language-files: + +Language Files +-------------- +Since this GUI module of `Hosts Setup Utility` is based on :mod:`PyQt4`, +it is very easy to internationalization this utility with the ways Qt +provided. + +The `Qt Language Files` are stored in "`gui/lang/`" directory with file +suffixes "`.qm`" and "`.ts`". + +.. note:: **File Types** + + `Hosts Setup Utility` makes use of two kinds of files: + + * TS: `translation source files` + are human-readable XML files containing source phrases and their + translations. These files are usually created and updated by `lupdate` + and are specific to an application. + + * QM: `Qt message files` + are binary files that contain translations used by an application at + run-time. These files are generated by lrelease, but can also be + generated by `Qt Linguist`. + +.. note:: You can use `_pylupdate4.py` provided in "`gui/pyqt/`" directory to + generate/update `TS` files with the Qt project file. And then use + `Qt Linguist` to make a `QM` file. + + .. seealso:: `_pylupdate4.py` in + :ref:`QT Project Scripts`. + + .. seealso:: `Qt Linguist + `_. + + +.. _qt-stylesheet: + +QT Stylesheet +------------- +With the GUI module of `Hosts Setup Utility` is based on :mod:`PyQt4`, Qt +style sheets are also supported to customize the appearance of GUI widgets. + +The `Qt Style Sheets` are stored in "`gui/theme/`" directory with file suffix +"`.qss`". + +You can create yourown Qt style sheet and use it in method +:meth:`~gui.qdialog_ui.QDialogUI.set_stylesheet` to customize new appearance +of GUI widgets. + +.. seealso:: Method :meth:`~gui.qdialog_ui.QDialogUI.set_stylesheet` in + :class:`~gui.qdialog_ui.QDialogUI` class. + +.. note:: Qt Style Sheets are a powerful mechanism that allows you to + customize the appearance of widgets, in addition to what is already + possible by subclassing QStyle. The concepts, terminology, and syntax + of Qt Style Sheets are heavily inspired by HTML Cascading Style Sheets + (CSS) but adapted to the world of widgets. + + .. seealso:: + `QT Stylesheet `_. +""" \ No newline at end of file diff --git a/gui/__init__.py b/gui/__init__.py new file mode 100644 index 0000000..1c4ab73 --- /dev/null +++ b/gui/__init__.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# __init__.py : Declare modules to be called in gui module. +# +# Copyleft (C) 2014 - huhamhire +# ===================================================================== +# Licensed under the GNU General Public License, version 3. You should +# have received a copy of the GNU General Public License along with +# this program. If not, see . +# ===================================================================== + +from hostsutil import HostsUtil + +__all__ = ["HostsUtil"] diff --git a/gui/__list_trans.py b/gui/__list_trans.py new file mode 100644 index 0000000..6de1382 --- /dev/null +++ b/gui/__list_trans.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# __list_trans.py : Name of items from the function list to be localized +# +# Copyleft (C) 2014 - huhamhire hosts team +# ===================================================================== +# Licensed under the GNU General Public License, version 3. You should +# have received a copy of the GNU General Public License along with +# this program. If not, see . +# +# This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING +# THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE. +# ===================================================================== + +__author__ = "huhamhire " + +from util_ui import _translate + +# Name of items from the function list to be localized +# __list_trans (list): A list containing names of function list items +# for translator to translate. +__list_trans = [ + _translate("Util", "customize", None), + _translate("Util", "google", None), + _translate("Util", "google-apis", None), + _translate("Util", "google(cn)", None), + _translate("Util", "google(hk)", None), + _translate("Util", "google(us)", None), + _translate("Util", "google-apis(cn)", None), + _translate("Util", "google-apis(us)", None), + _translate("Util", "activation-helper", None), + _translate("Util", "facebook", None), + _translate("Util", "twitter", None), + _translate("Util", "youtube", None), + _translate("Util", "wikipedia", None), + _translate("Util", "institutions", None), + _translate("Util", "steam", None), + _translate("Util", "github", None), + _translate("Util", "dropbox", None), + _translate("Util", "wordpress", None), + _translate("Util", "others", None), + _translate("Util", "adblock-hostsx", None), + _translate("Util", "adblock-mvps", None), + _translate("Util", "adblock-mwsl", None), + _translate("Util", "adblock-yoyo", None), ] diff --git a/gui/_checkconn.py b/gui/_checkconn.py new file mode 100644 index 0000000..a416ebe --- /dev/null +++ b/gui/_checkconn.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# _checkconn.py: Check connect to a specified server. +# +# Copyleft (C) 2014 - huhamhire +# ===================================================================== +# Licensed under the GNU General Public License, version 3. You should +# have received a copy of the GNU General Public License along with +# this program. If not, see . +# ===================================================================== + +__author__ = "huhamhire " + +from PyQt4 import QtCore + +import sys +sys.path.append("..") +from util import CommonUtil + + +class QSubChkConnection(QtCore.QThread): + """ + QSubChkConnection is a subclass of :class:`PyQt4.QtCore.QThread`. This + class contains methods to check the network connection with a specified + server. + + .. inheritance-diagram:: gui._checkconn.QSubChkConnection + :parts: 1 + + .. note:: The instance of this class should be created in an individual + thread. And an instance of class should be set as :attr:`parent` + here. + + :ivar PyQt4.QtCore.pyqtSignal trigger: An instance of + :class:`PyQt4.QtCore.pyqtSignal` to emit signal to the main dialog + which indicates current status. + + .. note:: The signal :attr:`trigger` should be a integer flag: + + ====== ======== + signal Status + ====== ======== + -1 Checking + 0 Failed + 1 OK + ====== ======== + """ + trigger = QtCore.pyqtSignal(int) + + def __init__(self, parent): + """ + Initialize a new instance of this class. Retrieve mirror settings from + the main dialog to check the connection. + + :param parent: An instance of :class:`~gui.qdialog_d.QDialogDaemon` + class to fetch settings from. + :type parent: :class:`~gui.qdialog_d.QDialogDaemon` + + .. warning:: :attr:`parent` MUST NOT be set as `None`. + """ + super(QSubChkConnection, self).__init__(parent) + self.link = parent.mirrors[parent.mirror_id]["test_url"] + + def run(self): + """ + Start operations to check the network connection with a specified + server. + """ + self.trigger.emit(-1) + status = CommonUtil.check_connection(self.link) + self.trigger.emit(status) \ No newline at end of file diff --git a/gui/_checkupdate.py b/gui/_checkupdate.py new file mode 100644 index 0000000..126beca --- /dev/null +++ b/gui/_checkupdate.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# _checkupdate.py: Check update info of the latest data file. +# +# Copyleft (C) 2014 - huhamhire +# ===================================================================== +# Licensed under the GNU General Public License, version 3. You should +# have received a copy of the GNU General Public License along with +# this program. If not, see . +# ===================================================================== + +__author__ = "huhamhire " + +import json +import socket +import urllib +from PyQt4 import QtCore + +from util_ui import _translate + + +class QSubChkUpdate(QtCore.QThread): + """ + QSubChkConnection is a subclass of :class:`PyQt4.QtCore.QThread`. This + class contains methods to retrieve the metadata of the latest hosts data + file. + + .. inheritance-diagram:: gui._checkupdate.QSubChkUpdate + :parts: 1 + + .. note:: The instance of this class should be created in an individual + thread. And an instance of class should be set as :attr:`parent` + here. + + :ivar PyQt4.QtCore.pyqtSignal trigger: An instance of + :class:`PyQt4.QtCore.pyqtSignal` to emit signal to the main dialog + which indicates current status. + + .. note:: The signal :attr:`trigger` should be a dictionary (`dict`) + containing metadata of the latest hosts data file. + + .. seealso:: Method :meth:`~gui.qdialog_d.QDialogDaemon.finish_update` + in :class:`~gui.qdialog_d.QDialogDaemon` class. + """ + trigger = QtCore.pyqtSignal(dict) + + def __init__(self, parent): + """ + Initialize a new instance of this class. Retrieve mirror settings from + the main dialog to check the update information. + + + :param parent: An instance of :class:`~gui.qdialog_d.QDialogDaemon` + class to fetch settings from. + :type parent: :class:`~gui.qdialog_d.QDialogDaemon` + + .. warning:: :attr:`parent` MUST NOT be set as `None`. + """ + super(QSubChkUpdate, self).__init__(parent) + self.url = parent.mirrors[parent.mirror_id]["update"] + parent.infofile + + def run(self): + """ + Start operations to retrieve the metadata of the latest hosts data + file. + """ + try: + socket.setdefaulttimeout(5) + urlobj = urllib.urlopen(self.url) + j_str = urlobj.read() + urlobj.close() + info = json.loads(j_str) + self.trigger.emit(info) + except: + info = {"version": unicode(_translate("Util", "[Error]", None))} + self.trigger.emit(info) \ No newline at end of file diff --git a/gui/_make.py b/gui/_make.py new file mode 100644 index 0000000..f20b3cb --- /dev/null +++ b/gui/_make.py @@ -0,0 +1,135 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# _make.py: Make a new hosts file. +# +# Copyleft (C) 2014 - huhamhire +# ===================================================================== +# Licensed under the GNU General Public License, version 3. You should +# have received a copy of the GNU General Public License along with +# this program. If not, see . +# ===================================================================== + +__author__ = "huhamhire " + +import time +from PyQt4 import QtCore + +import sys +sys.path.append("..") +from util import RetrieveData +from util import MakeHosts + + +class QSubMakeHosts(QtCore.QThread, MakeHosts): + """ + QSubMakeHosts is a subclass of :class:`PyQt4.QtCore.QThread` and class + :class:`~util.makehosts.MakeHosts`. This class contains methods to make a + new hosts file for client. + + .. inheritance-diagram:: gui._make.QSubMakeHosts + :parts: 1 + + .. note:: The instance of this class should be created in an individual + thread. And an instance of class should be set as :attr:`parent` + here. + + :ivar PyQt4.QtCore.pyqtSignal info_trigger: An instance of + :class:`PyQt4.QtCore.pyqtSignal` to emit signal to the main dialog + which indicates the current operation. + + .. note:: The signal :attr:`info_trigger` should be a tuple of + (`mod_name`, mod_num`): + + * mod_name(`str`): Tag of a specified hosts module in current + progress. + * mod_num(`int`): Number of current module in the operation + sequence. + + .. seealso:: Method + :meth:`~gui.qdialog_ui.QDialogUI.set_make_progress` + in :class:`~gui.qdialog_ui.QDialogUI` class. + + :ivar PyQt4.QtCore.pyqtSignal fina_trigger: An instance of + :class:`PyQt4.QtCore.pyqtSignal` to emit signal to the main dialog + which notifies statistics to the main dialog. + + .. note:: The signal :attr:`fina_trigger` should be a tuple of + (`time`, count`): + + * time(`str`): Total time uesd while generating the new hosts + file. + * count(`int`): Total number of hosts entries inserted into the + new hosts file. + + .. seealso:: Method :meth:`~gui.qdialog_d.QDialogDaemon.finish_make` + in :class:`~gui.qdialog_d.QDialogDaemon` class. + + :ivar PyQt4.QtCore.pyqtSignal move_trigger: An instance of + :class:`PyQt4.QtCore.pyqtSignal` to notify the main dialog while new + hosts is being moved to specified path on current operating system. + + .. note:: This signal does not send any data. + + .. seealso:: Method :meth:`~gui.qdialog_d.QDialogDaemon.move_hosts` + in :class:`~gui.qdialog_d.QDialogDaemon` class. + + .. seealso:: :class:`util.makehosts.MakeHosts` class. + """ + info_trigger = QtCore.pyqtSignal(str, int) + fina_trigger = QtCore.pyqtSignal(str, int) + move_trigger = QtCore.pyqtSignal() + + def __init__(self, parent): + """ + Initialize a new instance of this class. Retrieve configuration from + the main dialog to make a new hosts file. + + :param parent: An instance of :class:`~gui.qdialog_d.QDialogDaemon` + class to fetch settings from. + :type parent: :class:`~gui.qdialog_d.QDialogDaemon` + + .. warning:: :attr:`parent` MUST NOT be set as `None`. + """ + QtCore.QThread.__init__(self, parent) + MakeHosts.__init__(self, parent) + + def run(self): + """ + Start operations to retrieve data from the data file and generate new + hosts file. + """ + start_time = time.time() + self.make() + end_time = time.time() + total_time = "%.4f" % (end_time - start_time) + self.fina_trigger.emit(total_time, self.count) + if self.make_mode == "system": + self.move_trigger.emit() + + def get_hosts(self, make_cfg): + """ + Make the new hosts file by the configuration defined by `make_cfg` + from function list on the main dialog. + + :param make_cfg: Module settings in byte word format. + :type make_cfg: dict + + .. seealso:: :attr:`make_cfg` in :class:`~tui.curses_d.CursesDaemon` + class. + """ + for part_id in sorted(make_cfg.keys()): + mod_cfg = make_cfg[part_id] + if not RetrieveData.chk_mutex(part_id, mod_cfg): + return + mods = RetrieveData.get_ids(mod_cfg) + for mod_id in mods: + self.mod_num += 1 + hosts, mod_name = RetrieveData.get_host(part_id, mod_id) + self.info_trigger.emit(mod_name, self.mod_num) + if part_id == 0x02: + self.write_localhost_mod(hosts) + elif part_id == 0x04: + self.write_customized() + else: + self.write_common_mod(hosts, mod_name) diff --git a/gui/_update.py b/gui/_update.py new file mode 100644 index 0000000..dea9c74 --- /dev/null +++ b/gui/_update.py @@ -0,0 +1,150 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# _update.py: Fetch the latest data file. +# +# Copyleft (C) 2014 - huhamhire +# ===================================================================== +# Licensed under the GNU General Public License, version 3. You should +# have received a copy of the GNU General Public License along with +# this program. If not, see . +# ===================================================================== + +__author__ = "huhamhire " + +import os +import socket +import urllib +from PyQt4 import QtCore + +from util_ui import _translate + +import sys +sys.path.append("..") +from util import CommonUtil + + +class QSubFetchUpdate(QtCore.QThread): + """ + QSubFetchUpdate is a subclass of :class:`PyQt4.QtCore.QThread`. This + class contains methods to retrieve the latest hosts data file from a + server. + + .. inheritance-diagram:: gui._update.QSubFetchUpdate + :parts: 1 + + .. note:: The instance of this class should be created in an individual + thread. And an instance of class should be set as :attr:`parent` + here. + + :ivar PyQt4.QtCore.pyqtSignal prog_trigger: An instance of + :class:`PyQt4.QtCore.pyqtSignal` to emit signal to the main dialog + which indicates current downloading progress. + + .. note:: The signal :attr:`prog_trigger` should be a tuple of + (`progress`, message`): + + * progress(`int`): An number between `0` and `100` which indicates + current download progress. + * message(`str`): Message to be displayed to users on the progress + bar. + + :ivar PyQt4.QtCore.pyqtSignal finish_trigger: An instance of + :class:`PyQt4.QtCore.pyqtSignal` to emit signal to the main dialog + which notifies if current operation is finished. + + .. note:: The signal :attr:`finish_trigger` should be a tuple of + (`refresh_flag`, error_flag`): + + * refresh_flag(`int`): An flag indicating whether to refresh + function list in the main dialog or not. + + ============ ============== + refresh_flag Operation + ============ ============== + 1 Refresh + 0 Do not refresh + ============ ============== + * error_flag(`int`): An flag indicating whether the downloading + progress is successfully finished or not. + + ========== ======= + error_flag Status + ========== ======= + 1 Error + 0 Success + ========== ======= + + .. seealso:: Method :meth:`~gui.qdialog_d.QDialogDaemon.finish_fetch` + in :class:`~gui.qdialog_d.QDialogDaemon` class. + + """ + prog_trigger = QtCore.pyqtSignal(int, str) + finish_trigger = QtCore.pyqtSignal(int, int) + + def __init__(self, parent): + """ + Initialize a new instance of this class. Fetch download settings from + the main dialog to retrieve new hosts data file. + + :param parent: An instance of :class:`~gui.qdialog_d.QDialogDaemon` + class to fetch settings from. + :type parent: :class:`~gui.qdialog_d.QDialogDaemon` + + .. warning:: :attr:`parent` MUST NOT be set as `None`. + """ + super(QSubFetchUpdate, self).__init__(parent) + self.url = parent.mirrors[parent.mirror_id]["update"] + \ + parent.filename + self.path = "./" + parent.filename + self.tmp_path = self.path + ".download" + self.filesize = parent._update["size"] + + def run(self): + """ + Start operations to retrieve the new hosts data file. + """ + self.prog_trigger.emit(0, unicode(_translate( + "Util", "Connecting...", None))) + self.fetch_file() + + def fetch_file(self): + """ + Retrieve the latest data file to a specified local path with a url. + """ + socket.setdefaulttimeout(10) + try: + urllib.urlretrieve(self.url, self.tmp_path, self.set_progress) + self.replace_old() + self.finish_trigger.emit(1, 0) + except: + self.finish_trigger.emit(1, 1) + + def set_progress(self, done, block, total): + """ + Send message to the main dialog to set the progress bar. + + :param done: Block count of packaged retrieved. + :type done: int + :param block: Block size of the data pack retrieved. + :type block: int + :param total: Total size of the hosts data file. + :type total: int + """ + done = done * block + if total <= 0: + total = self.filesize + prog = 100 * done / total + done = CommonUtil.convert_size(done) + total = CommonUtil.convert_size(total) + text = unicode(_translate( + "Util", "Downloading: %s / %s", None)) % (done, total) + self.prog_trigger.emit(prog, text) + + def replace_old(self): + """ + Replace the old hosts data file with the new one. + """ + if os.path.isfile(self.path): + os.remove(self.path) + os.rename(self.tmp_path, self.path) \ No newline at end of file diff --git a/gui/hostsutil.py b/gui/hostsutil.py new file mode 100644 index 0000000..e099654 --- /dev/null +++ b/gui/hostsutil.py @@ -0,0 +1,128 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# hostsutil.py : Main entrance to GUI module of Hosts Setup Utility. +# +# Copyleft (C) 2014 - huhamhire hosts team +# ===================================================================== +# Licensed under the GNU General Public License, version 3. You should +# have received a copy of the GNU General Public License along with +# this program. If not, see . +# +# This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING +# THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE. +# ===================================================================== + +__author__ = "huhamhire " + +import sys + +from zipfile import BadZipfile + +from qdialog_slots import QDialogSlots + +sys.path.append("..") +from util import RetrieveData, CommonUtil + +# Path to store language files +LANG_DIR = "./gui/lang/" + + +class HostsUtil(QDialogSlots): + """ + HostsUtil class is the main entrance to the Graphical User Interface (GUI) + module of `Hosts Setup Utility`. This class contains methods to launch the + main dialog of this utility. + + .. note:: This class is subclass of + :class:`~gui.qdialog_slots.QDialogSlots` class. + + Typical usage to start a GUI session:: + + import gui + + util = gui.HostsUtil() + util.start() + + :ivar int init_flag: Times of the main dialog being initialized. This + value would be referenced for translator to set the language of the + main dialog. + :ivar str filename: Filename of the hosts data file containing data to + make hosts files from. Default by "`hostslist.data`". + :ivar str infofile: Filename of the info file containing metadata of the + hosts data file formatted in JSON. Default by "`hostslist.json`". + + .. seealso:: :attr:`filename` and :attr:`infofile` in + :class:`~tui.curses_ui.CursesUI` class. + """ + init_flag = 0 + # Data file related configuration + filename = "hostslist.data" + infofile = "hostsinfo.json" + + def __init__(self): + super(HostsUtil, self).__init__() + + def __del__(self): + """ + Clear up the temporary data file while TUI session is finished. + """ + try: + RetrieveData.clear() + except: + pass + + def start(self): + """ + Start the GUI session. + + .. note:: This method is the trigger to launch a GUI session of + `Hosts Setup Utility`. + """ + if not self.init_flag: + self.init_main() + self.show() + sys.exit(self.app.exec_()) + + def init_main(self): + """ + Set up the elements on the main dialog. Check the environment of + current operating system and current session. + + * Load server list from a configuration file under working directory. + * Try to load the hosts data file under working directory if it + exists. + + .. note:: IF hosts data file does not exists correctly in current + working directory, a warning message box would popup. And + operations to change the hosts file on current system could be + done only until a new data file has been downloaded. + + .. seealso:: Method :meth:`~tui.hostsutil.HostsUtil.__init__` in + :class:`~tui.hostsutil.HostsUtil` class. + """ + self.ui.SelectMirror.clear() + self.set_version() + # Set mirrors + self.mirrors = CommonUtil.set_network("network.conf") + self.set_mirrors() + # Read data file and set function list + try: + RetrieveData.unpack() + RetrieveData.connect_db() + self.set_func_list(1) + self.refresh_func_list() + self.set_info() + except IOError: + self.warning_no_datafile() + except BadZipfile: + self.warning_incorrect_datafile() + # Check if current session have root privileges + self.check_writable() + self.init_flag += 1 + + +if __name__ == "__main__": + HostsUtlMain = HostsUtil() + HostsUtlMain.start() diff --git a/lang/de_DE.ts b/gui/lang/de_DE.ts similarity index 64% rename from lang/de_DE.ts rename to gui/lang/de_DE.ts index 8114403..723e529 100644 --- a/lang/de_DE.ts +++ b/gui/lang/de_DE.ts @@ -1,404 +1,434 @@ - + - HostsUtlMain + Util - + + customize + + + + + google + + + + + google-apis + + + + google(cn) - + google(hk) - + google(us) - + google-apis(cn) - + google-apis(us) - + activation-helper - + facebook - + twitter - + youtube - + wikipedia - + institutions - + steam - + + github + + + + + dropbox + + + + + wordpress + + + + others - + adblock-hostsx - + adblock-mvps - + adblock-mwsl - + adblock-yoyo - - Backup hosts + + [Error] - - Backup File(*.bak) + + Connecting... - - Restore hosts + + Downloading: %s / %s - - [Error] + + Checking... - - Checking... + + [OK] - - Export hosts + + [Failed] - - hosts File + + Functions - - Building hosts file... + + Applying module: %s(%s/%s) - - Copying new hosts file to - %s + + Progress - - Remove temporary file + + Warning - - Operation completed + + You do not have permissions to change the +hosts file. +Please run this program as Administrator/root +so it can modify your hosts file. - - [OK] + + Error retrieving data from the server. +Please try another server. - - [Failed] + + Incorrect Data file! +Please use the "Download" key to +fetch a new data file. - - Functions + + Data file not found! +Please use the "Download" key to +fetch a new data file. - - Applying module: %s(%s/%s) + + Notice - - Progress + + Are you sure you want to apply changes +to the hosts file on your system? + +This operation could not be reverted if +you have not made a backup of your +current hosts file. - - Notice: %i hosts entries has - been applied in %ssecs. + + Data file is up-to-date. - - Operation Completed Successfully! + + Complete - - Error + + Operation completed - - Incorrect Data file! -Please use the "Download" key to -fetch a new data file. + + Export hosts - - Download Complete + + hosts File - - Warning + + Building hosts file... - - You do not have permissions to change the -hosts file. -Please run this program as Administrator/root -so it can modify your hosts file. + + Copying new hosts file to +%s - - Error retrieving data from the server. -Please try another server. + + Remove temporary file - - Data file not found! -Please use the "Download" key to -fetch a new data file. + + Notice: %i hosts entries has + been applied in %ssecs. - - Notice + + Operation Completed Successfully! - - Are you sure you want to apply changes -to the hosts file on your system? - -This operation could not be reverted if -you have not made a backup of your -current hosts file. + + Error - - Data file is up-to-date. + + Download Complete - - Complete + + Backup hosts - - Connecting... + + Backup File(*.bak) - - Downloading: %s / %s + + Restore hosts - + Hosts Setup Utility - + Config - + Server - + IP Version - + Status - + Connection - + N/A - + OS - + Hosts Info - + Version - + Release - + Latest - + Backup the hosts file of current system. - + Download data file - + Download the latest data file. - + Restore backup - + Restore a previous backup of hosts file. - + Apply hosts - + Apply changes to the hosts file. - + Exit - + Close this tool. - + Check update / Refresh - + Check the latest version of hosts data file. - + Save with ANSI - + Export to hosts file encoding by ANSI. - + Save with UTF-8 - + Export to hosts file encoding by UTF-8. - + Copyleft (C) 2011-2014 <a href="https://hosts.huhamhire.com/"><span style="text-decoration: none;color: #b1b1b1;">huhamhire-hosts</span></a> - + Powered by PyQT diff --git a/gui/lang/en_US.qm b/gui/lang/en_US.qm new file mode 100644 index 0000000..7cf3284 Binary files /dev/null and b/gui/lang/en_US.qm differ diff --git a/lang/en_US.ts b/gui/lang/en_US.ts similarity index 61% rename from lang/en_US.ts rename to gui/lang/en_US.ts index 89d19d9..bb10557 100644 --- a/lang/en_US.ts +++ b/gui/lang/en_US.ts @@ -1,314 +1,352 @@ - HostsUtlMain + Util - - Backup hosts - + + google(cn) + Google Web Service (CN) - - Functions - + + google(hk) + Google Web Service (HK) - - Hosts Setup Utility - + + google(us) + Google Web Service (US) - - Config - + + google-apis(cn) + Google API Service (CN) - - Server + + google-apis(us) + Google API Service (US) + + + + activation-helper - - IP Version + + facebook + Facebook + + + + twitter + twitter + + + + youtube + YouTube + + + + wikipedia + Wikipedia + + + + institutions + Academy and Research institutions + + + + steam + Steam + + + + others + Other services + + + + adblock-hostsx - - Status + + adblock-mvps - - Connection + + adblock-mwsl - - N/A + + adblock-yoyo - - OS + + Backup hosts - - Backup the hosts file of current system. + + Backup File(*.bak) - - Download data file + + Restore hosts - - Download the latest data file. + + [Error] - - Restore backup + + Checking... - - Restore a previous backup of hosts file. + + Operation completed - - Apply hosts + + [OK] - - Apply changes to the hosts file. + + [Failed] - - Exit + + Functions - - Close this tool. + + Applying module: %s(%s/%s) - - Check update / Refresh + + Progress - - Check the latest version of hosts data file. + + Warning - - Hosts Info + + Error retrieving data from the server. +Please try another server. - - Version + + Notice - - Release + + Data file is up-to-date. - - Latest + + Complete - - google(cn) - Google Web Service (CN) + + Hosts Setup Utility + - - google(us) - Google Web Service (US) + + Config + - - activation-helper + + Server - - others + + IP Version - - adblock-hostsx + + Status - - adblock-mvps + + Connection - - adblock-mwsl + + N/A - - adblock-yoyo + + OS - - Building hosts file... + + Hosts Info - - Backup File(*.bak) + + Version - - Restore hosts + + Release - - [Error] + + Latest - - Checking... + + Backup the hosts file of current system. - - Copying new hosts file to - %s + + Download data file - - Remove temporary file + + Download the latest data file. - - Operation completed + + Restore backup - - [OK] + + Restore a previous backup of hosts file. - - [Failed] + + Apply hosts - - Applying module: %s(%s/%s) + + Apply changes to the hosts file. - - Progress + + Exit - - Notice: %i hosts entries has - been applied in %ssecs. + + Close this tool. - - Operation Completed Successfully! + + Check update / Refresh - - Error + + Check the latest version of hosts data file. - - Incorrect Data file! -Please use the "Download" key to -fetch a new data file. + + Save with ANSI - - Download Complete + + Export to hosts file encoding by ANSI. - - Warning + + Save with UTF-8 - - You do not have permissions to change the -hosts file. -Please run this program as Administrator/root -so it can modify your hosts file. + + Export to hosts file encoding by UTF-8. - - Error retrieving data from the server. -Please try another server. + + Copyleft (C) 2011-2014 <a href="https://hosts.huhamhire.com/"><span style="text-decoration: none;color: #b1b1b1;">huhamhire-hosts</span></a> - - Data file not found! -Please use the "Download" key to -fetch a new data file. + + Powered by PyQT - - Notice + + Connecting... - - Data file is up-to-date. + + Downloading: %s / %s - - Complete + + Incorrect Data file! +Please use the "Download" key to +fetch a new data file. - - Connecting... + + You do not have permissions to change the +hosts file. +Please run this program as Administrator/root +so it can modify your hosts file. - - Downloading: %s / %s + + Data file not found! +Please use the "Download" key to +fetch a new data file. - + Are you sure you want to apply changes to the hosts file on your system? @@ -318,89 +356,81 @@ current hosts file. - - Save with ANSI + + Export hosts - - Export to hosts file encoding by ANSI. + + hosts File - - Save with UTF-8 + + Building hosts file... - - Export to hosts file encoding by UTF-8. + + Copying new hosts file to +%s - - Export hosts + + Remove temporary file - - hosts File + + Notice: %i hosts entries has + been applied in %ssecs. - - google-apis(cn) - Google API Service (CN) - - - - google-apis(us) - Google API Service (US) - - - - wikipedia + + Operation Completed Successfully! - - steam + + Error - - Copyleft (C) 2011-2014 <a href="https://hosts.huhamhire.com/"><span style="text-decoration: none;color: #b1b1b1;">huhamhire-hosts</span></a> + + Download Complete - - Powered by PyQT - + + google + Google Web Service - - google(hk) - Google Web Service (HK) + + google-apis + Google API Service - - facebook - Facebook + + customize + Customized Hosts - - twitter - twitter + + github + GitHub - - youtube - YouTube + + dropbox + Dropbox - - institutions - Academy and Research institutions + + wordpress + WordPress diff --git a/gui/lang/zh_CN.qm b/gui/lang/zh_CN.qm new file mode 100644 index 0000000..76f19ae Binary files /dev/null and b/gui/lang/zh_CN.qm differ diff --git a/lang/zh_CN.ts b/gui/lang/zh_CN.ts similarity index 67% rename from lang/zh_CN.ts rename to gui/lang/zh_CN.ts index 12407aa..29c4be6 100644 --- a/lang/zh_CN.ts +++ b/gui/lang/zh_CN.ts @@ -1,193 +1,163 @@ - HostsUtlMain + Util - + google(cn) Google Web服务(北京) - + google(hk) Google Web服务(香港) - + google(us) Google Web服务(美国) - + google-apis(cn) Google 应用程序服务(北京) - + google-apis(us) Google 应用程序服务(美国) - + activation-helper 屏蔽部分破解软件激活服务器 - + facebook Facebook - + twitter twitter - + youtube YouTube - + wikipedia 维基百科 - + institutions 教育科研机构 - + steam Steam 游戏平台 - + others 其他墙外站点 - + adblock-hostsx 广告屏蔽-hostsx 列表 - + adblock-mvps 广告屏蔽-mvps 列表 - + adblock-mwsl 广告屏蔽-mwsl 列表 - + adblock-yoyo 广告屏蔽-yoyo 列表 - + Backup hosts 备份 hosts 文件 - + Backup File(*.bak) 备份文件(*.bak) - + Restore hosts 还原备份的 hosts 文件 - + [Error] [错误] - + Checking... 正在连接... - + Export hosts 导出 hosts 文件 - + hosts File hosts 文件 - - Building hosts file... - 正在生成 hosts 文件... - - - + Copying new hosts file to %s - 正在将新的 hosts 配置到目标路径 + 正在将新的 hosts 配置到目标路径 %s - + Remove temporary file 清理临时文件 - + Operation completed 操作完成 - - [OK] - [正常] - - - - [Failed] - [失败] - - - - Functions - 功能列表 - - - - Applying module: %s(%s/%s) - 应用选定的模块: %s(%s/%s) - - - - Progress - 操作进度 - - - + Notice: %i hosts entries has been applied in %ssecs. 注意:共有 %i 条 hosts 条目在 %s秒内被插入到 hosts 文件中。 - + Operation Completed Successfully! 操作成功完成! - + Error 错误 - + Incorrect Data file! Please use the "Download" key to fetch a new data file. @@ -196,17 +166,42 @@ fetch a new data file. 新的数据文件。 - + Download Complete 下载完成 - + + [OK] + [正常] + + + + [Failed] + [失败] + + + + Functions + 功能列表 + + + + Applying module: %s(%s/%s) + 应用选定的模块: %s(%s/%s) + + + + Progress + 操作进度 + + + Warning 警告 - + You do not have permissions to change the hosts file. Please run this program as Administrator/root @@ -217,14 +212,14 @@ so it can modify your hosts file. 工具。 - + Error retrieving data from the server. Please try another server. 在从服务器获取数据是发生错误。 请在更换服务器之后尝试先前的操作。 - + Data file not found! Please use the "Download" key to fetch a new data file. @@ -233,12 +228,12 @@ fetch a new data file. 新的数据文件。 - + Notice 注意 - + Are you sure you want to apply changes to the hosts file on your system? @@ -252,179 +247,184 @@ current hosts file. 不可逆转。 - + Data file is up-to-date. 数据文件已经是最新版本。 - + Complete 完成 - - Connecting... - 正在连接服务器... - - - - Downloading: %s / %s - 正在下载: %s / %s + + Building hosts file... + 正在生成 hosts 文件... - + Hosts Setup Utility hosts 文件配置工具 - + Config 设置 - + Server 服务器 - + IP Version IP 协议版本 - + Status 状态 - + Connection 连接 - + N/A 无状态 - + OS 操作系统 - + Hosts Info 数据文件信息 - + Version 当前版本 - + Release 发布日期 - + Latest 最新版本 - + Backup the hosts file of current system. 备份当前系统的 hosts 文件。 - + Download data file 下载数据文件 - + Download the latest data file. 下载最新数据文件。 - + Restore backup 还原备份 - + Restore a previous backup of hosts file. 还原先前备份的 hosts 文件。 - + Apply hosts 更改 hosts - + Apply changes to the hosts file. 对 hosts 文件进行修改。 - + Exit 退出 - + Close this tool. 关闭本工具。 - + Check update / Refresh 检查更新/刷新 - + Check the latest version of hosts data file. 在线检查数据文件的最新版本。 - + Save with ANSI 保存为 ANSI 格式 - + Export to hosts file encoding by ANSI. 以 ANSI 的编码方式导出 hosts 文件。 - + Save with UTF-8 保存为 UTF-8 格式 - + Export to hosts file encoding by UTF-8. 以 UTF-8 的编码方式导出 hosts 文件。 - + Copyleft (C) 2011-2014 <a href="https://hosts.huhamhire.com/"><span style="text-decoration: none;color: #b1b1b1;">huhamhire-hosts</span></a> 公共版权 (C) 2011-2014 <a href="https://hosts.huhamhire.com/"><span style="text-decoration: none;color: #b1b1b1;">huhamhire-hosts</span></a> - + Powered by PyQT - + + Connecting... + 正在连接服务器... + + + + Downloading: %s / %s + 正在下载: %s / %s + + + Notice: %i hosts entries has been applied in %ssecs. 注意:共有 %i 条 hosts 条目在 %s秒内被插入到 hosts 文件中。 - + Incorrect Data file! Please use the "Download" key to fetch a new data file. @@ -433,7 +433,7 @@ fetch a new data file. 新的数据文件。 - + You do not have permissions to change the hosts file. Please run this program as Administrator/root @@ -444,7 +444,7 @@ so it can modify your hosts file. 工具。 - + Data file not found! Please use the "Download" key to fetch a new data file. @@ -453,7 +453,7 @@ fetch a new data file. 新的数据文件。 - + Are you sure you want to apply changes to the hosts file on your system? @@ -466,5 +466,42 @@ current hosts file. 若先前未对 hosts 文件进行备份,该操作将 不可逆转。 + + + Copying new hosts file to +%s + 正在将新的 hosts 配置到目标路径 + %s + + + + google + Google Web服务 + + + + google-apis + Google 应用程序服务 + + + + customize + 用户自定义列表 + + + + github + GitHub + + + + dropbox + Dropbox + + + + wordpress + WordPress + diff --git a/gui/lang/zh_TW.qm b/gui/lang/zh_TW.qm new file mode 100644 index 0000000..0d30797 Binary files /dev/null and b/gui/lang/zh_TW.qm differ diff --git a/lang/zh_TW.ts b/gui/lang/zh_TW.ts similarity index 59% rename from lang/zh_TW.ts rename to gui/lang/zh_TW.ts index 3c9adbf..30a9841 100644 --- a/lang/zh_TW.ts +++ b/gui/lang/zh_TW.ts @@ -1,419 +1,505 @@ - HostsUtlMain + Util - + google(cn) Google Web服務(大陸) - + + google(hk) + Google Web服務(香港) + + + google(us) Google Web服務(美國) - + + google-apis(cn) + Google API服務(大陸) + + + + google-apis(us) + Google API服務(美國) + + + activation-helper 遮罩部分破解軟體啟動伺服器 - + + facebook + Facebook + + + + twitter + twitter + + + + youtube + YouTube + + + + wikipedia + 維基百科 + + + + institutions + 教育科研機構 + + + + steam + Steam 遊戲平台 + + + others 其他網站 - + adblock-hostsx 廣告攔截-hostsx 清單 - + adblock-mvps 廣告攔截-mvps 清單 - + adblock-mwsl 廣告攔截-mwsl 清單 - + adblock-yoyo 廣告攔截-yoyo 清單 - - Building hosts file... - 正在創建 hosts 檔... - - - + Backup hosts 備份 hosts 檔 - + Backup File(*.bak) 備份檔(*.bak) - + Restore hosts 還原 hosts - + [Error] [錯誤] - + Checking... 正在連接伺服器... - + + Export hosts + 匯出 hosts 檔 + + + + hosts File + hosts 檔 + + + Copying new hosts file to %s - 將新的 hosts 檔案複製到 + 將新的 hosts 檔案複製到 %s - + Remove temporary file 刪除暫存檔案 - + Operation completed 作業完成 - - [OK] - [好] - - - - [Failed] - [失敗] - - - - Functions - 功能清單 - - - - Applying module: %s(%s/%s) - 應用選定的模組: %s(%s/%s) - - - - Progress - 作業進度 - - - - Notice: %i hosts entries has + + Notice: %i hosts entries has been applied in %ssecs. - 消息:共有 %i 条 hosts 条目在 + 消息:共有 %i 条 hosts 条目在 %s秒内被插入到 hosts 文件中。 - + Operation Completed Successfully! 作業已成功完成 ! - + Error 錯誤 - + Incorrect Data file! -Please use the "Download" key to +Please use the "Download" key to fetch a new data file. - 不正確的資料檔案 ! + 不正確的資料檔案 ! 請使用"下載"按鈕來獲得 一個新的資料檔案。 - + Download Complete 下載已完成 - + + [OK] + [好] + + + + [Failed] + [失敗] + + + + Functions + 功能清單 + + + + Applying module: %s(%s/%s) + 應用選定的模組: %s(%s/%s) + + + + Progress + 作業進度 + + + Warning 警告 - - You do not have permissions to change the + + You do not have permissions to change the hosts file. Please run this program as Administrator/root so it can modify your hosts file. - 您目前沒有許可權以更改 hosts 檔。 + 您目前沒有許可權以更改 hosts 檔。 請以管理員方式或者根使用者運行本 程式。 - + Error retrieving data from the server. Please try another server. 從伺服器中檢索資料時出錯。 請嘗試使用另一台伺服器。 - + Data file not found! -Please use the "Download" key to +Please use the "Download" key to fetch a new data file. - 找不到本地資料檔案! + 找不到本地資料檔案! 請使用"下載"按鈕來獲得 一個新的資料檔案。 - + Notice 消息 - + + Are you sure you want to apply changes +to the hosts file on your system? + +This operation could not be reverted if +you have not made a backup of your +current hosts file. + 您確認繼續執行當前操作以修改系統 hosts +檔嗎? + +若先前未對 hosts 檔進行備份,該操作將 +不可逆轉。 + + + Data file is up-to-date. 當前的資料檔案是最新的。 - + Complete 完成 - - Connecting... - 正在連接... - - - - Downloading: %s / %s - 正在下載: %s / %s + + Building hosts file... + 正在創建 hosts 檔... - + Hosts Setup Utility hosts 設置實用程式 - + Config 配置 - + Server 伺服器 - + IP Version IP 協定版本 - + Status 狀態 - + Connection 連接狀態 - + N/A 不適用 - + OS 作業系統 - + + Hosts Info + 資料檔案狀態 + + + + Version + 當前版本 + + + + Release + 發佈日期 + + + + Latest + 最新版本 + + + Backup the hosts file of current system. 備份當前系統的 hosts 檔。 - + Download data file 下載資料檔案 - + Download the latest data file. 下載最新的資料檔案。 - + Restore backup 還原備份 - + Restore a previous backup of hosts file. 還原以前的備份的 hosts 檔。 - + Apply hosts 更改 hosts 檔 - + Apply changes to the hosts file. 將更改應用到主 hosts 檔。 - + Exit 退出 - + Close this tool. 關閉此程式。 - + Check update / Refresh 檢查更新 / 刷新 - + Check the latest version of hosts data file. 檢查 hosts 檔案的最新版本。 - - Hosts Info - 資料檔案狀態 - - - - Version - 當前版本 - - - - Release - 發佈日期 - - - - Latest - 最新版本 - - - - Are you sure you want to apply changes -to the hosts file on your system? - -This operation could not be reverted if -you have not made a backup of your -current hosts file. - 您確認繼續執行當前操作以修改系統 hosts -檔嗎? - -若先前未對 hosts 檔進行備份,該操作將 -不可逆轉。 - - - + Save with ANSI 保存為 ANSI 格式 - + Export to hosts file encoding by ANSI. 匯出由 ANSI 編碼的 hosts 檔。 - + Save with UTF-8 保存為 UTF-8 格式 - + Export to hosts file encoding by UTF-8. 匯出由 UTF-8 編碼的 hosts 檔。 - - Export hosts - 匯出 hosts 檔 + + Copyleft (C) 2011-2014 <a href="https://hosts.huhamhire.com/"><span style="text-decoration: none;color: #b1b1b1;">huhamhire-hosts</span></a> + 公共版權 (C) 2011-2014 <a href="https://hosts.huhamhire.com/"><span style="text-decoration: none;color: #b1b1b1;">huhamhire-hosts</span></a> - - hosts File - hosts 檔 + + Powered by PyQT + - - google-apis(cn) - Google API服務(大陸) + + Connecting... + 正在連接... - - google-apis(us) - Google API服務(美國) + + Downloading: %s / %s + 正在下載: %s / %s - - wikipedia - 維基百科 + + Notice: %i hosts entries has + been applied in %ssecs. + 消息:共有 %i 条 hosts 条目在 + %s秒内被插入到 hosts 文件中。 - - steam - Steam 遊戲平台 + + Incorrect Data file! +Please use the "Download" key to +fetch a new data file. + 不正確的資料檔案 ! +請使用"下載"按鈕來獲得 +一個新的資料檔案。 - - Copyleft (C) 2011-2014 <a href="https://hosts.huhamhire.com/"><span style="text-decoration: none;color: #b1b1b1;">huhamhire-hosts</span></a> - 公共版權 (C) 2011-2014 <a href="https://hosts.huhamhire.com/"><span style="text-decoration: none;color: #b1b1b1;">huhamhire-hosts</span></a> + + You do not have permissions to change the +hosts file. +Please run this program as Administrator/root +so it can modify your hosts file. + 您目前沒有許可權以更改 hosts 檔。 +請以管理員方式或者根使用者運行本 +程式。 - - Powered by PyQT - + + Data file not found! +Please use the "Download" key to +fetch a new data file. + 找不到本地資料檔案! +請使用"下載"按鈕來獲得 +一個新的資料檔案。 - - google(hk) - Google Web服務(香港) + + Are you sure you want to apply changes +to the hosts file on your system? + +This operation could not be reverted if +you have not made a backup of your +current hosts file. + 您確認繼續執行當前操作以修改系統 hosts +檔嗎? + +若先前未對 hosts 檔進行備份,該操作將 +不可逆轉。 - - facebook - Facebook + + Copying new hosts file to +%s + 將新的 hosts 檔案複製到 + %s - - twitter - twitter + + google + Google Web服務 - - youtube - YouTube + + google-apis + Google API服務 - - institutions - 教育科研機構 + + customize + 使用者自訂清單 + + + + github + GitHub + + + + dropbox + Dropbox + + + + wordpress + Wordpress diff --git a/gui/language.py b/gui/language.py new file mode 100644 index 0000000..4be7b42 --- /dev/null +++ b/gui/language.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# language.py : Language utilities used in GUI module. +# +# Copyleft (C) 2013 - huhamhire hosts team +# ===================================================================== +# Licensed under the GNU General Public License, version 3. You should +# have received a copy of the GNU General Public License along with +# this program. If not, see . +# +# This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING +# THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE. +# ===================================================================== + +__author__ = "huhamhire " + +import locale + + +class LangUtil(object): + """ + LangUtil contains a set of language tools for Hosts Setup Utility to + use. + + .. note:: All methods from this class are declared as `classmethod`. + + :ivar dict language: Supported localized language name for a specified + locale tag. + + The default declaration of :attr:`language` is:: + + language = {"de_DE": u"Deutsch", + "en_US": u"English", + "ja_JP": u"日本語", + "ko_KR": u"한글", + "ru_RU": u"Русский", + "zh_CN": u"简体中文", + "zh_TW": u"正體中文", } + + .. note:: The keys of :attr:`language` are typically in the format of \ + IETF language tag. For example: en_US, en_GB, etc. + + .. note:: The Hosts Setup Utility would check whether the language + files exist in `gui/lang/` folder automatically while loading GUI. + If not, the language related wouldn't be available in language + selection combobox. + """ + language = {"de_DE": u"Deutsch", + "en_US": u"English", + "ja_JP": u"日本語", + "ko_KR": u"한글", + "ru_RU": u"Русский", + "zh_CN": u"简体中文", + "zh_TW": u"正體中文", } + + @classmethod + def get_locale(cls): + """ + Retrieve the default locale tag of current operating system. + + .. note:: This is a `classmethod`. + + :return: Default locale tag of current operating system. If the locale + is not found in cls.dictionary dictionary, the return value + "en_US" as default. + :rtype: str + """ + lc = locale.getdefaultlocale()[0] + if lc is None: + lc = "en_US" + return lc + + @classmethod + def get_language_by_locale(cls, l_locale): + """ + Return the name of a specified language by :attr:`l_locale`. + + .. note:: This is a `classmethod`. + + :param l_locale: Locale tag of a specified language. + :type l_locale: str + :return: Localized name of a specified language. + :type: str + """ + try: + return cls.language[l_locale] + except KeyError: + return cls.language["en_US"] + + @classmethod + def get_locale_by_language(cls, l_lang): + """ + Get the locale string connecting with a language specified by + :attr:`l_lang`. + + .. note:: This is a `classmethod`. + + :param l_lang: Localized name of a specified language. + :type l_lang: unicode + :return: Locale tag of a specified language. + :rtype: str + """ + for locl, lang in cls.language.items(): + if l_lang == lang: + return locl + return "en_US" diff --git a/_pylupdate4.py b/gui/pyqt/_pylupdate4.py similarity index 79% rename from _pylupdate4.py rename to gui/pyqt/_pylupdate4.py index d52f674..73387b8 100644 --- a/_pylupdate4.py +++ b/gui/pyqt/_pylupdate4.py @@ -3,7 +3,7 @@ # # _pylupdate4.py : Tools to update the language files for UI # -# Copyleft (C) 2013 - huhamhire hosts team +# Copyleft (C) 2013 - huhamhire hosts team # ===================================================================== # Licensed under the GNU General Public License, version 3. You should # have received a copy of the GNU General Public License along with @@ -17,6 +17,6 @@ import os for root, dirs, files in os.walk('.'): - for file in files: - if file.endswith('.pro'): - os.system('pylupdate4 %s' % file) + for f in files: + if f.endswith('.pro'): + os.system('pylupdate4 %s' % f) diff --git a/gui/pyqt/_pyuic4.py b/gui/pyqt/_pyuic4.py new file mode 100644 index 0000000..5e09438 --- /dev/null +++ b/gui/pyqt/_pyuic4.py @@ -0,0 +1,32 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# _pyuic4.py : Tools update the UI code from UI design +# +# Copyleft (C) 2013 - huhamhire hosts team +# ===================================================================== +# Licensed under the GNU General Public License, version 3. You should +# have received a copy of the GNU General Public License along with +# this program. If not, see . +# +# This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING +# THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE. +# ===================================================================== + +import os + +PROJ_DIR = '../' + +for root, dirs, files in os.walk(PROJ_DIR): + for f in files: + file_path = os.path.join(root, f) + out_path = os.path.join(PROJ_DIR, f.rsplit('.', 1)[0]) + if f.endswith('.ui'): + os.system('pyuic4 -o %s.py -x %s' % (out_path, file_path)) + print("make: %s.py" % out_path) + elif f.endswith('.qrc'): + os.system('pyrcc4 -o %s_rc.py %s' % (out_path, file_path)) + print("make: %s_rc.py" % out_path) + else: + pass diff --git a/gui/pyqt/style.qrc b/gui/pyqt/style.qrc new file mode 100644 index 0000000..a21e688 --- /dev/null +++ b/gui/pyqt/style.qrc @@ -0,0 +1,6 @@ + + + ../../res/img/style/checkbox.png + ../../res/img/style/down_arrow.png + + diff --git a/gui/pyqt/util.pro b/gui/pyqt/util.pro new file mode 100644 index 0000000..870bf64 --- /dev/null +++ b/gui/pyqt/util.pro @@ -0,0 +1,19 @@ +SOURCES = ../hostsutil.py \ + ../__list_trans.py \ + ../_checkconn.py \ + ../_checkupdate.py \ + ../_make.py \ + ../_update.py \ + ../qdialog_ui.py \ + ../qdialog_d.py \ + ../qdialog_slots.py \ + ../util_ui.py +TRANSLATIONS = ../lang/de_DE.ts \ + ../lang/en_US.ts \ + ../lang/zh_CN.ts \ + ../lang/zh_TW.ts +INTERFACES = util_ui.ui +RESOURCES = util.qrc \ + style.qrc +CODECFORTR = UTF-8 +CODECFORSRC = UTF-8 diff --git a/gui/pyqt/util.qrc b/gui/pyqt/util.qrc new file mode 100644 index 0000000..cee83bb --- /dev/null +++ b/gui/pyqt/util.qrc @@ -0,0 +1,23 @@ + + + ../../res/img/buttons/button_ansi.png + ../../res/img/buttons/button_ansi_disabled.png + ../../res/img/buttons/button_apply.png + ../../res/img/buttons/button_apply_disabled.png + ../../res/img/buttons/button_backup.png + ../../res/img/buttons/button_backup_disabled.png + ../../res/img/buttons/button_download.png + ../../res/img/buttons/button_download_disabled.png + ../../res/img/buttons/button_exit.png + ../../res/img/buttons/button_exit_disabled.png + ../../res/img/buttons/button_restore.png + ../../res/img/buttons/button_restore_disabled.png + ../../res/img/buttons/button_update.png + ../../res/img/buttons/button_update_disabled.png + ../../res/img/buttons/button_utf8.png + ../../res/img/buttons/button_utf8_disabled.png + + + ../../res/img/icons/utl_icon@256x256.png + + diff --git a/qthostsui.ui b/gui/pyqt/util_ui.ui similarity index 87% rename from qthostsui.ui rename to gui/pyqt/util_ui.ui index 6d01345..87b2915 100644 --- a/qthostsui.ui +++ b/gui/pyqt/util_ui.ui @@ -1,8 +1,8 @@ huhamhire - HostsUtlMain - + Util + true @@ -39,8 +39,8 @@ Hosts Setup Utility - - :/icon/img/utl_icon.png:/icon/img/utl_icon.png + + :/icon/res/img/icons/utl_icon@256x256.png:/icon/res/img/icons/utl_icon@256x256.png @@ -344,9 +344,9 @@ - - :/buttons/img/buttons/button_backup.png - :/buttons/img/buttons/button_backup_disabled.png:/buttons/img/buttons/button_backup.png + + :/buttons/res/img/buttons/button_backup.png + :/buttons/res/img/buttons/button_backup_disabled.png:/buttons/res/img/buttons/button_backup.png @@ -374,9 +374,9 @@ - - :/buttons/img/buttons/button_download.png - :/buttons/img/buttons/button_download_disabled.png:/buttons/img/buttons/button_download.png + + :/buttons/res/img/buttons/button_download.png + :/buttons/res/img/buttons/button_download_disabled.png:/buttons/res/img/buttons/button_download.png @@ -404,9 +404,9 @@ - - :/buttons/img/buttons/button_restore.png - :/buttons/img/buttons/button_restore_disabled.png:/buttons/img/buttons/button_restore.png + + :/buttons/res/img/buttons/button_restore.png + :/buttons/res/img/buttons/button_restore_disabled.png:/buttons/res/img/buttons/button_restore.png @@ -434,9 +434,9 @@ - - :/buttons/img/buttons/button_apply.png - :/buttons/img/buttons/button_apply_disabled.png:/buttons/img/buttons/button_apply.png + + :/buttons/res/img/buttons/button_apply.png + :/buttons/res/img/buttons/button_apply_disabled.png:/buttons/res/img/buttons/button_apply.png @@ -464,9 +464,9 @@ - - :/buttons/img/buttons/button_exit.png - :/buttons/img/buttons/button_exit_disabled.png:/buttons/img/buttons/button_exit.png + + :/buttons/res/img/buttons/button_exit.png + :/buttons/res/img/buttons/button_exit_disabled.png:/buttons/res/img/buttons/button_exit.png @@ -494,9 +494,9 @@ - - :/buttons/img/buttons/button_update.png - :/buttons/img/buttons/button_update_disabled.png:/buttons/img/buttons/button_update.png + + :/buttons/res/img/buttons/button_update.png + :/buttons/res/img/buttons/button_update_disabled.png:/buttons/res/img/buttons/button_update.png @@ -524,9 +524,9 @@ - - :/buttons/img/buttons/button_ansi.png - :/buttons/img/buttons/button_ansi_disabled.png:/buttons/img/buttons/button_ansi.png + + :/buttons/res/img/buttons/button_ansi.png + :/buttons/res/img/buttons/button_ansi_disabled.png:/buttons/res/img/buttons/button_ansi.png @@ -554,9 +554,9 @@ - - :/buttons/img/buttons/button_utf8.png - :/buttons/img/buttons/button_utf8_disabled.png:/buttons/img/buttons/button_utf8.png + + :/buttons/res/img/buttons/button_utf8.png + :/buttons/res/img/buttons/button_utf8_disabled.png:/buttons/res/img/buttons/button_utf8.png @@ -638,6 +638,19 @@ Powered by PyQT + + + + 380 + 20 + 250 + 25 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + SelectMirror @@ -654,14 +667,14 @@ SelectLang - + ButtonExit clicked() - HostsUtlMain + Util close() @@ -677,7 +690,7 @@ SelectIP currentIndexChanged(int) - HostsUtlMain + Util on_IPVersion_changed(int) @@ -693,7 +706,7 @@ SelectMirror currentIndexChanged(int) - HostsUtlMain + Util on_Mirror_changed(int) @@ -709,7 +722,7 @@ Functionlist itemChanged(QListWidgetItem*) - HostsUtlMain + Util on_Selection_changed(QListWidgetItem*) @@ -725,7 +738,7 @@ ButtonApply clicked() - HostsUtlMain + Util on_MakeHosts_clicked() @@ -741,7 +754,7 @@ ButtonBackup clicked() - HostsUtlMain + Util on_Backup_clicked() @@ -757,7 +770,7 @@ ButtonRestore clicked() - HostsUtlMain + Util on_Restore_clicked() @@ -773,7 +786,7 @@ ButtonCheck clicked() - HostsUtlMain + Util on_CheckUpdate_clicked() @@ -789,7 +802,7 @@ ButtonUpdate clicked() - HostsUtlMain + Util on_FetchUpdate_clicked() @@ -805,7 +818,7 @@ SelectLang currentIndexChanged(QString) - HostsUtlMain + Util on_Lang_changed(QString) @@ -821,7 +834,7 @@ ButtonANSI clicked() - HostsUtlMain + Util on_MakeANSI_clicked() @@ -837,7 +850,7 @@ ButtonUTF clicked() - HostsUtlMain + Util on_MakeUTF8_clicked() @@ -853,7 +866,7 @@ Copyright linkActivated(QString) - HostsUtlMain + Util on_LinkActivated(QString) diff --git a/gui/qdialog_d.py b/gui/qdialog_d.py new file mode 100644 index 0000000..0ad8418 --- /dev/null +++ b/gui/qdialog_d.py @@ -0,0 +1,440 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# qdialog_d.py : Operations on the main dialog. +# +# Copyleft (C) 2014 - huhamhire hosts team +# ===================================================================== +# Licensed under the GNU General Public License, version 3. You should +# have received a copy of the GNU General Public License along with +# this program. If not, see . +# +# This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING +# THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE. +# ===================================================================== + +__author__ = "huhamhire " + +import os +import shutil + +from zipfile import BadZipfile +from PyQt4 import QtCore, QtGui + +from _checkconn import QSubChkConnection +from _checkupdate import QSubChkUpdate +from _make import QSubMakeHosts +from _update import QSubFetchUpdate +from qdialog_ui import QDialogUI +from util_ui import _translate + +import sys +sys.path.append("..") +from util import RetrieveData, CommonUtil + + +class QDialogDaemon(QDialogUI): + """ + QDialogDaemon class contains methods used to manage the operations while + modifying the hosts file of current operating system. Including methods + to manage operations to update data file, download data file, configure + hosts, make hosts file, backup hosts file, and restore backup. + + .. note:: This class is subclass of :class:`~gui.qdialog_ui.QDialogUI` + class and parent class of :class:`~gui.qdialog_slots.QDialogSlots`. + + :ivar int_down_flag: An flag indicating the downloading status of + current session. 1 represents data file is being downloaded. + :ivar dict _update: Update information of the current data file on server. + :ivar int _writable: Indicating whether the program is run with admin/root + privileges. The value could be `1` or `0`. + + .. seealso:: `_update` and `_writable` in + :class:`~tui.curses_d.CursesDaemon` class. + + :ivar list _funcs: Two lists with the information of function list both + for IPv4 and IPv6 environment. + :ivar list choice: Two lists with the selection of functions both + for IPv4 and IPv6 environment. + :ivar list slices: Two lists with integers indicating the number of + function items from different parts listed in the function list. + + .. seealso:: `_funcs`, `choice`, and `slices` in + :class:`~tui.curses_ui.CursesUI` class. + + :ivar dict make_cfg: A set of module selection control bytes used to + control whether a specified method is used or not while generate a + hosts file. + + .. seealso:: :attr:`make_cfg` in + :class:`~tui.curses_d.CursesDaemon` class. + :ivar str platform: Platform of current operating system. The value could + be `Windows`, `Linux`, `Unix`, `OS X`, and of course `Unknown`. + :ivar str hostname: The hostname of current operating system. + + .. note:: This attribute would only be used on linux. + + :ivar str hosts_path: The absolute path to the hosts file on current + operating system. + :ivar str make_mode: Operation mode for making hosts file. The valid value + could be one of `system`, `ansi`, and `utf-8`. + + .. seealso:: :attr:`make_mode` in + :class:`~util.makehosts.MakeHosts` class. + + :ivar str sys_eol: The End-Of-Line marker. This maker could typically be + one of `CR`, `LF`, or `CRLF`. + + .. seealso:: :attr:`sys_eol` in + :class:`~tui.curses_ui.CursesUI` class. + """ + _down_flag = 0 + + _update = {} + _writable = 0 + + _funcs = [[], []] + choice = [[], []] + slices = [[], []] + make_cfg = {} + platform = '' + hostname = '' + hosts_path = '' + sys_eol = '' + + make_mode = '' + + def __init__(self): + super(QDialogDaemon, self).__init__() + self.set_platform() + self.set_platform_label() + + def check_writable(self): + """ + Check if current session is ran with root privileges. + + .. note:: IF current session does not has the write privileges to the + hosts file of current system, a warning message box would popup. + + .. note:: ALL operation would change the `hosts` file on current + system could only be done while current session has write + privileges to the file. + """ + writable = CommonUtil.check_privileges()[1] + self._writable = writable + if not writable: + self.warning_permission() + + def check_connection(self): + """ + Operations to check the connection to current server. + """ + thread = QSubChkConnection(self) + thread.trigger.connect(self.set_conn_status) + thread.start() + + def check_update(self): + """ + Retrieve the metadata of the latest data file from a server. + """ + self.set_update_start_btns() + self.set_label_text(self.ui.labelLatestData, unicode( + _translate("Util", "Checking...", None))) + thread = QSubChkUpdate(self) + thread.trigger.connect(self.finish_update) + thread.start() + + def fetch_update(self): + """ + Retrieve a new hosts data file from a server. + """ + self.set_fetch_start_btns() + thread = QSubFetchUpdate(self) + thread.prog_trigger.connect(self.set_down_progress) + thread.finish_trigger.connect(self.finish_fetch) + thread.start() + + def fetch_update_after_check(self): + """ + Decide whether to retrieve a new data file from server or not after + checking update information from a mirror. + """ + if self._update["version"] == \ + unicode(_translate("Util", "[Error]", None)): + self.finish_fetch(error=1) + elif self.new_version(): + self.fetch_update() + else: + self.info_uptodate() + self.finish_fetch() + + def export_hosts(self): + """ + Display the export dialog and get the path to save the exported hosts + file. + + :return: Path to export a hosts file. + :rtype: str + """ + filename = "hosts" + if self.platform == "OS X": + filename = "/Users/" + filename + filepath = QtGui.QFileDialog.getSaveFileName( + self, _translate("Util", "Export hosts", None), + QtCore.QString(filename), + _translate("Util", "hosts File", None)) + return filepath + + def make_hosts(self, mode="system"): + """ + Make a new hosts file for current system. + + :param mode: Operation mode for making hosts file. The valid value + could be one of `system`, `ansi`, and `utf-8`. + Default by `system`. + :type mode: str + """ + self.set_make_start_btns() + self.set_make_message(unicode(_translate( + "Util", "Building hosts file...", None)), 1) + # Avoid conflict while making hosts file + RetrieveData.disconnect_db() + self.make_mode = mode + self.set_config_bytes(mode) + thread = QSubMakeHosts(self) + thread.info_trigger.connect(self.set_make_progress) + thread.fina_trigger.connect(self.finish_make) + thread.move_trigger.connect(self.move_hosts) + thread.start() + + def move_hosts(self): + """ + Move hosts file to the system path after making. + + .. note:: This method is the slot responses to the move_trigger signal + from an instance of :class:`~gui._make.QSubMakeHosts` class while + making operations are finished. + + .. seealso:: :attr:`move_trigger` in + :class:`~gui._make.QSubMakeHosts`. + """ + filepath = "hosts" + msg = unicode( + _translate("Util", "Copying new hosts file to\n" + "%s", None)) % self.hosts_path + self.set_make_message(msg) + try: + shutil.copy2(filepath, self.hosts_path) + except IOError: + self.warning_permission() + os.remove(filepath) + return + except OSError: + pass + msg = unicode( + _translate("Util", "Remove temporary file", None)) + self.set_make_message(msg) + os.remove(filepath) + msg = unicode( + _translate("Util", "Operation completed", None)) + self.set_make_message(msg) + self.info_complete() + + def set_platform(self): + """ + Set the information of current operating system platform. + """ + system, hostname, path, encode, flag = CommonUtil.check_platform() + self.platform = system + self.hostname = hostname + self.hosts_path = path + self.plat_flag = flag + if encode == "win_ansi": + self.sys_eol = "\r\n" + else: + self.sys_eol = "\n" + + def set_config_bytes(self, mode): + """ + Generate the module configuration byte words by the selection from + function list on the main dialog. + + :param mode: Operation mode for making hosts file. The valid value + could be one of `system`, `ansi`, and `utf-8`. + + .. seealso:: Method + :meth:`~gui.qdialog_d.QDialogDaemon.make_hosts`. + """ + ip_flag = self._ipv_id + selection = {} + if mode == "system": + localhost_word = { + "Windows": 0x0001, "Linux": 0x0002, + "Unix": 0x0002, "OS X": 0x0004}[self.platform] + else: + localhost_word = 0x0008 + selection[0x02] = localhost_word + ch_parts = [0x08, 0x20 if ip_flag else 0x10, 0x40] + # Set customized module if exists + if os.path.isfile(self.custom): + ch_parts.insert(0, 0x04) + slices = self.slices[ip_flag] + for i, part in enumerate(ch_parts): + part_cfg = self._funcs[ip_flag][slices[i]:slices[i + 1]] + part_word = 0 + for i, cfg in enumerate(part_cfg): + part_word += cfg << i + selection[part] = part_word + self.make_cfg = selection + + def refresh_info(self, refresh=0): + """ + Reload the data file information and show them on the main dialog. The + information here includes both metadata and hosts module info from the + data file. + + :param refresh: A flag indicating whether the information on main + dialog needs to be reloaded or not. The value could be `0` or `1`. + + ======= ============= + refresh operation + ======= ============= + 0 Do NOT reload + 1 Reload + ======= ============= + + :type refresh: int + """ + if refresh and RetrieveData.conn is not None: + RetrieveData.clear() + try: + RetrieveData.unpack() + RetrieveData.connect_db() + self.set_func_list(refresh) + self.refresh_func_list() + self.set_info() + except (BadZipfile, IOError, OSError): + self.warning_incorrect_datafile() + + def finish_make(self, time, count): + """ + Start operations after making new hosts file. + + .. note:: This method is the slot responses to the fina_trigger signal + values :attr:`time`, :attr:`count` from an instance of + :class:`~gui._make.QSubMakeHosts` class while making operations + are finished. + + :param time: Total time uesd while generating the new hosts file. + :type time: str + :param count: Total number of hosts entries inserted into the new + hosts file. + :type count: int + + .. seealso:: :attr:`fina_trigger` in + :class:`~gui._make.QSubMakeHosts` class. + """ + self.set_make_finish_btns() + RetrieveData.connect_db() + msg = unicode( + _translate("Util", "Notice: %i hosts entries has " + "\n been applied in %ssecs.", None))\ + % (count, time) + self.set_make_message(msg) + self.set_down_progress(100, unicode( + _translate("Util", "Operation Completed Successfully!", None))) + + def finish_update(self, update): + """ + Start operations after checking update. + + .. note:: This method is the slot responses to the trigger signal + value :attr:`update` from an instance of + :class:`~gui._checkupdate.QSubChkUpdate` class while checking + operations are finished. + + :param update: Metadata of the latest hosts data file on the server. + :type update: dict + + .. seealso:: :attr:`trigger` in + :class:`~gui._checkupdate.QSubChkUpdate` class. + """ + self._update = update + self.set_label_text(self.ui.labelLatestData, update["version"]) + if self._update["version"] == \ + unicode(_translate("Util", "[Error]", None)): + self.set_conn_status(0) + else: + self.set_conn_status(1) + if self._down_flag: + self.fetch_update_after_check() + else: + self.set_update_finish_btns() + + def finish_fetch(self, refresh=1, error=0): + """ + Start operations after downloading data file. + + .. note:: This method is the slot responses to the finish_trigger + signal :attr:`refresh`, :attr:`error` from an instance of + :class:`~gui._update.QSubFetchUpdate` class while downloading is + finished. + + :param refresh: An flag indicating whether the downloading progress is + successfully finished or not. Default by 1. + :type refresh: int. + :param error: An flag indicating whether the downloading + progress is successfully finished or not. Default by 0. + :type error: int + + .. seealso:: :attr:`finish_trigger` in + :class:`~gui._update.QSubFetchUpdate` class. + """ + self._down_flag = 0 + if error: + # Error occurred while downloading + self.set_down_progress(0, unicode( + _translate("Util", "Error", None))) + try: + os.remove(self.filename) + except: + pass + self.warning_download() + msg_title = "Warning" + msg = unicode( + _translate("Util", "Incorrect Data file!\n" + "Please use the \"Download\" key to \n" + "fetch a new data file.", None)) + self.set_message(msg_title, msg) + self.set_conn_status(0) + else: + # Data file retrieved successfully + self.set_down_progress(100, unicode( + _translate("Util", "Download Complete", None))) + self.refresh_info(refresh) + self.set_fetch_finish_btns(error) + + def new_version(self): + """ + Compare version of local data file to the version from the server. + + :return: A flag indicating whether the local data file is up-to-date + or not. + :rtype: int + + ====== ============================================ + Return File status + ====== ============================================ + 1 The version of data file on server is newer. + 0 The local data file is up-to-date. + ====== ============================================ + """ + local_ver = self._cur_ver + server_ver = self._update["version"] + local_ver = local_ver.split('.') + server_ver = server_ver.split('.') + for i, ver_num in enumerate(local_ver): + if server_ver[i] > ver_num: + return 1 + return 0 \ No newline at end of file diff --git a/gui/qdialog_slots.py b/gui/qdialog_slots.py new file mode 100644 index 0000000..fabb8d2 --- /dev/null +++ b/gui/qdialog_slots.py @@ -0,0 +1,346 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# qdialog_slots.py : Qt slots response to signals on the main dialog. +# +# Copyleft (C) 2014 - huhamhire hosts team +# ===================================================================== +# Licensed under the GNU General Public License, version 3. You should +# have received a copy of the GNU General Public License along with +# this program. If not, see . +# +# This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING +# THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE. +# ===================================================================== + +__author__ = "huhamhire " + +import shutil +import time + +from PyQt4 import QtCore, QtGui + +from language import LangUtil +from qdialog_d import QDialogDaemon +from util_ui import _translate + +import sys +sys.path.append("..") +from util import RetrieveData + + +class QDialogSlots(QDialogDaemon): + """ + QDialogSlots class provides `Qt slots` to deal with the `Qt signals` + emitted by the widgets on the main dialog operated by users. + + .. note:: This class is subclass of :class:`~gui.qdialog_d.QDialogDaemon` + class and parent class of :class:`~gui.hostsutil.HostsUtil`. + + :ivar int ipv_id: An flag indicating current IP version setting. The + value could be 1 or 0: + + ====== ========== + ipv_id IP Version + ====== ========== + 1 IPv6 + 0 IPv4 + ====== ========== + + .. seealso:: + :meth:`~gui.qdialog_slots.QDialogSlots.on_IPVersion_changed`. in + this class. + + :ivar str make_path: Temporary path to store generated hosts file. The + default value of :attr:`make_path` is "`./hosts`". + :ivar int mirror_id: Index number of current selected server from the + mirror list. + """ + _ipv_id = 0 + + make_path = "./hosts" + mirror_id = 0 + + def __init__(self): + """ + Initialize a new instance of this class. + """ + super(QDialogSlots, self).__init__() + + def reject(self): + """ + Close this program while the reject signal is emitted. + + .. note:: This method is the slot responses to the reject signal from + an instance of the main dialog. + """ + self.close() + return QtGui.QDialog.reject(self) + + def close(self): + """ + Close this program while the close signal is emitted. + + .. note:: This method is the slot responses to the close signal from + an instance of the main dialog. + """ + try: + RetrieveData.clear() + except: + pass + super(QDialogDaemon, self).close() + + def mouseMoveEvent(self, e): + """ + Allow drag operations to set the new position for current cursor. + + :param e: Current mouse event. + :type e: :class:`PyQt4.QtGui.QMouseEvent` + """ + + if e.buttons() & QtCore.Qt.LeftButton: + try: + self.move(e.globalPos() - self.dragPos) + except AttributeError: + pass + e.accept() + + def mousePressEvent(self, e): + """ + Allow press operation to set the new position for current dialog. + + :param e: Current mouse event. + :type e: :class:`PyQt4.QtGui.QMouseEvent` + """ + if e.button() == QtCore.Qt.LeftButton: + self.dragPos = e.globalPos() - self.frameGeometry().topLeft() + e.accept() + + def on_Mirror_changed(self, mirr_id): + """ + Change the current server selection. + + .. note:: This method is the slot responses to the signal argument + :attr:`mirr_id` from SelectMirror widget while the value is + changed. + + :param mirr_id: Index number of current mirror server. + """ + self.mirror_id = mirr_id + self.check_connection() + + def on_IPVersion_changed(self, ipv_id): + """ + Change the current IP version setting. + + .. note:: This method is the slot responses to the signal argument + :attr:`ipv_id` from SelectIP widget while the value is changed. + + :param ipv_id: An flag indicating current IP version setting. The + value could be 1 or 0: + + ====== ========== + ipv_id IP Version + ====== ========== + 1 IPv6 + 0 IPv4 + ====== ========== + + :type ipv_id: int + """ + if self._ipv_id != ipv_id: + self._ipv_id = ipv_id + if not RetrieveData.db_exists(): + self.warning_no_datafile() + else: + self.set_func_list(0) + self.refresh_func_list() + + def on_Selection_changed(self, item): + """ + Change the current selection of modules to be applied to hosts file. + + .. note:: This method is the slot responses to the signal argument + :attr:`item` from Functionlist widget while the item selection is + changed. + + :param item: Row number of the item listed in Functionlist which is + changed by user. + :type item: int + """ + ip_flag = self._ipv_id + func_id = self.ui.Functionlist.row(item) + if self._funcs[ip_flag][func_id] == 0: + self._funcs[ip_flag][func_id] = 1 + else: + self._funcs[ip_flag][func_id] = 0 + mutex = RetrieveData.get_ids(self.choice[ip_flag][func_id][2]) + for c_id, c in enumerate(self.choice[ip_flag]): + if c[0] == self.choice[ip_flag][func_id][0]: + if c[1] in mutex and self._funcs[ip_flag][c_id] == 1: + self._funcs[ip_flag][c_id] = 0 + self.refresh_func_list() + + def on_Lang_changed(self, lang): + """ + Change the UI language setting. + + .. note:: This method is the slot responses to the signal argument + :attr:`lang` from SelectLang widget while the value is changed. + + :param lang: The language name which is selected by user. + + .. note:: This string is typically in the format of IETF language + tag. For example: en_US, en_GB, etc. + + .. seealso:: :attr:`language` in :class:`~gui.language.LangUtil` + class. + + :type lang: str + """ + new_lang = LangUtil.get_locale_by_language(unicode(lang)) + trans = QtCore.QTranslator() + from hostsutil import LANG_DIR + trans.load(LANG_DIR + new_lang) + self.app.removeTranslator(self._trans) + self.app.installTranslator(trans) + self._trans = trans + self.ui.retranslateUi(self) + self.init_main() + self.check_connection() + + def on_MakeHosts_clicked(self): + """ + Start operations to make a hosts file. + + .. note:: This method is the slot responses to the signal from + ButtonApply widget while the button is clicked. + + .. note:: No operations would be called if current session does not + have the privileges to change the hosts file. + """ + if not self._writable: + self.warning_permission() + return + if self.question_apply(): + self.make_path = "./hosts" + self.make_hosts("system") + else: + return + + def on_MakeANSI_clicked(self): + """ + Export a hosts file encoded in ANSI. + + .. note:: This method is the slot responses to the signal from + ButtonANSI widget while the button is clicked. + """ + self.make_path = self.export_hosts() + if unicode(self.make_path) != u'': + self.make_hosts("ansi") + + def on_MakeUTF8_clicked(self): + """ + Export a hosts file encoded in UTF-8. + + .. note:: This method is the slot responses to the signal from + ButtonUTF widget while the button is clicked. + """ + self.make_path = self.export_hosts() + if unicode(self.make_path) != u'': + self.make_hosts("utf-8") + + def on_Backup_clicked(self): + """ + Backup the hosts file of current operating system. + + .. note:: This method is the slot responses to the signal from + ButtonBackup widget while the button is clicked. + """ + l_time = time.localtime(time.time()) + backtime = time.strftime("%Y-%m-%d-%H%M%S", l_time) + filename = "hosts_" + backtime + ".bak" + if self.platform == "OS X": + filename = "/Users/" + filename + filepath = QtGui.QFileDialog.getSaveFileName( + self, _translate("Util", "Backup hosts", None), + QtCore.QString(filename), + _translate("Util", "Backup File(*.bak)", None)) + if unicode(filepath) != u'': + shutil.copy2(self.hosts_path, unicode(filepath)) + self.info_complete() + + def on_Restore_clicked(self): + """ + Restore a previously backed up hosts file. + + .. note:: This method is the slot responses to the signal from + ButtonRestore widget while the button is clicked. + This method would call + + .. note:: No operations would be called if current session does not + have the privileges to change the hosts file. + """ + if not self._writable: + self.warning_permission() + return + filename = '' + if self.platform == "OS X": + filename = "/Users/" + filename + filepath = QtGui.QFileDialog.getOpenFileName( + self, _translate("Util", "Restore hosts", None), + QtCore.QString(filename), + _translate("Util", "Backup File(*.bak)", None)) + if unicode(filepath) != u'': + shutil.copy2(unicode(filepath), self.hosts_path) + self.info_complete() + + def on_CheckUpdate_clicked(self): + """ + Retrieve update information (metadata) of the latest data file from a + specified server. + + .. note:: This method is the slot responses to the signal from + ButtonCheck widget while the button is clicked. + """ + if self.choice != [[], []]: + self.refresh_func_list() + self.set_update_click_btns() + if self._update == {} or self._update["version"] == \ + unicode(_translate("Util", "[Error]", None)): + self.check_update() + + def on_FetchUpdate_clicked(self): + """ + Retrieve the latest hosts data file. + + .. note:: This method is the slot responses to the signal from + ButtonUpdate widget while the button is clicked. + This method would call operations to + + .. note:: Method :meth:`~gui.qdialog_slots.on_CheckUpdate_clicked` + would be called if no update information has been set, + + .. note:: If the current data is up-to-date, no data file would be + retrieved. + """ + self.set_fetch_click_btns() + self._down_flag = 1 + if self._update == {} or self._update["version"] == \ + unicode(_translate("Util", "[Error]", None)): + self.check_update() + elif self.new_version(): + self.fetch_update() + else: + self.info_uptodate() + self.finish_fetch() + + def on_LinkActivated(self, url): + """ + Open external link in browser. + + .. note:: This method is the slot responses to the signal from a Label + widget while the text with a hyperlink which is clicked by user. + """ + QtGui.QDesktopServices.openUrl(QtCore.QUrl(url)) \ No newline at end of file diff --git a/gui/qdialog_ui.py b/gui/qdialog_ui.py new file mode 100644 index 0000000..c1e9fac --- /dev/null +++ b/gui/qdialog_ui.py @@ -0,0 +1,577 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# qdialog_ui.py : Draw the Graphical User Interface. +# +# Copyleft (C) 2014 - huhamhire hosts team +# ===================================================================== +# Licensed under the GNU General Public License, version 3. You should +# have received a copy of the GNU General Public License along with +# this program. If not, see . +# +# This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING +# THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE. +# ===================================================================== + +__author__ = "huhamhire " + +import os + +from PyQt4 import QtCore, QtGui + +import sys + +sys.path.append("..") +from util import RetrieveData, CommonUtil +from __version__ import __version__, __release__ +from language import LangUtil +from util_ui import Ui_Util, _translate, _fromUtf8 + +# Path to store language files +LANG_DIR = "./gui/lang/" + + +class QDialogUI(QtGui.QDialog, object): + """ + CursesUI class contains methods to draw the Graphical User Interface (GUI) + of Hosts Setup Utility. The methods to make GUI here are based on + `PyQT4 `_. + + .. note:: This class is subclass of :class:`QtGui.QDialog` class + and :class:`object` class. + + :ivar str _cur_ver: Current version of local hosts data file. + :ivar QtCore.QTranslator _trans: An instance of + :class:`QtCore.QTranslator` object indicating the current UI language + setting. + + :ivar QtGui.QApplication app: An instance of :class:`QtGui.QApplication` + object to launch the Qt application. + + :ivar list mirrors: Dictionaries containing `tag`, `test url`, and + `update url` of mirror servers. + :ivar str platform: Platform of current operating system. The value could + be `Windows`, `Linux`, `Unix`, `OS X`, and of course `Unknown`. + :ivar bool plat_flag: A flag indicating whether current operating system + is supported or not. + + :ivar object ui: Form implementation declares layout of the main dialog + which is generated from a UI file designed by `Qt Designer`. + :ivar str custom: File name of User Customized Hosts File. Customized + hosts would be able to select if this file exists. The default file + name is ``custom.hosts``. + + .. seealso:: :ref:`User Customized Hosts`. + """ + _cur_ver = "" + _trans = None + + app = None + mirrors = [] + platform = '' + plat_flag = True + ui = None + + custom = "custom.hosts" + + def __init__(self): + """ + Initialize a new instance of this class. Set the UI object and current + translator of the main dialog. + """ + self.app = QtGui.QApplication(sys.argv) + super(QDialogUI, self).__init__() + self.ui = Ui_Util() + self.ui.setupUi(self) + self.set_style() + self.set_stylesheet() + # Set default UI language + trans = QtCore.QTranslator() + trans.load(LANG_DIR + "en_US") + self._trans = trans + self.app.installTranslator(trans) + self.set_languages() + + def set_stylesheet(self): + """ + Set the style sheet of main dialog. + + .. seealso:: :ref:`QT Stylesheet`. + """ + with open("./gui/theme/default.qss", "r") as qss: + self.app.setStyleSheet(qss.read()) + + def set_style(self): + """ + Set the main dialog with a window style depending on the os platform. + """ + self.setWindowFlags(QtCore.Qt.FramelessWindowHint) + system = self.platform + if system == "Windows": + pass + elif system == "Linux": + # Set window style for sudo users. + QtGui.QApplication.setStyle( + QtGui.QStyleFactory.create("Cleanlooks")) + elif system == "OS X": + pass + + def set_languages(self): + """ + Set optional language selection items in the SelectLang widget. + """ + self.ui.SelectLang.clear() + langs = LangUtil.language + langs_not_found = [] + for locale in langs: + if not os.path.isfile(LANG_DIR + locale + ".qm"): + langs_not_found.append(locale) + for locale in langs_not_found: + langs.pop(locale) + LangUtil.language = langs + if len(langs) <= 1: + self.ui.SelectLang.setEnabled(False) + # Block the signal while set the language selecions. + self.ui.SelectLang.blockSignals(True) + sys_locale = LangUtil.get_locale() + if sys_locale not in langs.keys(): + sys_locale = "en_US" + for i, locale in enumerate(sorted(langs.keys())): + if sys_locale == locale: + select = i + lang = langs[locale] + self.ui.SelectLang.addItem(_fromUtf8("")) + self.ui.SelectLang.setItemText(i, lang) + self.ui.SelectLang.blockSignals(False) + self.ui.SelectLang.setCurrentIndex(select) + + def set_mirrors(self): + """ + Set optional server list. + """ + for i, mirror in enumerate(self.mirrors): + self.ui.SelectMirror.addItem(_fromUtf8("")) + self.ui.SelectMirror.setItemText( + i, _translate("Util", mirror["tag"], None)) + self.set_platform_label() + + def set_label_color(self, label, color): + """ + Set the :attr:`color` of a :attr:`label`. + + :param label: Label on the main dialog. + :type: :class:`PyQt4.QtGui.QLabel` + :param color: Color to be set on the label. + :type color: str + """ + if color == "GREEN": + rgb = "#37b158" + elif color == "RED": + rgb = "#e27867" + elif color == "BLACK": + rgb = "#b1b1b1" + else: + rgb = "#ffffff" + label.setStyleSheet("QLabel {color: %s}" % rgb) + + def set_label_text(self, label, text): + """ + Set the :attr:`text` of a :attr:`label`. + + :param label: Label on the main dialog. + :type: :class:`PyQt4.QtGui.QLabel` + :param text: Message to be set on the label. + :type text: unicode + """ + label.setText(_translate("Util", text, None)) + + def set_conn_status(self, status): + """ + Set the information of connection status to the current server + selected. + """ + if status == -1: + self.set_label_color(self.ui.labelConnStat, "BLACK") + self.set_label_text(self.ui.labelConnStat, unicode( + _translate("Util", "Checking...", None))) + elif status in [0, 1]: + if status: + color, stat = "GREEN", unicode(_translate( + "Util", "[OK]", None)) + else: + color, stat = "RED", unicode(_translate( + "Util", "[Failed]", None)) + self.set_label_color(self.ui.labelConnStat, color) + self.set_label_text(self.ui.labelConnStat, stat) + + def set_version(self): + version = "".join(['v', __version__, ' ', __release__]) + self.set_label_text(self.ui.VersionLabel, version) + + def set_info(self): + """ + Set the information of the current local data file. + """ + info = RetrieveData.get_info() + ver = info["Version"] + self._cur_ver = ver + self.set_label_text(self.ui.labelVersionData, ver) + build = info["Buildtime"] + build = CommonUtil.timestamp_to_date(build) + self.set_label_text(self.ui.labelReleaseData, unicode(build)) + + def set_down_progress(self, progress, message): + """ + Set :attr:`progress` position of the progress bar with a + :attr:`message`. + + :param progress: Progress position to be set on the progress bar. + :type progress: int + :param message: Message to be set on the progress bar. + :type message: str + """ + self.ui.Prog.setProperty("value", progress) + self.set_conn_status(1) + self.ui.Prog.setFormat(message) + + def set_platform_label(self): + """ + Set the information of the label indicating current operating system + platform. + """ + color = "GREEN" if self.plat_flag else "RED" + self.set_label_color(self.ui.labelOSStat, color) + self.set_label_text(self.ui.labelOSStat, "[%s]" % self.platform) + + def set_func_list(self, new=0): + """ + Draw the function list and decide whether to load the default + selection configuration or not. + + :param new: A flag indicating whether to load the default selection + configuration or not. Default value is `0`. + + === =================== + new Operation + === =================== + 0 Use user config. + 1 Use default config. + === =================== + :type new: int + """ + self.ui.Functionlist.clear() + self.ui.FunctionsBox.setTitle(_translate( + "Util", "Functions", None)) + if new: + for ip in range(2): + choice, defaults, slices = RetrieveData.get_choice(ip) + if os.path.isfile(self.custom): + choice.insert(0, [4, 1, 0, "customize"]) + defaults[0x04] = [1] + for i in range(len(slices)): + slices[i] += 1 + slices.insert(0, 0) + self.choice[ip] = choice + self.slices[ip] = slices + funcs = [] + for func in choice: + if func[1] in defaults[func[0]]: + funcs.append(1) + else: + funcs.append(0) + self._funcs[ip] = funcs + + def set_list_item_unchecked(self, item_id): + """ + Set a specified item to become unchecked in the function list. + + :param item_id: Index number of a specified item in the function list. + :type: int + """ + self._funcs[self._ipv_id][item_id] = 0 + item = self.ui.Functionlist.item(item_id) + item.setCheckState(QtCore.Qt.Unchecked) + + def refresh_func_list(self): + """ + Refresh the items in the function list by user settings. + """ + ip_flag = self._ipv_id + self.ui.Functionlist.clear() + + for f_id, func in enumerate(self.choice[self._ipv_id]): + item = QtGui.QListWidgetItem() + if self._funcs[ip_flag][f_id] == 1: + check = QtCore.Qt.Checked + else: + check = QtCore.Qt.Unchecked + item.setCheckState(check) + item.setText(_translate("Util", func[3], None)) + self.ui.Functionlist.addItem(item) + + def set_make_progress(self, mod_name, mod_num): + """ + Start operations to show progress while making hosts file. + + .. note:: This method is the slot responses to the info_trigger signal + :attr:`mod_name`, :attr:`mod_num` from an instance of + :class:`~gui._make.QSubMakeHosts` class while making operations + are proceeding. + + :param mod_name: Tag of a specified hosts module in current progress. + :type mod_name: str + :param mod_num: Number of current module in the operation sequence. + :type mod_num: int + + .. seealso:: :attr:`info_trigger` in + :class:`~gui._make.QSubMakeHosts` class. + """ + total_mods_num = self._funcs[self._ipv_id].count(1) + 1 + prog = 100 * mod_num / total_mods_num + self.ui.Prog.setProperty("value", prog) + module = unicode(_translate("Util", mod_name, None)) + message = unicode(_translate( + "Util", "Applying module: %s(%s/%s)", None) + ) % (module, mod_num, total_mods_num) + self.ui.Prog.setFormat(message) + self.set_make_message(message) + + def set_message(self, title, message): + """ + Show a message box with a :attr:`message` and a :attr:`title`. + + :param title: Title of the message box to be displayed. + :type title: unicode + :param message: Message in the message box. + :type message: unicode + """ + self.ui.FunctionsBox.setTitle(_translate("Util", title, None)) + self.ui.Functionlist.clear() + item = QtGui.QListWidgetItem() + item.setText(message) + item.setFlags(QtCore.Qt.ItemIsEnabled) + self.ui.Functionlist.addItem(item) + + def set_make_message(self, message, start=0): + """ + List message for the current operating progress while making the new + hosts file in function list. + + :param message: Message to be displayed in the function list. + :type message: unicode + :param start: A flag indicating whether the message is the first one + in the making progress or not. Default value is `0`. + + ===== ============== + start Status + ===== ============== + 0 Not the first. + 1 First. + ===== ============== + :type start: int + """ + if start: + self.ui.FunctionsBox.setTitle(_translate( + "Util", "Progress", None)) + self.ui.Functionlist.clear() + item = QtGui.QListWidgetItem() + item.setText("- " + message) + item.setFlags(QtCore.Qt.ItemIsEnabled) + self.ui.Functionlist.addItem(item) + + def warning_permission(self): + """ + Show permission error warning message box. + """ + QtGui.QMessageBox.warning( + self, _translate("Util", "Warning", None), + _translate("Util", + "You do not have permissions to change the \n" + "hosts file.\n" + "Please run this program as Administrator/root\n" + "so it can modify your hosts file." + , None)) + + def warning_download(self): + """ + Show download error warning message box. + """ + QtGui.QMessageBox.warning( + self, _translate("Util", "Warning", None), + _translate("Util", + "Error retrieving data from the server.\n" + "Please try another server.", None)) + + def warning_incorrect_datafile(self): + """ + Show incorrect data file warning message box. + """ + msg_title = "Warning" + msg = unicode(_translate("Util", + "Incorrect Data file!\n" + "Please use the \"Download\" key to \n" + "fetch a new data file.", None)) + self.set_message(unicode(msg_title), msg) + self.ui.ButtonApply.setEnabled(False) + self.ui.ButtonANSI.setEnabled(False) + self.ui.ButtonUTF.setEnabled(False) + + def warning_no_datafile(self): + """ + Show no data file warning message box. + """ + msg_title = "Warning" + msg = unicode(_translate("Util", + "Data file not found!\n" + "Please use the \"Download\" key to \n" + "fetch a new data file.", None)) + self.set_message(unicode(msg_title), msg) + self.ui.ButtonApply.setEnabled(False) + self.ui.ButtonANSI.setEnabled(False) + self.ui.ButtonUTF.setEnabled(False) + + def question_apply(self): + """ + Show confirm question message box before applying hosts file. + + :return: A flag indicating whether user has accepted to continue the + operations or not. + + ====== ========= + return Operation + ====== ========= + True Continue + False Cancel + ====== ========= + :rtype: bool + """ + msg_title = unicode(_translate("Util", "Notice", None)) + msg = unicode(_translate("Util", + "Are you sure you want to apply changes \n" + "to the hosts file on your system?\n\n" + "This operation could not be reverted if \n" + "you have not made a backup of your \n" + "current hosts file.", None)) + choice = QtGui.QMessageBox.question( + self, msg_title, msg, + QtGui.QMessageBox.Yes | QtGui.QMessageBox.No, + QtGui.QMessageBox.No + ) + if choice == QtGui.QMessageBox.Yes: + return True + else: + return False + + def info_uptodate(self): + """ + Draw data file is up-to-date message box. + """ + QtGui.QMessageBox.information( + self, _translate("Util", "Notice", None), + _translate("Util", "Data file is up-to-date.", None)) + + def info_complete(self): + """ + Draw operation complete message box. + """ + QtGui.QMessageBox.information( + self, _translate("Util", "Complete", None), + _translate("Util", "Operation completed", None)) + + def set_make_start_btns(self): + """ + Set button status while making hosts operations started. + """ + self.ui.Functionlist.setEnabled(False) + self.ui.SelectIP.setEnabled(False) + self.ui.ButtonCheck.setEnabled(False) + self.ui.ButtonUpdate.setEnabled(False) + self.ui.ButtonApply.setEnabled(False) + self.ui.ButtonANSI.setEnabled(False) + self.ui.ButtonUTF.setEnabled(False) + self.ui.ButtonExit.setEnabled(False) + + def set_make_finish_btns(self): + """ + Set button status while making hosts operations finished. + """ + self.ui.Functionlist.setEnabled(True) + self.ui.SelectIP.setEnabled(True) + self.ui.ButtonCheck.setEnabled(True) + self.ui.ButtonUpdate.setEnabled(True) + self.ui.ButtonApply.setEnabled(False) + self.ui.ButtonANSI.setEnabled(False) + self.ui.ButtonUTF.setEnabled(False) + self.ui.ButtonExit.setEnabled(True) + + def set_update_click_btns(self): + """ + Set button status while `CheckUpdate` button clicked. + """ + self.ui.ButtonApply.setEnabled(True) + self.ui.ButtonANSI.setEnabled(True) + self.ui.ButtonUTF.setEnabled(True) + + def set_update_start_btns(self): + """ + Set button status while operations to check update of hosts data file + started. + """ + self.ui.SelectMirror.setEnabled(False) + self.ui.ButtonCheck.setEnabled(False) + self.ui.ButtonUpdate.setEnabled(False) + + def set_update_finish_btns(self): + """ + Set button status while operations to check update of hosts data file + finished. + """ + self.ui.SelectMirror.setEnabled(True) + self.ui.ButtonCheck.setEnabled(True) + self.ui.ButtonUpdate.setEnabled(True) + + def set_fetch_click_btns(self): + """ + Set button status while `FetchUpdate` button clicked. + """ + self.ui.Functionlist.setEnabled(False) + self.ui.ButtonApply.setEnabled(False) + self.ui.ButtonANSI.setEnabled(False) + self.ui.ButtonUTF.setEnabled(False) + + def set_fetch_start_btns(self): + """ + Set button status while operations to retrieve hosts data file + started. + """ + self.ui.SelectMirror.setEnabled(False) + self.ui.ButtonCheck.setEnabled(False) + self.ui.ButtonUpdate.setEnabled(False) + self.ui.ButtonApply.setEnabled(False) + self.ui.ButtonANSI.setEnabled(False) + self.ui.ButtonUTF.setEnabled(False) + self.ui.ButtonExit.setEnabled(False) + + def set_fetch_finish_btns(self, error=0): + """ + Set button status while operations to retrieve hosts data file + finished. + + :param error: A flag indicating if error occurs while retrieving hosts + data file from the server. + :type error: int + """ + if error: + self.ui.ButtonApply.setEnabled(False) + self.ui.ButtonANSI.setEnabled(False) + self.ui.ButtonUTF.setEnabled(False) + else: + self.ui.ButtonApply.setEnabled(True) + self.ui.ButtonANSI.setEnabled(True) + self.ui.ButtonUTF.setEnabled(True) + self.ui.Functionlist.setEnabled(True) + self.ui.SelectMirror.setEnabled(True) + self.ui.ButtonCheck.setEnabled(True) + self.ui.ButtonUpdate.setEnabled(True) + self.ui.ButtonExit.setEnabled(True) diff --git a/style_rc.py b/gui/style_rc.py similarity index 94% rename from style_rc.py rename to gui/style_rc.py index af6e883..6ee4df0 100644 --- a/style_rc.py +++ b/gui/style_rc.py @@ -2,8 +2,8 @@ # Resource object code # -# Created: 周六 11月 30 15:06:56 2013 -# by: The Resource Compiler for PyQt (Qt v4.8.4) +# Created: 周三 1月 22 13:03:07 2014 +# by: The Resource Compiler for PyQt (Qt v4.8.5) # # WARNING! All changes made in this file will be lost! @@ -107,6 +107,10 @@ \x00\x71\ \x00\x73\x00\x73\ \x00\x03\ +\x00\x00\x78\xc3\ +\x00\x72\ +\x00\x65\x00\x73\ +\x00\x03\ \x00\x00\x70\x37\ \x00\x69\ \x00\x6d\x00\x67\ @@ -128,9 +132,10 @@ \x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\ \x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\ \x00\x00\x00\x0c\x00\x02\x00\x00\x00\x01\x00\x00\x00\x03\ -\x00\x00\x00\x18\x00\x02\x00\x00\x00\x02\x00\x00\x00\x04\ -\x00\x00\x00\x28\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ -\x00\x00\x00\x46\x00\x00\x00\x00\x00\x01\x00\x00\x01\x5b\ +\x00\x00\x00\x18\x00\x02\x00\x00\x00\x01\x00\x00\x00\x04\ +\x00\x00\x00\x24\x00\x02\x00\x00\x00\x02\x00\x00\x00\x05\ +\x00\x00\x00\x34\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ +\x00\x00\x00\x52\x00\x00\x00\x00\x00\x01\x00\x00\x01\x5b\ " def qInitResources(): diff --git a/theme/darkdefault.qss b/gui/theme/default.qss similarity index 98% rename from theme/darkdefault.qss rename to gui/theme/default.qss index a1c4309..7d3a06d 100644 --- a/theme/darkdefault.qss +++ b/gui/theme/default.qss @@ -113,7 +113,7 @@ QComboBox::drop-down QComboBox::down-arrow { - image: url(:/qss/img/style/down_arrow.png); + image: url(:/qss/res/img/style/down_arrow.png); } QGroupBox { @@ -280,7 +280,7 @@ QCheckBox::indicator{ QCheckBox::indicator:checked { - image:url(:/qss/img/style/checkbox.png); + image:url(:/qss/res/img/style/checkbox.png); } QCheckBox::indicator:disabled, QRadioButton::indicator:disabled diff --git a/qthosts_rc.py b/gui/util_rc.py similarity index 51% rename from qthosts_rc.py rename to gui/util_rc.py index 7c8b411..b727075 100644 --- a/qthosts_rc.py +++ b/gui/util_rc.py @@ -2,8 +2,8 @@ # Resource object code # -# Created: 周六 11月 30 15:06:55 2013 -# by: The Resource Compiler for PyQt (Qt v4.8.4) +# Created: 周三 1月 22 13:03:07 2014 +# by: The Resource Compiler for PyQt (Qt v4.8.5) # # WARNING! All changes made in this file will be lost! @@ -1735,13 +1735,13 @@ \x7f\x1d\x67\xb3\x59\xe7\x7f\xca\x1f\x01\x06\x00\x05\x3b\xea\xc8\ \xfa\x24\x85\x20\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ \ -\x00\x00\x12\x6c\ +\x00\x00\x69\xec\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x40\x00\x00\x00\x40\x08\x06\x00\x00\x00\xaa\x69\x71\xde\ +\x00\x01\x00\x00\x00\x01\x00\x08\x06\x00\x00\x00\x5c\x72\xa8\x66\ \x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\ \x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\ -\x79\x71\xc9\x65\x3c\x00\x00\x03\x69\x69\x54\x58\x74\x58\x4d\x4c\ +\x79\x71\xc9\x65\x3c\x00\x00\x03\x6b\x69\x54\x58\x74\x58\x4d\x4c\ \x3a\x63\x6f\x6d\x2e\x61\x64\x6f\x62\x65\x2e\x78\x6d\x70\x00\x00\ \x00\x00\x00\x3c\x3f\x78\x70\x61\x63\x6b\x65\x74\x20\x62\x65\x67\ \x69\x6e\x3d\x22\xef\xbb\xbf\x22\x20\x69\x64\x3d\x22\x57\x35\x4d\ @@ -1774,263 +1774,1663 @@ \x42\x46\x41\x46\x35\x41\x33\x39\x32\x45\x31\x31\x31\x41\x36\x37\ \x39\x38\x32\x45\x39\x30\x31\x31\x30\x33\x32\x43\x43\x22\x20\x78\ \x6d\x70\x4d\x4d\x3a\x44\x6f\x63\x75\x6d\x65\x6e\x74\x49\x44\x3d\ -\x22\x78\x6d\x70\x2e\x64\x69\x64\x3a\x32\x39\x39\x37\x43\x38\x33\ -\x33\x46\x32\x41\x33\x31\x31\x45\x32\x38\x38\x34\x36\x39\x36\x46\ -\x46\x33\x43\x35\x45\x44\x30\x33\x36\x22\x20\x78\x6d\x70\x4d\x4d\ +\x22\x78\x6d\x70\x2e\x64\x69\x64\x3a\x43\x41\x33\x32\x31\x42\x46\ +\x41\x35\x34\x30\x38\x31\x31\x45\x33\x41\x31\x35\x45\x45\x45\x33\ +\x31\x41\x34\x30\x39\x34\x30\x45\x31\x22\x20\x78\x6d\x70\x4d\x4d\ \x3a\x49\x6e\x73\x74\x61\x6e\x63\x65\x49\x44\x3d\x22\x78\x6d\x70\ -\x2e\x69\x69\x64\x3a\x32\x39\x39\x37\x43\x38\x33\x32\x46\x32\x41\ -\x33\x31\x31\x45\x32\x38\x38\x34\x36\x39\x36\x46\x46\x33\x43\x35\ -\x45\x44\x30\x33\x36\x22\x20\x78\x6d\x70\x3a\x43\x72\x65\x61\x74\ +\x2e\x69\x69\x64\x3a\x43\x41\x33\x32\x31\x42\x46\x39\x35\x34\x30\ +\x38\x31\x31\x45\x33\x41\x31\x35\x45\x45\x45\x33\x31\x41\x34\x30\ +\x39\x34\x30\x45\x31\x22\x20\x78\x6d\x70\x3a\x43\x72\x65\x61\x74\ \x6f\x72\x54\x6f\x6f\x6c\x3d\x22\x41\x64\x6f\x62\x65\x20\x50\x68\ -\x6f\x74\x6f\x73\x68\x6f\x70\x20\x43\x43\x20\x28\x57\x69\x6e\x64\ -\x6f\x77\x73\x29\x22\x3e\x20\x3c\x78\x6d\x70\x4d\x4d\x3a\x44\x65\ -\x72\x69\x76\x65\x64\x46\x72\x6f\x6d\x20\x73\x74\x52\x65\x66\x3a\ -\x69\x6e\x73\x74\x61\x6e\x63\x65\x49\x44\x3d\x22\x78\x6d\x70\x2e\ -\x69\x69\x64\x3a\x63\x62\x39\x36\x32\x37\x37\x63\x2d\x66\x65\x63\ -\x66\x2d\x30\x39\x34\x33\x2d\x39\x63\x33\x39\x2d\x61\x35\x36\x62\ -\x66\x66\x39\x30\x30\x61\x64\x62\x22\x20\x73\x74\x52\x65\x66\x3a\ -\x64\x6f\x63\x75\x6d\x65\x6e\x74\x49\x44\x3d\x22\x78\x6d\x70\x2e\ -\x64\x69\x64\x3a\x30\x43\x31\x42\x46\x41\x46\x35\x41\x33\x39\x32\ -\x45\x31\x31\x31\x41\x36\x37\x39\x38\x32\x45\x39\x30\x31\x31\x30\ -\x33\x32\x43\x43\x22\x2f\x3e\x20\x3c\x2f\x72\x64\x66\x3a\x44\x65\ -\x73\x63\x72\x69\x70\x74\x69\x6f\x6e\x3e\x20\x3c\x2f\x72\x64\x66\ -\x3a\x52\x44\x46\x3e\x20\x3c\x2f\x78\x3a\x78\x6d\x70\x6d\x65\x74\ -\x61\x3e\x20\x3c\x3f\x78\x70\x61\x63\x6b\x65\x74\x20\x65\x6e\x64\ -\x3d\x22\x72\x22\x3f\x3e\x48\xbe\xf1\xc7\x00\x00\x0e\x99\x49\x44\ -\x41\x54\x78\xda\xdc\x5a\x6b\x70\x5b\xe5\x99\x7e\xce\x45\xd2\x91\ -\x64\xc9\x96\xed\xc4\x8e\xe3\x6b\xc2\xa5\x04\x48\x42\x20\x4e\xb8\ -\x2c\xdd\xd2\x65\x97\x86\x26\xc4\x21\x85\x24\x04\x36\x24\x69\x68\ -\x3b\x3b\xbd\xb7\x69\x61\xb7\x3b\xf4\x4f\xdb\x9d\x94\x81\x05\x66\ -\xa7\x29\xdb\x2d\xdb\x81\x00\x25\x31\x25\x49\xc3\xad\x74\x98\xc2\ -\xd0\x86\x6d\xbb\x69\x73\xa5\x89\x43\x2e\xb6\xe5\xbb\xee\x3a\xd2\ -\x39\x3a\xa7\xef\xfb\x49\xb2\x53\x88\x65\x39\xbe\x24\xce\xf1\x7c\ -\x23\xc9\x3a\x3a\xe7\x7b\x9f\xef\x79\x9f\xf7\xf2\x1d\xc9\xb6\x6d\ -\x14\x3a\xf8\x7b\xc3\x30\x36\xd0\xeb\xb7\xe9\xa3\x86\x49\x3e\x82\ -\xc1\x60\xea\xa1\x87\x1e\x5a\xb5\x76\xed\xda\xf7\x9a\x9b\x9b\x11\ -\x8b\xc5\x0a\x9e\xaf\x28\x8a\x38\xe7\xc6\x1b\x6f\x84\xaa\xaa\x68\ -\x6b\x6b\x83\xc7\xe3\x19\xf6\x7c\x75\xa4\x09\x90\xe1\x0b\x2c\xcb\ -\x7a\x94\xde\x96\x4c\xb6\xf1\x92\x24\xc1\xe7\xf3\x61\xf3\xe6\xcd\ -\xcf\xb4\xb6\xb6\x2e\xaf\xae\xae\x3e\x38\x73\xe6\x4c\xa4\x52\xa9\ -\x61\x7f\x23\xcb\xb2\x18\xc5\x1e\xc5\x9c\x39\xeb\x7c\x18\x9f\x3f\ -\x32\x99\x0c\x6a\x6a\x6a\x2e\xbd\xe3\x8e\x3b\x76\xef\xdc\xb9\x73\ -\x4e\x5f\x5f\x1f\x34\x4d\x1b\x34\x74\xb8\xc1\xe0\x8d\x17\x00\x99\ -\xf3\x65\x3c\xbb\x1f\x1b\x42\x2e\x88\x86\x86\x86\xc6\x96\x96\x96\ -\xdd\x2f\xbe\xf8\xe2\x9c\x70\x38\x0c\xb7\xdb\x2d\x0c\x65\xca\x9f\ -\x6d\x8c\x1b\x03\x78\x12\xe7\x73\xe4\x8d\xcc\x83\xb0\x72\xe5\xca\ -\x5d\xdb\xb7\x6f\x9f\x43\xda\x00\xaf\xd7\x3b\x2c\x03\xc6\xd3\x05\ -\xce\xeb\xc1\x0c\xc8\xaf\x6a\x3a\x9d\x46\x5d\x5d\x5d\xd3\xd2\xa5\ -\x4b\x77\xbd\xf0\xc2\x0b\x73\xba\xba\xba\x84\xc0\x9d\x8d\x09\x17\ -\x0d\x00\x79\x10\x78\xb0\xa1\x0c\x42\x63\x63\x63\xd3\xea\xd5\xab\ -\x77\x6f\xdb\xb6\xed\xca\x8e\x8e\x0e\x01\x42\xfe\x9c\x33\xc7\x45\ -\x01\xc0\xd9\xe8\xcd\x51\xa0\xbe\xbe\xbe\x71\xcd\x9a\x35\xbb\x9e\ -\x7b\xee\xb9\x2b\xdb\xdb\xdb\x07\x41\xb8\xe8\x5c\xe0\x4c\x06\x7c\ -\x98\x09\xb5\xb5\xb5\x02\x84\xe7\x9f\x7f\x5e\x80\x90\x17\xc6\x62\ -\x57\x7f\xca\x68\xc0\x70\x83\x41\x60\x26\xac\x5a\xb5\x6a\xe7\x8e\ -\x1d\x3b\xae\xe0\xe8\xe0\x74\x3a\xc7\x1f\x80\xf3\x19\x05\x46\x02\ -\x81\xdd\x81\xa2\x43\xd3\x9d\x77\xde\xb9\x8b\x40\xf8\x58\x24\x12\ -\x81\xcb\xe5\xba\xb8\x18\x30\x52\xd2\x93\xd3\x84\x59\xb7\xde\x7a\ -\xeb\xee\x97\x5e\x7a\xe9\x63\xcc\x04\x4e\x83\x8b\x39\xd4\xa9\xa0\ -\x01\x6c\x24\xa5\xe3\xc3\x52\x3b\xcf\x84\xa6\xa6\xa6\x59\x14\x02\ -\x77\x51\xda\xfc\x69\x02\xe1\xb0\xdf\xef\x9f\xda\x0c\xc8\x67\x81\ -\xa6\x69\x8a\x15\xe5\xf8\x3e\x1c\x0b\xf2\xc9\x12\x85\xc8\xd9\x77\ -\xdf\x7d\xf7\xae\xc5\x8b\x17\x5f\x31\x6e\x0c\x28\x54\x31\xca\x92\ -\x0c\x33\x63\x8a\x09\x8c\x54\x59\x9e\xcb\xe1\x70\x38\x06\x2b\xc0\ -\x7c\x78\x1b\xe9\x3e\x14\x0d\x66\x6f\xdd\xba\x75\xd7\xbd\xf7\xde\ -\xfb\x77\xc9\x64\xb2\x63\x4c\xd5\xe0\x48\xd4\x8c\xc5\xa2\x90\x64\ -\x12\x24\xfa\xd3\xdc\x14\x8b\x8b\x98\x60\xb1\x87\xc5\xa5\x38\x15\ -\x43\x54\x11\x10\xc5\x75\x44\xe3\x09\x54\x04\xca\xe0\x22\xa5\x2f\ -\x74\x0f\x66\x42\x79\x79\xf9\xac\x8d\x1b\x37\xd6\x53\xe1\xd4\x31\ -\x61\x1a\xc0\x14\xd5\x75\x1d\x91\x58\x04\x8d\x0d\x8d\x08\x87\x07\ -\xe0\x2b\xf1\x8f\x99\x0d\x7c\x5d\x36\x3e\x91\x4c\x8a\xeb\xb3\xe1\ -\x3d\x03\x21\xd4\x4c\x9f\x86\xb2\xd2\xd2\x11\xc3\x5c\xfe\xde\xeb\ -\xd7\xaf\xcf\x4c\xa8\x06\x70\xa9\x3a\x7d\x7a\x15\xdd\x11\xe8\xe8\ -\xec\x40\x75\xd5\x8c\x71\x59\x7d\xbe\x44\x32\xa9\x0b\x00\x32\x24\ -\x7e\xc7\x3b\x3a\x71\x69\x63\x3d\x1a\x67\xd6\x0c\x1a\xc8\xa2\x38\ -\xdc\xc8\x87\x50\x7e\x3f\xe1\xd5\x20\x0b\x54\x53\xe3\x2c\x18\x69\ -\x03\x47\xde\x3f\x4c\xc9\x49\x4a\xb8\xc6\xb9\xc6\x7d\x5e\xdb\x14\ -\x25\x38\xf1\x64\x42\x20\xa1\xd0\xb5\xaa\x2a\xca\x11\x20\x45\x37\ -\xe8\x5e\x7c\x0e\x7f\x9f\x37\x6e\xa4\x1c\x62\xc2\xa3\x40\xfe\x66\ -\x0c\x42\x45\x79\x25\x0e\x1d\x3e\x88\x28\xb9\x04\x8b\xd7\x39\xf9\ -\x3d\x8d\xa4\x9e\x14\xc6\xcb\xb2\x22\x44\x36\xe0\xf3\x91\xd1\x86\ -\x60\x5c\x22\x91\xc4\x40\x24\x8a\xcc\x38\xe9\xcc\xb8\x84\x41\x06\ -\x80\x27\xe7\xf7\xf9\x71\xe5\x9c\xab\x71\xe0\xe0\x01\xa1\x07\x9c\ -\x96\x8e\xd6\xf7\x4d\xc3\x1c\x0c\x7b\xf9\xff\x95\x50\x8e\xcf\x22\ -\x18\x4f\x24\x10\x89\xc7\xd9\xe3\x08\x18\xe9\xc2\x01\x20\x7f\x70\ -\x38\xe4\x82\x64\xee\xd5\xf3\x05\x08\xfd\x03\x7d\x02\x84\x62\xe9\ -\xc8\xc6\x26\x88\xfa\xa2\x11\x6b\x66\x90\x26\x35\x97\xc8\x5c\xfe\ -\xe3\xf7\x31\xd2\x05\x9d\x5e\x5d\xc4\x2e\x25\xe7\x66\x93\x02\xc0\ -\x68\x7c\x98\x57\x4f\xa3\x5c\x7c\xfe\xbc\x05\x38\x78\xe8\x00\x7a\ -\x7b\xbb\x29\x6c\xb9\x46\xee\xfc\x90\xf1\xe1\x68\x14\x5d\x7d\xfd\ -\x88\xeb\x29\x38\x28\x92\x38\x54\x85\x50\xa1\x54\x97\x58\x91\x24\ -\x17\xe0\x57\x95\xfe\xcf\x8c\x28\xb6\x8e\x38\x2f\x99\x20\xbb\x03\ -\x17\x24\x0b\xe6\x5f\x87\x43\x47\x0e\xa1\xab\xbb\xb3\x60\x81\xa2\ -\x28\x32\xc2\x94\xec\x74\xf7\xf6\xc3\xe7\xf5\xc2\x47\x89\x0b\x83\ -\xc8\x42\x77\x82\x22\xc0\xb1\x93\xa7\x70\x3a\xd8\x45\xee\x91\x46\ -\x65\x59\x19\xe9\x8b\x3a\x6e\xb9\xc6\x84\xd5\x02\x0c\x02\xd3\xff\ -\xba\x05\xcd\x78\xef\xf7\xbf\xa3\x70\x96\x41\xcd\x8c\xda\x8f\xb4\ -\xb4\x39\x67\x88\x50\x32\xd5\x4d\x2b\x5f\xe2\x71\x8b\xcf\x6e\xcd\ -\x45\x54\x4f\x23\x41\x4c\xa8\x9e\x56\x41\x4c\x70\x90\x26\x28\xf0\ -\x72\xbd\xcf\x39\x42\x11\xe1\x6d\x5c\x01\x38\x57\xb4\xf3\x62\xb6\ -\xf0\xda\x45\xd8\xfb\x7f\xbf\x45\x86\xfc\xba\xae\xb6\x81\x14\x5d\ -\xa7\x55\x74\x91\x66\x28\x44\xfb\xfe\xac\xf1\xee\xac\xf1\x5e\x02\ -\x81\x69\x2f\xd1\x68\xa0\xb8\x2f\x53\x96\x99\xa5\x74\x4e\x6c\x47\ -\x61\x7c\x31\xf3\x9e\xf0\x62\x88\x99\xa0\x28\x2a\x16\x2d\x5c\x8c\ -\xb6\x0f\xda\x70\xe2\xd4\x71\x68\x5a\x89\xd0\x07\x3d\xfc\x16\x7a\ -\xfa\xc3\x43\xc6\xf3\x0a\xd3\xb9\x2c\x86\x7e\xaf\x07\x36\x19\x6b\ -\x12\x68\x99\xcc\x50\x82\x33\xde\xc7\xa4\x94\xc3\x16\xd1\x9f\x63\ -\xfa\xe2\xe6\xeb\xf1\xa7\xfd\xfb\xb1\x73\xcf\xaf\xd1\x54\xfa\x0c\ -\xe6\x55\xe9\x30\xdc\x0f\x22\x29\xdf\x4c\xc6\x5b\x90\xd5\xac\x6f\ -\xbb\xc8\xc7\x79\xc9\xed\x49\x98\xdb\xa4\x95\xc3\xbc\x82\x9a\xe6\ -\xc1\x9f\xf7\xbf\x0f\x35\xf6\x28\x5a\x16\x86\x60\x87\x75\x94\x2b\ -\xcf\x10\x03\x4c\xc8\x0e\x8f\x30\xde\xa9\x66\xeb\x08\x7b\x92\xe6\ -\x55\xcc\xde\xe0\x38\x51\x8f\x0a\x27\xd2\xbf\x1b\x2e\x7b\x0b\x73\ -\xff\x91\xf4\xc1\x9a\x05\xb3\xff\x24\x1c\xae\xbf\xc0\xe7\xf8\x31\ -\x62\xea\xd7\xe0\x74\x78\x89\xf6\xc9\xc9\x6d\xb6\x4c\xd6\x8d\x38\ -\x71\x89\x25\xa2\xa8\x68\x58\x06\x13\x0d\x24\x8e\xa7\xa0\x06\x02\ -\xb0\x52\x2a\x9c\xfa\x76\x94\x26\xbe\x0e\xc5\x38\x48\x38\x79\x04\ -\x58\x17\x15\x00\x5c\x1c\x71\x71\xd3\xd3\x1b\x84\x2e\xdd\x84\x36\ -\xf3\x31\x1c\xef\xbf\x99\x98\xd5\x03\xd9\x45\x53\xb0\x64\x28\x99\ -\x77\xe1\x8e\x3e\x00\x67\xea\x67\x70\x6a\xbe\x51\xf5\xf6\x27\x1c\ -\x80\x33\x4b\xcc\x51\xef\xed\x91\xa2\xc7\xa9\xac\xed\xa2\x24\x87\ -\x55\xde\xa1\x70\xb5\x48\x51\xa0\x7f\x1d\xba\xad\x0d\xa4\xfe\x71\ -\xc0\xe9\x87\xed\xbb\x9a\x0a\x21\x27\x5c\xa1\xef\x61\xdf\xef\xfe\ -\x87\x42\xa4\x43\x14\x54\x63\xed\x28\x9f\x37\x06\x88\x3d\x3d\xb1\ -\xf2\x49\x11\xe7\xbd\x6e\x2d\x97\xe4\x68\x28\x0f\xf8\xb0\x60\x4e\ -\x2d\xbc\x55\x9f\x43\x5a\x6e\x86\xa4\x4a\xc8\x44\x3a\xa1\x9f\xda\ -\x47\x61\x30\x85\x3f\xfe\xea\x41\x3c\xf4\xaf\x0f\xa3\x33\x18\x1c\ -\xdc\xfb\x9b\x32\x2e\x90\xef\xd7\x73\xfc\x0f\xe5\x72\x7b\xaf\xa6\ -\x0d\x26\x39\x1c\xe7\xf9\x3b\xaf\x9b\xca\x65\x5b\x42\xca\xf3\x39\ -\xd8\x7a\x08\xae\xe4\x01\xb8\x4a\x3c\xd0\x13\x2e\x2c\x5f\x12\x40\ -\xba\xf3\xa7\x78\xe0\xf3\x5f\x47\x6b\xeb\x0e\xb1\x9a\xe7\x5a\x5e\ -\x4f\x5a\x31\x24\xfa\xf7\x64\xa4\x49\x49\x4b\x9c\xaa\xb6\xfc\x50\ -\x45\x51\xa3\xc2\x4b\x89\x8d\x4c\x29\x2d\xbb\x93\x43\x96\x44\x72\ -\x63\x5b\x09\x98\xea\x7c\x24\x4a\xfe\x03\x7b\x0f\x57\xc0\xd2\xbc\ -\xd8\xfe\x2b\x07\xee\xfa\x66\x25\xee\xba\xa3\x1c\x15\xca\xef\xf1\ -\xfd\x2d\x4f\xe2\x87\x8f\x6c\xa1\xf2\x3a\x34\x58\x59\x5e\x70\x2e\ -\xc0\x49\x8e\x45\xf7\xe3\xde\x5d\x84\x8a\x1a\x23\x9d\x66\xd8\xe0\ -\xa6\x82\xc6\x5f\xe2\x15\xc6\xf3\x74\x32\x94\x1a\x3b\x14\xe9\x6f\ -\x63\x3c\x85\x3d\xc5\x7b\x0b\xf6\xf5\x7e\x0b\x1b\xbe\x61\x21\xe0\ -\x24\x36\x48\x61\x7c\xf7\xa9\x0a\xac\x5c\xe2\xc1\x4c\xdf\x69\xec\ -\xf8\xc5\xeb\xd8\xf2\x43\x06\x21\x3c\xea\x1e\xc3\x84\x03\x20\xf6\ -\xe8\x4c\x03\x51\x32\x9c\x9b\x16\x8a\xd8\xcf\xe7\x56\x79\x46\x0c\ -\x85\xc0\x89\x27\x92\x08\x85\x23\x94\xe1\x29\x38\xdb\xc2\x18\xe9\ -\x08\xd6\xde\xd3\x82\xe9\x97\xaf\xc5\xa3\xcf\xba\xb1\xee\xb6\x2e\ -\x78\x94\x01\x3c\xfe\xf3\x69\xb8\xeb\x36\x27\x6a\xfd\xdd\x78\xf5\ -\xf5\x77\xf0\xf8\x13\xff\x89\x34\x15\x53\x9c\x5a\x5f\x10\x2e\x20\ -\x26\x4f\xb9\x3a\xaf\x7a\x67\x4f\x2f\x22\xd1\x18\x81\x61\x8a\x8e\ -\xae\x83\x26\xc9\x35\x3d\x47\xf4\x8e\xee\x1e\xa1\x03\xf9\x82\xe6\ -\xc3\x23\x9f\xeb\x6f\xda\xb0\x1a\x72\xe9\x3c\xfc\xa8\xb5\x0c\x2d\ -\x37\x06\x89\x09\xfd\x78\x6a\x77\x15\x56\xdf\xee\x40\x43\xa0\x1b\ -\xad\xbf\x78\x03\x3f\xfd\xdf\xa7\x45\x65\x58\xac\x6b\x4e\x38\x03\ -\x74\xa2\xbb\x9e\x4a\xa3\xba\xb2\x42\x54\x6f\x1e\x32\x94\xf7\x08\ -\x38\x02\xb0\xef\x87\xc9\x2d\xd8\x38\xaf\x47\x2b\x58\xc6\x66\xb7\ -\xb6\x66\x61\xdd\xda\xcf\xc0\x50\x67\xe0\xe9\x57\x2a\xb0\x64\x61\ -\x37\x34\xb9\x1f\x3f\xd9\x53\x85\xfb\x96\xbb\x70\x75\x6d\x10\x3f\ -\x7a\xea\x39\xbc\xf6\xfa\x1b\x94\x56\xbb\xcf\xbf\x0b\x30\xc2\x1e\ -\x97\x13\x35\xd3\x2a\x51\xea\x2b\xc9\x7e\xa6\x70\xc7\x83\x97\x3e\ -\x65\x18\xa2\x95\xc5\x1d\xdd\x62\xb6\xac\x79\x0f\x60\xc9\x92\xdb\ -\xb1\x72\xc5\xed\x30\xe5\x0a\x3c\xff\x66\x00\xcb\x9a\xbb\xe0\xb0\ -\xfb\xf1\x44\x6b\x15\x36\xae\xf6\x61\xd9\xc2\x0f\xf0\xc8\x96\x47\ -\x70\xf2\xe4\xe9\x71\x89\x0c\x63\x6e\x8b\xb3\x61\xf9\xa6\x68\xfe\ -\x55\x6c\x8c\x90\x2a\x8a\x16\x16\xd1\xb5\xc4\xeb\xa6\xd5\x1f\x99\ -\xae\xf9\x6b\xac\xbf\x7f\x03\x56\xdd\xbd\x1c\x86\x52\x89\x6d\x6f\ -\x96\xe1\xbe\x5b\xbb\xe1\x56\x43\xf8\xce\xd6\x4a\xdc\xb9\x7c\x06\ -\x1e\xfb\xea\x3e\x94\xa8\x7f\x14\xc9\xd2\x05\xd3\x16\x1f\x4c\x7e\ -\xf8\x61\x26\x5a\xf5\x38\x51\x9a\x9b\x17\x5e\xb7\x07\x5a\xae\x27\ -\x58\x6c\x13\x85\xaf\xb1\xe9\xb3\x0f\xe0\xf3\x9b\xd6\x21\xa5\xd4\ -\x60\xdb\xab\x1a\xfe\x6d\x5d\x3f\x1c\x08\xe3\xbf\x5b\x5d\xb8\x66\ -\xb1\x07\x15\xa5\xfd\x74\xfd\xb1\xd7\x0c\xea\x78\x00\x70\xe6\x76\ -\x96\x4e\x25\x5f\x82\xa8\xcc\x3e\x50\xea\xf7\x51\x28\x74\x12\xca\ -\x59\xa1\x43\x2e\x00\x16\xc2\x82\x4d\xe2\xbd\x3d\x06\x61\xcd\x9a\ -\x7b\xd0\xd0\xd8\x80\xef\xfd\xe0\xbf\xb0\xe7\xad\xdf\xa2\xf5\xb1\ -\x30\xad\x7a\x8c\xe2\xe9\x4c\xe8\x8e\x85\x14\x42\x53\x63\xae\x9b\ -\xc6\xdc\x12\xe3\x89\x3a\x28\x36\x33\xc5\x8f\x1c\x6d\x43\x4c\x4f\ -\x8a\xfd\xbb\x24\xa5\xc0\xe1\xb6\x08\xca\xc9\xff\xcb\x2b\x02\xe2\ -\x7f\x4e\xa7\x43\xd4\x06\x3c\x78\x43\x35\x8f\x86\x70\xa3\x1c\x30\ -\x96\x6d\x09\xf7\xe1\xf7\xb1\x84\x8e\xd9\x97\x5c\x81\x8d\x9f\xbd\ -\x1f\x6f\xbe\x62\x93\x2e\x04\x21\x53\xa1\x14\xb4\xbe\x42\xa2\x3a\ -\x8f\x0a\xa8\x04\x38\xe5\x18\xd3\x3e\xe4\x48\x3f\x4e\xa7\xd3\x2d\ -\xe1\x50\x68\xc7\xd9\x44\xcc\xc6\xdf\x3e\xbe\x22\xf6\xf3\x68\xf5\ -\xb9\x0f\xc8\xdb\x57\xdc\xd2\xe2\x9d\x5c\x7e\xb4\x95\x13\x18\x65\ -\x30\xa7\xb7\x3f\xc4\x9e\xdc\xda\x4b\x43\xa0\xf0\x26\x19\x47\x0d\ -\x06\xd2\x14\xa1\x55\x26\xd0\x18\x2c\x15\x9a\xcb\x43\x39\x45\xbe\ -\x40\xb3\x30\x5c\xf7\xa4\x2c\x10\x68\xa6\xb9\xbc\x37\x26\x06\x38\ -\x55\xe8\x4e\xc5\xc6\xf0\x22\x3e\x44\x6b\xbf\xc7\x81\x52\xaf\x4b\ -\xfc\x4f\x92\xbc\x43\x1a\xc1\x2b\x0a\x13\xb6\x55\xf0\x12\x1f\x9d\ -\x1c\xdd\xd3\xe5\xe5\x87\x9e\xb4\x9c\xd8\x66\x6f\x64\xd9\x46\x4e\ -\x80\x0b\xb7\x0e\x54\x55\x4a\x9e\xb3\x0b\x1c\xed\xcf\x2c\x7a\xf1\ -\x60\x7a\xb3\x82\x4c\x8d\x61\x38\x47\xd5\xa4\x48\x9a\x9c\x08\x51\ -\x1e\x40\x2b\x66\x58\x3c\x24\x52\xf1\xe1\x0d\x3d\x53\x00\xd8\x48\ -\xfa\x39\xfd\xb6\xd0\xf9\xc5\x3d\x09\xaa\x38\xf4\xc7\x03\x6e\xe5\ -\xe8\xfa\x6b\xb4\x7f\x51\x65\xa4\x46\x05\x40\x77\xc2\xae\x7f\xe5\ -\x58\xba\x45\x4c\x5c\x2a\xfe\xd1\x53\x86\x69\xd9\x25\x32\xfe\xd4\ -\xa9\xe3\x40\xd8\x89\xb9\xd3\x64\xcc\x29\x33\xf0\xf2\x31\xb2\x46\ -\x76\x90\x72\xdb\xe2\x24\x35\x87\x27\x7f\xcc\xf0\x57\x52\x7e\x48\ -\xa8\xd6\x4c\xb4\x27\x24\xd1\x1a\x37\xad\xac\x67\x28\xe7\x20\x76\ -\x46\xc6\xfc\xfb\x19\x3e\x6b\xde\x3f\xcf\xd7\xbe\x4c\x66\xa4\x46\ -\x1b\x06\xcd\x4c\x6e\x35\x4c\x9a\x65\xa1\xc1\x3b\xb5\x3c\x4c\x41\ -\x4f\x60\xc5\xa5\x12\x6a\x1c\x54\x0a\xc7\x6d\xd4\xf9\x80\xe5\x8d\ -\x69\xf1\x4c\x5f\xd4\x00\x02\x6e\x09\x25\x0e\x1b\x91\xb4\x2d\x98\ -\xc2\x5e\x51\xeb\x97\xa1\x12\x5b\x42\x34\xc5\x7f\x9a\xa5\xe0\x0b\ -\x57\xe9\xc2\xef\xf5\x0c\x50\xed\x93\xe1\x23\x02\xf2\xfb\x2c\x58\ -\xb9\x7b\x59\x45\x0c\x21\xaa\x88\x15\xe2\x92\x5a\xc8\xfa\x68\x8a\ -\x0b\x1a\xe9\x23\x2b\x3c\x24\x5c\x79\xe5\xe6\x6d\x6d\x5b\xac\x16\ -\xdf\x6a\x80\x3c\xaf\xb6\xd4\x89\xf9\xd3\x80\x2a\xb7\x4d\x9f\x2d\ -\x1c\xed\x35\xf1\xd5\x9b\x80\x4f\xd5\x26\x45\x4f\xe0\xc9\x3f\x64\ -\xf0\xfa\x29\x19\x8f\x7d\x42\x12\x85\xd4\xf4\x52\x0f\xbe\xfb\x76\ -\x0a\xd7\x54\x78\x71\xd9\x74\x0d\x57\x68\x7d\x98\xdf\x54\x89\x19\ -\x94\x00\x55\xfa\xdd\x78\xe1\xa0\x81\x3d\xa7\x35\x78\x1d\x43\x4c\ -\x91\xce\x70\x9b\xb3\x49\x89\x41\xf3\x49\xb8\x0a\xbb\xde\xb0\x00\ -\xf0\x56\x16\xab\x3a\x1c\x43\x37\x82\x9d\xdd\xbf\xcf\x58\x59\x63\ -\x8d\xdc\x2b\x7f\xb6\x04\x53\xb2\x54\x8d\xa7\xbc\xb8\x61\x26\x50\ -\xef\xee\x43\x5d\xa5\x07\x3d\x51\x13\x97\x95\x1a\xf8\x4c\x63\x1c\ -\x4b\x9f\x6e\xc7\x55\x8d\xd3\xf1\xf0\xcd\x4e\xb4\xf7\x45\x09\x24\ -\x3f\x9e\xe9\x94\xb0\xf7\xd4\x00\x0e\xb4\x9b\x78\xed\xa8\x0a\x2b\ -\x16\x47\xeb\x11\x03\x5f\xba\x29\x83\x7d\xed\x12\x76\x1f\x8a\x63\ -\xef\xf1\x38\x82\x89\x52\xb8\x94\x2c\x00\x7c\x1f\x85\xde\x70\x6f\ -\x81\xfc\x9b\x86\x24\xfe\x9f\x9f\xa7\x2d\x5c\x80\x58\xe6\x52\x91\ -\x6d\xb2\x4b\xa3\x04\x20\xc3\x00\xc4\x21\x99\x92\xa0\x9c\x91\xc9\ -\x5e\x50\x50\xde\xca\xae\x78\x1e\x59\xe9\x0c\x7f\xb6\xe9\xbd\x93\ -\x72\xb6\x2d\x6f\x76\xe2\xc7\xfb\x14\x3c\x70\xbd\x8d\xfb\xaf\x92\ -\xe1\x97\x75\x02\xc6\x81\xfd\x03\x32\x2c\x8f\x01\x0f\x95\xc9\xc7\ -\x83\x03\xd8\xfc\x4a\x1a\x9f\xac\x97\xb0\x6c\x51\x19\xba\x93\x61\ -\x84\x13\x29\x24\xd3\xa4\x01\x31\x13\x9b\xf7\xf4\xe0\xb6\x46\xe0\ -\xae\x2b\xfd\x98\x5d\x2e\xe1\x0b\xbf\x0c\xc1\xa6\x5c\x42\xca\x99\ -\x34\x74\x7f\x49\x00\xc2\x40\x70\xbf\x81\x07\x83\xc3\x2c\x4e\xb9\ -\x1c\xe7\x16\x05\xb8\x79\x11\x89\x84\x90\xa0\x0b\x19\xb9\x15\xe6\ -\x1b\x16\xaa\x69\xf2\x00\x28\x19\x0f\x24\xcb\x40\x16\x29\x32\x96\ -\x7e\xf4\xce\xd1\x3e\x9c\xbc\x56\xc3\xcf\x96\xfb\x30\xbd\x44\xc5\ -\x8e\xf7\x4e\xd0\x3d\x6c\xac\x9a\x13\xc0\xcb\xb4\xc2\x97\x54\x10\ -\xd8\xd1\x4e\x02\xa1\x1c\x0b\x08\x8c\xb5\xb3\x43\xf8\xd4\xe5\x2e\ -\xbc\xdf\x63\xe0\x60\x30\x01\x35\x36\x80\x74\x22\x0d\x39\x73\x76\ -\x4e\x1b\xf9\x0c\x53\xca\x0a\x9b\x9a\x53\xcd\x4a\xa7\x36\xf4\xc5\ -\x68\x00\xe0\x7c\x3e\x3c\x10\x82\xcb\x25\xe7\x7e\x5a\x9c\x0c\xb3\ -\x8b\x7c\xf1\xe7\x61\x1c\x1f\x48\x13\x0f\x34\xbc\x71\x20\x86\x63\ -\x6d\x29\xf4\xc6\x14\xac\x7e\xb6\x0d\x2d\x97\xaa\x22\xc3\x7b\xe9\ -\x2f\x26\xe5\xf9\x1e\x7c\xe7\x97\x1f\xe0\xfa\x1a\x19\x0f\xbf\x7c\ -\x0a\xaf\x9d\x04\x15\x4e\x16\xbe\x6d\xf4\xe1\xfd\xce\x38\xde\x3e\ -\x1e\xc1\xed\xb3\x1d\xd8\x79\x2a\x85\x5d\x47\xd3\x62\xcf\xc0\x48\ -\xa6\x8a\xc9\x5d\x85\xe4\xa7\x09\xe0\x90\xd3\x53\x30\xfa\xaa\x85\ -\xf6\xf3\x60\xe8\x48\x59\x02\x8d\x51\x85\x9f\x37\x7a\x6d\x76\x50\ -\x1a\x3a\x8e\x45\x80\x63\x7c\x0d\xd2\x92\x9e\xa8\x8d\xad\x1d\xd9\ -\x30\x98\x0d\xf4\x21\xbc\x3b\x00\xbc\x7b\xc0\xe2\xbe\x9a\x38\x67\ -\x80\xc0\xf9\x49\xbb\x95\xe5\xb3\x94\xc2\x93\x27\x72\x71\xd0\x99\ -\x3d\x7f\x54\x33\x21\xb1\x4d\xa7\x0b\x3f\x49\x32\x3c\x00\x7a\x54\ -\xc6\xe9\xfd\xa8\xae\xaf\xc2\x75\x73\x9b\x84\x26\x4c\x99\x23\x17\ -\x21\x7e\xf3\x87\x23\x48\x07\x55\x2a\x45\x6f\x1e\x3d\x00\x8a\x1e\ -\xda\x8f\xff\x7f\xf5\xc4\xb5\x97\xdc\xd2\xb0\x63\x53\x8b\x78\x3e\ -\x67\xea\xd8\x9f\x15\xc1\x45\x6f\xb7\x22\x78\x3a\xb6\x47\xb2\xd7\ -\xe9\xa3\x06\xe0\x1f\x16\xcd\x3d\x72\xcf\x86\x15\x4b\x4f\x06\xbb\ -\x5b\x63\x29\x73\x76\x3c\x95\x99\x52\x00\x08\xaf\x71\x69\xcf\x3e\ -\xf1\xe0\xea\x4d\x9a\xcb\x69\x8d\xba\x21\xe2\xd1\x9c\xf8\xc1\x57\ -\xee\xfd\x73\x55\x45\xd9\xbf\xa7\x0d\x13\x53\xed\xb0\x44\xd8\xc2\ -\x23\x37\xcc\xbb\x3c\x5d\x28\x72\x15\xac\x06\x03\x7e\x2f\x56\xdc\ -\xd2\x1c\xe5\xa7\x34\x26\xe2\xe9\x8c\x89\x3c\x78\xbe\xf7\x7d\xfa\ -\xe3\x92\x57\x73\x8d\xad\x1f\x00\x2b\xb3\xe2\xe4\xe9\xf6\xed\x92\ -\x24\x4d\x39\x16\xd4\xd5\xd5\x2d\xa2\x97\xbd\x63\xea\x09\x26\x53\ -\xa3\x8c\x81\x17\xd0\x41\x05\xd8\x88\x73\x1f\xb1\x21\x22\xcb\xf2\ -\x3b\xf4\xf2\x16\x31\xe5\xe3\x53\xc9\x03\x88\xb1\xad\x34\xf7\xc3\ -\x23\x9d\xf8\x57\x01\x06\x00\x89\x1b\xb8\x83\xb7\x34\x04\xe3\x00\ +\x6f\x74\x6f\x73\x68\x6f\x70\x20\x43\x43\x20\x28\x4d\x61\x63\x69\ +\x6e\x74\x6f\x73\x68\x29\x22\x3e\x20\x3c\x78\x6d\x70\x4d\x4d\x3a\ +\x44\x65\x72\x69\x76\x65\x64\x46\x72\x6f\x6d\x20\x73\x74\x52\x65\ +\x66\x3a\x69\x6e\x73\x74\x61\x6e\x63\x65\x49\x44\x3d\x22\x78\x6d\ +\x70\x2e\x69\x69\x64\x3a\x39\x31\x66\x35\x61\x38\x61\x34\x2d\x36\ +\x31\x62\x36\x2d\x34\x66\x34\x62\x2d\x62\x38\x61\x38\x2d\x32\x31\ +\x61\x66\x32\x62\x30\x37\x63\x64\x31\x38\x22\x20\x73\x74\x52\x65\ +\x66\x3a\x64\x6f\x63\x75\x6d\x65\x6e\x74\x49\x44\x3d\x22\x78\x6d\ +\x70\x2e\x64\x69\x64\x3a\x30\x43\x31\x42\x46\x41\x46\x35\x41\x33\ +\x39\x32\x45\x31\x31\x31\x41\x36\x37\x39\x38\x32\x45\x39\x30\x31\ +\x31\x30\x33\x32\x43\x43\x22\x2f\x3e\x20\x3c\x2f\x72\x64\x66\x3a\ +\x44\x65\x73\x63\x72\x69\x70\x74\x69\x6f\x6e\x3e\x20\x3c\x2f\x72\ +\x64\x66\x3a\x52\x44\x46\x3e\x20\x3c\x2f\x78\x3a\x78\x6d\x70\x6d\ +\x65\x74\x61\x3e\x20\x3c\x3f\x78\x70\x61\x63\x6b\x65\x74\x20\x65\ +\x6e\x64\x3d\x22\x72\x22\x3f\x3e\x64\xc0\x1f\x2c\x00\x00\x66\x17\ +\x49\x44\x41\x54\x78\xda\xec\x7d\x07\x80\x1d\x75\xb5\xfe\x37\x73\ +\x7b\xdd\xbb\x7d\x37\xbd\x07\x12\x52\x08\x81\xd0\xa4\x13\xaa\x48\ +\x49\xa4\x17\x69\x02\x8a\xe2\x53\xf9\x8b\xa0\xf2\xb0\x20\x8a\xcf\ +\xf7\xf4\xd9\x50\x9f\x0a\x22\x9d\xa0\x14\xe9\x45\x29\xd2\x09\x25\ +\x05\x42\xfa\x6e\xb6\xef\xbd\x7b\x7b\x99\x99\xff\x39\x67\xe6\x6e\ +\x49\x93\x96\x6c\xc9\xef\xc0\xe4\xee\xde\x3b\xf7\xee\xcc\xdc\x39\ +\xdf\xf9\x4e\xf9\x9d\xa3\x59\x96\x05\x25\x4a\x06\x43\x0e\x3b\xec\ +\x30\x3c\xf5\xd4\x53\xbb\xdc\x79\xf3\x39\x1f\x72\xc8\x21\x43\xe2\ +\x58\xdc\xea\x36\x54\x32\x58\xd2\xdd\xdd\x8d\xda\xda\x5a\xdc\x78\ +\xe3\x8d\x35\x9d\x9d\x9d\x99\x1f\xfd\xe8\x47\x99\x96\x96\x96\x01\ +\xfb\x5c\x75\xd5\x55\x98\x3b\x77\x2e\x72\xb9\xdc\xb0\x3b\x3f\xbf\ +\xdf\x8f\xef\x7c\xe7\x3b\x58\xb1\x62\xc5\x80\xe7\x03\x81\xc0\x90\ +\x39\xc6\x21\x07\x00\xf1\x78\x1c\x6f\xbc\xf1\x86\x7c\xe1\x7b\xed\ +\xb5\x17\xdf\x20\x7e\xd3\x34\xab\xe9\xa5\x1a\xda\x82\xb4\x79\x69\ +\xd3\x94\xfa\x0c\x7f\xd9\x67\x9f\x7d\x90\xcd\x66\x23\xc7\x1d\x77\ +\xdc\x1e\xa4\xf8\x6f\xdd\x77\xdf\x7d\x8f\x13\x28\x88\xa6\xe7\xf3\ +\x79\xd9\x67\xdf\x7d\xf7\xc5\xc2\x85\x0b\x91\x4e\xa7\x87\xdd\xf9\ +\x85\x42\x21\xfc\xec\x67\x3f\xdb\xe2\xf9\x17\x5e\x78\x01\xd3\xa7\ +\x4f\x47\x2c\x16\x53\x00\xb0\xb9\xbc\xf6\xda\x6b\x38\xfc\xf0\xc3\ +\x31\x7e\xfc\x78\xdc\x71\xc7\x1d\x7a\x65\x65\xe5\x78\x02\x83\xc5\ +\x9a\xa6\x9d\xac\xeb\xfa\x74\x06\x01\xfa\x59\x69\xcf\x08\x90\xdf\ +\xfc\xe6\x37\x02\xf4\x1d\x1d\x1d\x08\x87\xc3\xef\x90\xa5\xbf\x64\ +\xe5\xca\x95\xaf\xa6\x52\x29\xd2\xff\xbc\xc9\xfb\xf0\xeb\xac\xfc\ +\xc3\x11\x00\x58\x0c\xc3\xd8\xe2\xb9\xaf\x7c\xe5\x2b\x98\x35\x6b\ +\x96\xdc\xe7\x83\x2d\xfa\x50\xbb\x60\x1c\x93\xf0\xf9\x7c\x78\xf0\ +\xc1\x07\x7d\x0b\x16\x2c\x38\x90\x6e\x80\x2f\xd0\xd3\x27\x91\xd2\ +\x4f\xa4\xcd\x4f\x9b\x0a\x5a\x8c\x10\xc9\x64\x32\xe8\xe9\xe9\x01\ +\x31\x3c\xfe\xde\x77\x3f\xf5\xd4\x53\x7f\x70\xd8\x61\x87\x1d\x44\ +\xca\xee\x25\x9a\xec\xe2\x7d\xbc\x5e\xef\x88\x3c\x77\x97\xcb\xa5\ +\x5c\x80\xad\x09\x23\xfe\x0d\x37\xdc\xa0\xcf\x9c\x39\x73\x36\xdd\ +\x08\x5f\x72\xbb\xdd\x9f\x76\x68\xbf\x92\x11\x26\x0c\xf6\xac\xfc\ +\x72\x23\xba\xdd\xfa\x98\x31\x63\x3e\x75\xde\x79\xe7\x7d\xd3\xe3\ +\xf1\x58\xf7\xdf\x7f\xff\xb3\xe4\x1e\x64\xd7\xad\x5b\x67\x1d\x75\ +\xd4\x51\x02\x16\xc3\x4d\x98\xa9\x0e\x75\xb6\xaa\x0f\xc5\x83\x9a\ +\x30\x61\xc2\x34\x7a\x38\x91\x28\xff\xd1\x4a\xf9\x77\x1d\x30\x60\ +\x65\xd9\x7d\xf7\xdd\x0f\xba\xe4\x92\x4b\xbe\x73\xf0\xc1\x07\xef\ +\x53\x53\x53\x13\xf8\xc6\x37\xbe\xa1\xdd\x79\xe7\x9d\x20\x57\x10\ +\x2a\x63\xb5\x8b\x00\x40\xb1\x58\x3c\xd1\xa1\xfd\x21\xf5\x15\xed\ +\x02\x37\xa1\xae\xf7\x5a\x4b\xfe\xb9\xb1\xb1\x71\xff\x2b\xaf\xbc\ +\xf2\xfb\x8b\x16\x2d\x3a\x98\x18\xa1\xe7\xa2\x8b\x2e\xd2\x96\x2c\ +\x59\x82\xea\xea\xea\x01\x96\x75\x38\x6c\x43\x5d\xdc\x43\xf4\x86\ +\x38\x84\x1e\xa6\x2b\xd5\xd8\x35\x99\x00\xb9\x00\x20\x77\x60\xff\ +\x53\x4e\x39\xe5\xca\x52\xa9\x64\xdc\x7a\xeb\xad\xff\xfc\xdc\xe7\ +\x3e\xc7\xd9\x01\xeb\xe4\x93\x4f\x96\xf4\xa1\x92\x11\x0c\x00\xb0\ +\xd3\x7d\xba\xfa\x7a\x76\x0d\xd9\x9a\xa5\xe4\xe7\x76\xdb\x6d\xb7\ +\x43\xce\x3f\xff\x7c\xcf\xc6\x8d\x1b\xbf\xf1\xf4\xd3\x4f\xbf\x7a\ +\xe1\x85\x17\x72\x6e\xd0\x24\x60\x40\x57\x57\xd7\x90\xb7\xb0\xc3\ +\x81\x01\x0c\x55\x25\x53\xce\x9e\x02\x05\x71\x07\x46\x8f\x1e\x7d\ +\xc0\x35\xd7\x5c\xf3\x7d\xb2\xfc\xfb\x16\x0a\x05\xf7\x05\x17\x5c\ +\xa0\xdd\x7b\xef\xbd\xa8\xaa\xaa\x52\x17\x69\x04\x33\x00\x25\xbb\ +\x38\x03\x28\x0b\xbb\x03\x13\x27\x4e\x3c\xe8\xec\xb3\xcf\xbe\x8a\ +\x00\xe0\x7b\x0f\x3e\xf8\xe0\xcb\x65\x77\x80\x99\xc0\x50\x76\x07\ +\x14\x03\x50\xa2\xe4\x13\x88\x09\xb0\x22\xcd\x9e\x3d\xfb\xc8\x2b\ +\xae\xb8\xe2\x3f\xf7\xd9\x67\x9f\x39\xf4\xb4\x97\x98\x80\xce\x4c\ +\x80\xb3\x03\x4a\x14\x00\x28\x19\xc6\xd6\xff\x83\x44\xd2\xd9\x1d\ +\x18\x37\x6e\xdc\xc1\xdf\xff\xfe\xf7\x7f\xf0\xd9\xcf\x7e\x76\x3f\ +\x62\x03\x2e\x62\x02\x3a\x67\x07\xd8\x1d\x50\x99\x00\xe5\x02\x28\ +\x19\x81\x2e\x40\x7f\xf1\x7a\xbd\xda\xe4\xc9\x93\x0f\x3d\xe7\x9c\ +\x73\x8a\xf4\xeb\xf5\x77\xdd\x75\xd7\x8b\xf4\x73\x8e\x58\x82\xb5\ +\x68\xd1\xa2\x21\x17\x18\x54\x2e\x80\x12\x25\x3b\xc0\x1d\x98\x35\ +\x6b\xd6\x42\x76\x07\x0e\x3a\xe8\xa0\x3d\x43\xa1\x90\xef\xa2\x8b\ +\x2e\xd2\x09\x0c\x54\x60\x50\x01\x80\x92\x91\xea\x06\xf4\xa7\xd3\ +\x1c\x18\x6c\x6c\x6c\xfc\xd4\xb5\xd7\x5e\xfb\xbd\xe3\x8e\x3b\x6e\ +\x41\x36\x9b\x75\x9d\x7f\xfe\xf9\x7a\x39\x3b\xa0\xe8\xff\x08\x71\ +\x01\x54\xe9\xe7\xc8\xb7\xe8\x1f\x85\x2a\x3b\x0b\xc6\xb4\xa9\x53\ +\xa7\xb2\x3b\x90\x8b\xc7\xe3\xdf\x7d\xf4\xd1\x47\x5f\xa7\x9f\xb9\ +\x4e\x60\xc8\xb8\x03\xca\x05\x50\xa2\x64\x07\x01\x7c\xd9\x1d\xd8\ +\x73\xcf\x3d\x8f\xb9\xfa\xea\xab\xaf\xdb\x7b\xef\xbd\x67\xea\xba\ +\xee\x3d\xef\xbc\xf3\xf4\xbb\xef\xbe\x5b\xb9\x03\x0a\x00\x94\x8c\ +\x44\x17\x60\xf3\xcd\xed\x76\x63\xd2\xa4\x49\x47\xdc\x70\xc3\x0d\ +\x3f\xf8\xcc\x67\x3e\xb3\x57\xa1\x50\xd0\x89\x09\xe8\xf7\xdc\x73\ +\xcf\x90\x71\x07\x94\x0b\xa0\x44\xc9\x0e\xa2\xca\xcc\x04\xb8\x67\ +\xc0\x8c\x19\x33\x16\x9e\x7b\xee\xb9\x05\xfa\xfd\x07\x8f\x3d\xf6\ +\xd8\x1b\x67\x9d\x75\x96\x14\x0b\x2d\x5e\xbc\x78\xd0\xdc\x01\x15\ +\x03\x50\xa2\x64\x27\x29\x0a\x7f\xc6\x3e\xfb\xec\x73\x7c\x5d\x5d\ +\x5d\xa0\xad\xad\xed\xaa\x57\x5e\x79\xe5\x4d\x02\x84\x22\x3d\x6f\ +\x0e\x16\x08\xa8\x18\x80\x12\x25\x3b\xd3\x9a\x91\x3b\x30\x66\xcc\ +\x98\x43\xaf\xbf\xfe\xfa\xef\x9f\x71\xc6\x19\xfb\x95\xdd\x01\x95\ +\x22\x54\x0c\x40\xc9\x10\xf7\xff\x3f\x09\x61\x77\xc0\xef\xf7\xeb\ +\xd3\xa7\x4f\x3f\xf2\x94\x53\x4e\x29\x90\xd5\xcf\x3f\xf1\xc4\x13\ +\x4b\xc9\x1d\xc8\x72\x2b\xb9\x9d\xcd\x04\x94\x0b\xa0\x44\xc9\x4e\ +\x96\x72\x76\x60\xaf\xbd\xf6\x3a\xae\xba\xba\x3a\xd0\xd4\xd4\x74\ +\xe5\x6b\xaf\xbd\xf6\xd6\xd9\x67\x9f\x5d\x32\x49\x4e\x3d\xf5\x54\ +\xd5\x4f\x40\xb9\x00\x4a\x46\xba\x70\xb1\xd0\xb8\x71\xe3\x0e\xbd\ +\xe1\x86\x1b\xbe\x7b\xf2\xc9\x27\xef\xcd\xee\xc0\xb9\xe7\x9e\x2b\ +\xee\x80\x6a\x2f\xa6\x18\x80\x92\x21\xe6\x06\xec\x08\x26\xc0\xc5\ +\x42\xb3\x67\xcf\x3e\xe6\x82\x0b\x2e\xb0\x7a\x7a\x7a\xfe\xf3\x9f\ +\xff\xfc\xe7\x9b\x67\x9e\x79\x66\x7e\x67\xb9\x03\xaa\x29\xa8\x12\ +\x25\x43\xc0\x1d\xd8\x73\xcf\x3d\x8f\xbd\xee\xba\xeb\x7e\x30\x7f\ +\xfe\xfc\x59\x4c\x0e\xce\x3a\xeb\x2c\x15\x18\x54\x00\xa0\x64\x57\ +\x01\x01\xc7\x1d\x38\xec\xfb\xdf\xff\xfe\xf7\x88\x01\x48\x76\xe0\ +\x8c\x33\xce\xe8\x75\x07\x94\x0b\xa0\x44\xc9\x08\x73\x01\xb6\xe6\ +\x0e\xec\xb6\xdb\x6e\x0b\xcf\x3e\xfb\x6c\x83\xe4\xfa\x7b\xef\xbd\ +\xf7\x55\x02\x81\x2c\x9c\x62\x21\x1e\x49\x37\xdc\xce\x4b\x31\x00\ +\x25\x4a\x3e\x04\x08\x70\x53\x91\x59\xb3\x66\x1d\xf5\xb5\xaf\x7d\ +\xed\xba\x03\x0f\x3c\x70\xb6\xcb\xe5\xf2\x96\xdd\x01\x9e\xd3\xb7\ +\x2b\x06\x06\x15\x00\x28\xd9\xa5\x84\xeb\x04\x1a\x1b\x1b\x0f\xfe\ +\xf6\xb7\xbf\x7d\xed\x79\xe7\x9d\x27\x9d\x85\x88\x09\x68\xbb\xaa\ +\x3b\xa0\x5c\x00\x25\x23\xde\x05\xd8\x9c\x09\x10\x08\x68\x33\x67\ +\xce\x3c\x8a\xa8\xbf\xd9\xda\xda\xfa\xdd\xa7\x9e\x7a\xea\x0d\x02\ +\x81\x1c\x67\x07\x78\x29\x71\x22\x91\x50\x2e\x80\x12\x25\x23\xdd\ +\x1d\x98\x3b\x77\xee\x31\xc4\x04\xbe\xb7\x60\xc1\x82\x59\xf4\x9c\ +\xe7\xcc\x33\xcf\x14\x77\xa0\xa2\xa2\x62\x97\x71\x07\x14\x00\x28\ +\xd9\x65\x41\x80\x57\x11\x8e\x1d\x3b\xf6\xd0\xab\xaf\xbe\xfa\xda\ +\x73\xce\x39\x47\xdc\x81\xd3\x4f\x3f\x5d\xfa\x09\x70\x4c\x40\xb9\ +\x00\x4a\x94\xec\x04\xfa\x3f\x58\x54\xb9\x9c\x1d\x98\x33\x67\xce\ +\x31\x04\x00\x56\x73\x73\xf3\x75\xaf\xbf\xfe\xfa\x5b\x04\x02\xec\ +\x0e\xc8\x04\x22\x1e\x5f\xae\x5c\x00\x25\x4a\x46\xb8\x3b\xb0\xc7\ +\x1e\x7b\x1c\xcd\xab\x08\xf7\xdf\x7f\xff\xd9\xa6\x69\xba\xd8\x1d\ +\x60\x26\x10\x8d\x46\x47\xb4\x3b\xa0\x00\x40\xc9\x90\x61\x01\x83\ +\xd9\xb1\x87\xb3\x03\xec\x0e\x5c\x71\xc5\x15\xd7\x9c\x75\xd6\x59\ +\x0b\xf2\xf9\x3c\xbb\x03\x32\x86\x8c\x63\x02\x1f\xe7\x38\x15\x00\ +\x28\x51\x32\x0c\x98\x00\xb9\x03\xfa\x9e\x7b\xee\x79\xdc\x79\xe7\ +\x9d\xf7\xff\x0e\x3b\xec\xb0\x39\xc1\x60\xd0\xc7\x31\x01\x06\x81\ +\x48\x24\xa2\x18\xc0\xce\xfc\x32\xca\x8f\x6a\x1b\xf9\xdb\x50\x63\ +\x23\xb3\x67\xcf\x3e\xf6\x86\x1b\x6e\xf8\x1e\xb9\x05\x33\xb8\x6c\ +\x98\x41\xe0\xcd\x37\xdf\x14\x10\x18\x69\xee\x80\x62\x00\x4a\x94\ +\x6c\x26\xec\x0e\x4c\x9a\x34\x69\xe1\x75\xd7\x5d\xf7\x9d\x4f\x7f\ +\xfa\xd3\xd2\x68\xf4\xca\x2b\xaf\xd4\x96\x2e\x5d\xca\x2c\x61\x44\ +\x9d\xab\xca\x02\x28\x19\x12\x56\x77\x08\xba\x03\xda\x82\x05\x0b\ +\x4e\xf8\xf2\x97\xbf\x6c\xb5\xb7\xb7\x5f\xfb\xe4\x93\x4f\xae\x58\ +\xb4\x68\x51\x9e\x40\xc0\x62\x10\x20\x50\x18\x76\xe7\xa5\x18\x80\ +\x12\x25\x1f\x02\x04\x58\x81\xe7\xcd\x9b\x77\xc2\xcf\x7e\xf6\xb3\ +\x1f\x1c\x78\xe0\x81\x33\x57\xad\x5a\xe5\xda\x7b\xef\xbd\xf5\xe5\ +\xcb\x97\x23\x1c\x0e\x8f\x08\x77\x40\x01\x80\x12\x25\xff\x86\x09\ +\x4c\x9e\x3c\xf9\xa8\xab\xae\xba\xea\x9a\x33\xcf\x3c\x73\xc1\xb2\ +\x65\xcb\xdc\x5f\xff\xfa\xd7\x35\x8e\x09\x8c\x04\x77\x40\xb9\x00\ +\x4a\x06\x9d\xfe\x0f\x65\xaa\x5c\xce\x0e\xec\xb7\xdf\x7e\x27\x7a\ +\xbd\x5e\x7d\xfd\xfa\xf5\xec\x0e\x2c\x27\x26\x90\x7f\xf9\xe5\x97\ +\xad\xd9\xb3\x67\x23\x9d\x4e\x2b\x17\x40\x89\x92\x91\x2c\x2e\x97\ +\x8b\xb3\x03\xc7\xfd\xe4\x27\x3f\xf9\xde\x21\x87\x1c\x32\x93\xcb\ +\x86\x2f\xbc\xf0\x42\x9d\x03\x83\xa1\x50\x48\xb9\x00\x4a\x94\x8c\ +\x74\x77\x20\x10\x08\xb8\xa6\x4e\x9d\x7a\x0c\xb9\x00\x57\x1f\x7b\ +\xec\xb1\xf3\x88\x01\xb8\xbf\xf5\xad\x6f\x69\xab\x57\xaf\x96\xae\ +\x43\xca\x05\x50\xa2\xe4\x23\xba\x01\xc3\x05\x04\xc8\x0d\xd0\x3e\ +\xf5\xa9\x4f\x9d\x44\x8f\xee\x8d\x1b\x37\x5e\xf3\xcc\x33\xcf\xac\ +\xe4\x91\x64\x0f\x3f\xfc\xb0\xc5\x4c\x80\xdd\x81\xe1\x72\x3e\x8a\ +\x01\x28\x51\xf2\x11\x01\x6b\xde\xbc\x79\xc7\xdf\x74\xd3\x4d\xd7\ +\x93\x5b\x30\xe3\xd9\x67\x9f\x75\x2d\x58\xb0\x40\x5f\xb2\x64\xc9\ +\xb0\x73\x07\x14\x00\x28\x51\xf2\x11\x98\x00\x67\x07\x66\xcc\x98\ +\x71\xec\x35\xd7\x5c\xf3\xed\x45\x8b\x16\xcd\x7f\xe7\x9d\x77\xdc\ +\x5f\xfd\xea\x57\xb5\xb7\xde\x7a\x0b\xc1\x60\x50\xb9\x00\x4a\x94\ +\x8c\x64\x31\x4d\x53\x66\x11\x1e\x78\xe0\x81\x27\xfa\xfd\x7e\x57\ +\x57\x57\xd7\xb5\xcf\x3f\xff\xfc\xb2\x93\x4f\x3e\x39\x7f\xef\xbd\ +\xf7\x5a\xb3\x66\xcd\x52\x0c\x40\x89\x92\x0f\x43\xab\x87\xdb\xc6\ +\x52\xce\x0e\xdc\x78\xe3\x8d\xd7\x1f\x7d\xf4\xd1\x73\xb8\x58\x68\ +\xfe\xfc\xf9\xfa\x7d\xf7\xdd\xa7\x00\x40\x89\x92\x0f\x42\xa7\xb9\ +\xac\xb6\x54\x2a\x0d\xdb\xe3\x27\xca\x2f\x6b\x07\x2e\xbb\xec\xb2\ +\x6f\x72\xb1\x10\x9d\x8f\x7b\xf1\xe2\xc5\xda\xc3\x0f\x3f\x3c\xe4\ +\x57\x11\x2a\x17\x40\xc9\xa0\x5b\xfd\x6c\x36\x2b\x74\x9a\xcb\x6b\ +\x87\xab\x3b\xc0\x55\x81\x07\x1c\x70\xc0\x09\x04\x06\xee\x35\x6b\ +\xd6\x7c\x6b\xe9\xd2\xa5\xec\x0e\x14\x02\x81\x80\xa9\x18\x80\x12\ +\x25\xdb\x00\x00\xee\xc6\x53\x66\x00\xc3\x29\x7d\xb6\x35\x26\xe0\ +\x74\x16\x3a\xe6\xe7\x3f\xff\xf9\x0d\x07\x1d\x74\xd0\x1c\x02\x36\ +\xbd\xab\xab\x4b\x57\x00\xa0\x44\xc9\x36\x00\x80\x2d\x3f\x6f\xc5\ +\x62\x51\x98\xc0\x70\x77\x67\xb8\xe5\xf8\xb4\x69\xd3\x8e\xf8\xea\ +\x57\xbf\x7a\x0d\xb9\x01\xfb\xd0\x39\x32\xcb\x1e\xb2\xc8\xe6\x1e\ +\xaa\x17\xb2\xff\xa3\x92\x91\x2b\x6c\x35\x49\x69\x90\x4a\xa5\xd0\ +\xdd\xdd\x2d\x03\x3b\xf9\xf7\xe1\x0c\x02\x5c\x15\xb8\xff\xfe\xfb\ +\x1f\x4f\xee\x80\x97\x18\xc0\xb7\x5b\x5b\x5b\xdf\xde\xb0\x61\x43\ +\x36\x91\x48\x0c\x39\x77\x40\x31\x00\x25\x83\x0e\x00\xac\x30\x9c\ +\x3b\x67\x46\xd0\xde\xde\x0e\x52\x18\xa9\xa8\x63\xdf\x7a\xb8\x82\ +\x80\x93\x1d\x58\xf8\xd3\x9f\xfe\xf4\xbf\x2e\xbf\xfc\xf2\x23\x08\ +\xd8\xc2\x43\x91\x09\xa8\x20\xa0\x92\x41\x17\x56\x96\xf2\xd2\xda\ +\x64\x32\x89\x7c\x3e\x2f\x3d\xfb\xf9\x39\x76\x0f\x86\xab\xf0\xf1\ +\x8f\x19\x33\x66\xbf\x99\x33\x67\x5e\x1c\x0a\x85\x5a\xe9\xa9\xd7\ +\x68\x2b\x32\x46\x28\x00\x18\x06\xfe\x29\x6f\x43\xb1\x6f\xdd\x48\ +\x92\x72\xe3\x0d\x4d\x23\x26\x40\x4a\xcf\x69\xb3\x5c\x2e\x27\x40\ +\xd0\xd1\xd1\x21\xc1\xc1\xe1\x7a\xfd\x19\xd8\x78\x23\x10\x58\x18\ +\x0e\x87\x1f\xa1\xa7\x56\xd3\xc6\x73\xc7\x0a\x43\x05\x04\x14\x00\ +\x6c\x83\x96\x66\x73\x59\xc4\xe3\x5d\xa8\xa9\xae\x13\x8a\x3a\x5c\ +\xe9\xe8\x50\x55\x7a\xd3\x32\x85\x11\x9b\x0c\x00\xd0\x9c\x8c\x80\ +\x0b\x1e\xb2\x9a\xac\x19\xa6\x69\xc1\xa0\x6b\xce\xfb\xf2\x63\xa1\ +\x58\x14\xfe\xec\xf5\x78\x69\x3f\xda\x97\x14\x0b\x43\x1c\x18\xf8\ +\x3e\xe2\xc5\x43\x95\x95\x95\x6e\x02\x82\xc9\xf4\xd4\x24\xda\xde\ +\xa7\xad\x8b\x36\x43\x01\xc0\x50\x55\xfe\x6c\x06\xad\x6d\x9b\xc4\ +\x22\xad\x5d\xbf\x1a\x0d\xf5\xa3\x10\x09\x47\xe9\xa6\x34\xd4\x05\ +\xfa\x98\x8a\x5f\x32\x0c\xa6\x57\xf0\xb8\x49\x81\xc9\xea\xbb\x49\ +\x87\x4b\x06\x59\x79\x52\x72\x83\x40\x81\x15\x5f\x7c\x68\x0f\xb9\ +\x05\x08\xc2\x4d\xdf\x81\x2c\xc5\xa5\xf7\xb1\xba\xeb\xf4\x5e\x2f\ +\x01\xb2\xcf\xef\x87\x6f\x18\x2c\xc1\x65\x60\x73\xe2\x1b\x8d\xf4\ +\xeb\x78\xda\xda\x68\x8b\x2b\x00\x18\xa2\x94\x8d\x2d\x7f\xf3\xa6\ +\x8d\x18\x3b\x66\x1c\x2a\x2a\x2a\xd1\xd1\xd9\x86\xb5\x6b\xdf\x47\ +\x5d\xdd\x28\xd4\xd6\xd6\xcb\x8d\xaa\x5c\x82\x0f\x2f\x6c\xc5\x99\ +\x45\x31\x9b\x62\x90\xe5\xeb\x58\x2a\x96\x60\x90\xf2\x97\x5f\x33\ +\xd8\xea\xf3\xef\xa4\xec\x86\xc3\xb8\xf8\x3b\x61\xcb\xaf\xd1\xfb\ +\x5c\xcc\x10\xbc\x9c\x36\xf4\xa0\xa5\xb3\x1b\x95\x91\x30\x2a\xa3\ +\x43\xbf\x55\x37\xc7\x31\x08\x00\xb8\x24\xb0\x8a\xb6\x00\x86\x50\ +\xf0\x5d\x01\x40\x3f\xcb\xcf\x69\xa8\xa6\xe6\xf5\x18\x37\x7e\x3c\ +\xdc\x74\xc3\x15\x8a\x05\x71\x01\xfc\xbe\x20\x56\xbc\xfb\x0e\x52\ +\xa9\x24\xc6\x8f\x9f\xd8\x1b\x1b\x50\xf2\xc1\x95\x9f\x85\xd3\x7b\ +\xcc\xa2\x8a\x85\x22\x59\x7b\x52\xf2\x52\x59\xf1\x0d\x9b\xf2\x93\ +\xf2\x0b\x43\x70\x8a\x6a\xca\xdf\x0b\xf1\x67\x04\x03\x41\x7a\xbf\ +\x0f\x89\x54\x0a\xab\x37\x6c\x44\x4f\x3a\x23\xaf\x57\x0e\x9f\x81\ +\x1d\xac\x6b\x4c\x59\x5c\x43\xea\xbe\x57\xb7\x67\x3f\x00\x20\x9f\ +\xff\xe9\xa7\x9e\x96\x14\x54\x8e\x98\x00\x6f\x0c\x02\x5c\xa2\x3a\ +\x67\xd6\x3c\x14\xf3\x79\xbc\xfd\xf6\x52\x29\x5a\x61\xcb\xa4\xe4\ +\x83\xd1\x7e\xcd\xa1\xed\x25\x52\xf8\x62\xa9\x24\x20\x60\x5b\x7d\ +\x43\x14\x9e\x2d\x3e\x07\xfb\x8a\x04\x00\x9a\x28\xbf\x4b\xde\xe3\ +\x21\x4b\xcf\xd7\xbe\xb6\xa6\x06\x9a\x4b\xc7\xd2\x15\xef\xe2\x1f\ +\x2f\xbd\x8a\xa6\x96\x56\x04\xbc\x3e\x54\x47\x23\xea\x02\x2b\x00\ +\xf8\x64\x84\x6f\xc0\x29\x93\xa7\x62\xf7\xdd\x67\xe0\x81\xfb\x1f\ +\x20\x25\x2f\x48\x34\x3a\x93\xc9\x08\x08\x30\x75\x9d\x33\x77\x2f\ +\x72\x0b\x62\x78\xf5\xf5\x97\x88\x2d\x74\x0d\xeb\x14\xd5\x4e\x74\ +\x82\xe5\x3a\x95\xc4\xba\x93\xe5\x37\x08\x04\x18\x00\x8a\x86\xfc\ +\x6c\x98\x44\xf9\x4b\xc4\x0a\x08\x04\x24\x18\x48\x40\xcc\x41\xbe\ +\x50\x20\x40\x8a\x5f\x8d\x00\xb1\x86\x37\x57\xbe\x8b\x25\x0f\x3f\ +\x8e\xd7\xde\x5e\x46\x6c\x40\xc7\xd4\x09\xe3\x31\x73\xca\x24\xc4\ +\xa2\x61\x58\x50\x4c\x4c\x01\xc0\x27\x08\x02\x47\x1c\xb1\x50\x82\ +\x36\x4f\x3c\xf9\x04\xdd\x8c\x16\x72\xf9\x2c\xb2\x04\x02\x0c\x08\ +\x2c\xbb\x4d\x9f\x81\x69\x53\x77\xc3\x1b\x4b\x5f\xc3\xba\x75\x6b\ +\xca\xfe\x9d\xba\x78\xdb\x61\x56\xa2\xe0\xb4\x99\x6c\xed\xf9\xb1\ +\x64\x08\xed\x2f\x99\xf6\x73\x45\x62\x03\xa4\xf3\x12\xe0\x73\xe9\ +\x6e\x54\xc7\x2a\x11\xa3\xed\xbd\xf5\x1b\x70\xcb\x7d\xf7\xe3\xef\ +\x4f\xfe\x03\xe9\x6c\x16\xd3\x27\x4d\xc0\xbe\x73\xe7\x60\xf2\xb8\ +\xb1\xf0\x79\x3d\xe2\x36\x28\x51\x31\x80\x4f\x94\xae\xf2\x76\xd2\ +\x49\xa7\xe0\x77\xbf\xbb\x09\xaf\xbe\xf6\x1a\xe6\xce\x99\x8b\x4c\ +\x2e\x03\x5b\xc7\x35\x61\x02\x63\x46\x8f\x45\x24\x1c\xc1\x0b\x2f\ +\x3e\x87\xae\x78\x37\x66\xed\x31\x47\xc5\x05\xb6\x61\xfd\xd9\x42\ +\xb3\x95\xb7\x7a\x7d\x7d\x93\x14\x9f\x23\xfe\xf4\x0a\x53\x7f\x93\ +\x5d\x04\x4b\xea\x00\xd8\x4d\xa8\xae\xaa\x42\x73\x7b\x07\x1e\x7f\ +\xee\x5f\x58\xf1\xfe\x6a\xf8\x3c\x6e\xcc\x9c\x3a\x05\x7b\xcf\xda\ +\x03\x63\x1a\xea\xa1\xbb\x34\x61\x0e\x4a\x14\x03\xd8\x31\x01\x2b\ +\xba\x29\xc3\xa1\x30\x16\x2f\xfe\x2c\xfe\xf5\xc2\x8b\x58\xbb\x6e\ +\xad\xe4\xac\x33\x4e\x4c\x80\x5f\x67\x61\x57\xe0\xb0\x43\x8f\x44\ +\x9e\xdc\x84\x17\x5e\x78\x4e\x18\x82\x62\x02\x9b\x29\x3f\xe1\x21\ +\x5b\x7c\xa1\xfa\xb4\x99\x42\xf9\xed\xc0\x9f\x55\xb2\xa3\xff\xbc\ +\x93\x4b\x94\xdf\x0d\xaf\xcf\x87\xbf\x3d\xf5\x0c\x7e\x78\xd3\x1f\ +\xf0\xec\xab\xaf\xa3\xbe\xa6\x0a\xc7\x1f\x76\xb0\x6c\xe3\xc7\x8c\ +\x92\x42\x5a\xa5\xfc\x0a\x00\x76\xb8\x70\x90\x8f\xd3\x80\xc7\x1c\ +\x7d\x0c\x1e\xf9\xfb\x23\x88\xc7\xe3\x44\x61\x8b\xe2\x0e\xe4\xf3\ +\xb9\x5e\x4b\xcf\x45\x29\x07\x1f\x74\x28\x1a\x1b\x1a\xd1\x93\xec\ +\x51\x00\xd0\xab\xfb\xe5\x0a\x4a\xd3\x49\xe9\x31\x08\x14\x9d\x9f\ +\x2d\x3b\xf2\x2f\xc5\x40\xa4\xfc\x9c\x12\xa4\xf7\x04\xc9\xe7\x5f\ +\xdb\xd4\x8c\xbb\x1f\x7e\x02\x01\x9f\x17\x27\x1e\x79\x28\xce\x39\ +\xf1\xd3\x98\x3f\x6b\xa6\x13\x40\x2c\x29\x86\xa5\x00\x60\xe7\x09\ +\xd7\xa3\xef\x35\x6f\x3e\xf6\x9c\x3b\x17\x0f\xff\xfd\xef\x62\xe1\ +\x19\x18\x72\x04\x00\x85\x42\x7e\xc0\xbe\x93\x26\x4d\x42\x28\x18\ +\x52\x37\x68\x2f\x02\x00\xec\x9e\x8b\xa2\x1b\xe5\x60\x1f\xa7\xf9\ +\x2c\x71\x05\x4c\xd3\x2e\xf8\xd1\x9d\x0a\x40\x4d\x40\xb7\x84\xa0\ +\x3f\x80\x86\xda\x6a\x9c\x7e\xdc\xd1\xf8\xf4\xc1\x9f\x42\x2c\x1a\ +\x95\xe7\x55\x15\xa6\x02\x80\x41\x11\x6e\x54\xb1\x70\xe1\x31\x08\ +\x91\x4b\xf0\xd8\x63\x8f\x43\xd3\x35\x61\x02\x0c\x06\xa6\xd1\x57\ +\xc8\xe5\x72\xb9\x7b\x63\x08\xca\xfa\xdb\x65\xbc\x96\xe9\x58\x7e\ +\xd3\xa6\xfe\x6c\xed\xc9\xe6\x4b\xea\x8f\x5f\xe7\xe8\x80\xcb\xa5\ +\xf7\x32\x06\x06\xd7\x48\x28\x88\xc6\xda\x1a\x79\xe4\xfd\x0d\x43\ +\x55\x5e\xee\x92\x00\x50\x0e\xc6\x0d\xf6\x26\x96\x87\x74\x7a\xf1\ +\xa2\xc5\xe8\xee\xe8\x04\xb7\x7c\x0e\xd1\xcd\x69\x95\xef\x74\x47\ +\xbc\x5e\x8f\xfc\x5a\xce\x14\x0c\x95\xe3\x1f\x9c\x0d\x92\xdb\x17\ +\xeb\x2f\x91\x7f\xbb\xc4\xd7\x32\xcc\x5e\x40\xe0\x8b\xea\xee\x57\ +\x47\xc1\x1c\x80\x9f\x0f\x07\xfc\x72\x2d\x57\xad\xdf\x38\xe2\xae\ +\xa3\x02\x80\x61\x2a\x9c\xbf\xe6\x75\x00\x8b\x16\x2f\xc6\xf3\xcf\ +\x3d\x87\x0d\xeb\x36\xc0\xe7\xf3\x4b\x8d\x00\xc7\x06\x78\x2b\xd1\ +\x8d\x1e\xab\xa8\x14\xea\x5b\x2a\x16\x76\xe9\xeb\x25\x96\xde\xa1\ +\xf8\x25\xc3\x49\xf5\x95\x4c\x94\x2c\xdb\x0d\xb0\x1c\xbf\xbf\xbf\ +\xf2\xf3\xff\xd2\x52\x4b\xd3\x51\x55\x51\x81\x8e\xee\x6e\x01\x84\ +\x8f\x1a\x51\x51\xb1\x98\x0f\x2e\x2a\x0d\xf8\x01\x5d\x81\xf1\xe3\ +\x26\xe2\xa8\xa3\x8e\x96\x78\xc0\xb8\xf1\xcb\xc4\xd2\x75\xc7\xbb\ +\xc1\x6b\x3a\x8e\x38\xe2\x08\x4c\x18\x3f\x19\x15\xd1\x18\x7a\x92\ +\x09\x29\x69\x2d\xbb\x05\xbb\x1c\x00\xb0\xf5\x77\x16\xfd\xf4\xd6\ +\xf8\xc3\x74\xd8\x94\x6d\xf9\xcb\x0a\x5a\x56\xfe\xb2\xef\x50\x20\ +\x7f\x7f\x5c\x63\x23\x5e\x7e\xeb\x6d\x64\x09\x60\xfd\xce\x42\xa0\ +\x0f\x05\xd8\xa5\x12\xf2\x85\xa2\x94\x0d\xf7\x07\x1a\x25\x0a\x00\ +\x3e\x76\x50\x70\xee\x9c\x79\xa4\xe8\x13\xe5\x67\x7f\x20\x80\x9e\ +\x9e\x1e\xfc\xf9\xb6\x3f\xe3\xa5\x57\x5e\x94\xe2\xa1\xba\x9a\x06\ +\x44\x89\x2d\xa4\xd2\x49\xf1\x75\xb9\xa4\x75\x97\x63\x4c\x8e\xf5\ +\xb7\xd3\x7f\xf6\x66\x3a\xe5\xc0\xe5\x85\x3d\x5b\x28\xbf\xf3\x8c\ +\x69\x19\xa8\xaf\xae\x44\xa2\x27\x89\x54\x26\x83\x00\x29\x31\xf9\ +\x0f\x1f\x28\xea\x28\x6b\x0c\xe8\x6f\x32\x33\xeb\x49\xa5\x88\x91\ +\x55\x10\x73\x0b\x0d\xf9\x25\xc3\xca\x05\x18\x46\xc2\x37\x33\xe7\ +\xff\xeb\xeb\x49\xd1\x23\x51\x4c\x24\x30\x38\xe5\xa4\x45\x78\xe5\ +\x95\xa5\x78\xf2\xa9\x27\xd1\xda\xbe\x89\x6d\xa0\x14\x09\x71\x39\ +\xeb\xae\x16\xbd\x96\xb8\x09\xf9\xfa\xec\x02\x14\x39\xed\x27\xa9\ +\x3b\x88\xd5\x77\xeb\xfa\x76\x95\x9f\x6f\x44\x8e\x11\x48\x1c\xc0\ +\xe3\x46\x53\x6b\xbb\xb8\x04\xff\x8e\xea\xdb\x73\x05\xf2\xc8\x66\ +\x73\x28\x12\x53\x2b\x4a\x65\xa1\x25\xeb\x0a\x54\xf6\x40\x01\xc0\ +\x27\x4f\x71\x9d\xd4\x16\x3f\xe6\xe9\xc6\x9b\xb1\xdb\xee\x38\xfb\ +\xcc\x73\xf0\xca\xab\x6f\xe0\x89\xa7\x1e\x17\x10\x90\x80\x56\x28\ +\x22\x51\x6e\xcb\xda\x35\x6e\x42\x56\xc6\x32\xe5\x2f\x95\x8a\x12\ +\x17\xb1\x78\x1d\x80\x6b\xa0\xe2\x6f\x4d\xf9\x9d\x22\x4b\x49\x17\ +\xf2\x22\x9f\xa3\x0f\x3a\x10\x95\xb1\x8a\x0f\xa0\xf8\x05\xe9\x24\ +\xcc\x8f\xd2\x44\x84\xfe\xe5\xc5\x46\xd2\x67\xc0\xed\xb6\x1b\x8a\ +\xa8\xb5\x02\x0a\x00\x76\xb4\x6b\xb0\xfb\x6e\xbb\xe1\x73\xe7\x7e\ +\x0e\xaf\xbd\xfa\x16\x1e\x7d\xec\x51\xb4\xb5\x37\x8b\xf5\x0b\x06\ +\x43\x12\x0b\xd8\x15\xd2\x83\xac\x94\x12\xfd\xb7\x9c\xee\x3d\xa4\ +\xd0\x6e\x6d\xcb\x60\xdf\xb6\x94\xbf\x0c\x0e\xac\xcc\x53\xc7\x8c\ +\x42\x7d\x65\x0c\x19\xa2\xf3\xe5\xd9\x01\xe5\xb8\x41\x7f\xc5\xe7\ +\x6b\x2f\xeb\x01\x9c\xd7\xb8\x66\x80\x19\x80\xdb\xa5\x09\x8b\x30\ +\x9c\x2c\x8e\x12\x05\x00\x3b\x1c\x04\xa6\x4f\x9f\x8e\xf3\xcf\x3b\ +\x1f\x6f\x2c\x7d\x07\x8f\x3c\xfe\x28\x5a\xca\x20\x10\x08\xc2\xe5\ +\x76\x8d\x78\x10\x90\xd6\x5d\x6c\xf5\x2d\x3b\xe7\x2f\xf4\xbd\xac\ +\xec\x16\xb6\xd2\x0f\x77\xa0\xf2\xf7\x43\x12\x64\xf3\x05\xa4\x33\ +\x59\x59\x89\xd9\x93\x4c\x4a\x93\x16\xd3\x61\x17\xac\xf8\x39\x51\ +\x7c\xb3\xfc\x66\x79\x64\xd0\xe1\x20\x62\x89\x58\x04\x57\x68\xf6\ +\xae\xcd\x50\x19\x01\x05\x00\x3b\x05\x04\x72\x79\x4c\x9b\x36\x0d\ +\x17\x9e\x7f\x21\xde\x5c\xba\x0c\x8f\x0a\x08\x6c\x94\xda\x80\x80\ +\x3f\x20\x94\x74\xa4\x82\x00\x2b\x1b\x5b\x62\x56\xc2\x62\xd1\x10\ +\xe5\xef\xd3\x3b\xad\xb7\x77\x42\xf9\xfc\xed\x1e\x80\x5b\x2a\x3f\ +\x3f\x57\x6e\xc6\xca\x31\x14\x51\x62\xfa\xdc\x7c\xae\x80\x54\x3a\ +\x8d\x0c\x29\xbf\x9d\x1e\xec\x73\x25\x98\x00\xc8\xdf\x25\xb0\x2d\ +\x39\x8b\x8d\x82\x7e\x1f\x4a\x45\x43\x90\x47\xa9\xbf\x02\x80\x9d\ +\xca\x04\xa6\x4e\x99\x4a\x20\x70\x11\xde\x7a\x6b\x39\x1e\xfa\xfb\ +\xc3\x04\x02\x4d\xb2\x0e\x9e\xd7\xb5\x7b\x3c\xee\x11\x0b\x00\x85\ +\x52\xc1\x29\x84\xb2\x06\x18\x5d\x17\x47\xff\x89\x01\xb9\xe9\xdc\ +\x03\x92\x9a\x73\x6d\xce\x01\x7a\x95\xbf\x4c\x13\x34\x6d\xe0\x67\ +\x73\x05\xa6\x66\x23\x48\xef\x7b\x4c\x27\x66\x20\x81\x3f\x2e\x17\ +\x36\x2c\xb1\xfe\xbc\x4c\xd8\xe5\xd6\x05\x0c\xf8\x6f\x29\x00\x50\ +\x00\x30\x08\x20\x30\x05\x9f\xbf\xf0\x62\xac\x5c\xf1\x1e\xee\x7f\ +\xe0\x01\x6c\x6a\x6d\x92\x12\x62\xbf\x6f\x84\x82\x00\x69\x59\x8e\ +\xac\xb4\xbe\xb9\xcf\xcf\x15\x7e\x96\x6d\xb1\x3d\x2e\x0f\x51\xf7\ +\xa2\xa4\xe8\x6c\x85\xde\xbe\xf2\xf7\xb7\xf2\xfd\x83\x88\xcc\x21\ +\xf8\x33\xd9\xe2\x73\xf9\xb0\xd4\x1b\x58\xf4\xb3\x65\x08\x00\x70\ +\x23\x11\x0e\xd2\x5a\xfd\x16\x1a\x29\x51\x00\xb0\xd3\x41\x60\xd2\ +\xa4\xc9\xb8\xe4\xf3\x97\xe2\xfd\xf7\xd7\xe2\xbe\x25\x4b\xb0\xa9\ +\x65\x83\x80\x80\xcf\xeb\x95\x9e\x02\x23\xc9\xfa\x17\x0b\xb6\x32\ +\xb2\x62\xf7\xc6\x04\x34\xbb\xf6\x3f\x14\x0a\x21\x12\x0e\x4b\xc6\ +\xa4\xb9\xb5\x45\x4a\xa9\x3d\xd2\x49\xc9\xda\xae\xe5\x07\xfa\x5c\ +\x7c\xcd\x21\xf3\x5c\x63\x20\xd5\x85\xe5\xa6\x22\x96\x5d\x66\xcc\ +\x15\x86\x5c\x03\xc0\xbd\x03\x78\xe5\x60\x81\x8e\x87\xd3\x8e\xaa\ +\x10\x48\x01\xc0\xa0\x82\xc0\xf8\x71\x13\x70\xd9\x25\x97\x61\xfd\ +\x86\x66\xdc\x75\xcf\x3d\x68\x12\x10\x28\xd9\x20\xe0\x1d\x19\x20\ +\xc0\x2d\xbc\x32\x32\xd4\xb3\xbf\x7f\x6f\x07\xe0\x22\xa1\x30\x42\ +\xc1\x00\xda\x3a\xda\xb1\x76\x43\x13\xc6\x8f\x1d\x2b\x1d\x7d\xb9\ +\xa1\xa7\xee\x72\x8b\x9f\xbf\x55\xda\xdf\xdf\xec\xf3\xda\x02\xd3\ +\x4e\xef\xf1\xc6\x81\x55\xae\x35\x28\x95\xab\x0c\x65\x6e\x00\xe4\ +\x39\xfe\x5b\xec\x8a\xb0\xab\xc0\xd7\x58\x95\x04\x2b\x00\x18\x54\ +\xe1\x02\x95\xb1\x63\xc6\xe2\x8b\x97\x7d\x11\x2d\xad\x1d\xb8\xf3\ +\xce\xbb\xb0\xb1\x79\x1d\x3d\x9f\x23\x6b\xe5\x95\xf1\x57\xc3\xfa\ +\xe6\x21\xe5\x2f\x4a\xf4\xbd\xdc\x0c\xa5\x4f\x71\xd9\x1d\xc8\x93\ +\x6f\xbe\x7e\x63\x33\xd6\x35\x35\x63\xd2\xf8\xf1\xe2\x26\xbc\xb5\ +\x6c\x85\xd4\x09\xb0\x75\xe6\x36\x5f\x76\xe0\x6f\x4b\xe5\x17\xaa\ +\xcf\x81\xc5\x92\xdd\x30\xb4\x5c\x51\xc8\x0a\x5f\x5e\x57\x50\x32\ +\xec\x25\xc6\xcc\x0a\x82\x01\x3f\x5c\x2e\x8d\x80\xb7\x28\x1d\x9d\ +\x3d\x6e\xb7\x5a\x9d\xa9\x00\x60\x28\x80\x40\x41\x1a\x86\x7c\x89\ +\x40\xa0\xb3\x33\x8e\xdb\xef\xbc\x93\x40\x60\xbd\xf4\x15\x60\xba\ +\xca\x29\xab\xe1\x2c\xdc\x41\xb9\xbc\x90\x87\x8b\x7e\xf4\x7e\xb4\ +\xdb\x20\x70\xc8\xe7\x0b\x98\x4c\xca\xcf\xb1\x8f\xd5\xeb\xd7\x63\ +\xca\x84\x71\xb4\x8d\x17\xd6\xc0\xa9\xbe\xfe\xeb\x02\xfa\x16\x06\ +\x41\xfc\x79\xc3\xa9\x2b\x30\xca\x0d\x44\x7a\x7b\x09\x38\xe5\xc6\ +\xce\x92\x61\x06\x93\x50\xd0\x2f\xeb\x07\x64\x10\x87\xdf\xa7\xac\ +\xff\x70\x07\x80\x91\xb4\x14\x94\x41\xa0\xb6\xb6\x8e\x98\xc0\x65\ +\x48\xc4\x93\xb8\xe3\x6e\x9b\x09\x64\xf3\x59\x59\xfe\xca\x31\x81\ +\xe1\x76\x4e\x9c\xda\x4b\x67\x32\xe4\xdb\x17\x6c\x6b\x4b\xff\xa5\ +\xb3\x39\x02\xb6\x82\x53\xb8\x63\xdf\x5a\x95\xb1\xa8\x44\xff\x79\ +\xbf\xe9\x13\x27\x60\xdc\xa8\x51\x88\x27\x7a\xb0\xb1\xa5\x45\x62\ +\x03\x76\x43\x90\x3e\x67\x9f\x95\xbb\x4c\xf5\x0d\x67\x5a\x50\x7f\ +\xe5\x37\x9c\x91\x61\x76\x97\x21\x3b\xd8\x17\x8b\x46\x48\xf9\xf3\ +\x28\x30\x13\xf0\xf9\xe0\xa5\xe3\x31\x9d\x01\x2e\x6a\x39\xb0\x62\ +\x00\x43\x42\x98\x2a\xd7\xd6\x10\x08\x5c\xfa\x05\xf4\xf4\xa4\x71\ +\xdb\xed\x77\x60\x43\xd3\x5a\x69\x33\xc6\x53\x64\x87\x93\x3b\xe0\ +\x76\xbb\x08\xbc\x72\x02\x00\x7c\xdc\xf1\x64\x0a\xef\xae\x5d\x8f\ +\x35\x4d\x9b\xb0\x81\x14\xbb\xad\x2b\x6e\xa7\xec\x9c\x68\x7f\x81\ +\x68\x79\xa1\x50\x94\x02\x9e\xf7\xd7\xaf\xc3\xb2\xf7\x56\xa1\xa6\ +\xaa\x52\xea\xfe\x2d\xa9\xe4\x73\x18\x83\x94\x11\x9b\xce\x8a\x42\ +\xfb\x77\xa6\xf8\xa6\x69\x39\xb3\x02\x1d\xe5\xb7\xd0\x1b\x03\xe0\ +\xe5\xc3\x05\x72\x29\xb2\xf4\xd9\x3e\xb7\x17\xe1\x60\x40\x51\x7f\ +\x05\x00\x43\x17\x04\x6a\x6a\x6a\x71\x39\x81\x40\xbe\x50\xc2\x9f\ +\xff\x7c\x1b\xd6\x6d\x78\x9f\x7c\xe3\x8c\x80\x80\x6f\x18\x80\x00\ +\x5b\x7e\xb6\xf2\xc9\x64\x9a\x2c\xad\x07\x9d\xf1\x04\xd6\x35\x6f\ +\x12\x1a\x5e\x11\x0a\x4a\x1a\x8e\xb3\x1d\x09\xc7\x35\x60\xdd\x66\ +\x1a\xef\x71\xeb\x52\xb0\x93\x22\xda\x3f\xa6\xa1\x81\x14\x37\xea\ +\x94\xf1\xc2\xd9\xc7\x92\xe8\xbe\x58\x76\xfa\x9d\xfd\x7c\x06\x87\ +\x5e\x17\xa0\xbc\xb1\xf2\x17\x4b\x72\x2c\xdc\x3e\x8c\xf7\x4f\xa6\ +\xd2\xb2\xe0\x28\x16\x09\x0f\x70\x41\x94\x28\x00\x18\x92\x20\x50\ +\x55\x55\x83\x2f\x5c\x72\x99\xd0\xdf\x9b\x6f\xf9\x0b\x56\xaf\x7b\ +\x8f\x40\x80\x14\xca\xe7\x1d\xd2\x20\xc0\xca\x25\xca\xdf\x93\x14\ +\x16\xd0\x1e\x8f\xa3\xa9\xb5\x15\xb5\x95\x95\xa4\x8c\x35\xb2\x80\ +\x27\x1a\x0a\x91\x65\x0f\xd8\xc5\x41\xa4\xa8\x52\x05\x4c\x1a\xce\ +\x00\x51\x15\x8b\x61\x6c\x63\x83\x44\xeb\xb9\x70\xc7\xbe\xf9\xec\ +\x02\x1e\x1e\x17\x66\xf7\x0e\x70\x56\x14\x3a\x9d\x84\x0c\xa3\x2f\ +\x06\xc0\xfb\x71\x00\xd1\xef\xf7\x62\x14\x29\x3f\xbb\x1c\xdd\x74\ +\x2c\x9c\x4d\xa8\x22\x37\xe0\xa3\xf4\x0f\x50\x00\xa0\x64\x50\x40\ +\xa0\x32\x56\x29\x29\x42\x9f\x2f\x80\x3f\xfd\xe9\xcf\x58\xb5\xfa\ +\x5d\x64\xb2\x69\x69\x8d\xed\xf3\x79\x81\x21\x56\xc3\xc6\x2b\x1b\ +\xf3\x74\xdc\x3d\x69\x52\x7e\x8f\x0b\x1d\x9d\xdd\xa4\xfc\x6d\x68\ +\xa8\xa9\x46\x4d\x65\x0c\x01\x3e\x6e\x9e\xda\xeb\xf5\xc8\xf1\xfb\ +\x69\x73\xf1\xd0\x14\xe9\xfb\x67\xb7\x08\x37\x1d\xbf\x5d\x14\x9c\ +\x9e\xe0\x81\x20\x9c\x29\xc8\x16\x0a\x02\x16\xec\xc3\x97\x0a\x86\ +\x3c\x72\x1c\x80\x33\x00\x85\x72\xfa\x8f\x6b\xfc\xbd\x6e\x34\x54\ +\x57\xc9\x50\xd0\x76\x62\x1e\xcc\x3e\xb8\xd2\xb0\x86\xae\x65\x38\ +\x18\x54\xca\xaf\x00\x60\x78\x81\x40\x45\x24\x86\x4b\x3f\x7f\x09\ +\xa2\xd1\x0a\xfc\xf1\xe6\x5b\xf0\xee\xaa\x15\xe4\x57\x27\xc5\xaf\ +\xf6\x49\x9d\xc0\xd0\x00\x01\xb6\xde\x39\x52\x52\x6e\x7d\xee\xd1\ +\xc9\xf2\x93\x8f\xdf\xd4\xd1\x81\xc6\x3a\xb2\xfa\x15\x31\xbb\x42\ +\xaf\x37\xd8\xa5\x49\x9f\x7f\x2e\xc4\xf1\x90\xf2\x33\x10\x30\x5b\ +\xe8\xbf\x2c\x8f\xfd\xf7\x24\x59\xef\x78\x3a\x43\x2e\x41\x8e\x3e\ +\xbb\x88\xbc\xe1\x4c\x0f\x92\xfd\x2c\xc9\x1a\x70\x40\x2f\x16\x89\ +\xa0\x8e\x00\xa6\xb1\xa6\x0a\x15\xe1\x10\x3a\xba\xba\xb0\x62\xf5\ +\x1a\x7a\xec\x96\x7d\x6a\x09\x10\xa2\x21\xa5\xfc\x1f\x39\x9e\xa3\ +\x2e\xc1\x20\x82\x00\xd1\x59\xee\x1b\x70\xc9\x45\x9f\xc7\x6f\xff\ +\xef\xf7\xb8\xf9\xe6\x5b\x71\xe6\x99\xa7\x61\xda\x94\xe9\x88\x84\ +\xa2\xc2\x9d\x39\x8d\x36\x98\x6b\x5a\x7b\x95\x3f\x95\x22\xe5\x77\ +\xa3\xa3\x3b\x61\x2b\x3f\x59\xfe\x4a\x02\x2e\x5b\xf1\xb6\x3c\x3e\ +\xcb\x71\x19\x3c\x6e\xaf\xf8\xf2\x9c\xcb\x07\xec\x74\x1f\x7f\x26\ +\x2b\x37\xbb\xeb\xb6\xde\xd2\xb3\x2e\x9b\x25\x70\x47\x1f\x66\x1b\ +\xc2\x08\x8a\x05\xa4\xd3\x29\x49\x17\x76\xf5\xf4\x88\xaf\xcf\x90\ +\x18\xab\x88\x12\xf8\xd4\x62\x54\x5d\x9d\xe4\xff\x2d\xd5\xf8\x43\ +\x01\xc0\x70\x15\x56\x8c\x40\x30\x8c\x8b\x2e\xb8\x10\xbf\xff\xc3\ +\xef\xf1\xe7\x5b\xff\x82\x33\x4e\x3b\x15\xd3\xa7\xed\x8e\x68\xd8\ +\x6e\x8a\x31\x58\x20\x50\x56\xfe\x24\x2b\xbf\x8b\x68\x3f\x51\xee\ +\xe6\xf6\x76\xa1\xfd\x55\xd1\xe8\x76\x15\x8f\x15\xdd\xed\x72\x8b\ +\x95\xe6\x61\x2a\xe5\xe7\xec\x71\xdf\xa4\xe0\x74\x4e\x5d\xf1\x38\ +\xda\xbb\xe3\xe8\xa4\x8d\x15\x9c\x0b\x85\xb8\x38\x48\xe6\x2f\xd0\ +\x75\xe1\xa5\x3c\x6e\xb7\x2e\xc1\xc6\x68\x38\x8c\x7a\xf2\xfb\x59\ +\xe9\x79\x44\x58\x55\xac\x42\x66\x09\xaa\xae\x3f\x0a\x00\x86\xbd\ +\x70\xce\x3b\xe0\x0f\xe1\x82\xf3\x2e\xc0\x1f\x6f\xf9\x23\x6e\xb9\ +\xf5\x36\x9c\x7e\xea\x62\xcc\xd8\x7d\x96\xb8\x09\x36\x08\xe4\x77\ +\xb2\xcf\xef\x92\xdc\x7d\x4f\x92\x94\xdf\xcd\xca\x4f\xb4\xbf\xad\ +\xa3\x4f\xf9\xad\xed\xf7\xda\x61\xda\x1f\x20\xeb\x5c\x94\x25\xc2\ +\x45\xbb\x50\x88\x9e\x63\x9a\xbf\x7e\x53\x8b\x04\x0f\xbb\x08\x50\ +\xf2\x64\xe5\x79\xd5\x5e\xd0\xe7\x47\x45\x28\x6c\x67\x05\x68\x5f\ +\x8b\x1e\x3d\x04\x20\x95\xd1\x08\x5d\x83\x08\xb9\x1a\x11\x62\x45\ +\x21\x04\xfd\x7e\x61\x46\x52\x17\xa0\x68\xbf\x02\x80\x11\x03\x02\ +\x06\x81\x40\x20\x88\xcf\x9d\x73\x1e\x6e\xfe\xcb\x9f\x71\xeb\x5f\ +\xee\xc0\xa9\xa7\x1a\x98\x3d\x73\x8e\x80\x00\xaf\x83\xe3\xd5\x74\ +\x3b\x83\x09\x94\x95\x3f\x41\x3e\x3f\x5b\x6b\xb6\xd2\x2d\x1d\x65\ +\xe5\x8f\x08\x6f\xdf\xe6\x51\xd0\x0b\xbc\xfc\x97\x2d\x36\x2b\x69\ +\x92\x00\x84\xb3\x00\xcc\x04\x92\x44\xe5\x37\x34\xb7\xa0\x2b\x11\ +\x97\xc6\x21\x13\xc6\x8e\x46\x6d\x55\x25\x7d\x66\x85\x2c\x97\xe6\ +\xa2\x28\xbd\xdc\x3b\x50\x1b\x78\xaa\xe2\x68\x94\x95\x5e\x29\xbe\ +\x02\x80\x91\x09\x02\x06\xfc\xfe\x20\xce\x3d\xf3\x1c\xdc\x7e\xe7\ +\x6d\x04\x02\xb7\xa3\xb4\xa8\x80\x3d\xe7\xce\x97\x66\xa4\x7e\xd2\ +\x8a\xdc\x0e\x76\x07\xf4\x5e\xcb\x9f\x84\x5b\x7c\xfe\x38\x36\x75\ +\x76\xd8\x69\xbe\x70\x44\xfe\xf4\xb6\xd4\x9f\x59\x01\x57\x35\x72\ +\x47\x5e\x56\x70\xee\x9a\xcc\x2d\xd1\xb8\x45\x77\x82\xdc\x88\x96\ +\x8e\x4e\xa9\xdb\xaf\x23\x20\x19\x55\x5b\x8b\xea\xca\x98\xdd\x2c\ +\xc4\x99\x13\x58\xbe\x06\x4a\x14\x00\xec\xd2\x20\xc0\x83\x47\xce\ +\x38\xed\x0c\x49\xa3\xdd\x76\xc7\xdd\xa2\x34\x7b\xcd\xdb\x9b\x68\ +\x70\x15\xfc\x9a\x8f\x7c\xe5\xfc\x0e\x01\x01\x56\xfe\x82\x43\xfb\ +\xd9\xff\x6f\xef\xee\x46\x4b\x67\x27\x1a\x49\x59\x63\x64\xd1\xb7\ +\x67\xf9\x59\x7f\x45\xf9\x63\x51\x69\xd3\x93\x24\x00\xe1\xb5\x0e\ +\x3e\x7f\x00\xdd\x3d\x09\x74\x27\x7a\xc4\xc2\x57\xd1\xeb\x75\x95\ +\x95\x52\xef\xa0\xc6\x7f\x29\x00\x50\xb2\x0d\x10\xf0\xb8\x7d\x38\ +\x6d\xf1\xa9\xb2\x6c\xf8\x8e\xbb\xee\x95\x8c\xc1\x3e\xf3\x17\x90\ +\xd5\xac\x15\x8b\xca\xd1\xf2\x4f\x96\xf6\xeb\xbd\x96\x9f\x7d\x72\ +\x2e\xe7\x6d\x25\xe5\x1f\x55\x67\x2b\xff\xf6\x7d\x7e\x4b\x94\x9b\ +\xa3\xf3\x9c\xe3\x4f\xa6\x53\x52\xd9\xc8\x0d\x50\xda\x89\xee\xf7\ +\x70\xa5\x9e\x9b\xfd\xf9\x28\x62\x91\x90\xf0\x7b\x43\x05\xef\x14\ +\x00\x6c\xf3\x76\x1a\x06\x8b\x28\x76\xb4\x94\x64\xba\x90\x07\xa7\ +\x7c\xe6\x64\x89\xa6\xdf\xbb\xe4\x7e\xc9\x18\x2c\xd8\x7b\x5f\xd4\ +\x54\xd5\x13\x08\xd8\xe3\xc9\x3e\x89\x6b\xc4\x3e\x7b\x39\xe0\xc7\ +\x91\x75\xb6\xfc\x6d\x5d\xdd\x42\xd3\xff\x9d\xf2\xf3\xf3\xbc\xa2\ +\xd1\x56\x7e\xbb\x2c\x97\x47\x7d\xfb\xe8\xf8\x38\x65\x98\xcd\xe4\ +\x84\x19\x54\xd2\xe7\xd8\x75\xfa\xd8\x65\x5a\xa5\x6f\x7e\x3f\x2b\ +\x00\x50\xf2\xa1\x99\x80\x4e\x7e\xf8\x09\xc7\x9f\x20\xe5\xae\x7f\ +\xbb\xff\x21\x01\x81\xfd\x16\xec\x8f\xfa\x9a\xc6\x4f\x04\x04\xec\ +\x80\x5f\x51\x82\x75\x1c\x80\xeb\x8c\xdb\x96\xbf\x4c\xfb\xb7\x17\ +\x69\xe7\xbf\xcb\x96\xbf\x92\x94\x9f\x6b\xf2\x53\xac\xfc\x74\x4c\ +\x7e\xa2\xfd\x9c\xd6\xe3\x05\x43\x1c\xfc\xe3\x69\xbf\x21\x7a\x5e\ +\x15\xeb\x28\x00\x50\xf2\x21\x85\x23\xe9\x2e\x02\x81\xe3\x8e\x39\ +\x5e\x2c\xf5\x43\x7f\x7f\x54\xca\x62\x0f\xd8\xef\x00\x34\xd4\x35\ +\x4a\xf4\x3c\xfb\x11\x41\x80\x3f\x8f\x1b\x6a\xb2\xbf\xce\xd5\x7b\ +\x9d\xf1\x1e\xb4\x74\x74\xa3\xa1\xba\x06\x15\x8e\xe5\xc7\xf6\x2c\ +\x3f\xf9\xf1\xbc\x14\x97\x2d\x7f\x2a\x95\x12\xc5\x0f\xf0\xc8\x34\ +\x02\x02\x0e\x56\xb2\xe5\x0f\x93\xbb\xc2\x65\xc2\x4a\xf9\x15\x00\ +\x28\xf9\x18\x20\xe0\x26\x77\xe0\xe8\x23\x8f\x11\xff\xfc\x91\x47\ +\x1e\x95\x95\x75\x87\x1c\x74\x30\x1a\xeb\x46\x4b\xbe\x9d\xfb\xe5\ +\x7f\x18\x1d\x63\xcb\xcf\xab\xef\x12\x64\xa9\x99\xf6\x77\x26\x12\ +\xd8\xd4\xd1\x85\xfa\xea\x2a\x54\x90\x52\x5b\xff\xc6\xf2\x7b\xca\ +\x3e\x3f\x59\x7e\xae\x12\xe4\x3c\xbe\x28\x3f\xf7\x04\x20\x96\xc2\ +\x01\xc0\x80\xcf\x03\x9f\xea\xcc\xa3\x00\x40\xc9\x27\x07\x02\x47\ +\x1e\xbe\x50\x94\xeb\x6f\xf7\x3f\x28\x2b\xe8\x8e\x38\xfc\x70\x02\ +\x81\x51\x0e\x13\xc8\x6f\x55\xd9\x98\xda\xf3\x56\x6e\xa0\xa1\xbb\ +\x02\xb2\x08\xa7\x27\xd9\x05\xb7\x28\x7f\x0f\x36\xb5\x77\xc8\xf2\ +\x5a\xae\xbd\xdf\x6e\xb4\x9f\xeb\xf4\xc9\xf2\xf3\x72\x5e\xfe\x3c\ +\x51\x7e\xb1\xfc\x7e\xf4\x48\x5d\x7f\x5e\x32\x08\x01\x02\x08\xaf\ +\x4b\x75\xe5\x55\x00\xa0\xe4\x13\x07\x81\x43\x0e\x3e\x4c\xfa\xec\ +\xdf\x7b\xef\x5f\xc5\x8a\x2f\x5c\x78\x24\x46\x11\x08\x70\xf0\x8d\ +\xc7\x69\xf5\x2f\xd1\x95\xf5\xfb\xf4\x5c\x47\x7b\x3b\x6a\xeb\x1b\ +\xe0\xf3\x85\x01\x63\x25\xb2\x69\x1e\xdf\x35\x1a\x1d\x89\x36\xb4\ +\x76\x74\xa2\xa1\xa6\x86\xe8\x3c\xa7\xf0\xcc\xed\xa6\xfa\x78\xc9\ +\x72\x2c\x5a\x21\x41\xca\x54\x32\x25\xeb\xff\xfd\xc1\x00\x92\x99\ +\x0c\x29\x7f\x49\x29\xbf\x02\x00\x25\x3b\x23\x26\x70\xe0\xfe\x07\ +\x49\x9d\xc0\xdd\x77\xdf\x23\x6b\xe4\x8f\x3a\xea\x28\x8c\x69\x1c\ +\x6b\x33\x81\x6c\x4e\x22\xed\xec\x83\xf3\xda\xfd\x3f\xdc\xf2\x27\ +\xbc\xfa\xc6\x9b\xd8\x7b\xcf\xbd\x71\xe9\x67\x4b\x08\xe5\x7f\x01\ +\x7f\x60\x22\x56\xe5\xbe\x8d\xe6\xf6\x20\x46\xd7\xb1\xcf\x1f\xd9\ +\x6e\x6d\xbf\xad\xfc\x76\x2a\x4f\x94\x9f\x2d\x7f\x28\x28\xa9\xbe\ +\x64\x3a\x2b\x2b\xfa\x94\xf2\x2b\x00\x50\xb2\x13\x41\x60\xbf\xbd\ +\xf7\x93\x01\x9c\x5c\x27\xc0\x9d\x73\x8e\x3e\xea\x68\x8c\x69\x18\ +\x8b\x60\xd0\xdf\x1b\xdc\xfb\xe5\x6f\x7e\x85\x17\x49\xf9\x03\xc1\ +\x08\x1a\x83\x77\xc0\x6b\xa4\x01\xcd\x07\x57\x6a\x29\xea\xbd\x3f\ +\x47\xa1\xe1\x3b\x64\xc5\xab\xe8\x53\x0b\xdb\x55\x7e\x8f\xd7\x2d\ +\xab\xff\x98\xf6\xb3\xe5\x0f\x06\x6d\xe5\x4f\x64\xec\xe9\xbc\xba\ +\x52\x7e\x05\x00\x4a\x76\x36\x08\x78\x30\x7f\xfe\x02\x59\x1c\xc3\ +\x73\x07\x78\x38\xe7\xd1\x47\x2d\xc4\x94\x49\xd3\x45\x6b\x59\xf9\ +\x1f\xff\xe7\x73\x88\x56\xd4\xe3\xcc\x23\xd7\xe1\x94\x63\x8b\x30\ +\xbc\x7b\xc2\xcc\xb4\x03\x49\x03\xb1\xe2\x73\x70\x55\x3e\x84\xb6\ +\xfc\x05\xb4\xfb\x36\xd6\x19\x38\x01\xbf\x01\xa9\x3e\xa2\xfc\x9c\ +\x82\xec\x21\xe5\xe7\xfa\x01\x65\xf9\x15\x00\x28\x19\x0c\x10\x20\ +\xab\xcf\xc5\x42\xf3\xf6\x9c\x4f\x20\x60\x11\x08\xdc\x2b\xad\xb8\ +\xdc\x47\xbb\x71\xcb\xad\x77\xe0\xaf\x7f\x7f\x04\xbe\x60\x0d\x4e\ +\x5d\xd8\x84\x53\x16\x76\xc1\x4a\x73\xcd\x7d\x0f\x11\x80\x4a\x68\ +\xa5\x06\x18\xa4\xcc\xa1\xcc\x6d\x08\xf9\xe7\x21\x9d\xdf\x13\xba\ +\x96\x42\xff\x26\x24\x76\x9e\xbf\x7f\x91\x4f\x4a\x06\x9d\x72\xba\ +\xcf\x0e\xf8\x29\xe5\x57\x00\xa0\x64\x50\x85\xfd\x76\x8f\xc7\x87\ +\x3d\x67\xef\x25\xfe\xff\x43\xa4\xf4\x57\x5d\xfd\x6d\xbc\xb9\x72\ +\x15\x2c\xbd\x02\x17\x2f\xee\xc2\x39\x9f\xe9\x81\x55\xd0\xa5\xa8\ +\x47\xcb\x6e\x80\x15\xdd\x0d\xf0\xd7\x13\x08\x24\x01\xfa\x3d\xe6\ +\xbd\x19\x39\xd7\x74\x52\x72\x37\x01\x88\xd1\xe7\xf3\x7b\x9d\x85\ +\x3d\xac\xfc\x44\xfb\x39\xcd\xc7\xee\x05\xd3\x7e\xa5\xfc\x23\x43\ +\x54\x4b\xb0\x11\x02\x02\xa1\x50\x54\xa8\x7f\x57\x57\x1c\xcf\xbe\ +\xf4\x3a\x7a\xd2\x6e\x7c\x7e\x51\x06\x9f\x3f\x2d\x49\x96\x1f\x28\ +\xf5\x14\x61\x66\x8b\xd0\x8a\x69\x1b\x04\x3c\x11\x58\x81\x46\x98\ +\xee\x0a\xf8\xd2\x4f\xa1\xc2\xfd\x20\x4c\x2d\x28\x6e\x80\x58\x7e\ +\x0f\xf9\xfc\xa4\xfc\x9c\xe7\x4f\xa4\x92\x42\xfb\x79\xeb\x49\xe7\ +\x24\xdd\xa8\x94\x5f\x31\x00\x25\x43\x44\x38\xda\x9f\xcb\x66\xf0\ +\x3f\xff\xfb\x0b\x2c\x79\xf0\x51\x64\x4b\x7e\x7c\xf3\xec\x3c\xae\ +\x38\x8f\xdc\x84\xa4\x07\x85\x44\x09\x16\xbd\x6e\x15\x73\xd0\xe8\ +\x1b\x77\x57\x11\x10\xb8\x43\xb0\x7c\xc4\x02\x02\x04\x10\xc9\x95\ +\x88\x14\x6e\x45\xca\xbd\x1f\x72\xc5\x06\x52\x6c\x43\x2c\x3f\x57\ +\x1c\x72\x1f\x40\xee\xe2\x1b\x08\x06\xd1\x93\xca\x22\xcb\x15\x7e\ +\xdc\xec\x83\x94\xdf\xad\xa6\xef\x2a\x00\xd8\x21\x16\x4d\x2d\x06\ +\xfa\xc0\xc2\x79\x7e\x4e\xc9\xfd\xf8\x27\x37\xe2\x57\xbf\xff\x13\ +\x32\x45\x2f\xae\xbb\x3c\x80\xff\x77\x29\x5d\xbf\x6c\x10\xc5\xbc\ +\x9f\x76\xca\x12\x4a\x98\x76\xff\xfd\x5c\x06\xc5\xf6\x14\xbc\xee\ +\x35\xd0\x6a\x08\x04\x42\x63\x09\x18\x7a\xe0\xca\x2c\x47\x2c\x7a\ +\x07\xba\xfd\x57\x21\x16\x71\x89\xe5\x4f\x92\xe5\x0f\x05\x82\x92\ +\xe7\x4f\x48\xaa\x2f\x2f\x2d\xba\x02\x1e\x47\xf9\xd5\xf7\xf3\x81\ +\xef\x67\xe5\x02\x28\xd9\x61\xd6\x7f\xc9\x92\x25\xf8\x9f\x9f\xff\ +\x0a\xa9\x8e\x1c\xbe\x75\x89\x1f\x57\x5f\xee\x81\x95\xd7\x50\x34\ +\xa2\xd0\x7c\x21\x68\xde\x00\x3d\x12\xbd\xf7\x06\xa1\xf9\x83\xe4\ +\x32\xe8\x30\xba\x53\xd0\x12\xef\x02\x9a\x8b\x40\x60\x02\xd1\xff\ +\x08\x02\x99\x7b\x50\x13\x5a\x06\xc3\x0c\x90\xcf\xdf\x23\x3e\x7f\ +\x80\x69\x7f\x2a\x23\x53\x7d\xb8\x3b\x10\x2b\xbf\x4b\xd7\x94\xf2\ +\xab\x18\x80\x92\xa1\x20\x6c\xd5\x0b\x45\x1e\xa4\xe1\xc5\x37\x2f\ +\xf7\xe1\xda\xaf\x04\x80\x3c\xf7\x19\x64\x7e\xe7\x23\x84\xf0\x43\ +\xf3\x04\xfa\x36\x02\x01\xdd\xeb\x07\x67\xfd\x8c\x44\x0f\xf4\xd4\ +\x6a\x58\xde\x18\xac\xf0\x44\xe8\xc5\x0e\x78\x12\x3f\x46\x26\xd7\ +\x86\x60\x20\x26\x3e\xbf\x58\x7e\xa2\xfd\xfd\x95\x5f\x89\x72\x01\ +\x94\x0c\x11\xe1\x99\x7b\x27\x9c\x70\x0a\xc6\x57\x3c\x8c\x63\x3e\ +\x45\x16\xbd\xe8\x86\x41\xcf\xf1\x6c\x4e\x4d\x27\x60\xd0\x83\xd2\ +\xa1\x47\x2b\x1b\x6c\x99\xc7\x6d\x92\xf6\x13\xcd\x2f\x1a\xd0\x7a\ +\xda\xa1\xbb\x89\x15\x04\xc7\xd2\xef\x49\xe8\x89\xc7\x51\x59\xfd\ +\x2b\x18\xd1\x6b\x91\x48\xf5\x48\x47\x5f\xee\x47\xa0\x94\x5f\x01\ +\x80\x92\x21\x28\xa6\xe6\xc5\xa8\x8a\x7f\x62\xdc\xc1\xcb\x60\x21\ +\x8a\x12\x6d\x9a\xa7\x00\x94\x12\xd0\x4b\x1d\x30\xfd\x51\x58\x3e\ +\xee\xc2\xd3\x0f\x04\x60\xda\xd4\xdf\xca\xc3\xcc\xd3\xf3\xc9\x26\ +\xe2\x82\x5e\x58\x15\xd3\x09\x18\x72\xf0\x75\xfd\x2f\xd2\x7a\x35\ +\xb2\xb8\x0c\x2e\x97\x86\x80\xb7\xa4\x94\x5f\x01\x80\x92\x21\xe7\ +\xbf\xb9\xc8\x8a\xf3\x0c\xbe\xcc\x13\x70\xa5\x5b\x48\xad\x4b\x44\ +\xf1\x0d\x52\xf8\x28\xe0\xaa\x81\x56\x88\x43\xcf\x31\xc5\x1f\x0b\ +\x8b\x5c\x01\xa9\xf4\x63\xeb\xaf\x31\x33\xd0\xa0\x95\x98\x0d\x30\ +\x08\x98\xd0\x93\xeb\xa0\x85\x2d\x58\x95\xf3\xc8\x35\x78\x03\xc1\ +\xf6\x6b\x51\x5d\x99\x46\x31\x74\x01\x5c\x1a\xaf\x10\xcc\xab\x0b\ +\xae\x00\x40\xc9\x50\x11\x8e\xfe\x73\x20\x2e\x1e\x4f\x10\x3d\x3f\ +\x01\x9e\xe8\x2a\x78\x92\x2f\x90\x01\x2f\x90\xa5\x27\x65\xf5\x90\ +\x5f\xef\xad\x24\x26\x90\x24\x20\x58\x4b\xbf\xd7\x13\x08\xf8\xe8\ +\x35\x0f\x2c\x8d\xa8\x3f\xd3\x01\xcd\x69\xb1\x6d\x16\x61\x15\x4c\ +\x68\xe9\xf5\xd0\x82\x25\x02\x81\xbd\x60\xba\xa3\x08\x75\xdf\x08\ +\xc3\x78\x17\xb9\xe8\x95\x04\x34\x93\x68\x5f\x5e\x2f\xa0\x9a\x78\ +\x2a\x00\x50\x32\xf8\xca\x4f\x8f\x89\x9e\x24\x3c\xba\x85\x96\xae\ +\x89\x78\x2f\xf3\x45\xcc\x69\x1c\x83\x70\xee\x61\x98\xb9\x0e\xc9\ +\xf9\xc3\x5f\x45\x8a\x4f\xd6\xdb\x20\x40\x28\x36\x43\xd3\x09\x14\ +\xdc\x21\x7a\x74\xf3\x0c\x5e\x9b\x0d\x94\x2c\xbb\xd5\xb7\x49\x00\ +\x40\xba\xad\x65\x68\xbf\x52\x06\x56\x78\x02\x0c\x3f\xb1\x88\xf8\ +\x53\x08\xe4\x96\xa1\x10\xbb\x14\x25\xdf\xb1\xe2\x66\xd8\x40\xa0\ +\x9a\x7a\x8e\x08\x16\xa9\x2e\xc1\xf0\x54\xfe\x78\x4f\x42\xe2\x79\ +\xf1\x54\x0a\xcd\x6d\xab\xe1\x76\x8f\x47\x5b\xf1\x2a\x74\x87\xae\ +\x81\x15\x99\x49\x5f\x6c\xca\x56\xe6\x5c\x1b\xd1\x7d\x17\x41\x3d\ +\x29\xbe\x11\x27\xa3\xdf\x49\xef\x23\x65\xf7\xb8\x68\xf3\x90\xcb\ +\xe0\xa5\x47\x2f\xb8\x42\xc8\x2a\x71\x7a\x8f\xf6\x2d\xd2\x7e\xf1\ +\xb7\xa0\x9b\x05\x58\xd5\xfb\x01\x3e\x1d\xbe\x8e\xab\x11\xe8\xbc\ +\x14\xee\xd2\x53\x36\x73\x80\x0f\x43\x6d\x82\xb1\x12\xc5\x00\x76\ +\x0d\xe5\x4f\x24\xc0\xf3\x73\x58\xf9\x37\xb4\xb4\xa2\xb6\xaa\x0a\ +\x55\x15\xa4\x90\x96\x86\x44\xf1\x64\xe4\xbd\x73\x11\x75\xdf\x8c\ +\x40\xe6\x01\x20\xd7\x0a\xd3\x60\x36\x50\x4b\x2e\x40\x98\xac\x7b\ +\x8a\x3e\x83\x9b\x81\x84\x79\xf8\x1f\xbd\x87\x3f\xd3\x6d\xab\x32\ +\xbb\x03\x86\xc9\xc1\x05\xfb\x0f\xa6\xd7\x49\xf7\x20\x8b\x98\x80\ +\xe5\xde\x03\x7a\x7a\x29\xfc\x6d\x97\xc1\x88\x1c\x87\x42\xe8\x6c\ +\x18\xfa\xee\xf4\x1e\xbe\x85\x8a\xea\xcb\x51\x0c\x40\xc9\x4e\xf1\ +\xf9\x59\xf9\x1d\xcb\xbf\xa1\xb5\x4d\x94\xbf\x3a\x5a\x61\x0f\xe3\ +\xf4\xb2\x4e\xa7\x90\xc9\x8f\xc1\xa6\xd2\xff\x43\x5b\xe0\x07\x28\ +\x56\x1c\x48\xcf\x65\xa0\x67\xd6\x41\x2b\x24\x6c\x17\x80\x19\x80\ +\x45\x0c\x82\x0b\x06\xdc\x6c\xfc\x5d\xfc\x07\xe8\x6e\x70\xcb\x2d\ +\x61\x99\x9a\x6d\xdc\x75\x8f\xd0\x7d\x2d\xb3\x91\xde\xdb\x0e\x04\ +\x1a\xc8\xf0\xc7\xe0\xea\xfa\x33\xb1\x81\xcb\xe0\x2f\xfe\x92\xf6\ +\x5e\x43\x3b\x0e\x9d\x51\xe6\x4a\x14\x03\x18\x79\xca\xef\x94\xdd\ +\xc6\x79\xd4\x16\xf1\xfe\x78\x92\x2d\x7f\x1b\x6a\xaa\x2a\x64\x50\ +\x27\xfb\x02\xdc\x22\x8c\xa5\xc4\x2d\xbd\xac\xb4\x14\x02\x75\x17\ +\x0f\x46\x97\x35\x03\x21\xeb\x6f\x68\xf4\xde\x01\x4f\x61\x3d\x19\ +\xf8\x3a\xb2\xe8\x75\xd0\x8a\x49\x52\xee\x24\xbd\xd5\x47\xc4\x81\ +\x18\x80\x9b\x14\xd8\x74\xd9\xae\xbd\x65\xd8\x6c\x40\x73\x6c\x84\ +\x66\x8f\xef\x02\xbf\x87\x41\xc1\x5f\x4f\xec\xe0\x7d\x78\xcc\xdf\ +\xc1\x55\xb1\x0c\x79\xdf\xa9\x28\xe1\x60\xd8\x01\x42\x55\x21\xa8\ +\x00\x40\xc9\x27\x47\xd1\x48\xf9\x59\x27\x39\xe0\xc7\x74\xad\x9b\ +\x94\x7f\x23\xd3\xfe\xca\x18\xaa\x2b\x62\x7d\x83\x32\x99\xbd\x5b\ +\x26\x29\x78\x89\x36\x7e\x8e\xde\x65\xf5\xa0\xbb\xdb\xc2\xf2\x8e\ +\x43\xd1\x58\x35\x15\x33\x6b\xef\x44\x94\x7c\x78\x33\x67\xc1\xf4\ +\x35\x90\x3b\x10\xa7\x7d\x72\x04\x02\x1e\xb9\x15\x2c\xe1\x83\x9a\ +\xf4\x14\x80\xcb\x6d\xc7\x05\x84\x22\xe8\x70\xc6\xf6\xda\x7f\x28\ +\x10\xa0\xdd\x69\xcb\x76\x40\x4b\xbd\x05\x9f\x46\x68\xe3\x0d\x11\ +\x08\x2c\x80\x94\x21\x2a\x51\x00\xf0\x71\x44\x2d\x06\xea\x53\x7e\ +\x3b\xda\x6f\xb7\xee\xee\x4a\x24\xd1\xd4\xd6\x2e\x13\x75\xab\x2b\ +\xa2\x65\xbd\x17\x10\xe0\x11\xdc\xac\xa3\x16\x29\xbf\x44\xf4\x69\ +\xff\x54\x3a\x45\x2e\x43\x07\x02\x5e\x1d\xdd\xe9\xf1\x78\x36\xfd\ +\x05\xcc\xa8\xab\xc5\x04\xcf\xdd\xb0\x4a\x1e\x58\x9e\x2a\xa2\xf6\ +\xdd\x8e\xe5\x36\xd8\x89\xb0\x03\x7c\xbc\xc4\x57\xf3\xdb\xd6\x9e\ +\x97\x0f\x4a\x4c\xc0\x61\x02\xbc\xd1\x71\xc1\x47\x4c\xc2\x2c\x42\ +\xcb\x75\x42\xf3\x34\xc3\xab\xdf\x05\xc3\x5d\x07\xd3\x1a\xa7\x62\ +\x02\x5b\xb9\x9f\x55\x0c\x40\xc9\x87\x56\x7e\xd6\xe8\x44\x32\xe9\ +\x28\x7f\x0f\x36\x92\xf2\xd7\x55\x57\xd9\xca\x6f\x6d\x79\x63\x09\ +\x6b\xb7\x6c\x5f\x9c\xc7\x7c\x75\x76\x25\xe0\x76\x7b\x64\x96\xa0\ +\x46\x74\x3f\x9e\xc8\xe1\xc9\x65\x9f\xc6\xea\xfc\x69\x70\x15\xc9\ +\x7a\x1b\x39\x58\xae\x20\xa3\x86\xf3\x09\x25\x27\xc2\xef\x95\xca\ +\x40\xd9\x5c\x3e\xe9\x21\x08\xde\xcf\xc5\x96\x3f\x28\xc0\x61\x95\ +\xd1\x87\x0f\x33\x4f\x20\x50\x78\x1f\x5e\xf3\xaf\x84\x15\x05\xe9\ +\x52\x24\x2c\x42\xc9\xd0\xbf\xcf\xd4\x25\x18\xa2\x96\x9f\x14\x88\ +\x6b\xf1\x75\xd2\x4d\xee\xdb\xbf\xb1\xb5\x1d\x0d\xd5\xd5\xb6\xcf\ +\x6f\x6d\xdd\xd3\x66\x95\x63\xbd\x0b\xf3\x2a\x3e\x9f\x4f\xca\x78\ +\x79\xe8\x27\xaf\x17\xc8\xe4\xf2\x64\x9d\x33\xa8\x8c\x78\xd1\x66\ +\x7c\x01\xb9\xd0\x69\xd0\x73\x76\x09\x70\x2f\xb5\x97\x0f\x71\x91\ +\x2b\xe0\xe9\x03\x00\x0e\xf0\x69\x5e\x9b\x0d\xb8\xbc\xb0\x08\x00\ +\x34\x66\x0c\xdd\x2b\xc8\x7b\x48\xd3\x73\x21\x3b\x6d\x98\xeb\x82\ +\xd7\x78\x11\x5a\xfa\x56\x64\x32\x19\x3a\x07\x97\x03\x62\x4a\x14\ +\x00\x28\xf9\x90\x96\x9f\x2c\x38\xd1\x7e\x36\xc6\x5d\x49\x9b\xf6\ +\x37\xd4\x90\xf2\x57\xfc\xbb\xa1\x1d\x4e\xf7\xde\x58\x05\x26\x8e\ +\x1b\x83\x49\xe3\xc6\x12\x10\xf8\x91\xce\x64\x61\x94\x4c\xd4\x54\ +\x56\x61\xe2\x98\x3a\x4c\x18\x55\x87\x42\xe4\x4b\x30\xfc\xb3\xed\ +\x3a\x01\xb6\xec\x65\x36\x21\x8b\x85\x5c\x0e\xed\x77\xdb\x8a\xcf\ +\xd3\x7d\x34\x72\x19\x1c\xb0\xb0\x3a\xdf\x82\x11\x6f\x86\x51\x2c\ +\xd8\xe5\x40\x2e\x72\x17\x0a\x9d\xc4\x54\x3a\xf0\xaf\x87\x7e\x8c\ +\x73\xcf\x39\x17\xf7\xff\xed\x7e\xf9\x28\x9e\x0a\xac\x44\xc5\x00\ +\x94\x7c\x50\xe5\x67\xcb\xcf\xca\x4f\xbf\x77\xa7\x52\xd8\xd8\xde\ +\x26\xe3\xba\x2a\x79\x06\x9f\xb9\x7d\x5f\xd2\x23\x6d\xbc\xa2\x32\ +\x7a\x9b\xe7\x01\xd4\x54\x56\xa2\xbe\xb6\x16\xf9\x37\xde\x24\x00\ +\x28\xa1\xb1\xa1\x0e\x75\x55\x55\xf0\x79\xb8\xab\x70\x23\x72\x91\ +\xcb\x11\xe8\xfc\x0f\x29\x05\x16\x85\x67\x57\x40\x73\x7c\x7d\x38\ +\x20\xa0\x3b\x81\x40\x97\xbd\x59\x9d\xcb\x50\x6c\x59\x49\xcf\xe9\ +\xf4\x5f\x11\x7a\x21\x0f\xcb\x1f\xb6\xb3\x0a\xd9\x16\x94\xf2\x05\ +\xbc\xf4\xe2\x2b\xc4\x38\xb2\x48\x26\xe3\x38\xe9\xa4\x93\x11\x8e\ +\x44\xa4\x75\xb8\x12\xc5\x00\x94\x6c\x47\xf9\x35\x51\xfe\xa4\x58\ +\x7e\x4e\xf5\x35\x33\xed\xe7\x3c\x7f\x24\xba\xfd\x40\x92\x05\xf1\ +\xf5\x63\xb1\x98\x00\x47\x0f\xb1\x06\x1f\xf9\xfd\xdc\x2c\x24\x93\ +\x2f\xa1\xa6\xaa\x1a\x63\x46\xd5\x0b\x8b\xe0\xb1\x62\xf6\xc4\xdf\ +\x02\x4a\xae\x03\x50\x0c\x7f\x9a\x94\xb8\xc3\x0e\xf6\x95\x69\x04\ +\x2b\x3f\xff\xae\x95\x59\x80\xad\xfc\x66\xf7\xbb\x28\x6c\x7c\x1d\ +\x26\xb1\x09\x0e\x34\x5a\x66\x09\x56\x29\x6f\x03\x88\xaf\x02\x20\ +\xe5\x9f\x33\x39\x8b\x39\xd3\x3c\xd8\xd8\xdc\x85\x87\x1e\xfe\x3b\ +\x7e\xfc\x93\x1f\xe3\xbd\x77\xdf\x95\xee\xc2\x5c\xcb\xa0\x44\x01\ +\x80\x92\xcd\x44\x14\x83\xf3\xfb\x64\xf9\xd9\x0a\x77\x93\x02\x37\ +\x73\xc0\xaf\xaa\x52\x26\xf1\x98\xd8\xde\xa0\x4e\x48\x0d\x00\xd3\ +\x7e\x8d\x7e\x61\xf6\xc0\xfe\x3f\x8f\x09\x93\xd6\xdd\xf9\x1c\xb1\ +\x82\x08\x6a\x89\xfe\xbb\xf5\xfe\x83\x3a\x4d\xa9\xe2\x2b\x06\xce\ +\x80\xe9\x69\xb4\x73\xfc\xa2\xf0\x96\x7d\x57\x30\x13\x20\xfa\x6f\ +\xb9\x1d\x17\xa0\x6b\x05\x8a\xeb\x5e\x82\x25\x95\x82\x7a\xef\xdf\ +\x16\x20\x28\xd9\x19\x08\x78\x2b\x51\x53\xed\xc1\x09\xfb\x16\xd0\ +\xd3\x1d\x47\x4b\x6b\x07\x56\xad\x5e\x8d\xeb\x6f\xf8\x21\xee\xbe\ +\xeb\x4e\x64\x33\x19\xf8\xe8\xd8\x54\x80\x50\x01\x80\x92\xb2\x0f\ +\x46\xca\xc5\x0a\x9e\x48\xd8\x3e\x7f\x9c\x18\x40\x73\x7b\xbb\x50\ +\x75\xa6\xfd\xdb\x1b\xf9\x6b\x3a\xca\x5f\x55\xc9\xca\x6f\xd2\x67\ +\x24\x64\x5a\x8f\xdf\x99\xd2\xcb\x03\x40\x39\x83\x10\xf0\x7b\xe1\ +\x96\x32\xe2\xcd\x3f\xab\x08\x43\x9b\x46\x2c\xe0\x54\x80\x27\x06\ +\x99\xce\x70\x10\xa7\x0e\xc0\xe2\x5c\x3f\xd7\x07\xb5\x2f\x45\x61\ +\xc3\xab\x76\xb2\x40\x77\xf7\xc5\x0a\xb8\x24\x91\x63\x12\xc2\x04\ +\x8a\x74\x1e\x6e\xb8\x2b\xaa\xb0\x70\xdf\x2c\x0e\x99\x03\xbc\xb7\ +\xaa\x09\xf1\xce\x2e\xa9\x4f\xb8\xfb\x9e\xbb\x71\xfd\xf5\x3f\xc0\ +\x6b\xaf\xbe\x22\xc7\xa4\x62\x03\x0a\x00\x76\x79\xca\xcf\x4a\xc0\ +\xbe\x71\x9c\x95\x9f\x53\x7d\x64\xbd\x9b\x3b\x3a\xc8\xe7\xaf\x46\ +\x4c\x46\x74\x63\xbb\x01\x3f\x6e\xdd\x5d\x4d\x96\xdf\x12\xe0\xe8\ +\x11\xab\x1f\x08\x04\x91\x64\xe5\x2f\x94\x44\x3f\x7d\xb4\x8f\x4b\ +\xdb\x56\xf7\x5e\x7b\x39\x70\xd1\xfb\x69\x58\x81\x3d\x88\xc2\x93\ +\x2b\x60\x95\x9c\x03\xf4\x0b\x28\x58\x2d\x2f\xc2\x68\x5d\x66\x57\ +\x08\x96\xd7\x08\x70\xb1\x90\xe3\xb2\xf4\xa3\x02\xc2\x0e\x74\x5f\ +\x18\xe3\xa6\x84\x70\xde\xc2\x2e\x4c\x6d\xd4\xb0\x7c\xe5\x5a\x74\ +\x10\x9b\x09\x84\xc3\x58\xb3\x7e\x3d\x7e\xf4\xe3\x1f\xe3\x77\xbf\ +\xbb\x09\x2d\x2d\x9b\xc4\x2d\xd0\x75\xe5\x16\x28\x00\xd8\xc5\x14\ +\x9f\x87\x7a\x72\xb5\x1e\xe7\xea\x79\xbc\xb6\x5b\x94\x3f\x81\x16\ +\xb2\xfc\x8d\x35\x35\x42\xfb\xb7\xe7\xf3\xcb\xac\x3e\xfa\x8c\x2a\ +\x56\x7e\xd8\xeb\x03\x58\xf9\xed\x89\x3d\x59\x49\xfb\xb1\xf2\xfb\ +\x49\xf9\xdd\xff\xb6\x93\x0f\x5b\xee\x46\x62\x01\xa7\x90\x07\xe0\ +\x25\x10\xe8\x06\x4a\xc4\x06\x32\xeb\xa1\xb5\xbd\x02\x2b\xd9\xea\ +\xd4\x03\xb8\x1d\x66\xc0\xab\x08\xdd\xb4\x2f\x3d\xc2\xd5\x0b\x02\ +\x72\xbc\xb4\xf1\x7c\xd1\x70\x4d\x1d\xf6\x99\xa7\xe1\xa2\x63\x93\ +\x88\xfa\x35\xac\x59\xbd\x01\xad\x9b\x5a\xe5\xbc\x83\x91\x30\x1e\ +\x7f\xea\x49\x7c\xf7\xba\xeb\xf0\xe0\xfd\x7f\x45\x2e\x97\x11\x20\ +\x50\x6e\xc1\x20\x31\x50\x75\x09\x76\xbc\xf0\xcd\x5d\x4e\xef\xf1\ +\x38\xef\x6c\x26\x2d\x33\xf5\xd8\x00\xb3\x15\x6f\xed\xec\x46\x5b\ +\x77\x37\x46\xf1\x88\xee\x48\xc4\x09\xd2\x6d\x47\xf9\x1d\x9f\xdf\ +\x72\x7c\xfe\xf2\xac\x3e\x6e\xe0\x59\x28\x16\x65\xbd\x80\xef\x03\ +\x29\x7f\xf9\x43\x4d\x14\x5c\x47\xc0\x1d\x7e\x02\x7a\xf7\x13\xd0\ +\x72\x9b\x48\xe9\x03\xe2\xfb\xeb\xa1\x18\x50\xc8\x42\x33\x4a\x03\ +\xa7\x07\x5b\xc4\x02\x5c\x9a\x64\x03\xca\xba\xeb\x92\xb5\x44\xb4\ +\x8f\xdb\x8b\xea\xf1\x8d\x38\xea\xc0\x26\xb4\xc5\x73\xf8\xe5\xfd\ +\x3e\x34\x6d\x6c\x92\x89\xc5\x91\x8a\x28\x62\x15\x31\xa4\xf3\x39\ +\xfc\xe1\xe6\x5b\xf0\xf2\xcb\xaf\x60\xd1\xe2\xc5\x98\x31\x63\xa6\ +\xb0\x14\x1e\x3e\xaa\x44\x01\xc0\xf0\x56\x78\x68\x7d\x55\x39\xb0\ +\x07\x79\xe6\x72\x39\x14\xc9\x32\x17\x4b\x76\xb5\x9d\x4e\xb4\x9c\ +\x3b\xed\xb6\x77\x27\xb0\xa9\xa3\x13\xa3\xea\x6a\x11\x65\xe5\x37\ +\xb7\x1d\xf2\xb3\x27\xf6\x78\x50\x59\x19\x13\x65\xe2\x8c\x41\xc0\ +\xef\x93\xf6\xdd\x89\xa4\xad\xfc\xba\xfe\x21\x95\xdf\x86\x25\xfa\ +\xbc\x5a\x14\x82\xe7\xc2\x6f\xac\x03\x52\xef\xdb\x77\x86\x27\x42\ +\xac\x9f\xac\x33\x29\xb4\xcb\x28\xc0\x34\x8a\x36\x08\xf4\x1e\x23\ +\xbb\x02\x9a\x64\x0c\xdc\x6e\x0d\xe9\xee\x34\x3a\x7b\x48\xf9\x6b\ +\x5c\x08\x55\x05\x51\x37\xa1\x0e\x9f\x3d\xa2\x05\xad\xf1\x4a\xdc\ +\xf6\x94\x45\xfb\xb4\x49\x2a\xb3\x44\xee\x49\x94\x5c\x9c\x6a\x02\ +\xbc\x15\xab\x56\xe1\x86\x1f\xdd\x80\xe3\x8f\x3d\x0e\xc7\xd0\x16\ +\xe1\xf1\xe3\xa5\x92\x6a\x3b\xbe\xab\x03\xc0\x70\x5a\x0b\x20\xf4\ +\x55\x13\xb5\x67\xc3\x28\xc1\x39\x1e\xa6\xc9\x93\x75\x78\xa2\x6e\ +\xc9\x30\x85\xaa\xf3\xf9\xd8\xbb\xea\x12\x08\xe3\x20\x5d\x73\x7b\ +\x07\x2a\xc2\x21\x84\x83\xc1\x5e\xdf\x7e\xdb\x96\x9f\x95\xbf\x42\ +\xfe\x80\x28\x7f\xc0\x4b\xd4\x3f\x88\x78\x2a\x83\x02\x29\x0d\x2b\ +\xbf\xdf\xe3\x22\xe5\xff\x28\xf5\xe7\x45\x14\xb4\xfd\x80\xc8\x97\ +\xe1\xd3\x6e\x82\x96\x5c\x6e\xc7\x03\x3c\x15\xa4\xdf\x3e\x9b\x0d\ +\xd0\xb9\x80\x03\x7e\x62\xa5\x4d\xbb\xf4\x98\xce\xc5\xe3\x75\x61\ +\xcd\xaa\x34\xfe\xe7\xce\x18\x56\x6e\xaa\x25\x30\xf3\xe1\xe2\x53\ +\x53\x58\x30\xdf\xc2\x98\x62\x01\x17\x1c\xdb\x85\xd6\xee\x1a\x3c\ +\xf6\x5a\x86\x5c\x97\x2e\x39\x49\xbe\x3e\xc1\x50\x48\x80\x80\xad\ +\xfe\x5d\xf7\xde\x8b\xe5\x2b\x96\xe3\xcc\x33\xce\xc2\x94\x69\xd3\ +\x04\x28\x07\x30\x8e\x61\x2c\x43\xf9\x3e\x56\x0c\xe0\x23\x5b\x79\ +\xbb\x29\x67\x59\xf9\xcb\x4a\x2a\x8a\x4f\x8a\x62\xca\xcd\xab\x49\ +\x90\xcb\x43\x8f\x0c\x0c\x7c\x53\xf3\xd3\x9a\x13\x0b\x48\x67\x53\ +\xf2\xc8\x43\x3d\xf5\xed\x68\x7f\x99\xf6\x57\x89\xe5\xb7\xa4\x4a\ +\xd0\x2f\x96\x3f\x88\x1e\xa1\xfd\x76\xb4\x9f\x95\x9f\xe9\xff\x47\ +\xbb\xdf\x38\x2d\xa8\x13\x08\x1c\x03\x33\x5c\x07\xbf\xfb\x0f\xd0\ +\x7b\x9e\x85\x5e\x8a\x43\xf3\xc7\xec\x62\x20\xcb\x4d\x4a\xc9\xa9\ +\x41\xd3\xa9\x48\xb4\xe8\xef\x59\x78\xff\xdd\x1e\x5c\xfd\x4b\x1f\ +\x5e\x5f\x13\x41\xc8\x9b\xc1\xfa\x8d\x39\xac\x6d\x8a\xe1\x3f\xbf\ +\xec\xc6\x81\xfb\x5a\x98\x4a\xee\xc3\x97\x4e\xee\x44\x47\x4f\x2d\ +\xde\x58\x6d\x9f\xb3\x69\x46\x6c\x70\x2c\x15\x11\x0a\x05\x51\x53\ +\x5b\x2b\x6c\xe0\xc6\x1b\x6f\xc4\xe9\xa7\x9f\x86\x03\x3f\x75\x90\ +\xc4\x1b\x94\x4b\xa0\x82\x80\x43\x0f\x35\xc9\x1a\x7a\x7d\x7e\x09\ +\x6a\x71\x4e\x9c\xd7\xe0\xb3\x05\x66\x0a\x5e\x2a\x99\x52\x89\xc7\ +\xd6\xbf\x48\x37\x77\x2e\x57\x40\x36\x9f\x17\x17\x80\xab\xf1\x34\ +\xe7\x3f\x59\x47\x43\x2e\x00\x2b\x36\x2b\x2f\x8f\xfa\xde\x5a\x91\ +\x2f\x03\x06\x07\xfc\x84\xf6\xc3\xee\x06\xc4\x80\x11\x24\xa5\xb1\ +\x47\x74\x17\x6d\xe5\x77\xdb\xca\xff\xf1\xc4\x94\x5e\x00\x25\x6b\ +\x6f\x64\x02\xdf\x46\xb1\xfa\x02\x3a\x37\x37\xf2\xf1\x4d\xd0\x72\ +\xdd\xd0\x4a\x69\xfa\x5b\x26\x74\x3a\x1e\xee\x2b\xe8\x62\xa7\x9f\ +\xce\xf3\xb6\x87\xf2\xf8\xc7\x9b\x41\x54\x04\x8a\xc4\x3e\x48\xa1\ +\x7d\x39\x74\xb4\xb5\xe1\xdb\xff\xed\xc3\x3f\x5f\xa8\x45\x60\x42\ +\x2d\xe6\xcc\xf5\xe3\xab\x9f\xed\xc2\xc4\x7a\x1d\x9b\xda\x7a\x90\ +\xcd\xa6\x91\x49\xa7\xa5\x68\x29\x91\x48\x22\x95\x4c\xc9\x3c\xc2\ +\x1c\xb9\x19\x37\xfd\xf6\xb7\xb8\xf7\x9e\xbb\x50\xa2\xeb\xa9\xd2\ +\x85\x0a\x00\x86\x94\x78\xb8\xa2\x4d\x72\xf7\xa6\x58\x74\x4e\xe3\ +\x95\x78\x23\x2b\xcc\x8f\xf9\x52\x41\x80\xa0\x58\xe4\xc7\x82\xac\ +\xcf\x67\x75\x97\x40\xa0\xe3\x26\xc8\xb2\x5d\xb2\xa0\x21\x7f\x00\ +\x41\x02\x92\x32\x60\x98\x5b\x59\xdd\xe7\xf5\x92\xe5\xaf\x8a\x49\ +\x17\xdf\x44\x3c\x8e\x10\x47\xfb\x43\x01\x19\xd4\xc9\x81\xc4\x32\ +\xed\xff\xe4\xfa\xf6\xdb\x55\x82\xa6\x59\x0f\x33\x70\x29\xee\x78\ +\xfa\x30\x1c\x7d\x5e\x0a\xdf\xfa\x51\x12\x4f\x3d\xbc\x09\x9b\x56\ +\xae\x23\x40\x68\xa7\x53\x28\x49\x2a\xd0\xa2\xe3\xea\xe8\x26\x06\ +\x82\xf2\xec\x00\xe1\x37\xf0\x79\x2d\x74\x92\x7b\x73\xcd\x4f\xbd\ +\x78\xfa\xf9\x06\x84\x26\x36\xe2\x80\x7d\x74\x5c\xb9\xb8\x1b\x95\ +\x21\x0d\x2d\x6d\x71\xb9\x76\xf9\x6c\x4e\x66\x10\x26\x89\xd5\x24\ +\xe2\x09\x78\x5c\x9c\x29\x88\xe0\xae\x25\x4b\x70\xf3\x9f\xfe\x48\ +\x40\x91\x55\x20\xa0\x00\x60\xf0\x7d\x38\xa6\xad\x3e\xbf\x5f\x2e\ +\x19\x2b\x38\xa7\xda\x8a\x85\x92\x58\xf9\xa2\x61\x48\xe0\x8a\x1f\ +\xb9\x20\xc6\x28\xf2\xf3\x36\x75\x65\xa5\xd0\x1c\xc5\xe7\x8d\x57\ +\xe7\x95\x3f\x93\x2b\x00\xb9\xce\xdf\x23\x8b\x6d\x9c\x75\x38\x9a\ +\xb3\x3f\xa7\xf1\xfc\xde\x5e\xda\x1f\xef\xe6\x54\x9f\x53\xe4\xc3\ +\xca\x5f\x2c\x0e\xa0\xfd\x9f\xbc\x14\x09\x90\x7c\xf0\x54\x1e\x8b\ +\xb5\xf1\xc9\xb8\xe9\x81\x0a\x5c\x72\x63\x14\x37\xfe\x31\x88\x57\ +\xfe\xd9\x81\x8e\x35\xeb\x61\xe4\xb3\xd0\xc3\x41\x1c\xb9\x9f\x07\ +\x85\x6c\x37\xb1\x11\xbb\x36\xa8\xec\x24\xf9\x09\x04\xe2\x9d\xed\ +\xb8\xe6\xbf\xbd\x78\xea\xf9\x51\x88\x4e\x6e\xc0\xe1\x07\x18\xb8\ +\x72\x11\x9d\x8b\x47\x43\x5b\x7b\x5c\x5c\x80\x62\xbe\x80\x14\xb1\ +\x81\x24\xb1\x01\x76\x6f\xf8\x5a\x56\x56\x56\xe2\xb1\x27\x9f\xc4\ +\x1f\x7e\xff\x3b\x64\x32\x69\x05\x02\x3b\x48\x5c\xd7\x5e\x7b\xed\ +\x90\x3a\xa0\xf7\xde\x7b\x8f\x2d\xc3\xe7\x76\xdf\x7d\xf7\xf1\xd2\ +\xe4\x62\x90\x85\xad\x32\x5b\x7c\x2e\x61\x65\x4b\x5d\x22\xab\xce\ +\x37\x28\x53\x7d\xd3\xb0\xe9\xbe\x3c\x96\x6c\x0b\x2e\x3f\xf3\x7b\ +\x74\xdb\xe2\x97\xe3\x05\x76\xe1\x8e\x87\x14\x38\x28\xcc\xc0\x76\ +\x04\x20\xa3\xb6\xbd\xd2\x95\xd7\xae\xe7\xe7\xcc\x00\x57\xed\x31\ +\xd8\x70\x56\x40\x52\x7d\x4e\x9e\x9f\x47\x74\x97\x53\x7d\x3b\x56\ +\xf9\x1d\xe0\xa3\x73\x9b\x34\x61\x2c\xc2\x21\x1d\x71\xb2\xfa\xe9\ +\xbc\x86\x67\xdf\x2c\x60\x6d\x4b\x18\x95\xee\x0c\x62\xbe\x6e\x3a\ +\x26\x2f\xa6\xef\x51\x89\x42\x22\x8e\x47\x9f\x2f\x20\x12\x0d\xdb\ +\x41\x48\xe7\xcc\xe9\x10\x91\x49\xa5\xf1\xdc\x1b\x11\x4c\x18\x1b\ +\xc1\x8c\x39\x06\x46\x07\xbb\xe1\x25\xe6\xf0\xd2\x0a\x2f\x32\xd9\ +\x02\x9d\x9b\x4f\xae\x50\xd1\x89\xfe\xcb\x35\x27\xc0\x0d\x47\x23\ +\x78\x97\xee\x87\xce\xb6\x56\xcc\x9a\x35\x5b\xbe\x03\x73\x18\x06\ +\x06\xb9\xce\xe1\xe6\x9b\x6f\x5e\xbd\x7e\xfd\xfa\x77\xe9\xd7\xb5\ +\xb4\x75\x9e\x77\xde\x79\xc6\x84\x09\x13\x54\x10\x70\xa8\x5b\x7e\ +\xb6\x3c\x1c\x7d\x67\x8a\x5f\x60\xeb\x4e\x56\xde\x34\x2c\x19\x9f\ +\xcd\x29\x2d\x01\x00\xde\x2c\x3b\x35\xc6\xd6\xdb\xad\xf5\xe5\xc6\ +\xcb\xea\xc9\x37\x34\xef\xc3\x3e\xaf\xf4\xf0\x13\x73\xef\xdc\xec\ +\xa4\xf4\x2e\xdd\x5e\x2c\xc3\x75\x01\x6e\xba\x61\xbc\x6e\x2f\x4a\ +\x46\x49\xa6\xf4\x72\x9e\xdf\x47\xee\x42\x77\x22\x25\x2c\x83\xd9\ +\xc8\x27\xe3\xf3\x7f\x80\xf3\xa7\x73\x3f\xfd\xb4\x33\xd1\xd2\xd2\ +\x86\xb7\x96\xbd\x4d\xa0\xe4\xc7\x8b\x2b\x9b\xd0\x1a\xaf\xc6\x85\ +\x64\xad\x8f\xca\x6f\xc4\xe8\xdd\xea\x71\xcd\x55\xa3\x51\x32\x9b\ +\xf0\xbf\x77\xb4\x61\xc2\xf8\x06\x3a\x1f\xc3\xa9\x64\xd4\xc4\x1d\ +\x48\x11\x80\x5c\xfb\x3f\xb5\xc8\x5e\x32\x09\xc7\x2d\x28\x62\x71\ +\xbe\x85\x5c\x18\x1d\xbf\x7c\x20\x80\xae\xae\x38\xaa\x6b\x2a\x25\ +\xbb\x91\xc9\x64\xe5\xba\x32\xf8\x70\x90\x90\x17\x38\x3d\xff\xd2\ +\x4b\x02\xc2\xe7\x9f\x7f\x21\xbc\x0c\xc4\x2a\x30\xa8\x00\x60\x67\ +\x59\x7e\x56\x7e\xf6\xb5\xb9\x80\xc7\xe0\xcd\xb2\x2d\x3e\x2b\xb3\ +\x28\xbe\x69\x47\xc3\x59\xa9\xb9\x55\xb7\xab\x9f\x53\x25\xc1\x3e\ +\xfa\x1c\x9f\xcf\x2b\x16\x9d\xa3\xf5\x9a\x56\xea\x05\x16\x8e\x13\ +\x58\xce\x12\x5f\x4d\xe2\x02\xa6\x54\x08\x6a\x64\x09\xd3\xec\x1f\ +\xe7\xf3\x62\xf9\x7d\x3e\x3f\x9a\x5b\x3b\xe4\xc6\xe7\xba\x7e\x9f\ +\x7b\xe7\x0d\xea\x94\xbf\x49\xcc\xe3\xcc\x33\xce\xc4\xf5\x37\xfc\ +\x80\xac\x99\x4f\x5c\x96\x55\xef\xaf\xc7\x7f\x2d\x09\xa3\xbd\xc7\ +\x23\xca\x3c\x61\x46\x11\xd7\x7e\xb3\x91\xae\xd3\x26\xfc\xe2\xae\ +\x16\x4c\x99\x54\x4f\x00\x65\xc2\x3e\x3d\x8d\xde\x67\xa1\xa7\xab\ +\x1d\x3f\xfc\x75\x3d\xf9\x09\xd3\x71\xdc\x9e\x45\x9c\x56\xe8\x44\ +\x22\xa3\xe3\x4f\x8f\xf9\xa0\x75\xc6\x51\x55\x5d\x29\xcc\x26\x97\ +\xcd\x3a\xa0\x6a\x09\x28\x44\xa3\x51\x3c\xff\xaf\x7f\x49\xbd\xc3\ +\x59\x67\x9f\x23\xc1\x47\x05\x02\x0a\x00\x76\xb8\xcf\x2f\xca\x4f\ +\xfe\x29\x53\x53\x43\xf2\xfa\x46\xaf\xd2\x1b\x96\xe1\xe4\xc1\x6d\ +\xaa\xaf\x43\x43\x7f\x83\x5c\x2e\x06\xe2\xd7\x58\xf9\x93\x99\x0c\ +\x36\x36\x35\x93\x35\xf4\x0a\xd5\x67\xe5\xe6\x00\xde\xa8\xfa\x7a\ +\x09\x12\x4a\x01\x10\x37\xde\x75\x02\x88\x70\x5c\x86\x00\x59\xff\ +\x75\x4d\x2d\xe4\xf7\xa7\x89\x8a\x07\xa5\xc8\xc7\xe3\xd2\x77\x6a\ +\x6e\x99\x83\x75\x13\x26\x4e\xc4\xe2\x93\x17\xe1\xb7\xff\xf7\x7b\ +\x34\x8e\x19\x25\xec\x65\xdd\xfa\x66\xfc\xe6\x01\x9f\xb0\x81\xcf\ +\x65\xba\x88\xde\x97\xf0\xbd\xab\x6b\x09\xc4\x3a\xf0\xeb\xbb\x5b\ +\x31\x65\x72\xbd\xa4\x09\xed\xe0\xa6\x0d\x02\xa9\x78\x1b\x7e\x7c\ +\x53\x3d\xf4\x4b\x66\xe2\x98\xd9\x6f\xe2\xfc\x52\x37\x31\xab\x4a\ +\xdc\xfa\x04\x5f\xcb\x38\x2a\xab\x79\x49\xb3\x2e\xd7\x87\x01\xb1\ +\xdc\x00\x25\x10\x0e\xe1\xa9\x67\x9e\xa1\x6b\x10\xc6\x29\x8b\x16\ +\x3b\xa9\x44\x35\x9d\x48\x01\xc0\x0e\x01\x00\x5e\x44\xe3\x95\xe0\ +\x94\x44\xf4\x85\xf6\x1b\xbd\xfe\x3e\xca\x51\x7d\xbd\x4c\xf1\xb7\ +\xae\xfc\x70\x7c\x7f\x56\x68\x0e\x0c\x56\xc7\x2a\xe1\x27\x36\x90\ +\x4e\xa7\xe1\x8f\x84\x51\x53\x55\x25\x37\x39\xbb\x05\xdc\xb9\xc7\ +\x74\x0a\x85\xca\x9d\x79\x43\xa1\x10\x36\xb5\x75\x60\x53\x6b\x2b\ +\x42\xc1\x10\xa2\xec\x0a\x10\x28\x0c\x46\x61\x09\x03\xd3\x81\x07\ +\x1d\x8c\x57\x5e\x7d\x0d\x6f\xbc\xb5\x14\xf5\xf5\x0d\x42\xef\x5b\ +\x36\xb5\xe0\xce\xa7\x81\x4d\x5d\xb5\xf8\x52\xb6\x03\xf3\xe7\x97\ +\x70\x3d\x81\x80\x85\x4e\xfc\xfa\x1e\x02\x81\x89\x75\x70\xbb\x34\ +\xa7\x99\x89\x46\xec\xc1\x42\xa2\xbd\x15\x3f\xfa\x0d\x01\xdf\xc5\ +\xb3\x70\xf4\xbc\x37\x71\x91\xd9\x45\x2e\x56\x25\x6e\x7b\x86\x63\ +\xac\x71\x49\x79\x72\xbb\x91\x42\xbe\x60\x57\x56\x95\x83\x8a\x01\ +\x3f\x1e\x7a\xe4\x61\x44\xe8\xda\x1d\x7d\xec\xf1\xaa\x71\xac\x02\ +\x80\x1d\x40\xfd\x49\xc9\x3d\x44\x73\xd9\xff\xce\x39\xd4\x9f\x23\ +\xd5\x86\x73\xb3\x49\x60\xcf\xb2\x33\xf9\xe5\xd8\xfe\x40\xe5\xc7\ +\x16\x33\x32\x0a\xf9\xa2\x7c\xae\xcf\x6b\xbb\x13\x41\xa2\xd4\xa3\ +\x1a\xea\x91\x24\xab\xbe\x6a\xdd\x7a\x44\xc9\xaa\x85\x02\x41\x72\ +\x07\xfa\x68\x2d\xd7\xf6\xf3\xdf\x5f\xb5\x76\x83\x44\xd6\x63\x91\ +\x10\xed\xe3\x1f\xb4\x1b\x9e\x8f\x9f\xfd\xef\xc5\x8b\x17\x61\xc5\ +\x8a\xe5\xd0\xdd\x2e\x54\xc6\xec\xb6\xe4\x1e\x57\x07\xfe\xf1\x66\ +\x06\x9d\xc9\x5a\x7c\x35\xdd\x85\x83\xf7\x6f\xc1\xf5\xdf\xa8\x25\ +\xc5\x8f\xe3\x97\x77\xb5\x61\xe2\xf8\x06\x52\x7c\x73\x00\x08\xc4\ +\x09\x04\x6e\xf8\x4d\x1d\xac\x8b\x66\xe3\x98\x3d\x97\xe2\x62\x83\ +\x40\xc0\xac\xc2\xdd\xff\x84\x0c\x2d\x89\x55\x56\x48\x11\x55\xb1\ +\x98\x47\x3a\x65\x39\x60\x6a\x49\xa3\x93\x7b\xef\xbb\x0f\xd1\x8a\ +\x18\x0e\x38\xf0\x20\x09\xa8\x42\x81\xc0\x47\x16\x95\x06\xdc\x8c\ +\xfa\xb3\x7f\xc9\x0a\x9d\x2b\xe4\xed\x74\x9f\xe4\xf2\x6d\xab\xef\ +\xb2\x1d\x75\x09\xda\x79\x39\x2d\x65\x61\x2b\xca\xbf\xb9\x6f\x6e\ +\x17\xfd\xf0\x7b\xba\x13\x09\xa4\x32\x69\xb2\xe6\x41\x24\x93\x29\ +\xac\x5a\xb3\x56\x9a\x77\xf0\x92\x5e\xa3\x9f\xf2\xf3\x3a\x01\x3f\ +\x3d\xbf\x66\x7d\x13\x12\xc9\x1e\xa2\xbd\x01\xd9\x67\xb0\x6f\xf4\ +\x22\x01\xd2\xa4\xc9\x53\x70\xcc\xd1\x47\x23\x1e\x8f\x23\x52\x11\ +\x41\x38\x1c\x46\x75\x4d\x2d\x1a\xea\xa2\x78\x67\x8d\x85\x6b\x6f\ +\xae\xc6\x83\x8f\x5b\xe8\x5a\xdb\x82\xef\x7e\x25\x84\x2f\x9d\x9a\ +\xc6\xba\x0d\xad\xe4\x46\xa1\x37\x2b\xc2\xd7\x48\x40\xa0\xa3\x15\ +\xd7\xff\xc6\x8b\x07\x97\xce\xc5\xac\x79\x31\x5c\xf2\xe9\x6e\x9c\ +\x72\x60\x91\xce\xb9\x80\xee\xee\x04\x01\x46\x49\x98\x10\xb3\x0f\ +\x66\x4d\xe9\x54\x8a\xc0\x34\x4f\xe0\x6c\xe0\x2f\xb7\xfd\x05\xef\ +\xbc\xfd\xa6\x44\xd8\x95\x8c\x30\x06\x30\x58\x73\x01\xc4\xc2\x93\ +\xa2\xe6\x88\x7a\xe6\x73\xce\xc2\x1d\x58\x7d\x4d\x73\xb9\xe3\x2e\ +\xf9\xe1\x9c\xe3\xe7\xd7\xb8\x28\x27\xcf\x69\x41\x8e\xcc\x6b\xfa\ +\x56\xa6\x63\x39\x85\x3f\x5c\x48\xeb\x72\xa1\xae\xba\x5a\x02\x82\ +\x5c\x1d\x98\x27\x80\xa9\x20\x2a\x1b\xe3\x8e\x3f\x8e\x2f\x6b\xf3\ +\x0a\xb2\xa8\xc4\x14\xd2\x99\x1c\x56\x6f\xd8\x00\x0f\x59\xc1\x7a\ +\x72\x15\x18\x40\x0c\x63\xf0\x7d\x5e\x66\x44\x47\x1e\x75\x14\x5e\ +\x7e\xf5\x15\xb4\xb4\xb5\x91\x25\xae\x70\xe2\x22\x06\x5c\x6e\x1d\ +\x1b\x5b\x7a\xf0\xbd\xbf\x54\x22\x91\xe9\xc1\x89\x87\x75\xe0\xda\ +\x2f\x55\xc1\xe3\x49\xe1\xbf\x6f\xb7\x30\x76\x54\x1d\x01\xa7\xd6\ +\x1b\x13\x60\x10\x48\x76\x11\x08\xfc\x96\xdd\x81\xb9\x38\x7a\xfe\ +\xeb\xb8\xd8\xe4\xb5\x02\x55\xb8\xef\x79\x66\x1d\x71\x54\x55\x55\ +\x08\x28\xf3\x42\xaa\xb4\x95\x16\xf0\x88\x10\xe8\x70\x4c\xe5\x0f\ +\x7f\xfa\x03\xbe\xf4\xc5\x2f\x61\xec\xb8\xf1\x43\xba\xe7\xe0\x50\ +\x76\x55\x14\x03\xd8\x2c\xf0\xc7\xca\x9c\xcb\xe5\x85\xf6\xdb\xb6\ +\xdb\x12\xc3\xcb\x16\x39\x1c\x0e\xc9\xca\xbd\x27\xff\xf5\x12\x1e\ +\x7c\xea\x1f\x78\x7d\xc5\x4a\x59\xd0\xc3\x5d\x78\x78\x55\xdc\xc0\ +\x52\x5e\x6d\xa0\x3b\xe0\xf4\xf1\xe7\xcf\xe6\xc9\xbe\x49\xb2\x68\ +\x59\x02\x01\xb1\xfc\x9a\xbd\x77\xb9\x8e\xc0\x4f\x56\x6d\x43\x73\ +\xb3\x34\x0a\x89\x91\x95\x65\xa0\x18\x2a\x01\x2f\x83\x5c\xa3\x58\ +\xac\x12\x27\x9f\x78\xb2\xc4\x48\xb8\x23\x11\xc7\x2a\xfc\xbc\x24\ +\x99\x5d\x9b\xc6\x0a\x74\x93\x9e\xfe\xd7\x3d\x51\xdc\xf6\x90\x1f\ +\xeb\x57\x74\xe0\xea\x8b\x7d\xb8\xe2\xd4\x14\xd6\x37\xb5\xa3\x68\ +\x0c\x2c\x16\x62\x10\x48\x77\x93\x3b\xf0\x5b\x2f\x1e\x5b\x31\x17\ +\xb3\xf7\x0e\xe3\x82\xe3\x3a\x71\xea\xc1\x36\x08\xf3\x6c\x03\xfe\ +\x9b\x7c\x7d\x38\x15\x9b\x65\x26\x40\x1b\x67\x4f\x5a\x5a\xdb\x71\ +\xfb\xed\xb7\x23\x45\xcc\x40\xf5\x1b\x54\x00\xf0\xf1\x00\xc0\xe6\ +\xde\xc8\x92\x82\x72\x75\x9f\x3d\x5c\xcb\x92\x4c\x40\x94\x14\x30\ +\x4b\x16\xe6\xd5\xb7\x97\xe1\xc5\x37\xde\x42\x36\x9b\xc3\xf8\xd1\ +\x8d\xa8\x21\x1f\xb8\xa9\xb5\x15\xef\xae\x5d\x2f\x2b\xfe\x58\x71\ +\xed\xd4\xdf\x40\xe5\xd7\xfa\x51\x03\x66\x13\xd5\x95\x31\x7b\xa8\ +\x27\x59\x4f\x59\x4c\x64\xd9\x69\x47\x8e\x0f\x30\x88\xf0\xcf\xeb\ +\x9a\x5b\x24\x17\xce\x83\x40\x74\x19\x0c\x3a\x74\xae\x15\x53\xf2\ +\x79\x7b\xcd\xc7\x82\xbd\xf7\x46\xbc\x3b\x8e\x10\x01\x63\x40\xd2\ +\x95\x3e\x89\x13\x34\xd4\xc7\x90\xcd\x03\x3f\xff\x5b\x18\xb7\x3c\ +\x14\xc2\xea\xe5\x5d\xb8\xfa\x42\x0f\xbe\xb4\x38\x89\xb5\xeb\xdb\ +\xc9\xd7\xd7\xb7\x00\x81\x04\x31\x81\x1b\x7e\xe7\xc7\x53\xab\xe6\ +\x62\xee\x3e\x61\x9c\xb5\xb0\x0b\xa7\x1d\x5c\x14\xcb\xdf\xd1\x19\ +\x97\x98\x0c\x5f\x3c\xae\xc0\x4c\xa7\x33\x52\x5c\xc4\x80\xfd\xe6\ +\xb2\x65\xb8\xe7\xee\xbb\x04\x20\xd5\x1c\x02\x15\x04\xfc\xc8\xca\ +\x2f\xd6\x9f\xe8\x2d\x53\x49\x4e\xf7\x71\x85\x1e\x2f\xb5\xe5\x45\ +\x3e\x1c\x88\x5b\xdf\xb2\x49\x5e\x1b\x5d\x57\x8b\x89\x63\x47\xa3\ +\xa1\xb6\x9a\x68\xbd\x5b\xaa\x02\x9b\x5a\x5a\xb1\x72\xf5\x1a\xa1\ +\xa6\xe3\x1a\xeb\x09\x08\x3c\x52\x34\xa4\x59\x03\x15\xbf\x7c\xc3\ +\x73\x30\x8c\x7f\x67\x70\x29\xbb\x16\x92\xfe\xa3\xcf\xe2\x85\x3e\ +\x9c\xf2\x5b\xdf\xbc\x89\x00\xa6\xc2\xee\xfa\x33\xc4\xd2\x5d\x52\ +\xc7\x40\xe7\x78\xe2\x49\x27\x61\xd9\x8a\xe5\x02\x5c\x11\x02\x01\ +\x5e\xec\xc4\xd9\x0e\x3e\xa7\xba\xba\x4a\xb4\x93\xe2\xde\xf4\x50\ +\x00\xdc\x33\xf4\x4c\xa2\xf3\xd7\x5c\x1c\x23\xe5\x4f\xe1\x7f\xef\ +\xd2\x30\x69\x7c\x2d\xdc\x7a\xbf\x3a\x01\x02\x81\xee\xb6\x56\xfc\ +\xe8\xb7\x75\xd0\x3f\x3f\x07\x9f\xda\x7b\x29\x81\x44\x37\x82\x9e\ +\x0a\xdc\xf2\x94\x86\xf6\x8e\x04\x6a\x6a\x22\xf4\xbd\x78\x25\x0e\ +\x90\x76\x00\x9b\xd9\xc7\x33\xcf\xfe\x03\xe3\xc6\x8d\xc3\xa1\x87\ +\x1f\x21\xaf\x29\x51\x0c\xe0\xc3\xdd\xd0\xdc\xa4\x82\x2c\x38\x5b\ +\x7f\x4e\xf7\xb1\x9f\xcf\xed\xb5\x5a\xba\xe2\x78\xfb\xdd\xf7\xd1\ +\x44\x37\x66\x8c\x94\x7b\xee\xee\xd3\xb1\xcf\x9c\x3d\x30\xba\xbe\ +\xd6\xa6\xa4\xb2\xba\x0f\xc4\x06\x46\x61\xde\x8c\xdd\x25\xc8\xf5\ +\xfa\xb2\x95\xd8\xb0\xa9\xd5\xa9\x0b\xd0\xfa\x29\xbf\xd6\x4b\x09\ +\xb4\x7e\x6e\x81\x28\xbf\xc4\x14\x0c\x29\x02\x62\x20\xea\x88\xc7\ +\xe9\x58\x72\xc2\x14\x38\x6d\x68\x0e\x41\xff\x91\x57\xea\x4d\x98\ +\x38\x09\x9f\x39\xe1\x04\xa4\xc8\x55\x71\x4b\xcd\x42\x50\xea\x16\ +\x5c\x4e\xdb\xb3\xda\x9a\x18\x34\x97\x07\xbf\x7d\x38\x88\x5f\x2f\ +\xa9\xc0\x7b\xcb\x12\xf8\xce\xc5\x2e\x7c\x71\x51\x12\xab\xd7\x6d\ +\x85\x09\xb8\x4c\x74\xb4\xb4\xe1\x87\xbf\x0e\xe1\x1f\x6b\xe6\x62\ +\xce\x82\x30\x16\x1d\x16\xc7\xc5\x47\x67\x10\x70\x15\xd1\xd2\x92\ +\x20\x10\xce\xcb\x75\x65\x45\x67\x16\xc0\x9d\x86\x39\x36\x72\xdf\ +\x5f\xff\x8a\x15\xcb\x97\xa9\xa0\xa0\x02\x80\x0f\x2f\xac\x90\xe5\ +\x95\x75\x42\xf7\xc9\xb7\x7d\x6f\xc3\x46\x34\x13\xbd\xe7\xe5\xba\ +\xd3\x27\x8c\x17\xe5\x1f\xd7\xd0\x20\xc1\x3c\xbe\xe1\xca\x41\x1d\ +\xfe\x97\xe3\x06\xbc\xdf\xcc\x29\x93\x08\x08\x76\x43\x82\x7c\x52\ +\x0e\xe2\xb9\x74\xbd\x9f\xf2\x0f\x64\x02\xe5\x5a\x01\xa6\xae\x45\ +\xa2\xb7\xbc\xa4\xb8\x64\xda\x0b\x84\xda\x3b\xbb\xa5\x24\xb8\x8e\ +\x2b\xe3\x86\x30\xad\x65\xc6\x74\xe8\xa1\x87\x63\xef\xbd\xe6\x21\ +\x1e\xef\xb6\x5d\x81\x60\x08\x7e\x02\x50\xe9\x7d\x48\x0c\xa9\xa6\ +\x3a\x06\x8f\xcf\x85\x9b\x1f\xf7\xe3\x17\xf7\x54\x60\xe5\x3b\x09\ +\x7c\xfb\xf3\x3a\xbe\xe8\xb8\x03\x45\x43\xdf\x2c\x3b\xc0\x20\xd0\ +\x82\xeb\x7f\x1d\xc0\x13\x2b\xe7\x62\xd6\x82\x18\x4e\x3c\xb4\x07\ +\x97\x1e\x9b\x46\x5d\xd4\xc0\x26\x02\x81\x1c\x29\x3d\x5f\xf7\x3c\ +\x01\x36\x67\x53\xd8\x25\xeb\xec\xee\x96\x78\x40\x77\x57\xa7\x5a\ +\x38\xa4\x5c\x80\x0f\x0b\x00\x1a\x82\x7e\x2f\xd2\xa4\xf8\x9b\xc8\ +\xea\x73\xa3\x4e\xee\xb1\x3f\x86\xe8\x3e\x5b\xe1\x00\x59\x61\xd6\ +\x77\x63\x3b\x54\x9c\x01\xa1\xe4\xb0\x07\x06\x01\x43\xda\x80\xe5\ +\x07\xa6\x09\xb5\x2d\xde\x44\xd4\xbf\x24\x0c\xc4\xb6\xf2\xf6\xda\ +\x02\xae\x0f\xe0\xde\x80\xd5\xb1\xd8\xbf\x9d\x06\x34\x98\xc2\x91\ +\x7f\xb6\xfa\xa7\x9e\x76\x3a\xd6\x6f\xdc\x28\x91\xf9\x48\x24\x24\ +\xcf\xb3\xdb\x92\x49\xdb\x33\x02\x6b\xaa\xb8\xc4\x37\x8e\xdb\x9f\ +\xf1\xd0\x6b\x31\x7c\x9e\xdc\x81\x6f\x5d\x18\x25\x30\x4d\xe2\xe7\ +\x77\x59\x18\x37\xba\x96\xac\x7f\xbf\xec\x80\xc7\x42\x17\xb1\xae\ +\x1f\xfc\xba\x1e\xc6\x45\x73\x70\xec\xde\xef\xd0\x73\x1d\x88\x86\ +\x4c\xfc\xe9\xf1\x30\x56\x34\xa5\x50\x55\x65\x22\x1c\x26\x17\x8d\ +\x98\x52\x92\xae\x1b\xd7\x05\xbc\xbf\x66\x35\x96\xdc\x77\x1f\xce\ +\x39\xe7\x5c\xe9\x54\x64\x59\xaa\x52\x50\x31\x80\x0f\x7a\x21\x78\ +\x01\x8f\x65\x2b\x65\x75\x34\x82\x49\x63\x46\x61\x4c\x7d\x1d\x02\ +\x44\x29\xa5\xde\xff\x03\xd2\x70\xbb\xa4\xd7\x2e\x18\xd2\xf5\xad\ +\xd0\xfe\x7e\xd6\x9f\x01\xc3\x70\x56\xbf\x19\x86\x6d\xfd\x39\xe2\ +\xcd\xf5\x07\x15\x04\x00\xdc\xef\x6f\xa8\x57\xba\x71\xec\x62\xdc\ +\xb8\xf1\x38\xf3\xf4\x33\xc4\xff\x67\xbc\xe2\xda\x80\x80\x2c\x60\ +\xf2\x4b\x28\x95\x41\xa0\x8a\x40\x20\x12\xf1\xe0\xce\x7f\xfa\xf0\ +\xdb\xbf\xc6\xb0\xec\xad\x1e\x7c\xe3\x73\x3a\xbe\x72\x5a\x12\x1b\ +\x9b\xdb\x90\x37\xd0\xaf\x33\xb0\x1d\x18\xec\xe9\x20\x26\xf0\x1b\ +\x37\x1e\x58\xba\x07\xa6\xce\xaf\xc7\xa1\xfb\xe6\xf1\xa5\xcf\x24\ +\x70\xd0\xee\x45\xf4\xc4\x53\xe8\xea\xea\x21\xd6\x64\x48\xc5\x60\ +\x4f\x22\x21\x6e\x1c\xc7\x03\x9e\x7d\xf6\x59\x49\xb7\x2a\x51\x0c\ +\xe0\x43\x05\xb6\xc2\xa4\x70\x41\x9f\xb7\x77\x4d\xfe\xc7\xf1\xbd\ +\xed\x51\x5f\xb6\x15\xda\x9a\xf2\xb3\x85\x2c\x39\xae\x84\x29\x0c\ +\xc0\x44\x90\x57\x00\x4a\x67\xa1\x02\xd1\xff\x7a\x3b\xb5\x35\x0c\ +\xaa\xdc\xb8\x54\x7a\x9f\x7d\xf7\x43\x6b\x4b\x2b\x6e\xbf\xfb\x4e\ +\x54\x10\x6b\x0a\x85\xc2\x36\xb3\xe1\x6e\x49\xbc\xee\x81\xae\x45\ +\x55\x65\x25\xd9\xea\x6e\xdc\xf3\x9c\x07\x3e\x4f\x05\xce\x30\x13\ +\xf8\xfa\x39\x11\xba\x3e\x49\xfc\xf4\x36\x60\xec\xe8\xba\x2d\x98\ +\x40\xaa\xbb\x0d\x37\xfc\xaa\x0e\xda\xa5\x33\x70\xdc\x82\x77\xe1\ +\xf5\xb7\x13\x13\x88\x63\x54\x4d\x14\xf7\xbf\x04\xb4\xb7\x1b\x04\ +\x2e\x21\xc9\x9e\x24\xe3\x09\x44\x63\x15\xb8\xef\xaf\xf7\x61\xca\ +\x94\x29\x18\x3d\x7a\x34\x86\xc2\x92\x72\x05\x00\xc3\x05\x04\x80\ +\x81\x7d\xee\x3f\xa6\x5b\xc1\x99\x84\x7c\xc1\x70\xd4\xbe\xdf\xfa\ +\x60\x5e\xf4\x53\x32\x7b\xad\xbf\xe9\x14\x8a\x70\x4e\x3d\x5f\x28\ +\xca\x20\x0f\x76\x01\x38\x86\x60\x0c\x83\x05\x2f\x0c\x66\x7c\x94\ +\x47\x1f\x7b\x2c\xda\x3b\xda\xf0\xc4\xd3\x4f\x13\x08\x54\xd9\x0b\ +\xa8\x1c\x46\x54\x94\x18\x8b\x2e\x53\x8c\x3a\x8d\x1e\x72\x07\x98\ +\x7e\x56\xe0\x34\x10\x08\x9c\xcb\x95\x90\x29\xfc\xf4\x2f\x1a\x26\ +\x8c\xab\x95\x1e\x02\xe5\xec\x00\x33\x81\x54\x77\x3b\x7e\xf4\x9b\ +\x3a\x7a\xff\x54\x1c\xb3\x9f\x5b\x40\x20\x12\x4e\xa0\xb1\x32\x8c\ +\xbb\x9e\x05\x36\xb6\x26\xc9\x55\x2b\x0a\xe0\x30\x68\x36\x11\x80\ +\x2e\x59\x72\x2f\x2e\xbd\xf4\x32\xf9\x1e\xd4\x7a\x01\x05\x00\x83\ +\xe0\x1f\x9b\x72\x33\xda\xdd\x80\x34\xf4\xd7\xff\xa2\x61\x2f\x2b\ +\xd6\x2c\xa7\x91\x28\x2f\x02\xa2\xe7\xb9\xbc\x98\xfb\xe3\x71\x24\ +\xdb\x6e\x92\x31\xbc\xce\xd7\xed\xf1\xe2\xd4\xd3\xce\x90\xee\x3e\ +\x2f\xbe\xfc\x0a\x2a\x24\x85\x69\x83\x5b\x9a\x83\x9d\x45\x6e\x1b\ +\xc6\xee\x40\x94\xe8\x7b\x02\x7f\xf9\x07\x2f\xba\x8a\xd2\x73\x3d\ +\xb8\xea\xfc\x28\x5d\x97\x24\x7e\x7e\x3b\x30\x69\x3c\x2f\x20\x32\ +\x07\x80\x40\x4f\x47\x2b\x7e\xfc\xeb\x7a\x68\xfa\x44\x1c\x7d\x80\ +\x1b\x81\x48\x37\xa2\xd1\x04\xc6\xd4\xe5\x71\xe7\x3f\x2a\xf0\xca\ +\xaa\x1c\x92\xe9\x82\x9d\x92\x8c\x86\xf1\xd8\xe3\x8f\xe1\xc0\x03\ +\x0e\xc4\x9e\xf3\xe6\xa9\xc9\xc4\x0a\x00\x06\xc7\xa5\xe0\x68\x74\ +\xb9\xd0\xa7\x4c\xff\xa5\xff\x9f\x69\x2f\x73\x2d\x5b\x7e\x53\xdc\ +\x04\x4d\x16\x0b\x65\xf3\x39\x04\x02\x7e\xe9\x03\x30\xdc\x2c\x17\ +\xc7\x2f\x42\xe4\xff\x9f\x7f\xc1\x85\x52\x13\xf0\xca\x1b\xaf\x23\ +\x5a\x51\x69\x57\x48\xd2\xff\xe9\x54\x5a\x3a\x2a\xb9\xdc\x0c\x02\ +\x11\x74\x74\xf6\x48\x8e\xdf\xed\xe2\xde\x0b\x09\x7c\xe7\x12\x5e\ +\x5c\x94\xc4\xcf\xee\x00\x26\x4f\xa8\x93\xe7\x7b\x17\x10\x79\x4c\ +\x74\x75\xb4\xe0\x47\xbf\x6c\xa0\xe7\xc6\xe0\xd8\x43\x3c\x98\x1a\ +\x09\xa1\xb2\xa6\x1d\x93\x46\x77\xe0\xd1\x97\x23\x78\xe4\xb5\x00\ +\xd6\xb6\xa5\xd0\xdc\x92\x14\x97\x63\x43\x53\x3b\xe6\xef\xad\xc2\ +\x5c\xc3\x0e\x00\x06\x6b\x2d\xc0\x27\x2d\xb6\x1b\xe0\x16\x3f\x94\ +\xd7\xb8\x9b\x92\x29\xe8\x6b\x7b\x65\xf5\x73\x01\xbc\x5c\xed\x67\ +\xda\x99\x04\x5e\x63\xc0\xcb\x91\xcd\x61\x78\x0d\xf8\x5c\xc3\xe1\ +\x08\x2e\xb8\xe8\x62\x68\xbf\xff\x1d\x5e\x23\x10\x08\x86\xc2\xbd\ +\x95\x91\xbc\xb2\xaf\xc4\x6e\x8e\xcb\x8b\x9a\xea\x0a\xb4\xb5\x27\ +\x08\x04\xfc\x08\xf9\x4b\x74\xad\xe2\xb8\xf6\x8b\xe4\x3a\x20\x85\ +\x5f\xdc\x69\x61\xf2\xf8\x7a\x02\x01\x38\x20\xa0\xd3\x35\x22\x10\ +\xe0\xa5\xc4\xbf\xac\x83\x59\x6a\xc0\xf1\x87\xb7\xa2\x31\x10\x44\ +\x45\x4d\x07\xc6\x8d\x4f\xe0\xd0\xf9\x19\xbc\xb8\x2c\x84\x95\x1b\ +\xc9\x4d\x70\x67\x31\x63\xd4\x0a\x72\xb5\x8e\x76\xc2\x28\xd6\xa0\ +\xde\xcf\x0a\x00\x76\x61\x37\x40\x16\x15\x59\x90\x00\x9f\x69\x95\ +\x01\xae\x2f\xbe\x27\xfd\x07\xbc\x5e\x27\x1b\x60\x4a\xf3\x4f\xdd\ +\x99\xbc\x3b\x1c\x85\x41\x20\x12\xad\xc0\x05\x17\x5c\x84\xc0\xad\ +\x37\xe3\x85\x97\x5f\x96\x75\x02\x65\x49\x27\xd3\xd2\x23\xc1\xed\ +\xf6\xa2\xae\x36\x8a\x4d\xad\x09\xfc\xf1\xf1\x30\x42\x3e\x62\x02\ +\x9e\x6e\x7c\xf7\x3f\xd8\xfa\x67\xf0\xb3\xbf\xb4\x60\xfc\xb8\x06\ +\x02\x43\x07\x04\xc8\xaa\x7b\x3d\xf6\x2a\xc2\x1f\xfe\xaa\x9e\x58\ +\x46\x2d\x3e\xb3\xb0\x0d\x41\x54\x13\x6b\xd2\x50\x3f\x2a\x8f\xbd\ +\xe7\xe7\x60\x14\x75\xf8\x42\x01\xb8\x46\xbd\x87\x9e\x52\x07\xfd\ +\xc5\x0a\xe6\x27\xea\x86\x54\x00\x30\x18\x7e\xb1\x07\x5a\xa1\xe0\ +\x28\xbf\xe5\xac\x31\x70\x5e\xe7\xbc\xbf\x83\x04\x7e\xf2\xf9\x19\ +\x28\xd8\x63\xe0\x49\x41\x1c\x30\x1b\xce\x0c\x88\x2b\x05\xc3\x91\ +\x08\xce\x39\xef\x7c\x84\x42\x11\x3c\xf9\xf4\xd3\xd2\xd0\xc3\x4e\ +\x81\x10\x08\xa4\x2d\x59\xdc\xc3\x20\xc0\x4b\x89\x9b\x9a\x13\xf8\ +\xd3\x93\x61\x84\x83\x06\x3c\xde\x4e\x5c\xfb\xb5\x06\x52\xf6\x38\ +\x7e\x7e\xdb\x26\xd4\x37\x34\xc0\xdf\x6f\x15\x21\x77\x16\x4a\x76\ +\x3b\xfd\x04\x50\x8f\x13\x0f\x6f\x23\xd4\x89\xc0\x6d\xd1\x7b\x19\ +\x2d\x3c\xf4\x77\x82\x63\x61\xb8\x0c\xb8\xf0\x1e\xa9\xfe\x02\x76\ +\xbe\xd4\x0d\xb9\x15\x51\x0e\xd2\x8e\xbc\xb8\x64\xfd\xb9\x72\x90\ +\x17\xfc\x31\xb5\x17\x85\xe6\xa8\xb8\x53\xf8\x23\xff\x31\x48\xd0\ +\x3e\x01\x02\x0a\x6e\x00\xc2\x01\x40\xff\x08\x29\x67\x65\xd0\xe3\ +\x66\xa6\xa7\x9d\x7e\x06\x4e\x38\xfe\x78\x89\x0b\x78\x7d\x5e\x02\ +\x86\xb0\xb8\x09\x1e\x29\xb0\xb2\x88\xfa\xfb\xd0\x58\x1f\xc1\xaa\ +\x16\x1d\xb7\x3d\x1d\xc5\x6b\xaf\x5b\x68\x59\xd9\x8e\x6f\x5e\x59\ +\x8b\x6f\x7f\x3e\x87\xf6\xd6\x16\xe4\x8a\x76\x07\xa6\xf2\x6d\xeb\ +\xf3\x6a\xc8\x24\xda\xf0\xa3\x9b\x3c\x78\xf2\x85\x18\xb4\xb0\x0f\ +\xa6\x1e\xb2\xfb\x36\xf2\x10\x96\x5c\x12\x56\x31\x4e\x00\xd0\xe6\ +\xac\xea\x54\xa2\x00\x60\x27\xfa\xfe\xac\xc8\xbc\x86\x9f\xab\xfa\ +\x0a\xa5\x42\x6f\xb4\xdf\x28\x2b\xbe\xf8\xfe\x90\x6c\x40\xc0\xeb\ +\x11\x57\xa1\xbc\xb4\x78\x24\xd5\xb3\xcb\x34\x24\xd2\xdc\xcf\x9c\ +\x74\x32\x4e\x5d\xfc\x59\xb8\xa4\xfd\xb9\x4b\x8a\x85\x78\x21\x8f\ +\xc7\xeb\xb6\x3b\xfd\xd0\x79\xd7\xd5\x84\xf1\xca\xfb\x3a\x1e\xf8\ +\x57\x14\xcb\xdf\xca\xa3\x7b\x55\x07\x2e\xbd\xbc\x11\xdf\xbc\x20\ +\x8b\x96\x96\x16\xba\x3e\xba\x53\x5c\x65\x07\x14\xe8\xb2\x21\x19\ +\xef\xc2\xed\x7f\xf3\x20\xd9\x03\xb8\x82\x21\xba\xa3\x3d\x36\xdd\ +\x37\x73\xb6\xd5\xb7\x4a\x83\xea\xff\x2b\x17\x60\x17\x53\x7c\x8e\ +\xfc\x33\x7d\xb7\x87\x87\xe4\x25\xaf\xcf\xae\x80\x51\x5e\x3b\xd0\ +\x2f\xf8\xc7\xd6\x9f\x53\x81\x21\xbf\x5f\x16\x02\x59\xf4\x7e\xae\ +\xfe\xe3\xfa\x81\x91\x94\xbb\x2e\x57\x47\x1e\xb9\xf0\x28\x44\xc8\ +\xf2\xdf\x7e\xd7\xed\x88\x27\x92\x02\x02\x7c\xcd\x78\x51\x0f\xa7\ +\xea\x42\x4e\xb3\x95\x47\x97\x66\x50\x17\xab\x40\x38\x1a\xc7\xac\ +\x48\x10\x5f\xb8\x62\x0c\xf2\xf9\x0d\xf8\xe1\x1f\x5b\x31\x7a\x34\ +\xb9\x03\x5e\x53\x2a\x27\xb9\xd0\xca\xed\x26\xb6\xd0\x6e\x20\xd1\ +\x55\x42\x64\x1c\x01\xa7\xdb\xcf\x5d\x4c\x6d\x47\x4b\x35\x0d\x55\ +\x00\xb0\xd3\x2e\xa4\xdb\x2d\x56\x5c\x9a\x56\x10\x05\xe5\x9b\x5e\ +\x5a\x89\x1b\x4e\x3e\xbb\x1c\xfc\x73\xd6\xfb\x8b\xff\x4f\xaf\x71\ +\x96\x80\x97\x00\xe7\xc9\x45\x60\x57\x20\x48\x66\x8d\xcb\x88\x47\ +\x9a\xcd\x92\xb5\x12\xc4\x06\xf6\x3b\xe0\x00\xd4\xd4\xd6\xe0\xe6\ +\x3f\xdf\x82\x75\x1b\x36\x48\x93\x15\x29\xd6\x49\x26\xa5\xa4\x37\ +\x1a\x0d\x09\x68\xfe\xf5\x45\xa0\xb1\x2a\x84\x58\x45\x2b\xa6\x44\ +\x42\xf8\x8f\xaf\x8d\xa5\x6b\xba\x8e\x40\x00\x18\x35\xba\x9e\x40\ +\x53\x93\xe1\x2c\xa9\x9c\x1b\x7b\x8c\x6d\x43\x6d\x84\x2c\x7d\x89\ +\x94\xdf\xe5\x73\x7a\xac\x8b\x0f\x46\xa0\x1a\xa6\xbf\xad\x43\x53\ +\xb7\xa8\x72\x01\x76\x84\xb0\xd2\xfb\x1c\xca\x9e\xce\x66\x44\xf9\ +\x79\x0d\x8a\x51\x32\x7a\x03\x7f\xe5\xb5\x04\x96\x13\xf4\x93\xd2\ +\x5f\xc3\x92\x32\xe1\x28\x47\xab\x39\x55\x48\x37\xb3\xdf\xe3\x91\ +\x55\x80\x23\x55\xf8\x1a\x30\x33\x9a\x3a\x6d\x3a\x2e\xff\xc2\xe5\ +\xd8\x6b\xee\x5c\x7b\x46\x62\x30\x28\xbd\xff\xb9\x99\x88\xae\xb9\ +\x50\x5d\x19\x41\x57\x46\xc7\x5d\xcf\x45\xf0\xf6\x72\x17\xba\xd6\ +\xb5\x00\x45\x17\xbe\xf6\xf5\xb1\xf8\xc6\xe7\x92\x68\xd9\xb4\x01\ +\x6b\x9a\x92\x68\xea\xc8\xe1\x90\x59\xcd\xb8\xec\xb3\x49\xf8\xc2\ +\x01\xbb\x65\x9a\xb4\x66\x73\x89\xf2\x9b\x7a\x94\x9c\x80\x31\x2a\ +\x06\xa0\x18\xc0\x0e\x40\x4e\x5d\x17\xab\xcf\xb7\x16\x17\xef\x94\ +\x9c\x59\x80\x6c\x7d\x64\x5e\x20\x37\xc7\x70\xd2\x7d\x26\xec\x62\ +\x1f\x48\x21\x90\xad\xf8\xbc\x3f\x2b\x7f\x65\x24\x22\x43\x40\xb8\ +\x38\x48\xa6\x02\x71\xf4\x7f\x24\x5f\x38\x06\x01\x3a\xdf\xda\xba\ +\x3a\x5c\x74\xe1\xc5\xf8\xfb\xdf\x1f\xc2\xa3\x8f\x3f\x2e\x2c\xa9\ +\x82\xce\x9d\x17\xf5\x68\x39\x0b\x35\x55\x61\x2c\x6f\x4a\xe3\xfe\ +\x17\xab\x30\x61\x6c\x07\xa2\x75\x71\x78\x2a\x2a\xf1\xb5\xaf\x8c\ +\xc1\x9c\xdd\x5a\xf1\xd4\x8b\x6d\x68\xa8\x71\xe3\xb4\x63\xbc\xa8\ +\x9b\x54\x05\x23\x5f\x2e\xb8\xd2\x6c\x10\x70\x07\x61\xba\xa6\xd1\ +\x77\x30\x01\x2a\x05\x38\xcc\x00\x60\x28\x17\x02\x95\xfd\x7c\x0e\ +\x6c\x31\x55\x95\x06\x15\xb0\xab\xfc\x4a\x4c\x73\x79\x44\xb8\xc9\ +\xa5\xbe\xe8\x5d\x4c\x24\xfe\x3e\x8f\x13\xe3\x49\x39\x9c\x0d\x20\ +\x2c\x88\x92\xbf\x5b\x5f\x5d\x49\xd4\xdf\x44\x86\x7b\x07\x38\xa3\ +\xbe\x76\x95\xba\x75\x06\x01\xb6\xf8\x27\x9e\x74\x32\x1a\x1b\x1b\ +\x71\xef\x5f\x97\xa0\xad\xad\x1d\xb1\xca\x2a\x99\x82\xcc\x53\x87\ +\xa3\xe1\x02\x9e\x79\xc7\x8d\x7d\xa6\x07\x30\x6e\x72\x17\xaa\x43\ +\x51\xba\x3e\x6e\x1c\x79\xfc\x28\x1c\x79\x6c\x11\x72\x91\x0d\x2f\ +\xcc\x7c\xb9\xa8\x42\xb3\x95\xdd\x45\xae\x80\xaf\x01\x39\xed\x50\ +\x62\x5b\x9c\x7a\x2c\x0c\xfa\xfd\xac\x00\x60\xc4\x50\x7e\xb7\xa4\ +\xf1\x32\xd9\xb4\x34\x0e\x61\xcb\xcd\x55\x7e\x96\x65\xd8\xf9\x7d\ +\xd3\xb6\xf8\x56\x39\xf8\xe5\x3c\x9a\x76\x05\x90\x04\xf8\x2a\x2b\ +\xa2\xb4\x85\x91\x2d\x96\x10\x4f\xa7\xc5\x65\xe5\x91\x62\xfa\x2e\ +\xe6\xa8\xf2\x62\x21\x06\xd4\xfd\xf6\x3f\x40\x56\xee\xdd\x76\xc7\ +\x1d\x78\xfb\x9d\xb7\x11\x8b\x55\xa0\xa7\x47\x17\x17\x6a\x63\x53\ +\x02\x4f\x2c\x0d\xe3\xd0\x05\x9d\xa8\x1e\x95\x80\xe5\x8a\xc0\x4c\ +\xf1\x6a\x4d\x4f\xef\xaa\xca\x7e\xaa\x46\x17\xbb\x04\x2d\x50\x8d\ +\xbc\xf7\x50\x14\xcd\x59\xe0\x29\xc7\x4a\x14\x00\x7c\x3c\x04\x87\ +\xdd\xdd\xdf\x1e\xdd\x55\x20\xe5\xcf\x49\x97\x60\x1e\xdd\x95\xcc\ +\x64\xe9\x46\xb6\x9b\x88\x5a\x4e\x9a\xaf\x7f\x80\x45\x46\x82\x13\ +\x5b\xe0\xa6\x22\xe1\x60\x80\xac\x5a\x50\xd8\x43\x3c\x95\x41\x32\ +\x9b\x95\xd7\xf9\x35\x29\x05\xde\x8a\xa5\xd0\x36\xff\xc1\xea\xfb\ +\xd9\xb2\xfa\x5e\xfa\x28\x36\x46\xdb\xca\x70\xd1\x8f\x62\xad\xb4\ +\x6d\xfe\xb2\xf9\x13\xd6\x16\x0f\x96\xe3\x12\x70\x6b\xef\x2f\x5c\ +\xf6\x05\x3c\xf2\xc8\xc3\x78\xe4\xd1\x87\x1d\x70\x00\x12\x89\x1c\ +\x96\xaf\xcb\x63\x7d\xab\x17\xd3\xb3\x3d\xd0\x3c\x74\xad\x5d\x21\ +\xda\x7c\xe8\xdf\x66\xcd\x0e\xbc\x64\xe9\x8e\x0e\xa0\x10\x3c\x12\ +\x19\xeb\x64\x09\xfe\x01\xa5\xad\x9e\xa7\x12\x05\x00\xdb\xa4\x6b\ +\xe5\x7e\x00\x72\x7b\xc9\xba\x7e\xad\x57\x61\xb8\xfd\x77\x67\xa2\ +\x07\x9b\x3a\xc9\x22\x45\xa3\x32\xae\xcb\xe3\xb8\x04\xe5\xae\x3e\ +\xe5\x15\x80\xd2\x16\x4b\x62\x05\x2e\xa7\x01\x88\x89\x1e\xa2\xfb\ +\x99\x7c\x4e\xd8\x82\xd7\xeb\x41\x24\x10\x40\x88\x1b\x7f\xf4\x57\ +\x3e\xa7\x76\xdd\x9e\xae\xdb\x57\x33\xbc\x5d\xe5\xec\x37\x8a\x7c\ +\x80\x2b\xb5\x99\x1a\x32\x1b\x29\x2f\x91\x2d\xa7\xe7\x7a\xcf\x4d\ +\xd3\x06\x9e\x6b\x3f\xc5\x91\xa0\xba\xa6\x95\xa1\x70\x80\x22\x7f\ +\x38\xf8\xb1\x27\x29\x69\xf6\x3f\xfd\x86\xa9\x68\xa8\xae\x0e\xe0\ +\xdc\x73\xcf\xc3\xdc\xb9\x73\x71\xcb\x2d\x37\xe3\xad\xb7\xde\x26\ +\x90\xd5\xd0\xde\x6d\x20\x57\x30\x05\x51\xad\x42\x86\xfe\x61\x45\ +\x67\x06\xe0\x11\x7f\x5f\xde\xed\xa2\xcf\x0a\xc4\x50\x8a\x1c\x8b\ +\xa2\x7e\x29\xdc\x16\x8f\x5d\xcb\xd1\xbe\x9e\x3e\x37\xac\xdf\xf9\ +\x96\xbf\x67\x05\x00\x4a\x06\x58\x44\x1e\xc9\x25\xdd\x7a\x45\x49\ +\x30\xe0\x86\x31\x9d\x7d\xc6\x8d\x1e\x03\xa3\xa9\x19\xeb\x5a\x5b\ +\x65\xb2\x4f\x2c\x1a\x91\x15\x7c\x9c\x0d\x90\xd9\x7d\xa6\x1d\x8d\ +\x36\x9d\xa8\xb7\x45\x16\x8e\xdf\xcb\xb1\x01\x8e\x72\x73\xe6\x20\ +\x93\x4a\xc1\xa0\x9f\x35\x4e\x65\xa5\xd2\x92\xd3\xe6\xc2\x21\x29\ +\x74\x91\x7d\xec\x5e\x79\x0c\x38\xbc\xb2\xcd\xfe\x59\x2f\xab\x90\ +\x7d\xd3\x6b\x5a\xef\x2c\x32\x4d\xeb\xaf\x5c\xfd\x3a\x10\xe8\x7d\ +\xe3\xcb\x74\x47\xf1\x75\xe7\xdc\xca\xca\x6b\x39\xfe\xb3\xad\xf4\ +\x7d\xec\x82\x7f\x36\xcb\xc0\xd3\x0b\x4a\x5a\x6f\xab\x2d\xd3\x01\ +\x28\xcb\x29\x67\xee\xbf\xe6\xa6\x0f\xcc\xb8\x57\x40\x1f\x68\xc8\ +\x75\x2c\x99\x42\xef\x0d\x27\x55\x6a\x96\x47\xae\xcb\x88\x75\x13\ +\x75\x8d\x63\xf0\xb9\x0b\x2f\xc1\x03\x7f\x5d\x82\x67\x9f\x7b\x11\ +\xd3\x8c\x6e\xcc\x9c\xbd\x1e\x18\x5b\x0b\x3d\x41\x00\x50\x48\xdb\ +\x39\x7e\x3e\x56\xbf\x97\x90\x34\x82\x92\x36\x0e\x79\xd7\x89\x30\ +\xdd\x27\xd2\xf3\x01\xb8\x35\x2e\x00\xf2\xf7\x82\x96\xa6\x59\x7d\ +\x6c\xc9\x61\x72\x79\xd5\x41\x58\x01\xc0\xe6\xc2\xca\xcf\x0a\x6a\ +\x6a\x8e\x3f\x4f\x37\x27\x5b\x6e\xa3\x3c\x0a\x9c\x1e\xdb\x3b\x3a\ +\xb1\x61\xdd\x7a\xbc\xff\xfe\x6a\x99\xde\x93\xa7\x9b\x38\x5c\x11\ +\x41\x5d\x6d\x8d\xf4\xa9\xe3\xd5\x7c\x1c\xe0\x72\x91\x22\x97\x41\ +\x80\x17\xc8\x24\xba\xe3\x58\xb7\xbe\x09\x2d\x04\x1e\x19\xf2\xfd\ +\x99\x39\xb0\x2b\xc1\x55\x70\x21\x62\x12\xa1\x50\x00\xe1\x50\x48\ +\xfa\xea\x45\x22\x11\x49\x8d\x55\xd0\xc6\x9f\x1d\xa5\xdf\xb9\xed\ +\x38\x1f\x9f\xe5\xe8\x35\x83\x46\x19\x04\x5c\x7c\x93\xeb\x8e\xe2\ +\xdb\xda\x2e\x37\xbb\x6e\x69\xc2\x42\x4c\x47\x39\xcb\x29\x72\x89\ +\x3b\xf4\x1b\xa6\xd1\x07\x76\x8e\x42\x6b\x70\xea\x15\x9c\xe7\xcd\ +\x72\x40\xb3\xbf\x15\x45\xef\x8a\xc5\xde\x05\x4e\x1a\x7a\x47\x9e\ +\x5b\x4e\xc6\xa3\x6c\x81\xf9\x78\x58\xe9\xba\xe2\x71\x74\x75\x76\ +\xa1\xbb\xbb\x1b\xf1\x78\x52\x22\xff\x89\x9e\xa4\x34\xf8\xe4\x5e\ +\x02\x9c\x4e\xe5\xe9\xdf\x7c\x3e\xde\x68\x2d\x8a\xf9\x00\xbe\x77\ +\x97\x1b\x07\xb4\xb8\x31\x75\x4a\x04\xb5\x81\x12\xbc\xba\x89\x78\ +\x8e\xd8\x41\x8f\x1b\xeb\xd6\x54\xa2\xbd\x6b\x06\xea\x1b\x62\x98\ +\x3c\xe9\x5d\x4c\x9c\xd8\x20\xd7\x8f\x81\x93\xcf\x5d\x26\x3d\xbb\ +\xec\xc7\xf2\x75\xe3\xe3\xca\xe5\x72\xbb\x3c\x0b\x50\x00\xb0\x19\ +\x65\x4e\x26\x93\x4e\x47\x5b\x97\xdc\x28\xba\x33\x05\x58\xb7\xff\ +\x11\xcf\xbe\xa1\xae\x1a\x41\xbf\x07\xb5\xd5\x95\x98\x30\x61\x1c\ +\xda\xba\xba\xd0\x95\x48\xc2\x20\xcb\x56\xc8\x15\xe1\x61\x6a\x4a\ +\x9a\xc0\xe0\xc1\xf1\x02\xbe\xe1\xe3\xdd\x49\x99\x75\xc7\x41\xc0\ +\xa9\x53\x26\x21\x1c\xe4\xd6\xe3\x3e\x29\x02\xe2\x61\xa1\x41\xfa\ +\xd9\x4f\xc0\xc1\xbf\x07\x82\x7e\x29\x09\xe6\x05\x42\xcc\x2a\xb8\ +\x7e\x9e\x47\x8a\x73\xa1\x10\x33\x82\x5e\xf7\xa4\x9f\xc5\xd6\x9c\ +\x51\xe5\xbd\x2d\xc7\xe0\x0c\x32\x75\x14\x5a\x46\x90\x5b\xf6\x7e\ +\x56\xef\x38\x72\xb3\xb7\x46\x41\xce\x55\x3e\xbb\xfc\x68\x7f\x96\ +\xe6\xac\x4a\x14\xb7\x81\x3f\x53\x66\x9e\xeb\xbd\x85\x4d\xae\xde\ +\xa0\x84\xd9\xeb\xb2\x94\x99\x80\x0d\x0e\xae\x3e\x46\x60\x41\xba\ +\x27\x7b\x3c\x5e\x54\x56\x54\x90\x02\x36\x22\x9b\xcd\x22\x9b\xc9\ +\x92\xe2\x67\xec\x81\x1f\xf4\x7b\x3a\x43\x8f\x99\x3c\xd1\xfe\x9c\ +\xdd\x32\x9d\x00\x38\x95\xd5\xb1\x62\x8d\x85\xae\x94\x17\x13\x46\ +\x05\x10\xab\xf0\x21\x91\x02\x36\x6e\x2a\x22\x12\xaa\xc2\x5e\x13\ +\xab\x30\xa6\x31\x86\x1a\xfa\x4e\x82\xe1\x90\x64\x54\x7a\xbb\x01\ +\x59\xf4\x19\x85\x92\x03\x52\x36\xeb\xb0\x9c\x1e\x0c\x8a\x01\x0c\ +\x61\x65\x1c\xac\xf4\x09\x07\xa0\x4a\x32\x17\x10\x03\x7d\xe2\xb2\ +\xab\x40\x0a\xc0\xe3\xba\x78\x68\xc7\x6e\xd3\x26\xf7\xbe\x56\x6e\ +\xee\x61\x4a\xf5\x9f\x35\xc0\xbf\xd6\x9d\x1b\x52\x77\xba\x04\x6d\ +\x1e\x26\xb3\x36\x0b\xc2\xf5\x4f\x85\x4a\x6c\x81\x2d\x2f\xdf\xb8\ +\x9b\x45\x06\xcb\xc1\xc9\xad\x06\xf3\x9c\x0f\xb6\x36\x8b\x21\x58\ +\xdb\x89\x29\xf4\x3f\xdf\xad\x3d\x6e\x3d\x78\xe8\x50\x6c\x6b\xfb\ +\xd1\x80\x32\x54\xf8\xc9\xaf\x0f\x78\x49\x89\xa3\xa1\x01\xe3\xd5\ +\xad\x7e\xd7\x42\xdb\xec\x98\x74\x69\xa1\xae\x09\x33\x30\x4c\x9b\ +\xca\xb8\x5c\xf6\xf3\xba\x66\xe7\x5d\xf8\xba\xf3\xa2\xab\x12\xd7\ +\x65\x38\x2e\x49\x7f\xb6\x52\x3e\xba\x9d\xad\xf8\x2a\x0d\x38\x4c\ +\xe3\x01\xd8\x46\x40\x8d\xef\x42\x06\x89\xcd\x5b\x4d\x7d\xe8\x1b\ +\xcb\xb2\x76\x58\xd1\x4f\xaf\x22\x6d\xe7\x98\xb6\xf7\xda\x07\x01\ +\x8b\x8f\x9a\x2d\xb0\xb6\x1b\xc7\xfc\xa0\xd7\xd0\x92\x89\x43\xfd\ +\x5d\x98\xed\x9d\xd3\x87\xfb\x6c\xc5\x00\x94\x7c\x04\x90\xf8\x08\ +\x1f\xb0\x43\x6f\x49\x6d\x28\x9d\xeb\x27\x7e\x5c\xda\x56\x14\x5c\ +\xc9\x87\x15\xb5\x16\x40\x89\x12\x05\x00\x4a\x94\x28\x51\x00\xa0\ +\x44\x89\x12\x15\x03\x18\x6c\x19\x29\x5d\x81\x95\x28\xe9\x7f\x3f\ +\x2b\x00\xf8\x40\x07\x64\xd0\x56\x32\x7c\x2e\xa0\xe4\x52\x37\x8f\ +\x92\xe1\x2f\x7c\x2f\x5b\xc5\x2c\xaf\x49\xe6\xd2\xc3\x0c\x3f\x7a\ +\xb4\xa1\xd1\xad\x48\x1b\x0c\x64\xba\x79\x69\xfe\x27\xef\x76\x96\ +\xe6\x79\x5d\x5b\x5e\x85\xce\xce\x4e\x5e\x5c\x33\xaf\xb6\xa6\x26\ +\x26\x0d\x1e\x94\x28\x19\xe6\xc2\x65\xdd\x6f\xbc\xb1\xb4\x2b\x91\ +\x48\x74\x40\xd3\xe2\xa4\x76\x99\xb9\x73\xe7\x9a\xb1\x58\x0c\x05\ +\xc3\xd2\x2f\x9a\x17\xb8\x62\x7c\x4c\x5f\xba\xcb\x30\x80\x77\x3b\ +\x8c\xfd\x5f\x69\x2e\xee\xcb\xad\x9e\xb7\x38\x20\x77\x95\xe4\x74\ +\x9a\x5a\x4b\x2a\x44\xa1\x64\xc4\x88\x7f\xd2\xbe\x55\x21\x5d\xaf\ +\x2a\xff\xbe\x2e\x9b\xc5\xea\xe6\x22\x72\x25\x0b\xa7\xed\xe1\xaf\ +\xde\xa5\x5c\x00\x8f\x0b\x59\x2f\x29\xbf\xd7\xbd\xb5\xe4\xad\x21\ +\x95\x22\xba\xa2\xff\x4a\x46\x90\x18\x85\xec\x80\xbe\x44\xdc\x1d\ +\xd9\xc5\xb3\x0e\x20\xcb\x36\x8c\x5d\x0a\x00\x4c\xa7\x2e\xdd\x1c\ +\xc1\x31\xbe\xd2\x66\xe7\x66\xf7\x05\xc0\x76\x6b\x65\xf9\x66\xd8\ +\xfc\x9a\xb8\x34\x55\xbf\x36\x92\xa5\xbc\xa2\x72\x97\x02\x80\xf2\ +\xe2\x32\x73\x84\x46\xf9\xb9\x2a\xad\xca\xcb\x2b\xf4\xcc\x5e\xed\ +\x2f\x9a\x1a\x12\x45\x7d\x9b\x0d\x2a\xf9\xd9\x00\xb1\x9e\x90\xdb\ +\x1a\xd0\xe9\xa3\x87\xde\x53\x30\x87\x17\x08\xf0\xd1\x97\xe8\x98\ +\xbd\x2e\x35\x9a\xfb\xdf\xdc\x29\xbd\x1d\xa3\x77\x2d\x06\xe0\x2c\ +\x1a\x19\x89\xad\x1a\xf9\xdc\x42\xba\x85\x2b\xe6\xbb\x31\x3a\xca\ +\xf9\x0c\x0b\x3e\xf2\x79\x5e\x79\xbf\x13\xd7\xfd\x33\x85\xaa\xfa\ +\x31\xd2\x3e\x6c\x73\xc9\x97\x80\x83\x46\x03\x9f\x9b\xed\xa1\x9f\ +\xed\x3b\xc2\x47\x14\xf1\xaa\x25\xab\xf0\x3e\x46\xc9\xb8\xf0\xa1\ +\xac\x4c\xe5\xb5\x07\x05\x3a\x35\x0f\x31\x9d\x09\x51\xf2\x73\x7b\ +\x8c\xde\x1e\x06\x4a\xb6\x76\xcd\x9c\xe5\xd6\xd8\xd5\x00\xc0\xa1\ +\xc8\xee\x11\x68\x1c\xca\xcd\x40\x2b\xfc\x1a\xaa\xc3\x6e\x14\xe9\ +\x09\x0f\x37\x0a\x29\xf5\x20\xd9\xb6\x09\x95\x8d\xe3\x60\x96\x8c\ +\xad\xbe\xcf\x4b\xc0\x11\x0b\xf3\x30\x51\x27\x56\x42\x9a\x94\x69\ +\x5d\x83\x42\x55\xad\x34\x2a\x19\xca\x00\xc0\x16\x9f\x95\x7f\x7a\ +\x25\x70\xce\x6c\x02\xab\x64\x1b\xfe\xe3\xa1\x2e\xd4\x4e\x98\x02\ +\xcb\x50\x5d\x79\xb7\x77\xbf\xec\x7a\x00\xe0\xb8\x00\x23\x31\xcb\ +\x27\x8d\x6a\xa5\xfd\x97\xd5\xbb\x79\xb8\x57\xa0\x69\xc9\xf9\x1a\ +\xdb\x18\x58\x23\xaf\x71\xfc\x53\xde\xe3\x00\x80\x6e\xf5\xee\xcf\ +\x8f\x43\x55\xff\xf9\x9c\xab\x7c\xc0\x09\x93\x75\x1c\x37\xd5\x85\ +\x68\xd8\x8b\xa7\x5f\xea\x41\x3a\xde\x81\x6a\x6b\x1a\x1d\xbf\x02\ +\x80\x6d\xd1\x26\xd3\x1a\xdc\xef\xd5\x3d\x68\x4a\xe2\x6c\x23\x12\ +\x00\xfa\x35\xeb\xec\xef\x17\x9b\xbd\x0c\x61\x1b\xcc\x61\x1b\xfe\ +\x74\xf9\x33\x87\x2a\x00\x64\x8b\xc0\x19\x73\x34\x7c\x7a\x86\x1b\ +\xf9\x02\x9c\x65\xce\x9a\xb4\x02\x33\x46\x78\xb0\xf7\xe3\xba\x4d\ +\x83\xad\x03\x83\xc6\x00\xd8\x22\x96\x46\xe0\x1a\xce\x32\x03\xe8\ +\xdf\x2a\x0b\x4e\x1b\x2d\x3e\xef\x92\xd9\xd7\xce\x6e\x73\x06\xd0\ +\xbf\xb5\x96\x28\x7f\x79\x9c\xb8\xb5\x6d\x4b\xa1\x6d\x06\x12\x65\ +\xe1\x8e\x38\x6e\xfd\xc3\xdd\x5d\x72\x6c\xfd\x28\xa9\xb4\x14\xe3\ +\x19\x1b\xe5\xc6\x22\xdb\x78\x1f\x3f\xef\x71\x71\xaf\x3f\x9b\xb1\ +\xf4\x3f\xdf\xed\x31\x3d\xd3\xf9\x9b\x76\xe3\x0e\x38\x7d\x0b\x77\ +\x9d\xcc\x07\x9f\xaf\x0c\x8a\xd9\x35\x01\x80\xb6\x11\x12\x1f\xd2\ +\xb6\xe2\xde\x58\x03\x7a\xe5\x59\x03\x15\xc2\xda\x92\x1d\x94\xca\ +\x96\x72\x33\x00\x60\xb0\x28\x3a\x5b\x7f\x00\xd0\x9c\xbf\x95\x33\ +\xec\x9f\xeb\xfd\x16\x62\x7e\xa9\xb1\x90\xb8\x43\x3c\x6b\xa1\x25\ +\x6b\xb7\xef\xe2\x82\xab\xed\x65\x1f\x72\x25\xfb\x33\x6a\xe9\x33\ +\x2a\x7d\x96\x44\xef\x21\xcf\x5b\x48\xe4\x2c\xb4\xe7\xe8\xdd\xda\ +\x96\x9f\xc3\x7b\xf1\xdf\x4f\x17\xe0\x04\x2e\xfb\x8e\x9d\xab\x38\ +\x93\x79\x03\x3d\xf4\x9a\x46\x88\xc8\x01\x4d\x69\xc3\xd5\x2f\x5e\ +\x10\xf2\x00\x0d\x01\x4b\x1e\x59\xf1\xb9\xa5\x7a\x3a\x6f\xa1\x23\ +\x07\xa4\x0c\x97\xd4\x89\xb0\x1b\xd4\xff\xd0\x47\x12\x99\xd0\xac\ +\xc1\x77\xed\x06\x07\x00\xd8\xfa\xd3\xe6\x1a\xca\x31\x00\xeb\x83\ +\x69\xbd\xb6\x19\x08\x88\x92\x6b\xd6\x56\x3f\x44\x5e\x33\x9d\xd9\ +\x80\x56\x9f\xe5\xb3\x1c\x1a\x5d\xdc\x0a\x1f\xe4\xe7\x92\x39\x13\ +\xa6\xc7\xb4\xdb\x5a\x3b\xcf\xe7\x0d\xfb\xe3\x0f\x1f\xab\xe1\xf8\ +\x29\x3a\xa6\x57\xeb\x88\x7a\xfb\x00\xa0\x87\x14\x77\x45\x47\x09\ +\x7f\x5b\x91\xc3\x63\x4d\x3a\x34\xb7\x4f\x82\x8c\xbd\x9d\xb1\x1c\ +\xff\x93\xbf\x87\x43\x46\x69\x38\x79\x37\x1d\xbb\x55\x93\xff\xee\ +\xed\x0f\x00\xa4\xc4\xa4\xc0\xef\x77\x19\xb8\x6f\x79\x16\x4f\x36\ +\xeb\xd0\x3d\x5e\x62\x04\x76\x83\x4f\x4e\x4f\xfe\xbf\x05\x6e\xcc\ +\xa8\xd1\x30\x2a\x64\x49\x26\x43\x5c\x82\x7c\x11\x7b\x4c\x1e\x85\ +\x5b\xce\xaf\x45\xa4\x42\xc3\x5b\x1b\xb2\xf8\xc9\xbf\xb2\x88\xc4\ +\xaa\xe4\x33\xeb\x09\xa8\x4e\x98\x06\xec\x3f\xd6\x85\xda\x80\x0d\ +\x04\xd2\x81\x98\x8e\x25\x5d\xb0\xd0\x49\x00\xf0\xd2\xc6\x02\xee\ +\x5f\x65\x60\x53\xde\x87\x00\xa7\x15\xfa\x75\x4f\xb2\x3e\xe4\x77\ +\x34\x94\x0d\x47\x69\x57\x64\x00\x76\x40\xcc\x0e\x90\x0d\x49\x6b\ +\xae\x6d\xfe\xdc\xd6\x07\x68\x94\xad\x30\xfa\x29\x32\x9f\x92\xa7\ +\xdc\x41\xb8\xdc\x8f\xcf\xb2\x27\x01\x77\xa7\x0d\x78\x32\x26\x8c\ +\xa2\xd9\xdb\x3c\xd3\x79\x19\x19\x02\x80\x8c\x33\xe2\xaa\x7f\xcb\ +\xee\x7c\x89\xbb\xdf\x1a\x28\x79\x0c\x87\x5a\x93\x82\xd1\x8f\xa3\ +\x49\x71\xbe\x75\x80\x1b\xc7\x4c\x75\x83\xc7\x0e\x14\xcd\x3e\x37\ +\xc1\x43\x96\x33\xec\xd7\x30\xbe\xca\x8d\x23\x26\x7b\x71\xd7\x1b\ +\x71\x5c\xf7\x5c\x06\x9d\x7a\xc4\x5e\x98\xe2\xfc\x5d\x0e\x36\x5e\ +\x31\xcf\x85\x2b\x48\x89\xf9\x3d\x45\x01\x64\x97\x93\xba\x23\x20\ +\xa0\xf3\xa8\x08\x02\x13\xe9\x73\x0e\x9b\xe4\xc1\xef\x9e\xef\xc0\ +\x75\x2f\x66\xe0\x0e\x45\xa1\xd3\xb1\x64\x49\xe1\xa7\x55\xba\xb0\ +\x0f\x81\x50\x36\x67\xcf\x3d\xb4\xad\x3f\xb1\x91\x48\x10\x07\x55\ +\x6a\xd0\xbd\x3a\x52\xcd\x6d\x68\x59\xdb\x82\xec\xf4\x05\x98\x1a\ +\x36\x71\xe3\x21\x6e\xcc\xac\xd7\xe5\xef\x19\x96\xdd\xf6\xdc\x6e\ +\xc3\x6e\xc2\x4f\x00\x54\x17\x05\x66\x37\xb8\x71\xf8\xb8\x0c\xbe\ +\xf1\x68\x07\x96\x67\x2a\x11\xf4\xba\x7a\xd9\x87\xbe\x9d\xf6\x5e\ +\xd6\xe6\x74\xc1\x1a\xba\xac\x81\xcf\xa3\x64\x0e\x6e\xcf\xc0\x41\ +\x72\x01\x6c\x85\x30\x77\x72\x0c\xa0\x7f\xf3\x4d\xed\xdf\x18\x7e\ +\x6b\x3b\x74\xc0\x42\xd9\xc7\xef\xa3\xfc\xe5\xc2\x26\xbe\xa9\x35\ +\x97\x29\x33\xec\xfa\x53\x7f\x9f\xd7\x8d\xba\xa0\x8e\x0a\x14\x60\ +\xe9\x25\x99\x7d\x57\xee\x51\x2f\xd6\x96\xb6\xb0\x4b\xef\x6b\xc3\ +\xed\x3c\xb2\x52\xf1\xe4\xe0\x82\xdc\x29\xa6\x7c\x7e\xad\x0f\xf8\ +\xf9\x11\x5e\xec\x37\xde\x8d\x14\x59\x4c\x66\x03\xfc\xf9\x3e\xa7\ +\x83\xaf\xcd\x10\x4c\xb2\xa6\x76\xe7\xdb\x33\xf6\xa9\xc2\xd8\x50\ +\x27\x2e\x78\xa0\x0d\x3d\xfe\x5a\x78\xe8\x6f\x33\xe0\x2c\x9e\xaa\ +\xe3\xeb\xfb\xbb\x85\xc6\x17\xe9\xf7\xa0\xcf\x83\x54\x2a\x85\x78\ +\x22\x25\x5d\x90\x6b\xab\x49\x13\x75\x0f\x7d\x8e\x21\x9d\x82\x2f\ +\xf9\x54\x2d\xd6\xb7\xad\xc2\x2f\xde\x05\xa2\xd1\xb0\x1c\x97\xed\ +\xb8\xf3\x6d\x64\xf4\xb6\x00\x2f\x2b\xa2\x26\xf5\xdc\x1e\xb9\xda\ +\x49\xa2\x38\x51\x42\x9c\xef\xec\xaf\x8b\xf2\x27\xf3\x96\xb4\x45\ +\xe7\x38\x45\x7b\x47\x97\xcc\x59\x8c\x86\x03\xa8\x88\x46\x88\x0d\ +\x99\xc8\xd1\x79\x8c\xaf\x0b\xe3\x3f\x0f\xca\xe3\x8c\xbb\x37\x22\ +\x11\x19\x2b\x0c\x46\xd7\xca\x2d\xd1\xed\x38\xc7\xc7\x31\xfe\xfd\ +\x9b\x8f\x0e\x86\x0a\x72\x0b\x75\x63\x90\x8d\xe0\x20\x01\x80\xe9\ +\x6c\x83\xc3\xd1\xfa\x1a\x66\x6e\xe5\x4b\xe9\xf5\xd3\xb7\x54\xf0\ +\x5e\x3f\xbe\x1c\xb4\xc3\x40\xf4\x96\x81\x13\x9c\xcf\x77\x9b\xce\ +\x5c\x40\x1b\x00\x32\xb9\x02\x66\x11\x25\x7e\xe8\xf2\x5a\xa7\x1e\ +\xd8\xb3\xe5\x2d\x4a\xfb\x79\x89\xea\x66\x8b\xd6\xc0\x46\x9c\xce\ +\xb5\xb2\x9c\x8d\x9b\x15\x7f\xe3\x20\x0f\x29\xbf\x8b\x68\xbe\xad\ +\x70\x41\xbf\x17\xcb\xdf\x5b\x8b\x07\x9e\x5f\x89\xf6\x8c\x85\x29\ +\xa3\x6b\xf0\x99\x7d\x27\xa3\x86\x14\x38\x5f\x24\x3f\x3c\xa3\xe1\ +\x53\x33\xaa\x71\x65\x73\x02\x57\xbe\xd0\x09\xbd\xa2\x0a\x01\x02\ +\xa9\xb3\xf7\xf0\xda\x60\x6c\x6a\x12\xb3\xff\xdf\xdb\x9f\xc0\xed\ +\x2f\x36\x23\xe5\x8e\x42\xf3\xf8\x30\xb9\xda\x8f\xeb\x4e\x9e\x81\ +\xe9\xe3\xab\xed\xef\xca\xe3\xc6\xe5\x07\xd7\xe1\x81\x95\xeb\xd0\ +\x5a\x9a\x48\xcc\x43\xc7\x13\xcb\x3b\xb1\x76\x59\x13\xf6\x9c\x3e\ +\x06\xe3\x1b\x6b\xa4\x2b\x2f\x4f\x42\x6a\x69\x8f\xe3\x99\xd7\xde\ +\x93\x71\x68\x6f\xae\x6e\x85\xe1\x0a\xe1\xa0\xd1\x16\xe6\x8f\x72\ +\x91\xf2\x9b\xd2\xe2\x7c\xe5\xea\x8d\xb8\xfe\xd6\x7f\x62\x65\x92\ +\x8e\xc3\x1b\x92\xe9\xc8\x67\xec\xd3\x80\x4b\x17\x4e\xe7\x55\x61\ +\x04\x29\x1a\x76\x9b\x52\x87\xd3\xa6\x6c\xc2\x0d\xef\x74\x92\x3b\ +\x11\xe3\xe5\xab\x72\xe9\x74\x27\x40\xa9\xa3\x0c\x08\x5a\x2f\x38\ +\xf4\x07\x08\xbd\x77\x50\xca\x40\x25\x2f\x7f\x6d\x83\xe9\x85\x6a\ +\x56\x9f\x21\xd9\xe5\x62\x00\x32\x68\xc3\xf8\x64\x01\xc0\xda\x06\ +\x8d\xdf\x3c\xe0\x66\x59\x7d\xca\x3b\xf0\x77\x27\x4f\xef\x0c\xc4\ +\x28\x53\xea\xb2\xf2\x97\x53\x71\xdb\xfb\xbe\x4a\x92\x05\x30\x07\ +\x06\xae\x78\x28\x28\xdd\xdc\xb5\x95\xee\xed\x46\x7c\xb6\x56\x1e\ +\x5d\x1e\xe1\xc5\x1b\xbb\x03\x0b\xea\xc8\xe7\x9f\x46\x96\x3f\x6f\ +\x03\x45\x30\xe0\xc5\x43\x4f\xbd\x82\x2f\xfc\xdf\x4b\x68\xab\x9c\ +\x01\x6f\xac\x01\xc6\x86\x12\x6e\x5f\xf9\x3e\x7e\x73\xda\x44\x4c\ +\x6c\xac\x10\xf6\x90\x22\xa5\x3b\x7d\xbf\x51\xb8\xfb\xad\x65\x78\ +\x36\x13\xc2\x8c\x2a\x17\xc6\x45\x2d\xf2\xe3\x35\xb1\xee\xa9\x44\ +\x0f\x6e\xfa\x47\x13\xde\x09\xef\x8d\x50\x75\x8d\x54\xf3\xad\xe4\ +\xde\xfc\x8f\x24\xf0\xbb\x93\x74\x74\xf7\xa4\xf0\x5e\x53\x37\xd6\ +\x37\x77\xc2\x4c\x96\x60\xfa\xc6\x90\xc2\x7a\xf1\x5f\x2f\x15\x10\ +\xdf\x94\xc3\x9f\x4e\x2f\x62\xda\x18\xdb\xad\xf0\x11\x00\x2c\x27\ +\xca\x7f\xfe\xff\x2d\x43\x70\xe2\x1e\xa4\x88\x15\x08\xd6\x8f\xc6\ +\x9c\xca\x92\x0c\xf5\xe4\xe3\xf6\xba\x75\x3c\xbb\x74\x35\xee\x58\ +\x13\x85\x7f\xb7\x7d\xe4\x77\xa6\xf8\xdf\x79\xb5\x07\x8d\x35\xed\ +\xe2\x72\xac\xd8\xd8\x85\xf5\xad\x71\xbc\xfb\x5e\x3b\xcc\xcc\x04\ +\x14\xc2\x15\x3c\xa9\x65\x0b\x30\xd7\x7a\x15\xbf\x4f\xe1\xf5\x32\ +\x48\x94\xe7\x3a\x68\x7d\xfb\xf6\xce\x54\xe8\xf7\xfb\x56\xb9\x9e\ +\xf5\xef\x19\xe3\xc7\xcd\x02\x88\xab\xb8\xcb\xad\x05\x30\x9d\xde\ +\xf9\x3b\xc8\x05\xd8\xd6\xe5\x34\x7b\x95\xdc\xea\x4d\x9b\x95\x03\ +\x72\xbd\x8a\xff\x31\x23\xce\xb6\xd1\x36\x37\xcb\x00\xd8\xd6\x4a\ +\x28\xb1\xb5\x5d\x64\xb4\x67\xf7\xf5\x53\x7e\xab\x3c\xd3\x8e\xb6\ +\x22\x59\xf3\x13\xa7\xf9\x10\xf6\x11\xa5\x2e\xd8\x93\x86\x9b\xc9\ +\xbf\xfe\xc6\x9f\x5f\x46\xf7\xf8\x43\x51\x4d\x4a\x66\x39\x0a\xf2\ +\x32\x71\xfc\xef\x3f\x4d\x4a\xbd\x38\x28\x43\x32\x39\xde\x12\x09\ +\xf8\x71\xd2\x34\x0f\x9e\x78\x96\x14\x2a\x56\x2f\xdf\x01\x7f\x3e\ +\x4f\x3e\x0a\x47\xc2\xf8\xdd\x97\x17\x62\x09\xd1\xfb\x17\x5a\x4d\ +\x6c\xe8\x21\x65\xf6\xfa\xf1\x5c\xc2\x8b\x85\x37\xad\x41\xaa\x75\ +\x3d\x5a\x0b\x3e\x68\xc1\x4a\x02\x88\x7a\xa2\xee\x3c\xf8\xa4\x08\ +\x7f\x30\x84\x8a\x09\x33\xe0\x0e\xba\xa4\xe0\xa7\x7c\xcc\xec\x42\ +\xf8\x1b\x27\x20\x36\x71\x26\x2c\xda\x2f\x9d\x2f\xc1\x28\x95\x7a\ +\x5f\xcf\xe4\x8a\x58\x74\xc4\x3c\x84\x1a\x92\x78\x60\x83\x86\xf7\ +\xbb\x4d\xac\x4f\x11\x83\x0a\x54\xe2\xca\x67\x12\xa8\xb8\xef\x2d\ +\x34\x75\x67\x91\x26\x36\xe2\xaf\x18\x87\x60\x55\x0d\x2c\x99\xd5\ +\xb0\x75\xe7\xcc\xd8\x8e\x0b\x20\x60\x40\xff\xb8\xb4\x81\x3f\xcb\ +\xa6\x3b\x63\xd3\x3e\x66\x4c\xf8\xa3\x02\x80\x31\xc8\xa5\x80\x83\ +\xb4\x18\xc8\xfa\x68\x2e\x80\x85\x2d\xb8\x5c\x7f\x0c\x29\xff\xdc\ +\x97\x7f\xb6\x06\x50\xf8\xb2\xe2\x5b\xe8\xa3\xf7\x7d\x53\x6c\xb6\ +\x16\xfc\xfb\x88\x00\x60\x0d\x04\x00\xb6\x36\x3c\x21\xa8\x65\x6d\ +\x87\x33\xad\x67\xeb\xd7\x84\x47\x82\x55\x57\xc5\xb6\x18\x0e\xc2\ +\x6e\x00\x33\xa6\x08\xb9\x16\xb3\xea\x74\x3b\x5b\xc0\x56\x94\x28\ +\xf8\xd3\xaf\xbe\x87\xf7\xb4\xd1\xa8\xae\x6d\x80\x59\xcc\xf5\x7e\ +\x5e\xd8\xab\xe1\x1f\x6d\x5e\xac\x68\x2d\x60\xf7\x46\xbf\xa4\xe9\ +\xf8\x7d\xb3\xc6\x46\x10\x2b\x6c\xc4\xba\x64\x2d\x56\x77\x15\x31\ +\x8a\x58\x49\x81\xdc\x0e\xc2\x13\xcc\x9e\x50\x89\xbd\x26\x5a\x48\ +\x10\x78\xac\x8d\x9b\x78\xa7\xad\x88\x97\x9b\x4c\x3c\xdf\x3c\x0a\ +\xdd\xae\x46\x62\x31\x7e\x89\xc8\x6b\x66\x3f\x7f\x5f\x26\x72\x93\ +\x62\x9b\xbe\x2d\x53\x9f\xa4\xf8\x66\xa9\x20\x8f\x7c\x1a\xaf\x6e\ +\xe2\x79\x80\x86\xcc\x17\xe4\xe8\x77\x80\x5c\x97\x33\xf7\xad\xc1\ +\x67\xf7\x2a\xa1\xb5\xc7\xc0\xf2\x4e\x03\xef\xb4\x14\xf1\xcc\x46\ +\x72\x1b\x5c\x7b\x20\x17\xf3\x20\xe2\xf3\x82\xbb\xe7\x08\xb0\x59\ +\x1f\xbc\xa2\xb0\x3f\xb5\x97\xb4\xab\x33\x6a\x4c\x00\xa1\x6c\xf9\ +\x9d\x19\x8a\x65\x20\xe8\xef\x42\xf4\x07\x8c\xfe\x41\xde\xfe\x71\ +\x9b\x8f\x9d\x75\xe8\x37\x8e\x6d\x17\x73\x01\x9c\x81\x90\xff\x86\ +\x01\x58\x5b\xbb\xb6\xda\xc0\xc0\x8d\x65\x6e\x49\xeb\xcb\x05\x2d\ +\x7d\x00\xd0\x07\x04\x9b\x07\x7e\xb4\xed\xc0\xfc\x47\x67\x00\xd6\ +\x00\x10\x08\xf8\x3c\xf8\xd7\x9b\xab\x70\xd6\x7f\x3f\x83\xc8\xa8\ +\x09\x9b\xcd\xb4\x77\x52\x67\x05\x03\x17\x1d\x30\x06\x57\x2f\xae\ +\xa4\x9f\xcd\x01\x31\x00\x5e\x3c\x54\x34\x4a\x18\x1d\x34\x51\x17\ +\xe0\x88\x7f\x99\x19\x18\x58\xd9\x94\x80\x9b\x68\xbf\x65\x16\x07\ +\xd0\x63\xbe\xc9\x3b\x0b\xc0\x2a\xb2\xac\x33\x1b\xed\xfd\x79\xc4\ +\x56\x5d\xd4\x8f\x06\x6f\x0e\xef\xe6\x8b\xf8\xcd\x4b\x69\xec\x3d\ +\xc6\x0d\x1f\x51\x79\x8e\x15\xc8\xc6\x11\x0a\xaf\x1b\x33\x1a\x80\ +\x59\x8d\x5e\x9c\x3e\x07\xe8\xce\x94\xf0\xf2\x86\x1c\x6e\x79\x23\ +\x8d\x47\xd6\x5b\x70\xf3\xdc\xc3\x7e\x57\x47\x66\x01\x5a\x9b\xb3\ +\x1e\x27\x6e\xc1\x0a\x4f\x1b\x91\x16\x3c\xb5\xbe\x88\x27\x96\x25\ +\x70\xcc\xec\x6a\x24\x72\x86\x04\x82\x53\x86\x4d\xc7\x6b\x2a\x3c\ +\x38\xbc\xd2\x83\x23\xa6\xfa\x71\x19\x7d\x81\x6b\x3a\x8b\x78\x70\ +\x45\x06\x7f\x7c\x33\x81\xa6\xbc\x87\x80\xc7\xf5\xb1\x12\xe6\x72\ +\x6f\x18\x5b\x82\x43\xf9\x1e\xd0\x37\x73\x23\xca\x00\xe0\xd6\xb7\ +\x74\x17\xfa\xbb\x0d\x96\xf5\x6f\xee\xd7\x0f\xc0\x00\x76\xcd\x18\ +\x00\x4f\xd7\xd1\x3f\x9e\x0b\xd0\xdf\xaa\xdb\x75\xf6\xe5\x9f\xad\ +\xed\x2a\xb1\xb5\x03\xa9\x5d\x39\xd7\x3f\x80\x01\x58\x76\x24\xbf\ +\x33\x34\x0e\x46\xe3\x5e\x0e\x95\x1d\x28\x19\xb2\xc2\xd9\xb0\xdb\ +\xb1\xae\x56\x3f\x36\x61\xd9\x80\xc9\xae\x01\xdd\xc1\x2e\x98\x03\ +\x94\x8c\x53\x71\x3c\x2a\xdb\x24\x80\xd8\x62\x91\x01\xed\x93\x2d\ +\x1a\x03\x8e\x85\x2d\x1d\x88\x29\xf8\xb4\x12\x1e\x5c\x6b\xe1\xab\ +\x77\xaf\xc5\x55\x47\xd4\x62\x54\x5d\x8c\x3e\x59\x97\x78\x01\x5b\ +\xe9\x5c\x3f\x63\xeb\xf7\xba\x70\xe4\xf4\x30\x0e\x9b\x12\xc0\x2f\ +\x9f\x69\xc1\xf7\x5e\x24\xa6\x11\x08\xf7\xa6\xe5\x6c\x37\x6a\x20\ +\x00\x88\x75\x93\xe3\x36\x7a\xdd\x92\x8c\xa9\xe3\x2b\x0f\x77\xa1\ +\x90\x4d\xe1\x88\xd9\xf5\xf0\xfa\xfc\xc2\x4a\x8a\x74\x2f\xf0\xf5\ +\x29\xfe\x7f\xf6\xae\x3d\x46\xae\xaa\x8c\xff\xe6\xce\x9d\xf7\xec\ +\xce\xec\xce\x6e\xb7\x5b\xb6\xed\x52\xba\x65\xdb\x42\x6d\x4b\x01\ +\x41\x4c\x45\x7c\x03\x82\x31\xc1\x10\x35\x24\x04\x12\x13\xd1\x48\ +\x9a\xa8\x91\x18\x05\xf5\x0f\x0d\x88\x09\x6a\x48\x8c\x51\x21\x5a\ +\x40\x4b\x83\x36\xb5\x40\x79\x75\xcb\xab\x2f\x4a\xcb\x62\xbb\xbb\ +\xdd\x2e\xbb\x2d\xfb\x7e\xcd\xec\xcc\xdc\x3b\xf7\xe5\x77\xce\xbd\ +\x77\x76\x66\x77\x8b\x15\x85\x99\x9d\x39\xbf\xe4\x64\xe7\x79\xf7\ +\xdc\x33\xdf\xf9\x9d\xdf\xf7\x9d\xef\x9c\x93\xef\x18\x1e\x5c\xd8\ +\xe0\xc7\xb6\xad\x01\x5c\xdf\x36\x83\x6f\xec\x1c\xc2\x9b\x69\x72\ +\x05\x7c\x1f\x4c\xe6\x98\x35\x87\x14\x0a\xad\xd2\x2b\xcd\x2a\x04\ +\x16\xbf\x2d\x54\x0b\xff\x17\x17\xa0\x1a\x63\x00\xcc\x28\xce\x49\ +\x00\x56\xb1\x9c\xf7\x14\x48\x3a\xb3\xc0\x4f\x67\x1d\x5e\x2f\x18\ +\xe1\x0b\x55\xc0\xff\x22\xe1\xff\x77\xf7\x86\x8a\x64\xcd\x3f\xdb\ +\x0f\xec\x20\x4d\xea\xc0\xbc\x83\xcf\x27\x00\x2f\x27\x0a\xa9\xe8\ +\x4c\xc1\x59\xff\xdf\xe0\x2b\xea\x74\x7e\x50\xe6\xec\x09\xbe\xac\ +\xb0\xce\xc9\xd4\x14\x5f\x71\x37\x87\x00\x0c\x92\xfd\x2c\x03\x0f\ +\xf9\x6b\x5a\xf6\xd9\x79\x8c\x80\xe8\x2f\x8b\xba\x3f\x7a\xca\x8f\ +\x17\x7b\xba\x70\x7d\xab\x85\xeb\xd6\x35\x90\x1b\x90\x40\xa2\xae\ +\x16\x12\x9b\x92\x24\xa9\x9e\xd3\xed\xd3\x91\xd9\x48\xcd\x0c\xff\ +\x5b\x9f\x58\x8a\x63\xbd\x6f\xe2\x6f\xc3\x1e\x84\x43\x41\xfb\xda\ +\x6c\x14\x33\xad\xf9\x0a\xc0\x51\x7a\x70\x56\x03\xb2\xb9\x8f\x41\ +\x23\x82\xdb\xff\x31\x8e\x2b\x3b\x06\x70\x63\x7b\x14\x57\x5f\xdc\ +\x88\x95\x4b\xe3\xfc\x74\x64\x36\x31\xca\x09\x81\xfe\xa7\x42\x84\ +\xa8\x10\x23\x5c\xdc\x52\x8b\x9f\x6d\x9d\xc6\x2d\x4f\x0c\x22\x57\ +\xdb\x5c\xa4\x3c\x3e\xb0\xdf\xb0\x30\xb6\x60\x14\x8f\xfa\xae\x42\ +\x90\x1d\x42\x70\x55\x83\x57\x9a\x3f\xa5\x68\xfd\x87\x08\x22\xcf\ +\x23\xd1\x4b\xbb\xc8\xa3\x74\x41\x40\x6e\xd4\xf3\x9d\x6e\xf7\x15\ +\x37\x45\xd2\xcd\x85\x37\x2c\x3b\x7b\xd0\x9a\xe3\xdb\x7f\xd8\x81\ +\x9b\xf3\x51\x00\xf3\x3b\x83\x95\x8f\x7b\xb0\xd1\x15\x0b\xac\x8e\ +\x73\x8f\x1e\xb7\xe6\x12\x00\x58\xac\xc4\x80\x44\x65\x30\xa5\x61\ +\x38\xad\xa3\xb9\xce\xcd\x31\xf0\x60\x4d\x4b\x02\xc6\xf1\x19\xfa\ +\x7e\x63\xd1\x75\x59\xfb\x25\xfc\x16\xda\xea\xbd\x79\x97\x81\x19\ +\xea\xe0\x58\x12\x43\xd3\x2a\xac\x04\xcb\xd8\x23\xff\xdc\x2b\xa3\ +\x5b\x6f\xc6\x83\x6f\xa7\xf1\xf0\xb1\x11\xac\x0e\xbf\x83\x8d\x4b\ +\x24\x5c\xb6\x22\x8a\x4b\x56\xd4\x63\xed\x8a\x04\x22\x35\xf6\x9c\ +\x3f\x0b\x24\x5a\x3e\x92\xe9\xab\x83\x78\xec\xe4\x30\xcc\xe0\x0a\ +\xfe\x3f\xe7\xd6\xbd\xf0\x7e\x4d\xe7\x7e\xd9\x1d\xb1\xce\xcd\xf8\ +\x4b\xf1\xc7\xb1\x67\x32\x8c\x3d\xcf\x4d\xa1\x65\x5f\x37\x56\x47\ +\x73\xf8\xd8\xca\x10\xd6\xb6\xc4\xf0\x91\x95\x09\x2c\x5f\x96\x80\ +\x0e\xc9\x3e\xad\x39\xab\xe1\xd2\xd6\x3a\x5c\xe4\xef\xc2\x1b\x4a\ +\x1d\x42\xfe\x0f\xdf\x64\x17\xb2\x23\xd7\x65\x70\xd5\x80\xad\x14\ +\x3c\x45\xb3\x0d\x85\xf1\x83\x85\x2e\xc2\x42\x60\xa6\x61\x55\x61\ +\x22\x10\x1f\x19\x74\x52\x00\xd2\xbc\x66\x76\xb3\xcd\x75\xb3\x60\ +\x09\xad\x93\x10\x33\x1b\x34\x2c\xdf\x8c\x70\x2b\x9f\xee\x6b\x38\ +\xd3\x77\x56\xfe\x2f\xef\x08\x26\x93\xea\xfa\x02\xa4\xc8\x3e\xe7\ +\x9d\xfd\x2c\xec\xd3\x70\x6d\x32\xd1\x49\x1d\xe8\x98\xce\x99\x38\ +\x76\x36\x83\x4d\x17\xf8\xf9\x67\xb2\x9a\x8e\x6b\xd6\x35\xa3\xed\ +\xa5\xe3\x38\xa5\xa8\x88\xf8\x66\x13\x81\x58\x2a\xf0\xe7\x2e\x0e\ +\x60\x55\x9d\x04\x25\x67\x77\x40\x26\x5f\x5f\xef\xec\xc3\x94\x27\ +\x8c\x35\x11\x3b\xa1\x28\x10\x90\x70\x31\xf9\xdf\xb5\x44\x04\x7f\ +\x3c\x2e\xe3\xa4\x21\xe3\xc4\x88\x82\x3f\xf7\x25\x11\xd6\x4e\x63\ +\x6d\x5d\x2f\x7e\xf5\xd5\x0d\x58\x47\x64\xc0\xd4\x00\x0b\x46\x26\ +\xa2\x3e\x18\xa9\x31\x58\x4d\x2d\xfc\x5e\x78\xdd\xad\xe2\xfb\x65\ +\xaa\x81\xfd\xc6\x16\x57\x7b\x3a\x42\xf4\xcf\x57\xc7\x25\x84\x03\ +\x1e\x24\x42\x5e\x6c\x48\x44\x71\x60\xc0\x83\x67\xcf\xc6\x31\x4a\ +\x9f\xed\xe8\x9c\x01\x0e\x4e\xa0\x51\x3e\x8b\xdb\x2e\x5f\x82\xef\ +\xdd\xbc\x9e\x9f\xf0\xcb\xc4\x4a\x94\x94\x46\xd8\x60\xc7\xaf\x67\ +\x88\x80\xa2\x76\xe0\xa7\xc4\x30\x9c\xa2\xc1\x8d\x0f\x58\x0e\x19\ +\x78\x78\xdc\x80\x3d\x66\x2a\xc1\x39\xa0\x7d\x01\x29\x60\xf1\x6f\ +\x1a\x66\x69\xb7\x04\x29\x11\x01\xd8\xc7\x38\x5b\x5e\xab\x60\x56\ +\xc0\x95\xf9\x76\xa7\xd7\xf3\x89\x38\x56\x51\x04\xb6\xdc\x91\x0f\ +\x02\x3a\x53\x7a\x6e\x87\xb0\xf2\x45\x5f\x50\x01\xd8\xdf\x31\x16\ +\x20\x00\x83\x07\x01\xdd\x28\xf8\xae\x13\x29\xdc\xba\x31\xca\x47\ +\xff\x1c\x11\xc2\x92\x86\x18\xee\xbb\x7e\x05\xee\x7e\x66\x02\xef\ +\x6a\xb5\x5c\x8a\xb2\xef\x5f\xde\xe4\xc3\xf7\xae\x89\xe6\xb3\x09\ +\xd9\xd6\xd4\xd3\xd3\x49\xec\x78\xad\x0f\x88\x6c\xc2\x35\x4d\x16\ +\x7e\xf9\x85\x7a\x72\x57\x98\xa1\x4a\x44\x30\x26\x4e\xf7\x9d\xc6\ +\x13\x03\xe4\x6b\xd3\x88\x2f\x07\x42\x50\xcc\x26\x1c\x4c\x2b\x38\ +\x3d\x65\xe2\x92\xe5\xba\xe3\xeb\x5b\x98\x9c\x9a\xe6\xf7\xe1\xde\ +\x8b\x1b\x04\x74\xeb\xce\x7c\xfa\xda\x20\xcb\x4c\x24\x22\xca\xa8\ +\xa8\x91\x2d\xde\x41\xee\xbf\xae\x0e\x9b\x96\x07\xb9\x8f\xcf\x76\ +\x49\xda\x47\xca\x65\xdf\xbf\xce\x42\xa9\xbd\x00\xfe\x68\x0c\x56\ +\x34\x8e\xa1\x8c\x8e\x97\xc6\x2c\x6c\xd3\x34\x98\x3c\x6c\xef\x41\ +\x26\xa3\x20\x93\x4e\x03\x7e\xa7\xfd\xca\x6c\x5d\xb4\x3b\xc0\x73\ +\x77\x81\x85\x58\x3c\xf6\xd4\xa2\xec\xb8\x06\x2c\xa0\x58\x98\xb0\ +\xe4\xe6\x9f\x70\x02\x70\xa6\x62\xab\x8e\x00\x4c\x27\xf0\xe3\x4e\ +\xc1\xe9\x8e\x71\x99\x05\x9d\xbf\x1c\x24\xfd\xfb\x72\x01\xa4\x62\ +\x9f\xd8\x3c\x87\x4f\x5c\xf4\x3d\xe3\x1c\x2e\x80\xe5\xe4\x4c\xd0\ +\x28\x1a\xa0\xeb\xbe\xd8\xa7\xe1\x9f\x6f\x4d\xe0\xa6\x4d\x8d\x98\ +\xce\x1a\xc8\xa8\x3a\x3e\xbd\xb1\x05\x3b\x1a\x93\x78\xba\x2b\x83\ +\xd1\x8c\x1d\x40\xbb\x61\x6d\x14\x0d\x35\x32\xf9\xd1\x26\x37\xc6\ +\x48\xc0\x87\x5f\xff\x75\x2f\x0e\x4d\x04\x11\x58\x13\xc7\x0b\xbd\ +\x29\xf4\x4f\x84\xd0\xd2\x10\x26\xb5\xa0\xc3\x47\xc3\xd6\x8f\x6f\ +\x6e\x43\xe4\xf9\x11\x1c\x1c\xd7\x78\x42\x4f\x30\xe0\xc5\x17\xaf\ +\xa8\xc5\xd6\xb6\x1a\x64\x34\x7b\x1d\x02\xfb\x81\xf6\xbf\x79\x9a\ +\xa4\x43\x82\x4f\xff\x71\x17\x80\xea\x3e\x99\xca\xe5\xeb\xce\x5c\ +\x8b\x75\xab\x9a\xf1\x9b\x5b\x89\xa4\x7c\x41\xa8\xaa\x8a\xbb\x9f\ +\x1a\xa2\xfa\x49\xd8\xb2\x32\x88\xa4\x6a\x62\x86\xc8\x6b\x73\xdb\ +\x12\xfc\xfa\x46\x1d\xbf\x3b\xa6\x60\x32\x27\xf3\xce\xb1\x72\x99\ +\x0f\xdb\xae\x8e\xf1\xf5\x08\x06\xd9\x07\x9b\x2a\x3c\xd9\x35\x80\ +\xde\x31\x15\xf2\x0a\xd9\x0e\x76\x96\xa9\x21\xcc\xcd\x49\xd0\x1c\ +\x12\x90\x24\x3b\x2b\x31\x1f\x3c\x74\xdd\x04\xf7\x77\xaf\xbe\x59\ +\x00\x16\x58\xd2\xb8\x7c\x9a\x8d\xde\x5b\x15\xb1\x71\x84\xab\x00\ +\x4c\xab\x58\x01\xb0\xe7\xff\xc9\x05\x58\x58\x01\x98\xb3\xa3\xad\ +\x65\xef\xa1\x70\xef\xde\x11\x5c\x44\xd2\x7e\x5d\x6b\x82\x7c\x64\ +\x9d\x27\xd9\xac\x5a\x1a\xc5\xb7\x97\xd9\x23\x3e\xeb\xf1\x39\x32\ +\xac\x2c\xcf\xe1\x97\x50\x13\xf6\xe3\xc9\x3d\x2f\xe3\x67\xbb\xba\ +\x20\xb7\x6d\x85\xdf\xa3\xa3\x2f\x69\xe0\x27\x7b\xce\xe0\x37\xb7\ +\xb4\x52\xe7\xf7\xf1\x29\xc0\xfa\x58\x04\x0f\x7c\xb9\x15\x13\x29\ +\x8d\xe7\xe3\xd7\x86\x64\xfa\xae\x4d\x22\x6c\xba\x2a\x56\x13\x46\ +\xc7\xab\x47\xb1\xfd\xd0\x30\x02\x6d\xed\x24\x48\x72\x76\x10\x90\ +\xea\xdb\x43\x1d\x54\xf2\xd8\xf7\x6a\x07\x71\x25\x7c\xe9\x8a\x16\ +\xbe\x88\xe7\xd0\xdb\x7d\x88\xe5\x86\xf0\xdb\x03\x61\x7c\xb4\xd9\ +\x83\x4f\x5e\xda\x84\x24\x91\x17\x9b\xc1\xb8\xe9\xf2\x16\x7c\x76\ +\x83\x86\xc9\xb4\xce\xeb\xca\x48\x8b\xd5\x5f\xa1\xfa\xf8\x65\x7a\ +\x4c\x76\xf2\xe0\xe3\x2f\x61\x58\x5e\x8a\x28\xdb\x64\xd4\xd0\x17\ +\x8f\x2d\x50\xc9\x15\xc5\x0c\x3c\xb3\xc1\x43\xc7\x5d\xb0\x0f\xbf\ +\xa9\x32\x05\xa0\xd1\xc8\xa1\xa8\x1a\x2c\x59\xca\xa7\xdd\x5a\x56\ +\x65\x6c\x02\xe1\x12\x40\x90\xfc\x71\xb6\xb8\xc6\x4b\x6e\x0e\xfb\ +\x1b\xf0\xc9\x0e\x01\x9c\x3b\x08\xe8\xf5\xb0\xcf\xca\xa4\x20\xec\ +\xd7\x58\xe7\x61\x9d\xca\x26\x00\xc3\x4e\xfe\xa1\xf7\x7a\x32\x32\ +\x6e\xfb\x4b\x2f\xee\xbb\x6e\x0a\xd7\x6e\x68\x21\xb9\xee\xe7\x01\ +\xb6\xac\xb3\xca\x90\x47\xab\xc9\xca\xc2\x41\x2f\xb4\x9c\x8a\xdf\ +\x3f\xf1\x2c\x7e\xf8\xe4\xdb\xc8\xae\xfc\x28\xf9\xfc\x41\xe2\x1f\ +\x0d\x21\xd9\x83\x1d\x3d\xf4\xf7\xf1\x13\xb8\xe7\xf3\x2b\xb0\xb4\ +\x91\x9f\x52\x03\x1a\x9c\x11\x8d\xf8\x51\xe3\x6c\x6a\x62\x2f\x34\ +\xf2\x71\xa3\x7d\xe1\xe5\xc3\xf8\xce\xef\xf6\x21\xbd\x64\x13\x82\ +\xd4\x39\xdd\xce\xc8\x62\x0b\xbb\xbb\x53\xb8\x63\x30\x82\xd5\xcb\ +\x62\xa4\x4a\x6c\xc2\xe3\xea\xc3\x2b\xa3\x2e\x12\x44\x4c\x56\xd1\ +\x4f\x1f\xbf\xeb\xa9\x41\xfc\x22\x93\xc5\x67\x36\x5d\x40\xef\xf9\ +\x90\xd3\xa9\xbe\x74\xad\x86\x98\xcc\xeb\xae\x5b\xac\x83\x10\x69\ +\x85\x24\x4c\x91\xab\xf1\xa3\x3f\xec\xc6\xdf\x4e\x51\x5b\xb6\xad\ +\x84\xa5\x6b\x58\xac\x3b\x02\xe8\xce\x88\x6f\x38\x6e\x80\x1d\x23\ +\xa0\xd7\xf5\x2a\x74\x01\x58\x4a\xab\x92\xcb\x41\xb2\xbc\x8b\x4e\ +\xe2\x9f\xd7\xfd\xd1\xaf\xfc\xca\xc9\x11\x9c\x7e\x27\xcb\x3b\x66\ +\x90\x3a\xff\x91\x93\x03\x7c\x14\xe0\x9d\x66\x81\x51\x8c\xed\x7b\ +\xd1\x3f\xa9\xe0\x99\x43\xbd\x50\x94\x2c\x1f\x05\x83\x64\x21\x13\ +\xc9\x0c\xa4\x80\x65\x7f\xcf\x31\x94\x20\x5d\xa8\x47\x8d\xe0\xb6\ +\x1d\x43\xf8\xd4\xcb\xfd\xb8\x79\x63\x23\x2e\x5d\x1e\x47\x22\x1e\ +\xe1\x6b\x0e\x34\x4d\xc7\x74\x2a\x83\xa3\xdd\x67\xf0\xc8\x73\x6f\ +\x61\xdf\x59\x09\x72\xeb\x55\xf0\x47\x6a\x60\x69\xb9\xfc\xff\x64\ +\xa4\xf4\x48\x97\x81\x83\x7d\xc7\xf0\x95\x4b\x42\xb8\xa6\xbd\x11\ +\xcd\x75\x11\xbe\x03\x31\x1b\x8d\xd9\xb4\xe3\x4c\x3a\x8b\xae\xfe\ +\x51\xec\x39\x70\x12\x4f\x1c\x19\xc5\x4c\xd3\x06\x84\xe2\x8d\x74\ +\x9d\xd9\xce\xe8\x23\xd3\xee\x4f\x99\xb8\x73\x7b\x0f\xbe\xff\xf1\ +\x38\xd5\x25\x86\x48\x38\xc8\xbf\x3f\x3c\x94\xc1\x2b\x47\xbb\xe9\ +\xf7\xd6\x48\x79\x18\x18\xd4\x43\xb8\xfd\xc9\x21\xdc\x70\x70\x00\ +\x37\x6e\x48\x60\x7d\x4b\x1d\x6a\xa2\x41\x9b\x20\x09\x19\x45\xc5\ +\xd8\xe4\x0c\x0f\x56\x6e\xdf\xd7\x85\xc3\xa9\x18\x82\x17\x6e\x74\ +\x94\x86\xb6\xb8\x07\x87\x02\xf7\x80\x11\x81\x41\x2a\x89\x29\x2f\ +\xab\x84\xd2\xb7\x24\x67\x03\x7e\xf6\xa1\x37\x9e\x7f\xa6\x73\xec\ +\x5a\xe6\x63\x56\x26\x48\xc2\x4e\x8f\x03\x53\x43\xb3\xb2\x80\x46\ +\xb9\x60\xe3\x72\xea\xbd\x91\x05\x83\x58\x2c\x20\xa4\x51\xe7\xd4\ +\x47\xcf\x90\x6e\xcc\xb8\x2b\x45\x20\xc7\xea\xe1\xab\x6b\x86\x35\ +\x27\x6b\x92\x8f\x26\x2c\x71\x47\x55\x80\xd4\x04\x96\xfa\xb2\x68\ +\x0c\x1a\x90\xad\x1c\x27\x00\x96\xbd\x77\x36\x4d\xed\x1b\x5f\x86\ +\x40\xfd\x12\x2e\x37\xad\x85\xa2\xe7\xcc\x5d\xa0\x51\xc8\x9c\x99\ +\x46\x8d\x39\x8d\xa5\x81\x1c\xc2\x5e\x93\x4f\x3b\xb2\x0e\x9c\x52\ +\x74\xf4\x27\x49\xa5\x85\x1b\xe0\x6b\x68\x86\xec\x0f\xe6\x13\x7b\ +\x8a\x2f\xe3\xe1\xb3\x0d\x52\x7a\x1c\x17\x05\x67\x10\x95\x54\xe8\ +\x44\xf2\xe3\x33\x54\x0f\xc5\x0b\xdf\x92\x0b\x21\x87\x63\xbc\x1b\ +\xb0\x55\x7e\xb9\x2c\xd5\x3b\x3d\x81\x96\x40\x16\x31\x9f\x0e\x3f\ +\xec\xfd\x0e\x32\x44\x14\xa3\x33\x06\x26\x72\x21\x20\xb1\x0c\xc1\ +\x58\x3d\x9c\x9c\xd9\xca\xb3\x12\x0f\x9b\x8a\x35\xb0\x6f\xdb\xe5\ +\x9f\xf8\xf8\xea\xf8\x4b\x55\xa3\x00\xf8\xbe\xf8\x5c\x0e\xa3\x62\ +\x11\xa8\x8d\x53\xe7\x4b\xcc\xf1\xf3\x8d\x05\x47\x7f\x77\x74\x60\ +\x91\x7a\xef\xb2\x0b\x8b\x17\x38\x38\x99\x80\x0b\xa5\x29\xb3\x13\ +\xa5\x58\x34\x1d\x0d\x4d\x18\x25\xa5\x31\xa4\x39\xe9\xc0\x24\xef\ +\xa5\x7a\x19\x81\x26\xd9\x9e\x86\x22\x17\xe2\xbd\x9a\x9a\xcd\x1e\ +\x7a\x6a\x63\x50\x10\x43\x0f\x91\x47\x5e\x6d\x90\xb6\xf7\xd4\x7a\ +\xe1\x4f\xf8\x9c\xe8\xb5\xc9\x73\xfb\xcf\x55\x7f\x3f\xcb\xd6\x8d\ +\x35\xa0\x5b\x8b\xdb\xd7\xa0\xe7\xbc\x1e\x7e\xd9\xb9\x0f\x3d\x4f\ +\x5e\x81\xa0\x0f\x08\x2d\xc5\xbb\x44\x3e\x67\xb8\x2a\x72\x7c\xe1\ +\x00\xa9\x95\x88\x6c\xbb\x18\xce\x7a\x82\x4a\x85\x65\x47\x7f\xab\ +\x73\x35\xa0\x3b\x7f\x5c\xd1\x78\x3f\xc6\x6b\xbe\x8f\x20\x97\xc9\ +\xfb\x1a\x3f\x6b\x8e\x1c\xea\x3c\x71\x30\x9f\xf9\x7c\x8d\xcb\x9d\ +\x66\xf5\xf1\xac\x16\x6f\xd1\x1b\x7c\x31\xcf\x7f\x61\xa4\x45\xd7\ +\x70\xea\x71\x2e\x07\x8f\x53\x14\x4f\xba\x2f\xfc\x9f\x8c\x68\x54\ +\x54\x05\x0c\xa3\xfa\x32\x01\x51\x05\x0a\x40\x40\xe0\xfc\xc8\xdb\ +\xa8\xce\x54\x60\x9b\x00\xc4\xb1\x97\x02\x82\x00\xaa\x6e\x53\x50\ +\xcb\x12\x04\x20\x20\x50\xbd\x0a\x20\xbf\xce\x5d\xf8\x00\x02\x55\ +\x0e\xab\x0a\x13\x81\x20\x14\x80\x80\xc0\xac\x02\xa8\x36\x17\xc0\ +\xde\x62\x45\x9a\xdd\xa3\xa9\xe8\x3d\xe2\x87\x9c\xbe\xf0\x09\x9a\ +\x02\x02\x8b\x72\x94\x07\x4b\xa7\xcc\x9f\xb7\x50\x7c\xc4\x93\x54\ +\x7d\x04\x60\x0d\xbc\x61\xe0\xc4\x5b\xb0\x42\x81\xf9\x6f\xe6\x72\ +\xb8\xf7\xae\x5b\xb0\xa5\xbd\x15\xd9\x9c\x26\x8c\x47\x60\xd1\x23\ +\x14\x08\xe0\xbb\xf7\xfe\x1c\x9d\x27\xfa\x80\xfa\x66\xc0\x1f\x46\ +\x7e\x1b\xd2\xac\x0a\x2b\x7b\x19\x3d\x68\xac\x22\x05\x90\x99\x50\ +\x90\x1c\x04\xf4\xe0\x02\xef\xa9\xd8\xdc\x1c\xc0\xa7\xdb\x1b\x90\ +\x56\x54\x61\x3d\x02\x8b\x1e\x91\x48\x08\x75\x2a\xd9\xfb\x70\xb7\ +\xbd\x3b\x69\xa8\x76\x76\xe4\xcf\xb0\xcd\x11\x4b\x37\xd2\x95\x86\ +\x00\x24\x6f\x07\x24\x79\x2d\x95\x55\xf3\xde\xf3\x1a\xfc\x50\x4a\ +\x76\x1a\x0d\x2b\x02\x02\x8b\x1e\x32\xf5\x71\x96\xa0\x25\xfb\x59\ +\xb6\x96\x7d\x92\x92\x4d\x00\x49\x7a\x7c\x90\x9e\x0c\x57\x17\x01\ +\x00\x7b\x1d\xcd\xf3\x35\x2a\x4d\xf3\x5c\x84\xc2\xcd\x25\x05\x04\ +\x16\x7b\x08\x60\xe1\x3d\xc1\x58\xca\xe7\x61\x2a\x8f\x52\x19\xa9\ +\x2a\x02\xc8\xaa\xb9\x4e\x92\x3e\x7f\x42\xd0\xcf\x0e\xa1\xbb\x81\ +\x5e\x5a\x4b\x25\x2c\x4c\x45\xa0\x0a\xc0\xa2\xdb\x6c\xc4\xdf\x4f\ +\xe5\x31\x28\xb9\x0e\xc3\x34\x67\x4a\x55\x99\x92\x84\x20\x1f\xbe\ +\xe7\x4e\xe5\x0f\x0f\x7d\xff\x78\xc0\xeb\xfd\x2d\x34\xfd\x61\x7a\ +\xe9\x10\x66\xf7\x4e\x10\x10\xa8\x64\x39\x30\x0a\xcb\x7c\x8a\x1e\ +\x3d\x00\x35\xf7\xf7\x07\x7f\xfa\xcd\xd1\xcb\xda\x57\x95\x4c\xea\ +\x96\x44\x01\x6c\x5c\xd3\xca\xcb\x5b\xbd\x67\x46\x7e\xff\xd8\x9e\ +\x9d\x53\x6a\xae\x86\x94\xc0\x05\xf4\x16\x8b\x09\x88\xe4\x00\x81\ +\x0a\x04\x5f\x97\x69\xc0\xd4\x8e\xc1\x34\x1e\x83\xd7\x7f\x90\xed\ +\x9c\x7a\xd5\xa6\x76\x7e\x94\x7a\x55\x29\x00\x17\xf7\xdf\xfd\x75\ +\x5c\xb6\xb9\x7d\x9c\x64\xd0\xd3\xf4\xf4\x49\x2a\xdd\xc2\x50\x04\ +\x2a\x73\xe4\x37\x75\xe8\xb9\x6e\xe4\x32\x7b\x31\xd6\x73\x1c\xe9\ +\x31\x93\x05\x04\xb3\x4a\x69\x85\xaf\x5c\xea\x76\xb1\xf7\x44\x43\ +\x0f\x95\xed\x60\x51\x51\xe0\x26\x2a\x9b\x60\xaf\x70\x15\x10\xa8\ +\x8c\xd1\xdf\xd0\x46\x90\x9d\x7a\x1d\x93\xea\xeb\x50\x92\x19\x44\ +\x1a\xec\x65\xd0\x25\x86\x5c\x26\x2d\xc4\x68\xf0\x38\x95\x33\x44\ +\x95\x67\x98\x38\xa0\x92\x10\x86\x23\x50\x31\xea\x3f\x97\xee\x83\ +\x3e\xdd\x89\x54\x72\x92\x2f\x82\x29\x71\x06\x60\x59\xb8\x00\x73\ +\xc0\xa6\x45\x46\xa9\xec\xa1\x06\x1a\x11\x91\x00\x81\xca\x52\x00\ +\x6c\x9f\x37\x28\x4e\x1a\x70\xd9\x58\xb7\x54\x5e\xed\xc4\xb6\x49\ +\x55\x86\xa9\x4c\x89\x58\xa0\x40\xe5\xc9\x80\xf2\x33\x6a\xb9\xbc\ +\xda\x88\xda\x47\xcd\x58\x50\xd3\x6c\xb3\x68\x91\x08\x24\x50\x11\ +\x28\x67\x3b\x2e\x39\x01\xb0\x53\x64\x90\x51\xec\x27\x2c\x4d\x32\ +\x93\x86\xc7\xd4\x2c\x78\x84\x02\x10\xa8\x60\x30\xbb\xd7\x14\xe7\ +\x6c\xc0\x2a\x26\x80\xab\x36\xac\x41\x88\x11\x64\xd0\x4f\x0a\xc0\ +\x8b\xf1\x6e\x99\x9f\x1b\x28\x20\x50\xc9\xd8\x48\x76\x1f\x4f\xb4\ +\x20\x11\xab\xa9\x6e\x02\x78\x70\xdb\x6d\x45\xcf\x9f\x7f\x6e\x2f\ +\xfa\xfa\x07\x84\x85\x08\x54\x34\x1e\xfe\xc1\x1d\xb8\xf2\xaa\xab\ +\x4b\x5e\x0f\xa9\xdc\x1a\x46\x21\x69\x24\xc4\xbf\x40\xa5\x83\xbb\ +\xbe\x65\x00\x49\xfc\x14\x02\x02\xd5\x0b\x41\x00\x02\x02\x82\x00\ +\x04\x04\x04\x04\x01\x08\x08\x08\x08\x02\x10\x10\x10\x10\x04\x20\ +\x20\x20\x50\xe1\x90\xcb\xb4\x5e\x92\xd8\x13\x50\xa0\x52\x50\xce\ +\x76\x5c\x96\x0a\x40\xd3\xb4\x7e\x55\x55\xd3\xc2\x74\x04\x16\x33\ +\xd8\xf9\x37\x64\xcb\x66\x7f\x7f\xbf\xaa\x28\x8a\x9b\xde\x6a\x09\ +\x02\x78\xaf\x0a\x49\x12\x26\x26\x26\x76\x8f\x8e\x8e\xbe\xe9\x36\ +\xa2\x80\xc0\x62\x84\xd7\xeb\xc5\xcc\xcc\x8c\xbe\x6b\xd7\xae\x77\ +\x87\x86\x86\xd8\x66\x37\x6c\xff\x7f\xa3\x9c\x48\x40\x2a\xc7\x46\ +\x7b\xed\xb5\xd7\x3a\x0e\x1f\x3e\xfc\xcc\xd4\xd4\xd4\x18\x23\x00\ +\x49\x12\xa1\x0a\x81\xc5\x35\xf2\x33\x3b\x26\x15\x6b\x1e\x39\x72\ +\x64\x72\xe7\xce\x9d\xfd\x34\xa0\xb1\xad\xbf\xa7\xc1\xf6\x04\x28\ +\x23\x02\x28\xbb\x18\x80\x2c\xcb\xd8\xbd\x7b\x37\x6b\xac\xe7\xa8\ +\xe3\xb7\xad\x5f\xbf\xfe\x73\xf1\x78\xbc\xce\xe7\xf3\x49\xac\x51\ +\x45\x5c\x40\xa0\x9c\x3b\xbe\xe3\xc2\x5a\x4c\xf2\x1f\x3d\x7a\x74\ +\x6a\xc7\x8e\x1d\x03\x27\x4e\x9c\x38\xab\xeb\x7a\x3f\xbd\x35\x48\ +\x85\x6d\x01\x6e\x0a\x02\x78\x6f\xbf\x49\xdb\xbf\x7f\x7f\x57\x2a\ +\x95\xda\xbe\x65\xcb\x96\x99\xcd\x9b\x37\x6f\x6d\x6f\x6f\x6f\x6d\ +\x6c\x6c\x0c\x12\x11\x08\x45\x20\x50\x76\x60\x03\x93\x61\x18\xc8\ +\x64\x32\xd6\xa9\x53\xa7\x52\x1d\x1d\x1d\xa3\x7b\xf7\xee\x1d\xee\ +\xec\xec\x3c\x93\xcd\x66\x4f\xc1\xde\xf7\xf2\xac\x20\x80\xf3\x83\ +\x39\x3e\x3e\x3e\xfd\xea\xab\xaf\x1e\xa5\xc6\xf3\x27\x93\x49\xab\ +\xaf\xaf\x6f\x73\x5d\x5d\x5d\x63\x38\x1c\x8e\x10\x09\xb0\x7a\xbb\ +\xc1\x01\x11\x24\x10\x28\x0b\x02\xa0\x51\x5f\x27\x5b\xd5\x7a\x7b\ +\x7b\x93\x07\x0e\x1c\x18\x24\x05\xd0\x4f\xf6\xdb\x4b\x6f\x9f\x74\ +\x08\x80\x29\x5b\x55\xb8\x00\xe7\xd1\x9e\x4c\x49\x51\xe3\x4d\x10\ +\x09\x1c\xa4\x32\x44\xcf\x5f\xa6\xd2\x4a\x65\x09\x15\xb6\x88\xda\ +\x07\x91\xc7\x20\x50\x7e\x76\x6b\x3a\x9d\x7c\x8a\xca\xbb\x54\xd8\ +\xe8\xdf\xeb\x3c\x4e\x97\xd3\xe8\x5f\xce\x04\x80\x82\x86\x1c\x87\ +\xbd\x61\x68\xd6\x79\xcc\xce\x14\x8c\x50\xf1\x0b\x02\x10\x28\x43\ +\x02\x60\x51\x7e\xc5\x21\x80\x61\xa7\xe3\x8f\x39\x9d\x5f\x47\x99\ +\x4d\x03\xca\x65\xde\xa0\x2e\x09\x4c\x50\xc9\x38\x0d\x1a\x72\x46\ +\x7f\xaf\x90\xff\x02\x65\x6a\xb3\x2e\x09\x64\x9c\xa2\x3a\xaf\x97\ +\x5d\x04\xfb\xdf\x02\x0c\x00\x0b\x53\x81\x73\x51\x45\x40\xdb\x00\ \x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ " @@ -2044,6 +3444,10 @@ \x00\x62\ \x00\x75\x00\x74\x00\x74\x00\x6f\x00\x6e\x00\x73\ \x00\x03\ +\x00\x00\x78\xc3\ +\x00\x72\ +\x00\x65\x00\x73\ +\x00\x03\ \x00\x00\x70\x37\ \x00\x69\ \x00\x6d\x00\x67\ @@ -2123,36 +3527,44 @@ \x00\x62\ \x00\x75\x00\x74\x00\x74\x00\x6f\x00\x6e\x00\x5f\x00\x72\x00\x65\x00\x73\x00\x74\x00\x6f\x00\x72\x00\x65\x00\x2e\x00\x70\x00\x6e\ \x00\x67\ -\x00\x0c\ -\x09\xf7\x1c\xa7\ +\x00\x05\ +\x00\x6f\xa6\x53\ +\x00\x69\ +\x00\x63\x00\x6f\x00\x6e\x00\x73\ +\x00\x14\ +\x0d\x75\xab\xe7\ \x00\x75\ -\x00\x74\x00\x6c\x00\x5f\x00\x69\x00\x63\x00\x6f\x00\x6e\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x74\x00\x6c\x00\x5f\x00\x69\x00\x63\x00\x6f\x00\x6e\x00\x40\x00\x32\x00\x35\x00\x36\x00\x78\x00\x32\x00\x35\x00\x36\x00\x2e\ +\x00\x70\x00\x6e\x00\x67\ " qt_resource_struct = "\ \x00\x00\x00\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00\x01\ -\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x15\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x16\ \x00\x00\x00\x0e\x00\x02\x00\x00\x00\x01\x00\x00\x00\x03\ \x00\x00\x00\x22\x00\x02\x00\x00\x00\x01\x00\x00\x00\x04\ -\x00\x00\x00\x0e\x00\x02\x00\x00\x00\x10\x00\x00\x00\x05\ -\x00\x00\x00\xb8\x00\x00\x00\x00\x00\x01\x00\x00\x12\x2c\ -\x00\x00\x02\xb6\x00\x00\x00\x00\x00\x01\x00\x00\x57\x58\ -\x00\x00\x02\x6a\x00\x00\x00\x00\x00\x01\x00\x00\x48\xda\ -\x00\x00\x03\x04\x00\x00\x00\x00\x00\x01\x00\x00\x62\xfc\ -\x00\x00\x02\x8e\x00\x00\x00\x00\x00\x01\x00\x00\x50\x67\ -\x00\x00\x01\xbe\x00\x00\x00\x00\x00\x01\x00\x00\x33\x9e\ -\x00\x00\x01\x64\x00\x00\x00\x00\x00\x01\x00\x00\x25\x54\ -\x00\x00\x00\x68\x00\x00\x00\x00\x00\x01\x00\x00\x04\xfc\ -\x00\x00\x00\xf0\x00\x00\x00\x00\x00\x01\x00\x00\x18\xef\ -\x00\x00\x01\x88\x00\x00\x00\x00\x00\x01\x00\x00\x2c\x15\ -\x00\x00\x01\x2e\x00\x00\x00\x00\x00\x01\x00\x00\x1e\x93\ -\x00\x00\x01\xfa\x00\x00\x00\x00\x00\x01\x00\x00\x3a\x5d\ -\x00\x00\x02\xdc\x00\x00\x00\x00\x00\x01\x00\x00\x5e\x00\ -\x00\x00\x00\x2e\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ -\x00\x00\x02\x30\x00\x00\x00\x00\x00\x01\x00\x00\x41\xe9\ -\x00\x00\x00\x94\x00\x00\x00\x00\x00\x01\x00\x00\x0a\xa0\ -\x00\x00\x00\x22\x00\x02\x00\x00\x00\x01\x00\x00\x00\x16\ -\x00\x00\x03\x2e\x00\x00\x00\x00\x00\x01\x00\x00\x69\xb1\ +\x00\x00\x00\x2e\x00\x02\x00\x00\x00\x01\x00\x00\x00\x05\ +\x00\x00\x00\x0e\x00\x02\x00\x00\x00\x10\x00\x00\x00\x06\ +\x00\x00\x00\xc4\x00\x00\x00\x00\x00\x01\x00\x00\x12\x2c\ +\x00\x00\x02\xc2\x00\x00\x00\x00\x00\x01\x00\x00\x57\x58\ +\x00\x00\x02\x76\x00\x00\x00\x00\x00\x01\x00\x00\x48\xda\ +\x00\x00\x03\x10\x00\x00\x00\x00\x00\x01\x00\x00\x62\xfc\ +\x00\x00\x02\x9a\x00\x00\x00\x00\x00\x01\x00\x00\x50\x67\ +\x00\x00\x01\xca\x00\x00\x00\x00\x00\x01\x00\x00\x33\x9e\ +\x00\x00\x01\x70\x00\x00\x00\x00\x00\x01\x00\x00\x25\x54\ +\x00\x00\x00\x74\x00\x00\x00\x00\x00\x01\x00\x00\x04\xfc\ +\x00\x00\x00\xfc\x00\x00\x00\x00\x00\x01\x00\x00\x18\xef\ +\x00\x00\x01\x94\x00\x00\x00\x00\x00\x01\x00\x00\x2c\x15\ +\x00\x00\x01\x3a\x00\x00\x00\x00\x00\x01\x00\x00\x1e\x93\ +\x00\x00\x02\x06\x00\x00\x00\x00\x00\x01\x00\x00\x3a\x5d\ +\x00\x00\x02\xe8\x00\x00\x00\x00\x00\x01\x00\x00\x5e\x00\ +\x00\x00\x00\x3a\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ +\x00\x00\x02\x3c\x00\x00\x00\x00\x00\x01\x00\x00\x41\xe9\ +\x00\x00\x00\xa0\x00\x00\x00\x00\x00\x01\x00\x00\x0a\xa0\ +\x00\x00\x00\x22\x00\x02\x00\x00\x00\x01\x00\x00\x00\x17\ +\x00\x00\x00\x2e\x00\x02\x00\x00\x00\x01\x00\x00\x00\x18\ +\x00\x00\x03\x3a\x00\x02\x00\x00\x00\x01\x00\x00\x00\x19\ +\x00\x00\x03\x4a\x00\x00\x00\x00\x00\x01\x00\x00\x69\xb1\ " def qInitResources(): diff --git a/qthostsui.py b/gui/util_ui.py similarity index 60% rename from qthostsui.py rename to gui/util_ui.py index a393ff8..bafe7e2 100644 --- a/qthostsui.py +++ b/gui/util_ui.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# Form implementation generated from reading ui file 'qthostsui.ui' +# Form implementation generated from reading ui file '../pyqt\util_ui.ui' # -# Created: Sat Nov 30 15:06:56 2013 -# by: PyQt4 UI code generator 4.10.2 +# Created: Wed Jan 22 13:03:09 2014 +# by: PyQt4 UI code generator 4.10.3 # # WARNING! All changes made in this file will be lost! @@ -23,26 +23,26 @@ def _translate(context, text, disambig): def _translate(context, text, disambig): return QtGui.QApplication.translate(context, text, disambig) -class Ui_HostsUtlMain(object): - def setupUi(self, HostsUtlMain): - HostsUtlMain.setObjectName(_fromUtf8("HostsUtlMain")) - HostsUtlMain.setEnabled(True) - HostsUtlMain.resize(640, 420) +class Ui_Util(object): + def setupUi(self, Util): + Util.setObjectName(_fromUtf8("Util")) + Util.setEnabled(True) + Util.resize(640, 420) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(HostsUtlMain.sizePolicy().hasHeightForWidth()) - HostsUtlMain.setSizePolicy(sizePolicy) - HostsUtlMain.setMinimumSize(QtCore.QSize(640, 420)) - HostsUtlMain.setMaximumSize(QtCore.QSize(640, 420)) - HostsUtlMain.setContextMenuPolicy(QtCore.Qt.DefaultContextMenu) + sizePolicy.setHeightForWidth(Util.sizePolicy().hasHeightForWidth()) + Util.setSizePolicy(sizePolicy) + Util.setMinimumSize(QtCore.QSize(640, 420)) + Util.setMaximumSize(QtCore.QSize(640, 420)) + Util.setContextMenuPolicy(QtCore.Qt.DefaultContextMenu) icon = QtGui.QIcon() - icon.addPixmap(QtGui.QPixmap(_fromUtf8(":/icon/img/utl_icon.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off) - HostsUtlMain.setWindowIcon(icon) - HostsUtlMain.setLocale(QtCore.QLocale(QtCore.QLocale.English, QtCore.QLocale.UnitedStates)) - HostsUtlMain.setSizeGripEnabled(False) - HostsUtlMain.setModal(False) - self.mainFrame = QtGui.QFrame(HostsUtlMain) + icon.addPixmap(QtGui.QPixmap(_fromUtf8(":/icon/res/img/icons/utl_icon@256x256.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off) + Util.setWindowIcon(icon) + Util.setLocale(QtCore.QLocale(QtCore.QLocale.English, QtCore.QLocale.UnitedStates)) + Util.setSizeGripEnabled(False) + Util.setModal(False) + self.mainFrame = QtGui.QFrame(Util) self.mainFrame.setGeometry(QtCore.QRect(0, 40, 640, 351)) self.mainFrame.setFrameShape(QtGui.QFrame.StyledPanel) self.mainFrame.setFrameShadow(QtGui.QFrame.Raised) @@ -164,8 +164,8 @@ def setupUi(self, HostsUtlMain): self.ButtonBackup.setGeometry(QtCore.QRect(0, 10, 48, 48)) self.ButtonBackup.setText(_fromUtf8("")) icon1 = QtGui.QIcon() - icon1.addPixmap(QtGui.QPixmap(_fromUtf8(":/buttons/img/buttons/button_backup.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off) - icon1.addPixmap(QtGui.QPixmap(_fromUtf8(":/buttons/img/buttons/button_backup_disabled.png")), QtGui.QIcon.Disabled, QtGui.QIcon.Off) + icon1.addPixmap(QtGui.QPixmap(_fromUtf8(":/buttons/res/img/buttons/button_backup.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off) + icon1.addPixmap(QtGui.QPixmap(_fromUtf8(":/buttons/res/img/buttons/button_backup_disabled.png")), QtGui.QIcon.Disabled, QtGui.QIcon.Off) self.ButtonBackup.setIcon(icon1) self.ButtonBackup.setIconSize(QtCore.QSize(32, 32)) self.ButtonBackup.setObjectName(_fromUtf8("ButtonBackup")) @@ -173,8 +173,8 @@ def setupUi(self, HostsUtlMain): self.ButtonUpdate.setGeometry(QtCore.QRect(60, 70, 48, 48)) self.ButtonUpdate.setText(_fromUtf8("")) icon2 = QtGui.QIcon() - icon2.addPixmap(QtGui.QPixmap(_fromUtf8(":/buttons/img/buttons/button_download.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off) - icon2.addPixmap(QtGui.QPixmap(_fromUtf8(":/buttons/img/buttons/button_download_disabled.png")), QtGui.QIcon.Disabled, QtGui.QIcon.Off) + icon2.addPixmap(QtGui.QPixmap(_fromUtf8(":/buttons/res/img/buttons/button_download.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off) + icon2.addPixmap(QtGui.QPixmap(_fromUtf8(":/buttons/res/img/buttons/button_download_disabled.png")), QtGui.QIcon.Disabled, QtGui.QIcon.Off) self.ButtonUpdate.setIcon(icon2) self.ButtonUpdate.setIconSize(QtCore.QSize(32, 32)) self.ButtonUpdate.setObjectName(_fromUtf8("ButtonUpdate")) @@ -182,8 +182,8 @@ def setupUi(self, HostsUtlMain): self.ButtonRestore.setGeometry(QtCore.QRect(60, 10, 48, 48)) self.ButtonRestore.setText(_fromUtf8("")) icon3 = QtGui.QIcon() - icon3.addPixmap(QtGui.QPixmap(_fromUtf8(":/buttons/img/buttons/button_restore.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off) - icon3.addPixmap(QtGui.QPixmap(_fromUtf8(":/buttons/img/buttons/button_restore_disabled.png")), QtGui.QIcon.Disabled, QtGui.QIcon.Off) + icon3.addPixmap(QtGui.QPixmap(_fromUtf8(":/buttons/res/img/buttons/button_restore.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off) + icon3.addPixmap(QtGui.QPixmap(_fromUtf8(":/buttons/res/img/buttons/button_restore_disabled.png")), QtGui.QIcon.Disabled, QtGui.QIcon.Off) self.ButtonRestore.setIcon(icon3) self.ButtonRestore.setIconSize(QtCore.QSize(32, 32)) self.ButtonRestore.setObjectName(_fromUtf8("ButtonRestore")) @@ -191,8 +191,8 @@ def setupUi(self, HostsUtlMain): self.ButtonApply.setGeometry(QtCore.QRect(0, 220, 48, 48)) self.ButtonApply.setText(_fromUtf8("")) icon4 = QtGui.QIcon() - icon4.addPixmap(QtGui.QPixmap(_fromUtf8(":/buttons/img/buttons/button_apply.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off) - icon4.addPixmap(QtGui.QPixmap(_fromUtf8(":/buttons/img/buttons/button_apply_disabled.png")), QtGui.QIcon.Disabled, QtGui.QIcon.Off) + icon4.addPixmap(QtGui.QPixmap(_fromUtf8(":/buttons/res/img/buttons/button_apply.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off) + icon4.addPixmap(QtGui.QPixmap(_fromUtf8(":/buttons/res/img/buttons/button_apply_disabled.png")), QtGui.QIcon.Disabled, QtGui.QIcon.Off) self.ButtonApply.setIcon(icon4) self.ButtonApply.setIconSize(QtCore.QSize(32, 32)) self.ButtonApply.setObjectName(_fromUtf8("ButtonApply")) @@ -200,8 +200,8 @@ def setupUi(self, HostsUtlMain): self.ButtonExit.setGeometry(QtCore.QRect(60, 220, 48, 48)) self.ButtonExit.setText(_fromUtf8("")) icon5 = QtGui.QIcon() - icon5.addPixmap(QtGui.QPixmap(_fromUtf8(":/buttons/img/buttons/button_exit.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off) - icon5.addPixmap(QtGui.QPixmap(_fromUtf8(":/buttons/img/buttons/button_exit_disabled.png")), QtGui.QIcon.Disabled, QtGui.QIcon.Off) + icon5.addPixmap(QtGui.QPixmap(_fromUtf8(":/buttons/res/img/buttons/button_exit.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off) + icon5.addPixmap(QtGui.QPixmap(_fromUtf8(":/buttons/res/img/buttons/button_exit_disabled.png")), QtGui.QIcon.Disabled, QtGui.QIcon.Off) self.ButtonExit.setIcon(icon5) self.ButtonExit.setIconSize(QtCore.QSize(32, 32)) self.ButtonExit.setObjectName(_fromUtf8("ButtonExit")) @@ -209,8 +209,8 @@ def setupUi(self, HostsUtlMain): self.ButtonCheck.setGeometry(QtCore.QRect(0, 70, 48, 48)) self.ButtonCheck.setText(_fromUtf8("")) icon6 = QtGui.QIcon() - icon6.addPixmap(QtGui.QPixmap(_fromUtf8(":/buttons/img/buttons/button_update.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off) - icon6.addPixmap(QtGui.QPixmap(_fromUtf8(":/buttons/img/buttons/button_update_disabled.png")), QtGui.QIcon.Disabled, QtGui.QIcon.Off) + icon6.addPixmap(QtGui.QPixmap(_fromUtf8(":/buttons/res/img/buttons/button_update.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off) + icon6.addPixmap(QtGui.QPixmap(_fromUtf8(":/buttons/res/img/buttons/button_update_disabled.png")), QtGui.QIcon.Disabled, QtGui.QIcon.Off) self.ButtonCheck.setIcon(icon6) self.ButtonCheck.setIconSize(QtCore.QSize(32, 32)) self.ButtonCheck.setObjectName(_fromUtf8("ButtonCheck")) @@ -218,8 +218,8 @@ def setupUi(self, HostsUtlMain): self.ButtonANSI.setGeometry(QtCore.QRect(0, 160, 48, 48)) self.ButtonANSI.setText(_fromUtf8("")) icon7 = QtGui.QIcon() - icon7.addPixmap(QtGui.QPixmap(_fromUtf8(":/buttons/img/buttons/button_ansi.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off) - icon7.addPixmap(QtGui.QPixmap(_fromUtf8(":/buttons/img/buttons/button_ansi_disabled.png")), QtGui.QIcon.Disabled, QtGui.QIcon.Off) + icon7.addPixmap(QtGui.QPixmap(_fromUtf8(":/buttons/res/img/buttons/button_ansi.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off) + icon7.addPixmap(QtGui.QPixmap(_fromUtf8(":/buttons/res/img/buttons/button_ansi_disabled.png")), QtGui.QIcon.Disabled, QtGui.QIcon.Off) self.ButtonANSI.setIcon(icon7) self.ButtonANSI.setIconSize(QtCore.QSize(32, 32)) self.ButtonANSI.setObjectName(_fromUtf8("ButtonANSI")) @@ -227,8 +227,8 @@ def setupUi(self, HostsUtlMain): self.ButtonUTF.setGeometry(QtCore.QRect(60, 160, 48, 48)) self.ButtonUTF.setText(_fromUtf8("")) icon8 = QtGui.QIcon() - icon8.addPixmap(QtGui.QPixmap(_fromUtf8(":/buttons/img/buttons/button_utf8.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off) - icon8.addPixmap(QtGui.QPixmap(_fromUtf8(":/buttons/img/buttons/button_utf8_disabled.png")), QtGui.QIcon.Disabled, QtGui.QIcon.Off) + icon8.addPixmap(QtGui.QPixmap(_fromUtf8(":/buttons/res/img/buttons/button_utf8.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off) + icon8.addPixmap(QtGui.QPixmap(_fromUtf8(":/buttons/res/img/buttons/button_utf8_disabled.png")), QtGui.QIcon.Disabled, QtGui.QIcon.Off) self.ButtonUTF.setIcon(icon8) self.ButtonUTF.setIconSize(QtCore.QSize(32, 32)) self.ButtonUTF.setObjectName(_fromUtf8("ButtonUTF")) @@ -241,93 +241,97 @@ def setupUi(self, HostsUtlMain): self.Prog.setTextVisible(True) self.Prog.setInvertedAppearance(False) self.Prog.setObjectName(_fromUtf8("Prog")) - self.TitleLabel = QtGui.QLabel(HostsUtlMain) + self.TitleLabel = QtGui.QLabel(Util) self.TitleLabel.setGeometry(QtCore.QRect(20, 20, 250, 25)) self.TitleLabel.setObjectName(_fromUtf8("TitleLabel")) - self.Copyright = QtGui.QLabel(HostsUtlMain) + self.Copyright = QtGui.QLabel(Util) self.Copyright.setGeometry(QtCore.QRect(330, 390, 300, 16)) self.Copyright.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) self.Copyright.setObjectName(_fromUtf8("Copyright")) - self.label = QtGui.QLabel(HostsUtlMain) + self.label = QtGui.QLabel(Util) self.label.setGeometry(QtCore.QRect(10, 390, 150, 16)) self.label.setObjectName(_fromUtf8("label")) + self.VersionLabel = QtGui.QLabel(Util) + self.VersionLabel.setGeometry(QtCore.QRect(380, 20, 250, 25)) + self.VersionLabel.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) + self.VersionLabel.setObjectName(_fromUtf8("VersionLabel")) self.labelIP.setBuddy(self.SelectMirror) self.labelMirror.setBuddy(self.SelectIP) - self.retranslateUi(HostsUtlMain) - QtCore.QObject.connect(self.ButtonExit, QtCore.SIGNAL(_fromUtf8("clicked()")), HostsUtlMain.close) - QtCore.QObject.connect(self.SelectIP, QtCore.SIGNAL(_fromUtf8("currentIndexChanged(int)")), HostsUtlMain.on_IPVersion_changed) - QtCore.QObject.connect(self.SelectMirror, QtCore.SIGNAL(_fromUtf8("currentIndexChanged(int)")), HostsUtlMain.on_Mirror_changed) - QtCore.QObject.connect(self.Functionlist, QtCore.SIGNAL(_fromUtf8("itemChanged(QListWidgetItem*)")), HostsUtlMain.on_Selection_changed) - QtCore.QObject.connect(self.ButtonApply, QtCore.SIGNAL(_fromUtf8("clicked()")), HostsUtlMain.on_MakeHosts_clicked) - QtCore.QObject.connect(self.ButtonBackup, QtCore.SIGNAL(_fromUtf8("clicked()")), HostsUtlMain.on_Backup_clicked) - QtCore.QObject.connect(self.ButtonRestore, QtCore.SIGNAL(_fromUtf8("clicked()")), HostsUtlMain.on_Restore_clicked) - QtCore.QObject.connect(self.ButtonCheck, QtCore.SIGNAL(_fromUtf8("clicked()")), HostsUtlMain.on_CheckUpdate_clicked) - QtCore.QObject.connect(self.ButtonUpdate, QtCore.SIGNAL(_fromUtf8("clicked()")), HostsUtlMain.on_FetchUpdate_clicked) - QtCore.QObject.connect(self.SelectLang, QtCore.SIGNAL(_fromUtf8("currentIndexChanged(QString)")), HostsUtlMain.on_Lang_changed) - QtCore.QObject.connect(self.ButtonANSI, QtCore.SIGNAL(_fromUtf8("clicked()")), HostsUtlMain.on_MakeANSI_clicked) - QtCore.QObject.connect(self.ButtonUTF, QtCore.SIGNAL(_fromUtf8("clicked()")), HostsUtlMain.on_MakeUTF8_clicked) - QtCore.QObject.connect(self.Copyright, QtCore.SIGNAL(_fromUtf8("linkActivated(QString)")), HostsUtlMain.on_LinkActivated) - QtCore.QMetaObject.connectSlotsByName(HostsUtlMain) - HostsUtlMain.setTabOrder(self.SelectMirror, self.SelectIP) - HostsUtlMain.setTabOrder(self.SelectIP, self.Functionlist) - HostsUtlMain.setTabOrder(self.Functionlist, self.ButtonApply) - HostsUtlMain.setTabOrder(self.ButtonApply, self.ButtonExit) - HostsUtlMain.setTabOrder(self.ButtonExit, self.ButtonCheck) - HostsUtlMain.setTabOrder(self.ButtonCheck, self.ButtonUpdate) - HostsUtlMain.setTabOrder(self.ButtonUpdate, self.ButtonBackup) - HostsUtlMain.setTabOrder(self.ButtonBackup, self.ButtonRestore) - HostsUtlMain.setTabOrder(self.ButtonRestore, self.ButtonANSI) - HostsUtlMain.setTabOrder(self.ButtonANSI, self.ButtonUTF) - HostsUtlMain.setTabOrder(self.ButtonUTF, self.SelectLang) + self.retranslateUi(Util) + QtCore.QObject.connect(self.ButtonExit, QtCore.SIGNAL(_fromUtf8("clicked()")), Util.close) + QtCore.QObject.connect(self.SelectIP, QtCore.SIGNAL(_fromUtf8("currentIndexChanged(int)")), Util.on_IPVersion_changed) + QtCore.QObject.connect(self.SelectMirror, QtCore.SIGNAL(_fromUtf8("currentIndexChanged(int)")), Util.on_Mirror_changed) + QtCore.QObject.connect(self.Functionlist, QtCore.SIGNAL(_fromUtf8("itemChanged(QListWidgetItem*)")), Util.on_Selection_changed) + QtCore.QObject.connect(self.ButtonApply, QtCore.SIGNAL(_fromUtf8("clicked()")), Util.on_MakeHosts_clicked) + QtCore.QObject.connect(self.ButtonBackup, QtCore.SIGNAL(_fromUtf8("clicked()")), Util.on_Backup_clicked) + QtCore.QObject.connect(self.ButtonRestore, QtCore.SIGNAL(_fromUtf8("clicked()")), Util.on_Restore_clicked) + QtCore.QObject.connect(self.ButtonCheck, QtCore.SIGNAL(_fromUtf8("clicked()")), Util.on_CheckUpdate_clicked) + QtCore.QObject.connect(self.ButtonUpdate, QtCore.SIGNAL(_fromUtf8("clicked()")), Util.on_FetchUpdate_clicked) + QtCore.QObject.connect(self.SelectLang, QtCore.SIGNAL(_fromUtf8("currentIndexChanged(QString)")), Util.on_Lang_changed) + QtCore.QObject.connect(self.ButtonANSI, QtCore.SIGNAL(_fromUtf8("clicked()")), Util.on_MakeANSI_clicked) + QtCore.QObject.connect(self.ButtonUTF, QtCore.SIGNAL(_fromUtf8("clicked()")), Util.on_MakeUTF8_clicked) + QtCore.QObject.connect(self.Copyright, QtCore.SIGNAL(_fromUtf8("linkActivated(QString)")), Util.on_LinkActivated) + QtCore.QMetaObject.connectSlotsByName(Util) + Util.setTabOrder(self.SelectMirror, self.SelectIP) + Util.setTabOrder(self.SelectIP, self.Functionlist) + Util.setTabOrder(self.Functionlist, self.ButtonApply) + Util.setTabOrder(self.ButtonApply, self.ButtonExit) + Util.setTabOrder(self.ButtonExit, self.ButtonCheck) + Util.setTabOrder(self.ButtonCheck, self.ButtonUpdate) + Util.setTabOrder(self.ButtonUpdate, self.ButtonBackup) + Util.setTabOrder(self.ButtonBackup, self.ButtonRestore) + Util.setTabOrder(self.ButtonRestore, self.ButtonANSI) + Util.setTabOrder(self.ButtonANSI, self.ButtonUTF) + Util.setTabOrder(self.ButtonUTF, self.SelectLang) - def retranslateUi(self, HostsUtlMain): - HostsUtlMain.setWindowTitle(_translate("HostsUtlMain", "Hosts Setup Utility", None)) - self.ConfigBox.setTitle(_translate("HostsUtlMain", "Config", None)) - self.labelIP.setText(_translate("HostsUtlMain", "Server", None)) - self.labelMirror.setText(_translate("HostsUtlMain", "IP Version", None)) - self.StatusBox.setTitle(_translate("HostsUtlMain", "Status", None)) - self.labelConn.setText(_translate("HostsUtlMain", "Connection", None)) - self.labelConnStat.setText(_translate("HostsUtlMain", "N/A", None)) - self.labelOS.setText(_translate("HostsUtlMain", "OS", None)) - self.labelOSStat.setText(_translate("HostsUtlMain", "N/A", None)) - self.InfoBox.setTitle(_translate("HostsUtlMain", "Hosts Info", None)) - self.labelVersion.setText(_translate("HostsUtlMain", "Version", None)) - self.labelVersionData.setText(_translate("HostsUtlMain", "N/A", None)) - self.labelRelease.setText(_translate("HostsUtlMain", "Release", None)) - self.labelReleaseData.setText(_translate("HostsUtlMain", "N/A", None)) - self.labelLatest.setText(_translate("HostsUtlMain", "Latest", None)) - self.labelLatestData.setText(_translate("HostsUtlMain", "N/A", None)) - self.FunctionsBox.setTitle(_translate("HostsUtlMain", "Functions", None)) - self.ButtonBackup.setToolTip(_translate("HostsUtlMain", "Backup hosts", None)) - self.ButtonBackup.setWhatsThis(_translate("HostsUtlMain", "Backup the hosts file of current system.", None)) - self.ButtonUpdate.setToolTip(_translate("HostsUtlMain", "Download data file", None)) - self.ButtonUpdate.setWhatsThis(_translate("HostsUtlMain", "Download the latest data file.", None)) - self.ButtonRestore.setToolTip(_translate("HostsUtlMain", "Restore backup", None)) - self.ButtonRestore.setWhatsThis(_translate("HostsUtlMain", "Restore a previous backup of hosts file.", None)) - self.ButtonApply.setToolTip(_translate("HostsUtlMain", "Apply hosts", None)) - self.ButtonApply.setWhatsThis(_translate("HostsUtlMain", "Apply changes to the hosts file.", None)) - self.ButtonExit.setToolTip(_translate("HostsUtlMain", "Exit", None)) - self.ButtonExit.setWhatsThis(_translate("HostsUtlMain", "Close this tool.", None)) - self.ButtonCheck.setToolTip(_translate("HostsUtlMain", "Check update / Refresh", None)) - self.ButtonCheck.setWhatsThis(_translate("HostsUtlMain", "Check the latest version of hosts data file.", None)) - self.ButtonANSI.setToolTip(_translate("HostsUtlMain", "Save with ANSI", None)) - self.ButtonANSI.setWhatsThis(_translate("HostsUtlMain", "Export to hosts file encoding by ANSI.", None)) - self.ButtonUTF.setToolTip(_translate("HostsUtlMain", "Save with UTF-8", None)) - self.ButtonUTF.setWhatsThis(_translate("HostsUtlMain", "Export to hosts file encoding by UTF-8.", None)) - self.TitleLabel.setText(_translate("HostsUtlMain", "Hosts Setup Utility", None)) - self.Copyright.setText(_translate("HostsUtlMain", "Copyleft (C) 2011-2014 huhamhire-hosts", None)) - self.label.setText(_translate("HostsUtlMain", "Powered by PyQT", None)) + def retranslateUi(self, Util): + Util.setWindowTitle(_translate("Util", "Hosts Setup Utility", None)) + self.ConfigBox.setTitle(_translate("Util", "Config", None)) + self.labelIP.setText(_translate("Util", "Server", None)) + self.labelMirror.setText(_translate("Util", "IP Version", None)) + self.StatusBox.setTitle(_translate("Util", "Status", None)) + self.labelConn.setText(_translate("Util", "Connection", None)) + self.labelConnStat.setText(_translate("Util", "N/A", None)) + self.labelOS.setText(_translate("Util", "OS", None)) + self.labelOSStat.setText(_translate("Util", "N/A", None)) + self.InfoBox.setTitle(_translate("Util", "Hosts Info", None)) + self.labelVersion.setText(_translate("Util", "Version", None)) + self.labelVersionData.setText(_translate("Util", "N/A", None)) + self.labelRelease.setText(_translate("Util", "Release", None)) + self.labelReleaseData.setText(_translate("Util", "N/A", None)) + self.labelLatest.setText(_translate("Util", "Latest", None)) + self.labelLatestData.setText(_translate("Util", "N/A", None)) + self.FunctionsBox.setTitle(_translate("Util", "Functions", None)) + self.ButtonBackup.setToolTip(_translate("Util", "Backup hosts", None)) + self.ButtonBackup.setWhatsThis(_translate("Util", "Backup the hosts file of current system.", None)) + self.ButtonUpdate.setToolTip(_translate("Util", "Download data file", None)) + self.ButtonUpdate.setWhatsThis(_translate("Util", "Download the latest data file.", None)) + self.ButtonRestore.setToolTip(_translate("Util", "Restore backup", None)) + self.ButtonRestore.setWhatsThis(_translate("Util", "Restore a previous backup of hosts file.", None)) + self.ButtonApply.setToolTip(_translate("Util", "Apply hosts", None)) + self.ButtonApply.setWhatsThis(_translate("Util", "Apply changes to the hosts file.", None)) + self.ButtonExit.setToolTip(_translate("Util", "Exit", None)) + self.ButtonExit.setWhatsThis(_translate("Util", "Close this tool.", None)) + self.ButtonCheck.setToolTip(_translate("Util", "Check update / Refresh", None)) + self.ButtonCheck.setWhatsThis(_translate("Util", "Check the latest version of hosts data file.", None)) + self.ButtonANSI.setToolTip(_translate("Util", "Save with ANSI", None)) + self.ButtonANSI.setWhatsThis(_translate("Util", "Export to hosts file encoding by ANSI.", None)) + self.ButtonUTF.setToolTip(_translate("Util", "Save with UTF-8", None)) + self.ButtonUTF.setWhatsThis(_translate("Util", "Export to hosts file encoding by UTF-8.", None)) + self.TitleLabel.setText(_translate("Util", "Hosts Setup Utility", None)) + self.Copyright.setText(_translate("Util", "Copyleft (C) 2011-2014 huhamhire-hosts", None)) + self.label.setText(_translate("Util", "Powered by PyQT", None)) -import qthosts_rc +import util_rc import style_rc if __name__ == "__main__": import sys app = QtGui.QApplication(sys.argv) - HostsUtlMain = QtGui.QDialog() - ui = Ui_HostsUtlMain() - ui.setupUi(HostsUtlMain) - HostsUtlMain.show() + Util = QtGui.QDialog() + ui = Ui_Util() + ui.setupUi(Util) + Util.show() sys.exit(app.exec_()) diff --git a/hoststool.py b/hoststool.py new file mode 100644 index 0000000..505b8d3 --- /dev/null +++ b/hoststool.py @@ -0,0 +1,143 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# hoststool.py : Launch the utility. +# +# Copyleft (C) 2014 - huhamhire hosts team +# ===================================================================== +# Licensed under the GNU General Public License, version 3. You should +# have received a copy of the GNU General Public License along with +# this program. If not, see . +# +# This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING +# THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE. +# ===================================================================== + +__author__ = "huhamhire " + +import os + +from optparse import OptionParser + +import gui +import tui + +from __version__ import __version__ + +import sys +sys.path.append("..") +from util import CommonUtil + + +class UtilLauncher(object): + """ + HostsUtil class is the entrance to launch `Hosts Setup Utility`. This + class contains methods for user to decide whether to start a session in + Graphical User Interface (GUI) mode or in Text-based User Interface (TUI) + mode. + + Usage can be printed to screen via terminal by using ``-h`` or ``--help`` + argument. The help message would be like this:: + + Usage: hoststool.py [-g] [-t] [-h] [--version] + + Options: + --version show program's version number and exit + -h, --help show this help message and exit + -g, --graphicui launch in GUI(QT) mode + -t, --textui launch in TUI(Curses) mode + + .. seealso:: :ref:`Get Started `. + + .. note:: All methods from this class are declared as `classmethod`. + """ + + @classmethod + def launch(cls): + """ + Launch `Hosts Setup Utility`. + + .. note:: This is a `classmethod`. + """ + options, args = cls.set_commands() + if options.tui: + cls.launch_tui() + elif options.gui: + cls.launch_gui() + + @classmethod + def set_commands(cls): + """ + Set the command options and arguments to launch `Hosts Setup Utility`. + + .. note:: This is a `classmethod`. + + :return: :meth:`hoststool.UtilLauncher.set_commands` returns two + values: + + (:attr:`options`, :attr:`args`) + + * options(`optparse.Values`): An instance of + :class:`optparse.Values` containing values for all of your + options—e.g. if --file takes a single string argument, then + options.file will be the filename supplied by the user, or None + if the user did not supply that option args, the list of + positional arguments leftover after parsing options. + * args(`list`): Positional arguments leftover after parsing + options. + + .. seealso:: `OptionParser + `_. + + :rtype: :class:`optparse.Values`, list + """ + usage = "usage: %prog [-g] [-t] [-h] [--version]" + version = "Hosts Setup Utility v" + __version__ + parser = OptionParser(usage=usage, version=version) + parser.add_option("-g", "--graphicui", dest="gui", + default=True, action="store_true", + help="launch in GUI(QT) mode") + parser.add_option("-t", "--textui", dest="tui", + default=False, action="store_true", + help="launch in TUI(Curses) mode ") + return parser.parse_args() + + @classmethod + def launch_gui(cls): + """ + Start a Graphical User Interface (GUI) session of + `Hosts Setup Utility`. + + .. note:: This is a `classmethod`. + """ + main = gui.HostsUtil() + main.start() + + @classmethod + def launch_tui(cls): + """ + Start a Text-based User Interface (TUI) session of + `Hosts Setup Utility`. + + .. note:: This is a `classmethod`. + """ + + # Set code page for Windows + system = CommonUtil.check_platform()[0] + cp = "437" + if system == "Windows": + chcp = os.popen("chcp") + cp = chcp.read().split()[-1] + chcp.close() + os.popen("chcp 437") + + main = tui.HostsUtil() + main.start() + + # Restore the default code page for Windows + if system == "Windows": + os.popen("chcp " + cp) + +if __name__ == "__main__": + UtilLauncher.launch() \ No newline at end of file diff --git a/hostsutl.pro b/hostsutl.pro deleted file mode 100644 index 11f4793..0000000 --- a/hostsutl.pro +++ /dev/null @@ -1,10 +0,0 @@ -SOURCES = hostsutl.py \ - qthostsui.py -TRANSLATIONS = lang/de_DE.ts \ - lang/en_US.ts \ - lang/zh_CN.ts \ - lang/zh_TW.ts -INTERFACES = qthostsui.ui -RESOURCES = qthosts.qrc -CODECFORTR = UTF-8 -CODECFORSRC = UTF-8 diff --git a/hostsutl.py b/hostsutl.py deleted file mode 100644 index 753501e..0000000 --- a/hostsutl.py +++ /dev/null @@ -1,1559 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# hostsutl.py : Main parts of Hosts Setup Utility -# -# Copyleft (C) 2013 - huhamhire hosts team -# ===================================================================== -# Licensed under the GNU General Public License, version 3. You should -# have received a copy of the GNU General Public License along with -# this program. If not, see . -# -# This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING -# THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR -# PURPOSE. -# ===================================================================== - -__version__ = "1.9.7" -__revision__ = "$Id$" -__author__ = "huhamhire " - -__all__ = [ - "LANG_DIR", "MainDialog", "QSubChkConnection", "QSubFetchUpdate", - "QSubMakeHosts", "QSubChkUpdate",] - -import json -import os -import shutil -import socket -import sys -import time -import urllib - -from PyQt4 import QtCore, QtGui -from zipfile import BadZipfile - -from qthostsui import Ui_HostsUtlMain, _fromUtf8, _encoding, _translate -from retrievedata import RetrieveData, make_hosts -from utilities import Utilities, LangUtilities - -# Path to store language files -LANG_DIR = "./lang/" - -class MainDialog(QtGui.QDialog): - """A class to manage the operations and UI of Hosts Setup Utility - - MainDialog class is a subclasse of PyQt4.QtGui.QDialog which is used to - make the main dialog of this hosts setup utility. - This class contains a set of tools used to manage the operations while - modifying the hosts file of current operating system. Including methods - to manage operations to update data file, download data file, configure - hosts, make hosts file, backup hosts file, and restore backup. - The MainDialog class also provides QT slots to deal with the QT singles - emitted by the widgets on the main dialog operated by users. Extend - methods dealing with the user interface is also given by this class. - - Attributes: - _cur_ver (str): A string indicating the current version of hosts data - file. - _ipv_id (int): An integer indicating current IP version setting. The - value could be 1 or 0. 1 represents IPv6 while 1 represents IPv4. - _is_root (int): An integer indicating whether the program is run with - admin/root privileges. The value could be 1 or 0. - _down_flag (int) An integer indicating the downloading status of - current session. 1 represents data file is being downloaded. - _funcs (list): A list containing two lists with the information of - function list for IPv4 and IPv6 environment. - _make_cfg (dict): A dictionary containing the selection control bytes - to make a hosts file. - _make_mode (str): A string indicating the operation mode for making - hosts file. - _make_path (str): A string indicating the path to store the hosts file - in export mode. - _sys_eol (str): A string indicating the End-Of-Line marker. - _update (dict): A dictionary containing the update information of the - current data file on server. - _trans (obj): A QtCore.QTranslator object indicating the current UI - language setting. - choice (list): A list containing two lists with the selection of - functions for IPv4 and IPv6 environment. - slices (list): A list containing two lists with integers indicating - the number of function items from different parts listed in the - function list. - initd (int): An integer indicating how many times has the main dialog - been initialized. This value would be referenced for translator - to set the language of the main dialog. - platform (str): A string indicating the platform of current operating - system. The value could be "Windows", "Linux", "Unix", "OS X", and - of course "Unkown". - plat_flag (bool): A boolean flag indicating whether the current os is - supported or not. - hostname (str): A string indicating the hostname of current operating - system. This attribute would be used for linux clients. - hostspath (str): A string indicating the absolute path of the hosts - file on current operating system. - Ui (str): A user interface object indicating the main dialog of this - program. - _mirr_id (int): An integer indicating current index number of mirrors. - mirrors (list): A dictionary containing tag, test url, and update url - of mirrors. - __list_trans (list): A list containing names of function list items - for translator to translate. - filename (str): A string indicating the filename of the data file - containing data to make a hosts file. - infofile (str): A string indicating the filename of the info file - containing metadata of the data file in JSON format. - """ - _cur_ver = "" - _ipv_id = 0 - _is_root = 0 - _down_flag = 0 - _funcs = [[], []] - _make_cfg = {} - _make_mode = "" - _make_path = "./hosts" - _sys_eol = "" - _update = {} - _trans = None - - choice = [[], []] - slices = [[], []] - - initd = 0 - - Ui = None - # OS related configuration - platform = '' - plat_flag = True - hostname = '' - hostspath = '' - # Mirror related configuration - _mirr_id = 0 - mirrors = [] - # Name of items from the function list to be localized - __list_trans = [ - _translate("HostsUtlMain", "google(cn)", None), - _translate("HostsUtlMain", "google(hk)", None), - _translate("HostsUtlMain", "google(us)", None), - _translate("HostsUtlMain", "google-apis(cn)", None), - _translate("HostsUtlMain", "google-apis(us)", None), - _translate("HostsUtlMain", "activation-helper", None), - _translate("HostsUtlMain", "facebook", None), - _translate("HostsUtlMain", "twitter", None), - _translate("HostsUtlMain", "youtube", None), - _translate("HostsUtlMain", "wikipedia", None), - _translate("HostsUtlMain", "institutions", None), - _translate("HostsUtlMain", "steam", None), - _translate("HostsUtlMain", "others", None), - _translate("HostsUtlMain", "adblock-hostsx", None), - _translate("HostsUtlMain", "adblock-mvps", None), - _translate("HostsUtlMain", "adblock-mwsl", None), - _translate("HostsUtlMain", "adblock-yoyo", None), - ] - # Data file related configuration - filename = "hostslist.data" - infofile = "hostsinfo.json" - - def __init__(self, Ui, trans): - """Initialize a new instance of this class - Private Method - - Set the UI object and current translator of the main dialog. - - Args: - Ui (obj): A user interface object indicating the main dialog of - this program. - trans (obj): A PyQt4.QtCore.QTranslator object indicating the - current UI language setting. - """ - super(MainDialog, self).__init__() - self.Ui = Ui - self._trans = trans - self.set_platform() - self.set_style() - self.set_stylesheet() - - def on_Mirror_changed(self, mirr_id): - """Change the current mirror setting - Public Method - - The slot response to the signal ({mirr_id}) from SelectMirror widget - while the value is changed. - - Args: - mirr_id (int): An integer indicating current index number of - mirrors. - """ - self._mirr_id = mirr_id - self.check_connection() - - def on_IPVersion_changed(self, ipv_id): - """Change the current IP version setting - Public Method - - The slot response to the signal ({ipv_id}) from SelectIP widget while - the value is changed. - - Args: - ipv_id (int): An integer indicating current IP version setting. - The value could be 1 or 0. 1 represents IPv6 while 1 - represents IPv4. - """ - if self._ipv_id != ipv_id: - self._ipv_id = ipv_id - if not RetrieveData.db_exists(): - self.warning_no_datafile() - else: - self.set_func_list(0) - self.refresh_func_list() - - def on_Selection_changed(self, item): - """Change the function selection setting - Public Method - - The slot response to the signal ({item}) from Functionlist widget - while the selection of the items is changed. This method would change - the current selection of functions. - - Args: - item (int): An integer indicating the row number of the item - listed in Functionlist which is changed by user. - """ - ip_flag = self._ipv_id - func_id = item.listWidget().row(item) - if self._funcs[ip_flag][func_id] == 0: - self._funcs[ip_flag][func_id] = 1 - else: - self._funcs[ip_flag][func_id] = 0 - mutex = RetrieveData.get_ids(self.choice[ip_flag][func_id][2]) - for c_id, c in enumerate(self.choice[ip_flag]): - if c[0] == self.choice[ip_flag][func_id][0]: - if c[1] in mutex and self._funcs[ip_flag][c_id] == 1: - self._funcs[ip_flag][c_id] = 0 - item = self.Ui.Functionlist.item(c_id) - self.refresh_func_list() - - def on_Lang_changed(self, lang): - """Change the UI language setting - Public Method - - The slot response to the signal ({lang}) from SelectLang widget while - the value is changed. This method would change the language of the UI. - - Args: - lang (str): A string indicating the language which is selected by - user. - This string uses the for of IETF language tag. For example: - en_US, en_GB, etc. - """ - new_lang = LangUtilities.get_locale_by_language(unicode(lang)) - trans = QtCore.QTranslator() - global LANG_DIR - trans.load(LANG_DIR + new_lang) - QtGui.QApplication.removeTranslator(self._trans) - QtGui.QApplication.installTranslator(trans) - self._trans = trans - self.Ui.retranslateUi(self) - self.init_main() - self.check_connection() - - def on_MakeHosts_clicked(self): - """Start making hosts file - Public Method - - The slot response to the signal from ButtonApply widget while the - button is clicked. This method would call operations to make a hosts - file. - No operations would be called if current session does not have the - privileges to change the hosts file. - """ - if not self._is_root: - self.warning_permission() - return - if self.question_apply(): - self._make_path = "./hosts" - self.make_hosts("system") - else: - return - - def on_MakeANSI_clicked(self): - """Export hosts ANSI - Public Method - - The slot response to the signal from ButtonANSI widget while the - button is clicked. This method would call operations to export a hosts - file encoding in ANSI. - """ - self._make_path = self.export_hosts() - if unicode(self._make_path) != u'': - self.make_hosts("ansi") - - def on_MakeUTF8_clicked(self): - """Export hosts in UTF-8 - Public Method - - The slot response to the signal from ButtonUTF widget while the - button is clicked. This method would call operations to export a hosts - file encoding in UTF-8. - """ - self._make_path = self.export_hosts() - if unicode(self._make_path) != u'': - self.make_hosts("utf-8") - - def on_Backup_clicked(self): - """Backup system hosts file - Public Method - - The slot response to the signal from ButtonBackup widget while the - button is clicked. This method would call operations to backup the - hosts file of current operating system. - """ - l_time = time.localtime(time.time()) - backtime = time.strftime("%Y-%m-%d-%H%M%S", l_time) - filename = "hosts_" + backtime + ".bak" - if self.platform == "OS X": - filename = "/Users/" + filename - filepath = QtGui.QFileDialog.getSaveFileName( - self, _translate("HostsUtlMain", "Backup hosts", None), - QtCore.QString(filename), - _translate("HostsUtlMain", "Backup File(*.bak)", None)) - if unicode(filepath) != u'': - shutil.copy2(self.hostspath, unicode(filepath)) - self.info_complete() - - def on_Restore_clicked(self): - """Restore hosts file - Public Method - - The slot response to the signal from ButtonRestore widget while the - button is clicked. This method would call operations to restore a - previously backed up hosts file. - No operations would be called if current session does not have the - privileges to change the hosts file. - """ - if not self._is_root: - self.warning_permission() - return - filename = '' - if self.platform == "OS X": - filename = "/Users/" + filename - filepath = QtGui.QFileDialog.getOpenFileName( - self, _translate("HostsUtlMain", "Restore hosts", None), - QtCore.QString(filename), - _translate("HostsUtlMain", "Backup File(*.bak)", None)) - if unicode(filepath) != u'': - shutil.copy2(unicode(filepath), self.hostspath) - self.info_complete() - - def on_CheckUpdate_clicked(self): - """Check data file update - Public Method - - The slot response to the signal from ButtonCheck widget while the - button is clicked. This method would call operations to fetch update - information of the latest data file. - """ - if self.choice != [[], []]: - self.refresh_func_list() - self.Ui.ButtonApply.setEnabled(True) - self.Ui.ButtonANSI.setEnabled(True) - self.Ui.ButtonUTF.setEnabled(True) - if self._update == {} or self._update["version"] == \ - unicode(_translate("HostsUtlMain", "[Error]", None)): - self.check_update() - - def on_FetchUpdate_clicked(self): - """Fetch data file update - Public Method - - The slot response to the signal from ButtonUpdate widget while the - button is clicked. This method would call operations to fetch the - latest data file. - If no update information has been got from the server, the method to - check the update would be called. - If the current data is up-to-date, no data file would be retrieved. - """ - self._down_flag = 1 - self.Ui.Functionlist.setEnabled(False) - self.Ui.ButtonApply.setEnabled(False) - self.Ui.ButtonANSI.setEnabled(False) - self.Ui.ButtonUTF.setEnabled(False) - if self._update == {} or self._update["version"] == \ - unicode(_translate("HostsUtlMain", "[Error]", None)): - self.check_update() - elif self.new_version(): - self.fetch_update() - else: - self.info_uptodate() - self.finish_fetch() - - def on_LinkActivated(self, url): - """Open external link in browser - Public Method - - The slot response to the signal from Label widget while the text with - a hyperlink is clicked by user. - """ - QtGui.QDesktopServices.openUrl(QtCore.QUrl(url)) - - def init_main(self): - """Initialize the main dialog - Public Method - - Set up the elements on the main dialog. Check the environment of - current operating system and current session. - """ - self.Ui.SelectMirror.clear() - # Set mirrors - self.mirrors = Utilities.set_network("network.conf") - for i, mirror in enumerate(self.mirrors): - self.Ui.SelectMirror.addItem(_fromUtf8("")) - self.Ui.SelectMirror.setItemText( - i, _translate("HostsUtlMain", mirror["tag"], None)) - self.set_platform_label() - # Read data file and set function list - try: - RetrieveData.unpack() - RetrieveData.connect_db() - self.set_func_list(1) - self.refresh_func_list() - self.set_info() - except IOError: - self.warning_no_datafile() - except BadZipfile: - self.warning_incorrect_datafile() - # Check if current session have root privileges - self.check_root() - self.initd += 1 - - def reject(self): - """Response to the reject signal - Public Method - - The slot response to the reject signal from an instance of the main - dialog. Close this program while the reject signal is emitted. - """ - self.close() - return QtGui.QDialog.reject(self) - - def close(self): - """Response to the close signal - Public Method - - The slot response to the close signal from an instance of the main - dialog. Close this program while the reject signal is emitted. - """ - try: - RetrieveData.clear() - except: - pass - super(MainDialog, self).close() - - def check_root(self): - """Check root privileges - Public Method - - Check if current session is ran with root privileges. - """ - is_root = Utilities.check_privileges()[1] - self._is_root = is_root - if not is_root: - self.warning_permission() - - def check_connection(self): - """Operations to check connection - Public Method - - Call operations to check the connection to current server. - """ - thread = QSubChkConnection(self) - thread.trigger.connect(self.set_conn_status) - thread.start() - - def check_update(self): - """Operations to check data file update - Public Method - - Call operations to retrieve the metadata of the latest data file from - a server. - """ - self.Ui.SelectMirror.setEnabled(False) - self.Ui.ButtonCheck.setEnabled(False) - self.Ui.ButtonUpdate.setEnabled(False) - self.set_label_text(self.Ui.labelLatestData, unicode( - _translate("HostsUtlMain", "Checking...", None))) - thread = QSubChkUpdate(self) - thread.trigger.connect(self.finish_update) - thread.start() - - def fetch_update(self): - """Operations to fetch new data file - Public Method - - Call operations to retrieve a new hosts data file from a server. - """ - self.Ui.SelectMirror.setEnabled(False) - self.Ui.ButtonCheck.setEnabled(False) - self.Ui.ButtonUpdate.setEnabled(False) - self.Ui.ButtonApply.setEnabled(False) - self.Ui.ButtonANSI.setEnabled(False) - self.Ui.ButtonUTF.setEnabled(False) - self.Ui.ButtonExit.setEnabled(False) - thread = QSubFetchUpdate(self) - thread.prog_trigger.connect(self.set_downprogbar) - thread.finish_trigger.connect(self.finish_fetch) - thread.start() - - def fetch_update_aftercheck(self): - """Check to fetch data file after check for update - Public Method - - Decide whether to retrieve a new data file from server or not after - checking update information from a mirror. - """ - if self._update["version"] == \ - unicode(_translate("HostsUtlMain", "[Error]", None)): - self.finish_fetch(error=1) - elif self.new_version(): - self.fetch_update() - else: - self.info_uptodate() - self.finish_fetch() - - def export_hosts(self): - """Draw export hosts dialog - Public Method - - Show the export dialog and get the path to save the exported hosts - file. - - Returns: - A string indicating the path to export a hosts file - """ - filename = "hosts" - if self.platform == "OS X": - filename = "/Users/" + filename - filepath = QtGui.QFileDialog.getSaveFileName( - self, _translate("HostsUtlMain", "Export hosts", None), - QtCore.QString(filename), - _translate("HostsUtlMain", "hosts File", None)) - return filepath - - def make_hosts(self, mode="system"): - """Operations to make hosts file - Public Method - - Call operations to make a new hosts file for current system. - - Args: - mode (str): A string indicating the operation mode for making - hosts file. - """ - self.Ui.Functionlist.setEnabled(False) - self.Ui.SelectIP.setEnabled(False) - self.Ui.ButtonCheck.setEnabled(False) - self.Ui.ButtonUpdate.setEnabled(False) - self.Ui.ButtonApply.setEnabled(False) - self.Ui.ButtonANSI.setEnabled(False) - self.Ui.ButtonUTF.setEnabled(False) - self.Ui.ButtonExit.setEnabled(False) - self.set_makemsg(unicode(_translate( - "HostsUtlMain", "Building hosts file...", None)), 1) - # Avoid conflict while making hosts file - RetrieveData.disconnect_db() - self._make_mode = mode - self.set_cfgbytes(mode) - thread = QSubMakeHosts(self) - thread.info_trigger.connect(self.set_makeprog) - thread.fina_trigger.connect(self.set_makefina) - thread.move_trigger.connect(self.move_hosts) - thread.start() - - def move_hosts(self): - """Move hosts file to the system path after making - Public Method - - The slot response to the move_trigger signal from an instance of - QSubMakeHosts class while making operations are finished. - """ - filepath = "hosts" - msg = unicode(_translate("HostsUtlMain", - "Copying new hosts file to\n" - " %s", None)) % self.hostspath - self.set_makemsg(msg) - try: - shutil.copy2(filepath, self.hostspath) - except IOError: - self.warning_permission() - os.remove(filepath) - return - msg = unicode(_translate("HostsUtlMain", - "Remove temporary file", None)) - self.set_makemsg(msg) - os.remove(filepath) - msg = unicode(_translate("HostsUtlMain", - "Operation completed", None)) - self.set_makemsg(msg) - self.info_complete() - - def set_languages(self): - """Set items in SelectLang widget - Public Method - - Set optional language selection items in the SelectLang widget. - """ - self.Ui.SelectLang.clear() - langs = LangUtilities.language - langs_not_found = [] - for locale in langs: - if not os.path.isfile(LANG_DIR + locale + ".qm"): - langs_not_found.append(locale) - for locale in langs_not_found: - langs.pop(locale) - LangUtilities.language = langs - if len(langs) <= 1: - self.Ui.SelectLang.setEnabled(False) - # Block the signal while set the language selecions. - self.Ui.SelectLang.blockSignals(True) - sys_locale = LangUtilities.get_locale() - if sys_locale not in langs.keys(): - sys_locale = "en_US" - for i, locale in enumerate(sorted(langs.keys())): - if sys_locale == locale: - select = i - lang = langs[locale] - self.Ui.SelectLang.addItem(_fromUtf8("")) - self.Ui.SelectLang.setItemText(i, lang) - self.Ui.SelectLang.blockSignals(False) - self.Ui.SelectLang.setCurrentIndex(select) - - def set_platform(self): - """Set OS info - Public Method - - Set the information of current operating system platform. - """ - system, hostname, path, encode, flag = Utilities.check_platform() - self.platform = system - self.hostname = hostname - self.hostspath = path - self.plat_flag = flag - if encode == "win_ansi": - self._sys_eol = "\r\n" - else: - self._sys_eol = "\n" - - def set_platform_label(self): - """Set label of OS info - Public Method - - Set the information of the label indicating current operating system - platform. - """ - color = "GREEN" if self.plat_flag else "RED" - self.set_label_color(self.Ui.labelOSStat, color) - self.set_label_text(self.Ui.labelOSStat, "[%s]" % self.platform) - - - def set_style(self): - """Set window style - Public Method - - Set the main dialog with a window style depending on the os platform. - """ - self.setWindowFlags(QtCore.Qt.FramelessWindowHint) - system = self.platform - if system == "Windows": - pass - elif system == "Linux": - # Set window style for sudo users. - QtGui.QApplication.setStyle( - QtGui.QStyleFactory.create("Cleanlooks")) - elif system == "OS X": - pass - - def set_stylesheet(self): - """Set Stylesheet for main frame - Public Method - - Define the style sheet of main dialog. - """ - app = QtGui.QApplication.instance() - with open("./theme/darkdefault.qss", "r") as qss: - app.setStyleSheet(qss.read()) - - def mouseMoveEvent(self, e): - """Set mouse drag event - Public Method - - Allow drag operations to set the new position for current dialog. - - Args: - e (QMouseEvent): A QMouseEvent object indicating current mouse - event. - """ - if e.buttons() & QtCore.Qt.LeftButton: - try: - self.move(e.globalPos() - self.dragPos) - except AttributeError: - pass - e.accept() - - def mousePressEvent(self, e): - """Set mouse press event - Public Method - - Allow drag operations to set the new position for current dialog. - - Args: - e (QMouseEvent): A QMouseEvent object indicating current mouse - event. - """ - if e.button() == QtCore.Qt.LeftButton: - self.dragPos = e.globalPos() - self.frameGeometry().topLeft() - e.accept() - - - def set_label_color(self, label, color): - """Set the color of a label - Public Method - - Set a specified label ({label}) to show with specified color - ({color}). - - Args: - label (obj): An instance of PyQt4.QtGui.QLabel class on the main - dialog. - color (str): A string indicating the color to be shown on the - lable. - """ - if color == "GREEN": - rgb = "#37b158" - elif color == "RED": - rgb = "#e27867" - elif color == "BLACK": - rgb = "#b1b1b1" - label.setStyleSheet("QLabel {color: %s}" % rgb) - - def set_label_text(self, label, text): - """Set the text of a label - Public Method - - Set a specified label ({label}) to show specified text ({text}). - - Args: - label (obj): An instance of PyQt4.QtGui.QLabel class on the main - dialog. - text (str): A string indicating the message to be shown on the - lable. - """ - label.setText(_translate("HostsUtlMain", text, None)) - - def set_conn_status(self, status): - """Set connection status info - Public Method - - Set the information of connection status to the current server - selected. - """ - if status == -1: - self.set_label_color(self.Ui.labelConnStat, "BLACK") - self.set_label_text(self.Ui.labelConnStat, unicode( - _translate("HostsUtlMain", "Checking...", None))) - elif status in [0, 1]: - if status: - color, stat = "GREEN", unicode(_translate( - "HostsUtlMain", "[OK]", None)) - else: - color, stat = "RED", unicode(_translate( - "HostsUtlMain", "[Failed]", None)) - self.set_label_color(self.Ui.labelConnStat, color) - self.set_label_text(self.Ui.labelConnStat, stat) - - def set_func_list(self, new=0): - """Set the function list - Public Method - - Draw the function list and decide whether to load the default - selection configuration or not. - - Arg: - new (int): A flag integer indicating whether to load the default - selection configuration or not. 0 -> user user config, - 1 -> use default config. Default by 0. - """ - ip_flag = self._ipv_id - self.Ui.Functionlist.clear() - self.Ui.FunctionsBox.setTitle(_translate( - "HostsUtlMain", "Functions", None)) - if new: - for ip in range(2): - choice, defaults, slices = RetrieveData.get_choice(ip) - self.choice[ip] = choice - self.slices[ip] = slices - funcs = [] - for func in choice: - item = QtGui.QListWidgetItem() - if func[1] in defaults[func[0]]: - funcs.append(1) - else: - funcs.append(0) - self._funcs[ip] = funcs - - def refresh_func_list(self): - """Refresh the function list - Public Method - - Refresh the items in the function list by user settings. - """ - ip_flag = self._ipv_id - self.Ui.Functionlist.clear() - for f_id, func in enumerate(self.choice[self._ipv_id]): - item = QtGui.QListWidgetItem() - if self._funcs[ip_flag][f_id] == 1: - check = QtCore.Qt.Checked - else: - check = QtCore.Qt.Unchecked - item.setCheckState(check) - item.setText(_translate("HostsUtlMain", func[3], None)) - self.Ui.Functionlist.addItem(item) - - def set_message(self, title, msg): - """Set a message box - Public Method - - Show a message box with a specified message ({msg}) with a specified - title ({title}). - - Args: - title (str): A string indicating the title of the message box. - msg (str): A string indicating the message to be shown in the - message box. - """ - self.Ui.FunctionsBox.setTitle(_translate( - "HostsUtlMain", title, None)) - self.Ui.Functionlist.clear() - item = QtGui.QListWidgetItem() - item.setText(msg) - item.setFlags(QtCore.Qt.ItemIsEnabled) - self.Ui.Functionlist.addItem(item) - - def set_info(self): - """Set data file info - Public Method - - Set the information of the current local data file. - """ - info = RetrieveData.get_info() - ver = info["Version"] - self._cur_ver = ver - self.set_label_text(self.Ui.labelVersionData, ver) - build = info["Buildtime"] - build = Utilities.timestamp_to_date(build) - self.set_label_text(self.Ui.labelReleaseData, build) - - def set_downprogbar(self, prog, msg): - """Set progress bar - Public Method - - Set the progress bar to a specified progress position ({prog}) with a - specified message ({msg}). - - Args: - prog (int): An integer indicating the progress to be set on the - progress bar. - msg (str): A string indicating the message to be shown on the - progress bar. - """ - self.Ui.Prog.setProperty("value", prog) - self.set_conn_status(1) - self.Ui.Prog.setFormat(msg) - - def set_listitemunchecked(self, item_id): - """Set list item to be unchecked - Public Method - - Set a specified item ({item_id}) to become unchecked in the function - list. - - Arg: - item_id (int): An integer indicating the id number of a specified - item in the function list. - """ - self._funcs[self._ipv_id][item_id] = 0 - item = self.Ui.Functionlist.item(item_id) - item.setCheckState(QtCore.Qt.Unchecked) - - def set_cfgbytes(self, mode): - """Set configuration byte words - Public Method - - Calculate the module configuration byte words by the selection from - function list on the main dialog. - - Args: - mode (str): A string indicating the operation mode for making - hosts file. - """ - ip_flag = self._ipv_id - selection = {} - if mode == "system": - localhost_word = { - "Windows": 0x0001, "Linux": 0x0002, - "Unix": 0x0002, "OS X": 0x0004}[self.platform] - else: - localhost_word = 0x0008 - selection[0x02] = localhost_word - ch_parts = (0x08, 0x20 if self._ipv_id else 0x10, 0x40) - slices = self.slices[ip_flag] - for i, part in enumerate(ch_parts): - part_cfg = self._funcs[ip_flag][slices[i]:slices[i + 1]] - part_word = 0 - for i, cfg in enumerate(part_cfg): - part_word += cfg << i - selection[part] = part_word - self._make_cfg = selection - - def refresh_info(self, refresh=0): - """Refresh data file information - Public Method - - Reload the data file information and show them on the main dialog. The - information here includes both metadata and hosts module info from the - data file. - - Arg: - refresh (int): A flag integer indicating whether the information - needs to be reloaded or not. 1: reload, 0: do not reload. - Default by 0. - """ - if refresh and RetrieveData._conn != None: - RetrieveData.clear() - try: - RetrieveData.unpack() - RetrieveData.connect_db() - self.set_func_list(refresh) - self.refresh_func_list() - self.set_info() - except (BadZipfile, IOError, OSError): - self.warning_incorrect_datafile() - - def set_makeprog(self, mod_name, mod_num): - """Operations to show progress while making hosts file - Public Method - - The slot response to the info_trigger signal ({mod_name}, {mod_num}) - from an instance of QSubMakeHosts class while making operations are - proceeded. - - Args: - mod_name (str): A string indicating the name of a specified hosts - module in current progress. - mod_num (int): An integer indicating the number of current module - in the operation sequence. - """ - total_mods_num = self._funcs[self._ipv_id].count(1) + 1 - prog = 100 * mod_num / total_mods_num - self.Ui.Prog.setProperty("value", prog) - format = unicode(_translate( - "HostsUtlMain", "Applying module: %s(%s/%s)", None)) % ( - mod_name, mod_num, total_mods_num) - self.Ui.Prog.setFormat(format) - self.set_makemsg(format) - - def set_makemsg(self, msg, start=0): - """Operations to show making progress in function list - Public Method - - List message for the current operating progress while making the new - hosts file. - - Args: - msg (str): A string indicating the message to show in the function - list. - start (int): A flag integer indicating whether the message is the - first of the making progress or not. 1: first, 0: not the - first. Default by 0. - """ - if start: - self.Ui.FunctionsBox.setTitle(_translate( - "HostsUtlMain", "Progress", None)) - self.Ui.Functionlist.clear() - item = QtGui.QListWidgetItem() - item.setText("- " + msg) - item.setFlags(QtCore.Qt.ItemIsEnabled) - self.Ui.Functionlist.addItem(item) - - def set_makefina(self, time, count): - """Operations after making new hosts file - Public Method - - The slot response to the fina_trigger signal ({time}, {count}) from - an instance of QSubMakeHosts class while making operations are - finished. - - Args: - time (str): A string indicating the total time uesd to make the - new hosts file. - count (int): An integer indicating the total number of hosts - entries inserted into the new hosts file. - """ - self.Ui.Functionlist.setEnabled(True) - self.Ui.SelectIP.setEnabled(True) - self.Ui.ButtonCheck.setEnabled(True) - self.Ui.ButtonUpdate.setEnabled(True) - self.Ui.ButtonApply.setEnabled(False) - self.Ui.ButtonANSI.setEnabled(False) - self.Ui.ButtonUTF.setEnabled(False) - self.Ui.ButtonExit.setEnabled(True) - RetrieveData.connect_db() - msg = unicode(_translate("HostsUtlMain", - "Notice: %i hosts entries has " - "\n been applied in %ssecs.", None)) % (count, time) - self.set_makemsg(msg) - self.set_downprogbar(100, - unicode(_translate("HostsUtlMain", - "Operation Completed Successfully!", None))) - - def finish_update(self, update): - """Operations after checking update - Public Method - - The slot response to the trigger signal ({update}) from an instance - of QSubChkUpdate class while checking operations are finished. - - Arg: - update (dict): A dictionary containing metadata of the latest - hosts file from the server. - """ - self._update = update - self.set_label_text(self.Ui.labelLatestData, update["version"]) - if self._update["version"] == \ - unicode(_translate("HostsUtlMain", "[Error]", None)): - self.set_conn_status(0) - else: - self.set_conn_status(1) - if self._down_flag: - self.fetch_update_aftercheck() - else: - self.Ui.SelectMirror.setEnabled(True) - self.Ui.ButtonCheck.setEnabled(True) - self.Ui.ButtonUpdate.setEnabled(True) - - def finish_fetch(self, refresh=1, error=0): - """Operations after downloading data file - Public Method - - The slot response to the finish_trigger signal ({refresh}, {error}) - from an instance of QSubFetchUpdate class while downloading is - finished. - - Args: - refresh (int): A flag integer indicating whether a refresh for - function list is needed or not. 1: refresh, 0: no refresh. - Default by 1. - error (int): A flag integer indicating errors have occurred while - downloading new data file. 1: error, 0:success. Default by 0. - """ - self._down_flag = 0 - if error: - # Error occurred while downloading - self.set_downprogbar(0, - unicode(_translate("HostsUtlMain", - "Error", None))) - try: - os.remove(self.filename) - except: - pass - self.warning_download() - msg_title = "Warning" - msg = unicode(_translate("HostsUtlMain", - "Incorrect Data file!\n" - "Please use the \"Download\" key to \n" - "fetch a new data file.", None)) - self.set_message(msg_title, msg) - self.Ui.ButtonApply.setEnabled(False) - self.Ui.ButtonANSI.setEnabled(False) - self.Ui.ButtonUTF.setEnabled(False) - self.set_conn_status(0) - else: - # Data file retrieved successfully - self.set_downprogbar(100, - unicode(_translate("HostsUtlMain", - "Download Complete", None))) - self.refresh_info(refresh) - self.Ui.ButtonApply.setEnabled(True) - self.Ui.ButtonANSI.setEnabled(True) - self.Ui.ButtonUTF.setEnabled(True) - self.Ui.Functionlist.setEnabled(True) - self.Ui.SelectMirror.setEnabled(True) - self.Ui.ButtonCheck.setEnabled(True) - self.Ui.ButtonUpdate.setEnabled(True) - self.Ui.ButtonExit.setEnabled(True) - - def new_version(self): - """Compare version of data file - Public Method - - Compare version of local data file to the version from the server. - - Returns: - A flag integer indicating whether the local data file is - up-to-date or not. - 1 -> The version of data file on server is newer. - 0 -> The local data file is up-to-date. - """ - local_ver = self._cur_ver - server_ver = self._update["version"] - local_ver = local_ver.split('.') - server_ver = server_ver.split('.') - for i, ver_num in enumerate(local_ver): - if server_ver[i] > ver_num: - return 1 - return 0 - - def warning_permission(self): - """Show permission error warning - Public Method - - Draw permission error warning message box. - """ - QtGui.QMessageBox.warning( - self, _translate("HostsUtlMain", "Warning", None), - _translate("HostsUtlMain", - "You do not have permissions to change the \n" - "hosts file.\n" - "Please run this program as Administrator/root\n" - "so it can modify your hosts file." - , None)) - - def warning_download(self): - """Show download error warning - Public Method - - Draw download error warning message box. - """ - QtGui.QMessageBox.warning( - self, _translate("HostsUtlMain", "Warning", None), - _translate("HostsUtlMain", - "Error retrieving data from the server.\n" - "Please try another server.", None)) - - def warning_incorrect_datafile(self): - """Show incorrect data file warning - Public Method - - Draw incorrect data file warning message box. - """ - msg_title = "Warning" - msg = unicode(_translate("HostsUtlMain", - "Incorrect Data file!\n" - "Please use the \"Download\" key to \n" - "fetch a new data file.", None)) - self.set_message(msg_title, msg) - self.Ui.ButtonApply.setEnabled(False) - self.Ui.ButtonANSI.setEnabled(False) - self.Ui.ButtonUTF.setEnabled(False) - - def warning_no_datafile(self): - """Show no data file warning - Public Method - - Draw no data file warning message box. - """ - msg_title = "Warning" - msg = unicode(_translate("HostsUtlMain", - "Data file not found!\n" - "Please use the \"Download\" key to \n" - "fetch a new data file.", None)) - self.set_message(msg_title, msg) - self.Ui.ButtonApply.setEnabled(False) - self.Ui.ButtonANSI.setEnabled(False) - self.Ui.ButtonUTF.setEnabled(False) - - def question_apply(self): - """Show confirm make question - Public Method - - Draw confirm make question message box. - - Returns: - A bool flag indicating whether user has accepted to continue the - operations or not. True: Continue, False: Cancel. - """ - msg_title = unicode(_translate("HostsUtlMain", "Notice", None)) - msg = unicode(_translate("HostsUtlMain", - "Are you sure you want to apply changes \n" - "to the hosts file on your system?\n\n" - "This operation could not be reverted if \n" - "you have not made a backup of your \n" - "current hosts file.", None)) - choice = QtGui.QMessageBox.question(self, msg_title, msg, - QtGui.QMessageBox.Yes | QtGui.QMessageBox.No, - QtGui.QMessageBox.No) - if choice == QtGui.QMessageBox.Yes: - return True - else: - return False - - def info_uptodate(self): - """Show up-to-date message - Public Method - - Draw data file is up-to-date message box. - """ - QtGui.QMessageBox.information( - self, _translate("HostsUtlMain", "Notice", None), - _translate("HostsUtlMain", "Data file is up-to-date.", None)) - - def info_complete(self): - """Show complete message - Public Method - - Draw operation complete message box. - """ - QtGui.QMessageBox.information( - self, _translate("HostsUtlMain", "Complete", None), - _translate("HostsUtlMain", "Operation completed", None)) - - -class QSubChkConnection(QtCore.QThread): - """A class to check connection with server - - QSubChkConnection is a subclasse of PyQt4.QtCore.QThread. This class - contains methods to check the network connection with a specified mirror. - - The instance of this class should be created in an individual thread. And - the object instance of MainDialog class should be set as parent here. - - Attribute: - trigger (obj): A PyQt4.QtCore.pyqtSignal object to emit suatus signal - to the main dialog. The meaning of the signal arguments is listed - here: - -1 -> checking..., 0 -> Failed, 1 -> OK. - """ - trigger = QtCore.pyqtSignal(int) - - def __init__(self, parent=None): - """Initialize a new instance of this class - Private Method - - Get mirror settings from the main dialog to check the connection. - - Args: - parent (obj): An instance of MainDialog object to get settings - from. - """ - super(QSubChkConnection, self).__init__(parent) - self.link = parent.mirrors[parent._mirr_id]["test_url"] - - def run(self): - """Check connection - Public Method - - Operations to check the network connection with a specified mirror. - """ - self.trigger.emit(-1) - status = Utilities.check_connection(self.link) - self.trigger.emit(status) - -class QSubFetchUpdate(QtCore.QThread): - """A class to fetch the latest data file - - QSubFetchUpdate is a subclasse of PyQt4.QtCore.QThread. This class - contains methods to retrieve the latest hosts data file. - - The instance of this class should be created in an individual thread. And - the object instance of MainDialog class should be set as parent here. - - Attributes: - prog_trigger (obj): A PyQt4.QtCore.pyqtSignal object to emit progress - signal to the main dialog indicating the current download - progress. The meaning of the signal arguments is listed here: - (int, str) -> (progress, message) - progress (int): An integer indicating the current download - progress. - message (str): A string indicating the message to be shown to - users on the progress bar. - finish_trigger (obj): A PyQt4.QtCore.pyqtSignal object to emit finish - signal to the main dialog. The meaning of the signal arguments is - listed here: - (int, int) -> (refresh_flag, error_flag) - refresh_flag (int): An integer indicating whether to refresh - the funcion list or not. 1: refresh, 0: do not refresh. - error_flag (int): An integer indicating whether the - downloading is successfully finished or not. - 1: error, 0: success. - """ - prog_trigger = QtCore.pyqtSignal(int, str) - finish_trigger = QtCore.pyqtSignal(int, int) - - def __init__(self, parent=None): - """Initialize a new instance of this class - Private Method - - Get download settings from the main dialog to retrieve new hosts data - file. - - Args: - parent (obj): An instance of MainDialog object to get settings - from. - """ - super(QSubFetchUpdate, self).__init__(parent) - self.url = parent.mirrors[parent._mirr_id]["update"] + parent.filename - self.path = "./" + parent.filename - self.tmp_path = self.path + ".download" - self.filesize = parent._update["size"] - - def run(self): - """Fetch data file - Public Method - - Operations to retrieve the new hosts data file. - """ - self.prog_trigger.emit(0, unicode(_translate( - "HostsUtlMain", "Connecting...", None))) - self.fetch_file(self.url, self.path) - - def fetch_file(self, url, path): - """Fetch the data file - Public Method - - Retrieve the latest data file to a specified path ({path}) by url - ({url}). - - Args: - url (str): A string indicating the url to fetch the latest data - file. - path (str): A string indicating the path to save the data file on - current machine. - """ - socket.setdefaulttimeout(10) - try: - urllib.urlretrieve(url, self.tmp_path, self.set_progress) - self.replace_old() - self.finish_trigger.emit(1, 0) - except: - self.finish_trigger.emit(1, 1) - - def set_progress(self, done, blocksize, total): - """Set progress bar in the main dialog - Public Method - - Send message to the main dialog to set the progress bar Prog. - - Args: - done (int): An integer indicating the number of data blocks have - been downloaded from the server. - blocksize (int): An integer indicating the size of a data block. - """ - done = done * blocksize - if total <= 0: - total = self.filesize - prog = 100 * done / total - done = Utilities.convert_size(done) - total = Utilities.convert_size(total) - text = unicode(_translate( - "HostsUtlMain", "Downloading: %s / %s", None)) % (done, total) - self.prog_trigger.emit(prog, text) - - def replace_old(self): - """Replace the old data file - Public Method - - Overwrite the old hosts data file with the new one. - """ - if os.path.isfile(self.path): - os.remove(self.path) - os.rename(self.tmp_path, self.path) - - -class QSubMakeHosts(QtCore.QThread): - """A class to make a new hosts file - - QSubMakeHosts is a subclasse of PyQt4.QtCore.QThread. This class contains - methods to make a new hosts file for client. - - The instance of this class should be created in an individual thread. And - the object instance of MainDialog class should be set as parent here. - - Attributes: - info_trigger (obj): A PyQt4.QtCore.pyqtSignal object to emit message - signal to the main dialog indicating the current operation.The - meaning of the signal arguments is listed here: - (str, int) - (mod_name, mod_num) - mod_name (str): A string indicating the name of a specified hosts - module in current progress. - mod_num (int): An integer indicating the number of current module - in the operation sequence. - fina_trigger (obj): A PyQt4.QtCore.pyqtSignal object to emit message - signal to the main dialog indicating finish information. The - meaning of the signal arguments is listed here: - (str, int) - (time, count) - time (str): A string indicating the total time uesd to make the - new hosts file. - count (int): An integer indicating the total number of hosts - entries inserted into the new hosts file. - move_trigger (obj): A PyQt4.QtCore.pyqtSignal object to emit signal - to the main dialog while new hosts is being moved to specified - path on current system. This signal does not - count (int): An integer indicating id of the module being processed - currently. - mod_num (int): An integer indicating total number of modules being - operated while making hosts file. - make_cfg (dict): A dictionary containing the selection control bytes - to make a hosts file. - make_mode (str): A string indicating the operation mode for making - hosts file. - eol (str): A string indicating the End-Of-Line marker. - """ - info_trigger = QtCore.pyqtSignal(str, int) - fina_trigger = QtCore.pyqtSignal(str, int) - move_trigger = QtCore.pyqtSignal() - - count = 0 - mod_num = 0 - make_cfg = {} - make_mode = "" - eol = "" - - def __init__(self, parent=None): - """Initialize a new instance of this class - Private Method - - Fetch settings from the main dialog to make a new hosts file. - - Args: - parent (obj): An instance of MainDialog object to get settings - from. - """ - super(QSubMakeHosts, self).__init__(parent) - self.count = 0 - self.make_cfg = parent._make_cfg - self.make_mode = parent._make_mode - make_path = parent._make_path - self.hostname = parent.hostname - if parent._make_mode == "system": - self.eol = parent._sys_eol - self.hosts_file = open("hosts", "wb") - elif parent._make_mode == "ansi": - self.eol = "\r\n" - self.hosts_file = open(unicode(make_path), "wb") - elif parent._make_mode == "utf-8": - self.eol = "\n" - self.hosts_file = open(unicode(make_path), "wb") - - def run(self): - """Make new hosts file - Public Method - - Operations to retrieve data from the data file and make the new hosts - file for current system. - """ - RetrieveData.connect_db() - start_time = time.time() - self.maketime = start_time - self.write_head() - self.write_info() - self.get_hosts(self.make_cfg) - self.hosts_file.close() - end_time = time.time() - total_time = "%.4f" % (end_time - start_time) - self.fina_trigger.emit(total_time, self.count) - if self.make_mode == "system": - self.move_trigger.emit() - RetrieveData.disconnect_db() - - def get_hosts(self, make_cfg): - """Make hosts by user config - Public Method - - Make the new hosts file by the configuration ({make_cfg}) from - function list on the main dialog. - - Args: - make_cfg (dict): A dictionary containing module settings in byte - word format. - """ - for part_id in sorted(make_cfg.keys()): - mod_cfg = make_cfg[part_id] - if not RetrieveData.chk_mutex(part_id, mod_cfg): - return - mods = RetrieveData.get_ids(mod_cfg) - for mod_id in mods: - self.mod_num += 1 - if part_id == 0x02: - self.write_localhost_mod(part_id, mod_id) - else: - self.write_common_mod(part_id, mod_id) - - def write_head(self): - """Write head section - Public Method - - Write the head part of new hosts file. - """ - for head_str in RetrieveData.get_head(): - self.hosts_file.write("%s%s" % (head_str[0], self.eol)) - - def write_info(self): - """Write info section - Public Method - - Write the information part of new hosts file. - """ - info = RetrieveData.get_info() - info_lines = ["#"] - info_lines.append("# %s: %s" % ("Version", info["Version"])) - info_lines.append("# %s: %s" % ("Buildtime", info["Buildtime"])) - info_lines.append("# %s: %s" % ("Applytime", int(self.maketime))) - info_lines.append("#") - for line in info_lines: - self.hosts_file.write("%s%s" % (line, self.eol)) - - def write_common_mod(self, part_id, mod_id): - """Write module section - Public Method - - Write hosts entries in a specified module ({mod_id}) from a specified - part ({part_id}) of the data file to the new hosts file. - - Args: - part_id (int): An integer indicating the index number of a part - in the data file. - mod_id (int): An integer indicating the index number of a module - in the data file. - """ - hosts, mod_name = RetrieveData.get_host(part_id, mod_id) - self.info_trigger.emit(mod_name, self.mod_num) - self.hosts_file.write( - "%s# Section Start: %s%s" % (self.eol, mod_name, self.eol)) - for host in hosts: - self.hosts_file.write("%s %s%s" % (host[0], host[1], self.eol)) - self.count += 1 - self.hosts_file.write("# Section End: %s%s" % (mod_name, self.eol)) - - def write_localhost_mod(self, part_id, mod_id): - """Write localhost section - Public Method - - Write hosts entries in a localhost module ({mod_id}) from a specified - part ({part_id}) of the data file to the new hosts file. - - Args: - part_id (int): An integer indicating the index number of a part - in the data file. - mod_id (int): An integer indicating the index number of a module - in the data file. - """ - hosts, mod_name = RetrieveData.get_host(part_id, mod_id) - self.info_trigger.emit(mod_name, self.mod_num) - self.hosts_file.write( - "%s# Section Start: Localhost%s" % (self.eol, self.eol)) - for host in hosts: - if "#Replace" in host[1]: - host = (host[0], self.hostname) - self.hosts_file.write("%s %s%s" % (host[0], host[1], self.eol)) - self.count += 1 - self.hosts_file.write("# Section End: Localhost%s" % (self.eol)) - - -class QSubChkUpdate(QtCore.QThread): - """A class to check update info of the latest data file - - QSubChkConnection is a subclasse of PyQt4.QtCore.QThread. This class - contains methods to retrieve the metadata of the latest hosts data file. - - The instance of this class should be created in an individual thread. And - the object instance of MainDialog class should be set as parent here. - - Attribute: - trigger (obj): A PyQt4.QtCore.pyqtSignal object to emit suatus signal - to the main dialog. The meaning of the signal is listed here: - (dict) -> (update_info) - update_info (dict): A dictionary containing metadata of the - latest hosts data file. - """ - trigger = QtCore.pyqtSignal(dict) - - def __init__(self, parent=None): - """Initialize a new instance of this class - Private Method - - Get mirror settings from the main dialog to check the connection. - - Args: - parent (obj): An instance of MainDialog object to get settings - from. - """ - super(QSubChkUpdate, self).__init__(parent) - self.url = parent.mirrors[parent._mirr_id]["update"] + parent.infofile - - def run(self): - """Check update - Public Method - - Operations to retrieve the metadata of the latest hosts data file. - """ - try: - socket.setdefaulttimeout(5) - urlobj = urllib.urlopen(self.url) - j_str = urlobj.read() - urlobj.close() - info = json.loads(j_str) - self.trigger.emit(info) - except: - info = {"version": unicode(_translate("HostsUtlMain", - "[Error]", None))} - self.trigger.emit(info) - - -def qt_main(): - """Load main dialog - - Start the main dialog of Hosts Setup Utility. - """ - trans = QtCore.QTranslator() - trans.load("lang/en_US") - app = QtGui.QApplication(sys.argv) - app.installTranslator(trans) - ui = Ui_HostsUtlMain() - HostsUtlMain = MainDialog(ui, trans) - ui.setupUi(HostsUtlMain) - HostsUtlMain.set_languages() - if not HostsUtlMain.initd: - HostsUtlMain.init_main() - HostsUtlMain.show() - sys.exit(app.exec_()) - -if __name__ == "__main__": - qt_main() diff --git a/img/hosts_utl.icns b/img/hosts_utl.icns deleted file mode 100644 index 72b8f81..0000000 Binary files a/img/hosts_utl.icns and /dev/null differ diff --git a/img/hosts_utl.ico b/img/hosts_utl.ico deleted file mode 100644 index d2e3c4d..0000000 Binary files a/img/hosts_utl.ico and /dev/null differ diff --git a/img/utl_icon.png b/img/utl_icon.png deleted file mode 100644 index e6db3d8..0000000 Binary files a/img/utl_icon.png and /dev/null differ diff --git a/img/utl_icon_256x256.png b/img/utl_icon_256x256.png deleted file mode 100644 index 68d6d0e..0000000 Binary files a/img/utl_icon_256x256.png and /dev/null differ diff --git a/lang/en_US.qm b/lang/en_US.qm deleted file mode 100644 index 6ea3657..0000000 Binary files a/lang/en_US.qm and /dev/null differ diff --git a/lang/zh_CN.qm b/lang/zh_CN.qm deleted file mode 100644 index d842be4..0000000 Binary files a/lang/zh_CN.qm and /dev/null differ diff --git a/lang/zh_TW.qm b/lang/zh_TW.qm deleted file mode 100644 index ca362e0..0000000 Binary files a/lang/zh_TW.qm and /dev/null differ diff --git a/mac_res/.DS_Store b/mac_res/.DS_Store deleted file mode 100644 index 54998dc..0000000 Binary files a/mac_res/.DS_Store and /dev/null differ diff --git a/qthosts.qrc b/qthosts.qrc deleted file mode 100644 index fc4f101..0000000 --- a/qthosts.qrc +++ /dev/null @@ -1,23 +0,0 @@ - - - img/utl_icon.png - - - img/buttons/button_ansi.png - img/buttons/button_ansi_disabled.png - img/buttons/button_apply.png - img/buttons/button_apply_disabled.png - img/buttons/button_backup.png - img/buttons/button_backup_disabled.png - img/buttons/button_download.png - img/buttons/button_download_disabled.png - img/buttons/button_exit.png - img/buttons/button_exit_disabled.png - img/buttons/button_restore.png - img/buttons/button_restore_disabled.png - img/buttons/button_update.png - img/buttons/button_update_disabled.png - img/buttons/button_utf8.png - img/buttons/button_utf8_disabled.png - - diff --git a/img/buttons/button_ansi.png b/res/img/buttons/button_ansi.png similarity index 100% rename from img/buttons/button_ansi.png rename to res/img/buttons/button_ansi.png diff --git a/img/buttons/button_ansi_disabled.png b/res/img/buttons/button_ansi_disabled.png similarity index 100% rename from img/buttons/button_ansi_disabled.png rename to res/img/buttons/button_ansi_disabled.png diff --git a/img/buttons/button_apply.png b/res/img/buttons/button_apply.png similarity index 100% rename from img/buttons/button_apply.png rename to res/img/buttons/button_apply.png diff --git a/img/buttons/button_apply_disabled.png b/res/img/buttons/button_apply_disabled.png similarity index 100% rename from img/buttons/button_apply_disabled.png rename to res/img/buttons/button_apply_disabled.png diff --git a/img/buttons/button_backup.png b/res/img/buttons/button_backup.png similarity index 100% rename from img/buttons/button_backup.png rename to res/img/buttons/button_backup.png diff --git a/img/buttons/button_backup_disabled.png b/res/img/buttons/button_backup_disabled.png similarity index 100% rename from img/buttons/button_backup_disabled.png rename to res/img/buttons/button_backup_disabled.png diff --git a/img/buttons/button_download.png b/res/img/buttons/button_download.png similarity index 100% rename from img/buttons/button_download.png rename to res/img/buttons/button_download.png diff --git a/img/buttons/button_download_disabled.png b/res/img/buttons/button_download_disabled.png similarity index 100% rename from img/buttons/button_download_disabled.png rename to res/img/buttons/button_download_disabled.png diff --git a/img/buttons/button_exit.png b/res/img/buttons/button_exit.png similarity index 100% rename from img/buttons/button_exit.png rename to res/img/buttons/button_exit.png diff --git a/img/buttons/button_exit_disabled.png b/res/img/buttons/button_exit_disabled.png similarity index 100% rename from img/buttons/button_exit_disabled.png rename to res/img/buttons/button_exit_disabled.png diff --git a/img/buttons/button_restore.png b/res/img/buttons/button_restore.png similarity index 100% rename from img/buttons/button_restore.png rename to res/img/buttons/button_restore.png diff --git a/img/buttons/button_restore_disabled.png b/res/img/buttons/button_restore_disabled.png similarity index 100% rename from img/buttons/button_restore_disabled.png rename to res/img/buttons/button_restore_disabled.png diff --git a/img/buttons/button_update.png b/res/img/buttons/button_update.png similarity index 100% rename from img/buttons/button_update.png rename to res/img/buttons/button_update.png diff --git a/img/buttons/button_update_disabled.png b/res/img/buttons/button_update_disabled.png similarity index 100% rename from img/buttons/button_update_disabled.png rename to res/img/buttons/button_update_disabled.png diff --git a/img/buttons/button_utf8.png b/res/img/buttons/button_utf8.png similarity index 100% rename from img/buttons/button_utf8.png rename to res/img/buttons/button_utf8.png diff --git a/img/buttons/button_utf8_disabled.png b/res/img/buttons/button_utf8_disabled.png similarity index 100% rename from img/buttons/button_utf8_disabled.png rename to res/img/buttons/button_utf8_disabled.png diff --git a/res/img/icons/hosts_utl.icns b/res/img/icons/hosts_utl.icns new file mode 100644 index 0000000..4743cba Binary files /dev/null and b/res/img/icons/hosts_utl.icns differ diff --git a/res/img/icons/hosts_utl.ico b/res/img/icons/hosts_utl.ico new file mode 100644 index 0000000..4a76b01 Binary files /dev/null and b/res/img/icons/hosts_utl.ico differ diff --git a/res/img/icons/utl_icon@256x256.png b/res/img/icons/utl_icon@256x256.png new file mode 100644 index 0000000..cbc99fa Binary files /dev/null and b/res/img/icons/utl_icon@256x256.png differ diff --git a/res/img/icons/utl_icon@512x512.png b/res/img/icons/utl_icon@512x512.png new file mode 100644 index 0000000..53e479e Binary files /dev/null and b/res/img/icons/utl_icon@512x512.png differ diff --git a/img/style/checkbox.png b/res/img/style/checkbox.png similarity index 100% rename from img/style/checkbox.png rename to res/img/style/checkbox.png diff --git a/img/style/down_arrow.png b/res/img/style/down_arrow.png similarity index 100% rename from img/style/down_arrow.png rename to res/img/style/down_arrow.png diff --git a/mac_res/HostsUtl.scpt b/res/mac/HostsUtl.scpt similarity index 100% rename from mac_res/HostsUtl.scpt rename to res/mac/HostsUtl.scpt diff --git a/mac_res/Info.plist b/res/mac/Info.plist similarity index 100% rename from mac_res/Info.plist rename to res/mac/Info.plist diff --git a/mac_res/dmg_resource/DS_Store_dmg b/res/mac/dmg/DS_Store_dmg similarity index 67% rename from mac_res/dmg_resource/DS_Store_dmg rename to res/mac/dmg/DS_Store_dmg index 7655ef6..bd9d541 100644 Binary files a/mac_res/dmg_resource/DS_Store_dmg and b/res/mac/dmg/DS_Store_dmg differ diff --git a/mac_res/dmg_resource/background.png b/res/mac/dmg/background.png similarity index 100% rename from mac_res/dmg_resource/background.png rename to res/mac/dmg/background.png diff --git a/retrievedata.py b/retrievedata.py deleted file mode 100644 index 93cef40..0000000 --- a/retrievedata.py +++ /dev/null @@ -1,310 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# retrievedata.py : Read data from the hosts data file -# -# Copyleft (C) 2013 - huhamhire hosts team -# ===================================================================== -# Licensed under the GNU General Public License, version 3. You should -# have received a copy of the GNU General Public License along with -# this program. If not, see . -# -# This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING -# THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR -# PURPOSE. -# ===================================================================== - -__version__ = "0.8" -__revision__ = "$Id$" -__author__ = "huhamhire " - -__all__ = ["RetrieveData", "make_hosts"] - -import os -import sqlite3 -import time -import zipfile - -class RetrieveData(object): - """A class to fetch data from data file - - RetrieveData class contains a set of tools to retrieve information from - the hosts data file. All methods from this class are defined as class - methods. - - Attributes: - _conn (obj): An instance of sqlite3.connect object to set the - connection with a SQLite database. - _cur (obj): An instance of sqlite3.connect.cursor object to operate - SQL queries in the database. - _database (str): A string indicating the filename of a SQLite database - file. - """ - _conn = None - _cur = None - _database = None - - @classmethod - def db_exists(cls, database="hostslist.s3db"): - """Check if database exists - Class Method - - Check whether the database file exists or not. - - Args: - database (str): A string indicating the SQLite database file. - "hostslist.s3db" by default. - - Returns: - A boolean indicating if the database file exists. - """ - return os.path.isfile(database) - - @classmethod - def connect_db(cls, database="hostslist.s3db"): - """Connect to database - Class Method - - Set up connection with a SQLite database. - - Args: - database (str): A string indicating the SQLite database file. - "hostslist.s3db" by default. - """ - cls._conn = sqlite3.connect(database) - cls._cur = cls._conn.cursor() - cls._database = database - - @classmethod - def disconnect_db(cls): - """Disconnect to database - Class Method - - Close the connection with a SQLite database. - """ - cls._conn.close() - - @classmethod - def get_info(cls): - """Get data file information - Class Method - - Retrieve the metadata of current data file. - - Returns: - A dictionary containing the metadata of current data file. - """ - cls._cur.execute("SELECT sect, info FROM info") - info = dict(cls._cur.fetchall()) - return info - - @classmethod - def get_head(cls): - """Get head info from data file - Class Method - - Retrieve the head information from hosts data file. - - Returns: - A list containing hosts head information. - """ - cls._cur.execute("SELECT str FROM hosts_head ORDER BY ln") - head = cls._cur.fetchall() - return head - - @classmethod - def get_ids(cls, id_cfg): - """Get id numbers - Class Method - - Calculate the id numbers covered by config word ({id_cfg}). - - Args: - id_cfg (int): A hex number indicating the config word of id - selections. - - Returns: - A list containing the id numbers covered by config word. - """ - cfg = bin(id_cfg)[:1:-1] - ids = [] - for i, id_en in enumerate(cfg): - if int(id_en): - ids.append(0b1 << i) - return ids - - @classmethod - def get_host(cls, part_id, mod_id): - """Get hosts entries - Class Method - - Retrieve the hosts of a specified module ({mod_id}) from a specified - part ({part_id}) in the data file. - - Args: - part_id (int): An integer indicating the id number of a specified - part from the hosts data file - mod_id (int): An integer indicating the id number of a specified - module number from a specified part. - - Returns: - (hosts, mod_name) - hosts (list): A list containing hosts entries of a specified - module. - mod_name (str): A string indicating the name of a specified - module. - """ - cls._cur.execute("SELECT part_name FROM parts " - "WHERE part_id=%s" % part_id) - part_name = cls._cur.fetchone()[0] - cls._cur.execute("SELECT ip, host FROM %s " - "WHERE cate=%s" % (part_name, mod_id)) - hosts = cls._cur.fetchall() - cls._cur.execute("SELECT mod_name FROM modules " - "WHERE part_id=%s AND mod_id = %s" % (part_id, mod_id)) - mod_name = cls._cur.fetchone()[0] - return hosts, mod_name - - @classmethod - def get_choice(cls, flag_v6=False): - """Get module selection choices - Class Method - - Retrieve module selection items from the hosts data file with default - selection for users. - - Args: - flag_v6 (bool): A bool flag indicating whether to receive the IPv6 - entries or the IPv4 ones. True: IPv6, False: IPv4. - - Returns: - (modules, defaults, slices) - modules (list): A list containing information of modules for users - to select. - defaults (dict): A dictionary containing default selection for - selected parts. - slices (list): A list containing the number of modules in each - part. - """ - ch_parts = (0x08, 0x20 if flag_v6 else 0x10, 0x40) - cls._cur.execute("SELECT * FROM modules " - "WHERE part_id IN (?, ?, ?)", ch_parts) - modules = cls._cur.fetchall() - cls._cur.execute("SELECT part_id, part_default FROM parts " - "WHERE part_id IN (?, ?, ?)", ch_parts) - default_cfg = cls._cur.fetchall() - defaults = {} - for default in default_cfg: - defaults[default[0]] = cls.get_ids(default[1]) - slices = [0] - for ch_part in ch_parts: - cls._cur.execute("SELECT COUNT(mod_id) FROM modules " - "WHERE part_id=?", (ch_part, )) - slices.append(cls._cur.fetchone()[0]) - for s in range(1, len(slices)): - slices[s] = slices[s] + slices[s - 1] - return modules, defaults, slices - - @classmethod - def chk_mutex(cls, part_id, mod_cfg): - """Check conflict in selections - Class Method - - Check if there is conflict in user selections ({mod_cfg}) of a - specified part ({part_id}) - - Args: - part_id (int): An integer indicating the id number of a specified - part from the hosts data file - mod_cfg (int): A hex number indicating the config word of id - selections for a specified part. - - Returns: - A bool flag indicating whether there is a conflict or not. - True : Conflict, False: No conflicts. - """ - cls._cur.execute("SELECT mod_id, mutex FROM modules " - "WHERE part_id=%s" % part_id) - mutex_tuple = dict(cls._cur.fetchall()) - mutex_info = [] - mod_info = cls.get_ids(mod_cfg) - for mod_id in mod_info: - mutex_info.extend(cls.get_ids(mutex_tuple[mod_id])) - mutex_info = set(mutex_info) - for mod_id in mod_info: - if mod_id in mutex_info: - return False - return True - - @classmethod - def unpack(cls, packfile="hostslist.data", dbfile="hostslist.s3db"): - """Unpack local data file - Class Method - - Unzip the zipped data file ({packfile}) to a SQLite database file - ({dbfile}). - """ - datafile = zipfile.ZipFile(packfile, "r") - datafile.extract(dbfile) - - @classmethod - def clear(cls): - """Clear up workspace - Class Method - - Close the connection to the database and delete the database file. - """ - cls._conn.close() - os.remove(cls._database) - -def make_hosts(cfgs, hostname): - """Operations to make a hosts filename - - Make a new hosts file by data from the local data file. - - Args: - cfgs (dict): A dictionary containing the hex config words for - different parts of the data file. - hostname (str): A string indicating the hostname of current operating - system. - """ - # Operations start - start_time = time.time() - hosts_file = open("hosts", "w") - RetrieveData.unpack() - RetrieveData.connect_db() - # Fetches head section - for head_str in RetrieveData.get_head(): - hosts_file.write("%s\n" % head_str[0]) - # Fetches info section - info = RetrieveData.get_info() - info_lines = ["#"] - info_lines.append("# %s: %s" % ("Version", info["Version"])) - info_lines.append("# %s: %s" % ("Buildtime", info["Buildtime"])) - info_lines.append("# %s: %s" % ("Applytime", int(start_time))) - info_lines.append("#") - for line in info_lines: - hosts_file.write("%s\n" % line) - # Fetches hosts section - for part_id in sorted(cfgs.keys()): - mod_cfg = cfgs[part_id] - if not RetrieveData.chk_mutex(part_id, mod_cfg): - return - mods = RetrieveData.get_ids(mod_cfg) - if part_id == 0x02: - # Retrieve localhost module - for mod_id in mods: - hosts, mod_name = RetrieveData.get_host(part_id, mod_id) - hosts_file.write("\n# Section Start: %s\n" % mod_name) - for host in hosts: - if hosts[1] == "#Replace Your Device Name Here!": - hosts[1] = hostname - hosts_file.write("%s %s\n" % (host[0], host[1])) - hosts_file.write("# Section End: %s\n" % mod_name) - else: - # Retrieve common modules - for mod_id in mods: - hosts, mod_name = RetrieveData.get_host(part_id, mod_id) - hosts_file.write("\n# Section Start: %s\n" % mod_name) - for host in hosts: - hosts_file.write("%s %s\n" % (host[0], host[1])) - hosts_file.write("# Section End: %s\n" % mod_name) - hosts_file.close() - RetrieveData.clear() - # Operations end - end_time = time.time() - total_time = "%.4f" % (end_time - start_time) - -if __name__ == "__main__": - # Module Test - selection = {0x02: 0x0001, 0x08: 0x0001, 0x10: 0x003F, 0x40: 0x000F} - make_hosts(selection, "TEST-PC") diff --git a/style.qrc b/style.qrc deleted file mode 100644 index 9c88d05..0000000 --- a/style.qrc +++ /dev/null @@ -1,6 +0,0 @@ - - - img/style/checkbox.png - img/style/down_arrow.png - - diff --git a/tui/__doc__.py b/tui/__doc__.py new file mode 100644 index 0000000..7849776 --- /dev/null +++ b/tui/__doc__.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# __doc__.py Document in reST format of tui module. +# +# Copyleft (C) 2014 - huhamhire +# ===================================================================== +# Licensed under the GNU General Public License, version 3. You should +# have received a copy of the GNU General Public License along with +# this program. If not, see . +# ===================================================================== +""" +.. _tui-module: + +Text-based User Interface (TUI) +=============================== + +The following sections describe the objects and methods from the Text-based +User Interface (TUI) module of huhamhire-hosts HostsUtil. The methods to make +TUI here are based on +`curses `_. + + +HostsUtil(TUI) +-------------- +.. autoclass:: tui.hostsutil.HostsUtil + :members: + + .. automethod:: tui.hostsutil.HostsUtil.__init__ + .. automethod:: tui.hostsutil.HostsUtil.__del__ + + +CursesDaemon +------------ +.. autoclass:: tui.curses_d.CursesDaemon + :members: + + +CursesUI +-------- +.. autoclass:: tui.curses_ui.CursesUI + :members: + + .. automethod:: tui.curses_ui.CursesUI.__init__ + .. automethod:: tui.curses_ui.CursesUI.__del__ + + +FetchUpdate +----------- + +.. autoclass:: tui._update.FetchUpdate + :members: + + .. automethod:: tui._update.FetchUpdate.__init__ +""" \ No newline at end of file diff --git a/tui/__init__.py b/tui/__init__.py new file mode 100644 index 0000000..d53ebf1 --- /dev/null +++ b/tui/__init__.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# __init__.py: Declare modules to be called in tui module. +# +# Copyleft (C) 2014 - huhamhire +# ===================================================================== +# Licensed under the GNU General Public License, version 3. You should +# have received a copy of the GNU General Public License along with +# this program. If not, see . +# ===================================================================== + +from hostsutil import HostsUtil + +__all__ = ["HostsUtil"] diff --git a/tui/_update.py b/tui/_update.py new file mode 100644 index 0000000..831bd6c --- /dev/null +++ b/tui/_update.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# _update.py: Retrieve hosts data file. +# +# Copyleft (C) 2013 - huhamhire +# ===================================================================== +# Licensed under the GNU General Public License, version 3. You should +# have received a copy of the GNU General Public License along with +# this program. If not, see . +# ===================================================================== + +__author__ = "huhamhire " + +import os +import socket +import urllib + + +class FetchUpdate(object): + """ + FetchUpdate class contains methods to retrieve the latest hosts data file + from the project server. + + :ivar str url: The URL of the latest hosts data file. + :ivar str path: Destination path to save the data file downloaded. + :ivar str tmp_path: Temporary path to save the data file while + downloading. + :ivar int file_size: Size of the data file in bytes. + :ivar CursesDaemon parent: An instance of + :class:`~tui.curses_d.CursesDaemon` class to get configuration with. + """ + + def __init__(self, parent): + """ + Initialize a new instance of this class + + :param parent: An instance of :class:`~tui.curses_d.CursesDaemon` + class to get configuration with. + :type parent: :class:`~tui.curses_d.CursesDaemon` + """ + mirror_id = parent.settings[0][1] + mirror = parent.settings[0][2][mirror_id] + self.url = mirror["update"] + parent.filename + self.path = "./" + parent.filename + self.tmp_path = self.path + ".download" + self.file_size = parent._update["size"] + self.parent = parent + + def get_file(self): + """ + Fetch the latest hosts data file from project server. + """ + socket.setdefaulttimeout(10) + try: + urllib.urlretrieve(self.url, self.tmp_path, + self.parent.process_bar) + self.replace_old() + except Exception, e: + raise e + + def replace_old(self): + """ + Replace the old hosts data file with the new one. + """ + if os.path.isfile(self.path): + os.remove(self.path) + os.rename(self.tmp_path, self.path) \ No newline at end of file diff --git a/tui/curses_d.py b/tui/curses_d.py new file mode 100644 index 0000000..4031cd3 --- /dev/null +++ b/tui/curses_d.py @@ -0,0 +1,511 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# curses_d.py: Operations for TUI window. +# +# Copyleft (C) 2014 - huhamhire +# ===================================================================== +# Licensed under the GNU General Public License, version 3. You should +# have received a copy of the GNU General Public License along with +# this program. If not, see . +# ===================================================================== + +__author__ = "huhamhire " + +import curses +import json +import os +import shutil +import socket +import urllib +import sys + +from curses_ui import CursesUI +from _update import FetchUpdate + +sys.path.append("..") +from util import CommonUtil, RetrieveData +from util import MakeHosts + + +class CursesDaemon(CursesUI): + """ + CursesDaemon class contains methods to deal with the operations related to + the TUI window of `Hosts Setup Utility`. Including methods to interactive + with users. + + .. note:: This class is subclass of :class:`~tui.curses_ui.CursesUI` + class. + + :ivar dict _update: Update information of the current data file on server. + :ivar int _writable: Indicating whether the program is run with admin/root + privileges. The value could be `1` or `0`. + + .. seealso:: `_update` and `_writable` in + :class:`~gui.qdialog_d.QDialogDaemon` class. + + :ivar dict make_cfg: A set of module selection control bytes used to + control whether a specified method is used or not while generate a + hosts file. + + * `Keys` of :attr:`make_cfg` are typically 8-bit control byte + indicating which part of the hosts data file would be effected by + the corresponding `Value`. + + +----+----------------+ + |Key |Part | + +====+================+ + |0x02|Localhost | + +----+----------------+ + |0x08|Shared hosts | + +----+----------------+ + |0x10|IPv4 hosts | + +----+----------------+ + |0x20|IPv6 hosts | + +----+----------------+ + |0x40|AD block hosts | + +----+----------------+ + + * `Values` of :attr:`make_cfg` are typically 16-bit control bytes that + decides which of the modules in a specified part would be inserted + into the `hosts` file. + + * `Value` of `Localhost` part. The Value used in `Localhost` part + are usually bytes indicating the current operating system. + + +---------------+-------------------+ + |Hex |OS | + +===============+===================+ + |0x0001 |Windows | + +---------------+-------------------+ + |0x0002 |Linux, Unix | + +---------------+-------------------+ + |0x0004 |Mac OS X | + +---------------+-------------------+ + + * `Values` of `Shared hosts`, `IPv4 hosts`, `IPv6 hosts`, and + `AD block hosts` parts are usually sum of module IDs selected + by user. + + .. note:: + If modules in specified part whose IDs are `0x0002` and + `0x0010`, the value here should be `0x0002 + 0x0010 = 0x0012`, + which is `0b0000000000000010 + 0b0000000000010000 = + 0b0000000000010010` in binary. + + .. warning:: + Only one bit could be `1` in the binary form of a module ID, + which means `0b0000000000010010` is an INVALID module ID while + it could be a VALID `Value` in `make_cfg`. + + :ivar str platform: Platform of current operating system. The value could + be `Windows`, `Linux`, `Unix`, `OS X`, and of course `Unknown`. + :ivar str hostname: The hostname of current operating system. + + .. note:: This attribute would only be used on linux. + + :ivar str hosts_path: The absolute path to the hosts file on current + operating system. + :ivar str make_mode: Operation mode for making hosts file. The valid value + could be one of `system`, `ansi`, and `utf-8`. + + .. seealso:: :attr:`make_mode` in + :class:`~util.makehosts.MakeHosts` class. + + :ivar str make_path: Temporary path to store generated hosts file. The + default value of :attr:`make_path` is "`./hosts`". + :ivar list _ops_keys: Hot keys used to start a specified operation. + Default operation keys are `F5`, `F6`, and `F10`. + :ivar list _hot_keys: Hot keys used to select a item or confirm an + operation. And the default :attr:`_hot_keys` is defined as:: + + _hot_keys = [curses.KEY_UP, curses.KEY_DOWN, 10, 32] + + .. seealso:: :attr:`~tui.curses_ui.CursesUI.funckeys` in + :class:`~tui.curses_ui.CursesUI` class. + """ + _update = {} + _writable = 0 + + make_cfg = {} + platform = '' + hostname = '' + hosts_path = '' + + make_mode = '' + make_path = "./hosts" + + _ops_keys = [curses.KEY_F5, curses.KEY_F6, curses.KEY_F10] + _hot_keys = [curses.KEY_UP, curses.KEY_DOWN, 10, 32] + + def __init__(self): + super(CursesDaemon, self).__init__() + self.check_writable() + + def check_writable(self): + """ + Check if current session has write privileges to the hosts file. + + .. note:: IF current session does not has the write privileges to the + hosts file of current system, a warning message box would popup. + + .. note:: ALL operation would change the `hosts` file on current + system could only be done while current session has write + privileges to the file. + """ + self._writable = CommonUtil.check_privileges()[1] + if not self._writable: + self.messagebox("Please check if you have writing\n" + "privileges to the hosts file!", 1) + exit() + + def session_daemon(self): + """ + Operations processed while running a TUI session of `Hosts Setup + Utility`. + + :return: A flag indicating whether to reload the current session or + all operations have been finished. The return value could only be + `0` or `1`. To be specific: + + ==== ========= + flag operation + ==== ========= + 0 Finish + 1 Reload + ==== ========= + + .. note:: Reload operation is called only when a new data file is + retrieved from server. + + :rtype: int + + .. note:: IF hosts data file does not exists in current working + directory, a warning message box would popup. And operations to + change the hosts file on current system could be done only until + a new data file has been downloaded. + """ + + screen = self._stdscr.subwin(0, 0, 0, 0) + screen.keypad(1) + # Draw Menu + self.banner() + self.footer() + # Key Press Operations + key_in = None + tab = 0 + pos = 0 + tab_entry = [self.configure_settings, self.select_func] + while key_in != 27: + self.setup_menu() + self.status() + self.process_bar(0, 0, 0, 0) + for i, sec in enumerate(tab_entry): + tab_entry[i](pos if i == tab else None) + if key_in is None: + test = self.settings[0][2][0]["test_url"] + self.check_connection(test) + key_in = screen.getch() + if key_in == 9: + if self.choice == [[], []]: + tab = 0 + else: + tab = not tab + pos = 0 + elif key_in in self._hot_keys: + pos = tab_entry[tab](pos, key_in) + elif key_in in self._ops_keys: + if key_in == curses.KEY_F10: + if self._funcs == [[], []]: + self.messagebox("No data file found! Press F6 to get " + "data file first.", 1) + else: + msg = "Apply Changes to hosts file?" + confirm = self.messagebox(msg, 2) + if confirm: + self.set_config_bytes() + self.make_mode = "system" + maker = MakeHosts(self) + maker.make() + self.move_hosts() + elif key_in == curses.KEY_F5: + self._update = self.check_update() + elif key_in == curses.KEY_F6: + if self._update == {}: + self._update = self.check_update() + # Check if data file up-to-date + if self.new_version(): + self.fetch_update() + return 1 + else: + self.messagebox("Data file is up-to-date!", 1) + else: + pass + return 0 + + def configure_settings(self, pos=None, key_in=None): + """ + Perform operations to config settings if `Configure Setting` frame is + active, or just draw the `Configure Setting` frame with no items + selected while it is inactive. + + .. note:: Whether the `Configure Setting` frame is inactive is decided + by if :attr:`pos` is `None` or not. + + =========== ======== + :attr:`pos` Status + =========== ======== + None Inactive + int Active + =========== ======== + + :param pos: Index of selected item in `Configure Setting` frame. The + default value of `pos` is `None`. + :type pos: int or None + :param key_in: A flag indicating the key pressed by user. The default + value of `key_in` is `None`. + :type key_in: int or None + :return: Index of selected item in `Configure Setting` frame. + :rtype: int or None + """ + id_num = range(len(self.settings)) + if pos is not None: + if key_in == curses.KEY_DOWN: + pos = list(id_num[1:] + id_num[:1])[pos] + elif key_in == curses.KEY_UP: + pos = list(id_num[-1:] + id_num[:-1])[pos] + elif key_in in [10, 32]: + self.sub_selection(pos) + self.info(pos, 0) + self.configure_settings_frame(pos) + return pos + + def select_func(self, pos=None, key_in=None): + """ + Perform operations if `function selection list` is active, or just + draw the `function selection list` with no items selected while it is + inactive. + + .. note:: Whether the `function selection list` is inactive is decided + by if :attr:`pos` is `None` or not. + + .. seealso:: :meth:`~tui.curses_d.CursesDaemon.configure_settings`. + + :param pos: Index of selected item in `function selection list`. The + default value of `pos` is `None`. + :type pos: int or None + :param key_in: A flag indicating the key pressed by user. The default + value of `key_in` is `None`. + :type key_in: int or None + :return: Index of selected item in `function selection list`. + :rtype: int or None + """ + list_height = 15 + ip = self.settings[1][1] + # Key Press Operations + item_len = len(self.choice[ip]) + item_sup, item_inf = self._item_sup, self._item_inf + if pos is not None: + if item_len > list_height: + if pos <= 1: + item_sup = 0 + item_inf = list_height - 1 + elif pos >= item_len - 2: + item_sup = item_len - list_height + 1 + item_inf = item_len + else: + item_sup = 0 + item_inf = item_len + if key_in == curses.KEY_DOWN: + pos += 1 + if pos >= item_len: + pos = 0 + if pos not in range(item_sup, item_inf): + item_sup += 2 if item_sup == 0 else 1 + item_inf += 1 + elif key_in == curses.KEY_UP: + pos -= 1 + if pos < 0: + pos = item_len - 1 + if pos not in range(item_sup, item_inf): + item_inf -= 2 if item_inf == item_len else 1 + item_sup -= 1 + elif key_in in [10, 32]: + self._funcs[ip][pos] = not self._funcs[ip][pos] + mutex = RetrieveData.get_ids(self.choice[ip][pos][2]) + for c_id, c in enumerate(self.choice[ip]): + if c[0] == self.choice[ip][pos][0]: + if c[1] in mutex and self._funcs[ip][c_id] == 1: + self._funcs[ip][c_id] = 0 + self.info(pos, 1) + else: + item_sup = 0 + if item_len > list_height: + item_inf = list_height - 1 + else: + item_inf = item_len + self.show_funclist(pos, item_sup, item_inf) + return pos + + def sub_selection(self, pos): + """ + Let user to choose settings from `Selection Dialog` specified by + :attr:`pos`. + + :param pos: Index of selected item in `Configure Setting` frame. + :type pos: int + + .. warning:: The value of `pos` MUST NOT be `None`. + + .. seealso:: :meth:`~tui.curses_ui.CursesUI.sub_selection_dialog` in + :class:`~tui.curses_ui.CursesUI`. + """ + screen = self.sub_selection_dialog(pos) + i_pos = self.settings[pos][1] + # Key Press Operations + id_num = range(len(self.settings[pos][2])) + key_in = None + while key_in != 27: + self.sub_selection_dialog_items(pos, i_pos, screen) + key_in = screen.getch() + if key_in == curses.KEY_DOWN: + i_pos = list(id_num[1:] + id_num[:1])[i_pos] + elif key_in == curses.KEY_UP: + i_pos = list(id_num[-1:] + id_num[:-1])[i_pos] + elif key_in in [10, 32]: + if pos == 0 and i_pos != self.settings[pos][1]: + test = self.settings[pos][2][i_pos]["test_url"] + self.check_connection(test) + self.settings[pos][1] = i_pos + return + + def check_connection(self, url): + """ + Check connection status to the server currently selected by user and + show a status box indicating current operation. + + :param url: The link of the server chose by user.This string could be + a domain name or the IP address of a server. + + .. seealso:: :attr:`link` in + :meth:`~util.common.CommonUtil.check_connection`. + :type url: str + :return: A flag indicating connection status is good or not. + + .. seealso:: :meth:`~util.common.CommonUtil.check_connection`. in + :class:`~util.common.CommonUtil` class. + :rtype: int + """ + self.messagebox("Checking Server Status...") + conn = CommonUtil.check_connection(url) + if conn: + self.statusinfo[0][1] = "OK" + self.statusinfo[0][2] = "GREEN" + else: + self.statusinfo[0][1] = "Error" + self.statusinfo[0][2] = "RED" + self.status() + return conn + + def check_update(self): + """ + Check the metadata of the latest hosts data file from server and + show a status box indicating current operation. + + :return: A dictionary containing the `Version`, `Release Date` of + current hosts data file and the `Latest Version` of the data file + on server. + + IF error occurs while checking update, the dictionary would be + defined as:: + + {"version": "[Error]"} + :rtype: dict + """ + self.messagebox("Checking Update...") + srv_id = self.settings[0][1] + url = self.settings[0][2][srv_id]["update"] + self.infofile + try: + socket.setdefaulttimeout(5) + url_obj = urllib.urlopen(url) + j_str = url_obj.read() + url_obj.close() + info = json.loads(j_str) + except: + info = {"version": "[Error]"} + self.hostsinfo["Latest"] = info["version"] + self.status() + return info + + def new_version(self): + """ + Compare version of local data file to the version from the server. + + :return: A flag indicating whether the local data file is up-to-date + or not. + + ====== ============================================ + Return Data file status + ====== ============================================ + 1 The version of data file on server is newer. + 0 The local data file is up-to-date. + ====== ============================================ + :rtype: int + """ + local_ver = self.hostsinfo["Version"] + if local_ver == "N/A": + return 1 + server_ver = self._update["version"] + local_ver = local_ver.split('.') + server_ver = server_ver.split('.') + for i, ver_num in enumerate(local_ver): + if server_ver[i] > ver_num: + return 1 + return 0 + + def fetch_update(self): + """ + Retrieve the latest hosts data file from server and show a status box + indicating current operation. + """ + self.messagebox("Downloading...") + fetch_d = FetchUpdate(self) + fetch_d.get_file() + + def set_config_bytes(self): + """ + Calculate the module configuration byte words by the selection from + function list on the main dialog. + """ + ip_flag = self.settings[1][1] + selection = {} + localhost_word = { + "Windows": 0x0001, "Linux": 0x0002, + "Unix": 0x0002, "OS X": 0x0004}[self.platform] + selection[0x02] = localhost_word + ch_parts = [0x08, 0x20 if ip_flag else 0x10, 0x40] + # Set customized module if exists + if os.path.isfile(self.custom): + ch_parts.insert(0, 0x04) + slices = self.slices[ip_flag] + for i, part in enumerate(ch_parts): + part_cfg = self._funcs[ip_flag][slices[i]:slices[i + 1]] + part_word = 0 + for i, cfg in enumerate(part_cfg): + part_word += cfg << i + selection[part] = part_word + self.make_cfg = selection + + def move_hosts(self): + """ + Move hosts file to the system path after making operations are + finished. + """ + filepath = "hosts" + try: + shutil.copy2(filepath, self.hosts_path) + except IOError: + os.remove(filepath) + return + os.remove(filepath) + self.messagebox("Operation completed!", 1) diff --git a/tui/curses_ui.py b/tui/curses_ui.py new file mode 100644 index 0000000..3e0328b --- /dev/null +++ b/tui/curses_ui.py @@ -0,0 +1,584 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# curses_ui.py: Draw TUI using curses. +# +# Copyleft (C) 2014 - huhamhire +# ===================================================================== +# Licensed under the GNU General Public License, version 3. You should +# have received a copy of the GNU General Public License along with +# this program. If not, see . +# ===================================================================== + +__author__ = "huhamhire " + +import curses +import locale + +import sys +sys.path.append("..") +from util import CommonUtil +from __version__ import __version__, __release__ + + +class CursesUI(object): + """ + CursesUI class contains methods to draw the Text-based User Interface + (TUI) of Hosts Setup Utility. The methods to make TUI here are based on + `curses `_. + + :ivar str __title: Title of the TUI utility. + :ivar str __copyleft: Copyleft information of the TUI utility. + :ivar WindowObject _stdscr: A **WindowObject** which represents the whole + screen. + :ivar int _item_sup: Upper bound of item index from `function selection + list`. + :ivar int _item_inf: Lower bound of item index from `function selection + list`. + :ivar str _make_path: Temporary path to store the hosts file in while + building. The default _make_path is `./hosts`. + :ivar list _funcs: Two lists with the information of function list both + for IPv4 and IPv6 environment. + :ivar list choice: Two lists with the selection of functions both + for IPv4 and IPv6 environment. + :ivar list slices: Two lists with integers indicating the number of + function items from different parts listed in the function list. + + .. seealso:: `_funcs`, `choice`, and `slices` in + :class:`~gui.qdialog_d.QDialogDaemon` class. + + :ivar str sys_eol: The End-Of-Line marker. This maker could typically be + one of `CR`, `LF`, or `CRLF`. + + .. seealso:: :attr:`sys_eol` in + :class:`~gui.qdialog_d.QDialogDaemon` class. + + :ivar list colorpairs: Tuples of `(foreground-color, background-color)` + used while drawing TUI. + + The default colorpairs is defined as:: + + colorpairs = [(curses.COLOR_WHITE, curses.COLOR_BLUE), + (curses.COLOR_WHITE, curses.COLOR_RED), + (curses.COLOR_YELLOW, curses.COLOR_BLUE), + (curses.COLOR_BLUE, curses.COLOR_WHITE), + (curses.COLOR_WHITE, curses.COLOR_WHITE), + (curses.COLOR_BLACK, curses.COLOR_WHITE), + (curses.COLOR_GREEN, curses.COLOR_WHITE), + (curses.COLOR_WHITE, curses.COLOR_BLACK), + (curses.COLOR_RED, curses.COLOR_WHITE), ] + + :ivar list settings: Two list containing the server selection and IP + protocol version of current session. + + The settings should be like:: + + settings = [["Server", 0, []], + ["IP Version", 0, ["IPv4", "IPv6"]]] + + :ivar list funckeys: Lists of hot keys with their function to be shown on + TUI. + + The default :attr:`funckeys` is defined as:: + + funckeys = [["", "Select Item"], ["Tab", "Select Field"], + ["Enter", "Set Item"], ["F5", "Check Update"], + ["F6", "Fetch Update"], ["F10", "Apply Changes"], + ["Esc", "Exit"]] + + :ivar list statusinfo: Two lists containing the connection and OS checking + status of current session. + + The default :attr:`statusinfo` is defined as:: + + statusinfo = [["Connection", "N/A", "GREEN"], + ["OS", "N/A", "GREEN"]] + + :ivar dict hostsinfo: Containing the `Version`, `Release Date` of current + hosts data file and the `Latest Version` of the data file on server. + + The default hostsinfo is defined as:: + + hostsinfo = {"Version": "N/A", "Release": "N/A", "Latest": "N/A"} + + .. note:: IF the hosts data file does NOT exist in current working + directory, OR the file metadata has NOT been checked, the values + here would just be `N/A`. + + :ivar str filename: Filename of the hosts data file containing data to + make hosts files from. Default by "`hostslist.data`". + :ivar str infofile: Filename of the info file containing metadata of the + hosts data file formatted in JSON. Default by "`hostslist.json`". + + .. seealso:: :attr:`filename` and :attr:`infofile` in + :class:`~gui.hostsutil.HostsUtil` class. + + :ivar str custom: File name of User Customized Hosts File. Customized + hosts would be able to select if this file exists. The default file + name is ``custom.hosts``. + + .. seealso:: :ref:`User Customized Hosts`. + """ + __title = "HOSTS SETUP UTILITY" + version = "".join(['v', __version__, ' ', __release__]) + __copyleft = "%s Copyleft 2011-2014, huhamhire-hosts Team" % version + + _stdscr = None + _item_sup = 0 + _item_inf = 0 + + _make_path = "./hosts" + _funcs = [[], []] + choice = [[], []] + slices = [[], []] + sys_eol = "" + + colorpairs = [(curses.COLOR_WHITE, curses.COLOR_BLUE), + (curses.COLOR_WHITE, curses.COLOR_RED), + (curses.COLOR_YELLOW, curses.COLOR_BLUE), + (curses.COLOR_BLUE, curses.COLOR_WHITE), + (curses.COLOR_WHITE, curses.COLOR_WHITE), + (curses.COLOR_BLACK, curses.COLOR_WHITE), + (curses.COLOR_GREEN, curses.COLOR_WHITE), + (curses.COLOR_WHITE, curses.COLOR_BLACK), + (curses.COLOR_RED, curses.COLOR_WHITE), ] + + settings = [["Server", 0, []], + ["IP Version", 0, ["IPv4", "IPv6"]]] + funckeys = [["", "Select Item"], ["Tab", "Select Field"], + ["Enter", "Set Item"], ["F5", "Check Update"], + ["F6", "Fetch Update"], ["F10", "Apply Changes"], + ["Esc", "Exit"]] + statusinfo = [["Connection", "N/A", "GREEN"], ["OS", "N/A", "GREEN"]] + hostsinfo = {"Version": "N/A", "Release": "N/A", "Latest": "N/A"} + + filename = "hostslist.data" + infofile = "hostsinfo.json" + custom = "custom.hosts" + + def __init__(self): + """ + Initialize a new TUI window in terminal. + """ + locale.setlocale(locale.LC_ALL, '') + self._stdscr = curses.initscr() + curses.start_color() + curses.noecho() + curses.cbreak() + curses.curs_set(0) + # Set colors + curses.use_default_colors() + for i, color in enumerate(self.colorpairs): + curses.init_pair(i + 1, *color) + + def __del__(self): + """ + Reset terminal before quit. + """ + curses.nocbreak() + curses.echo() + curses.endwin() + + def banner(self): + """ + Draw the banner in the TUI window. + """ + screen = self._stdscr.subwin(2, 80, 0, 0) + screen.bkgd(' ', curses.color_pair(1)) + # Set local variable + title = curses.A_NORMAL + title += curses.A_BOLD + normal = curses.color_pair(4) + # Print title + screen.addstr(0, 0, self.__title.center(79), title) + screen.addstr(1, 0, "Setup".center(10), normal) + screen.refresh() + + def footer(self): + """ + Draw the footer in the TUI window. + """ + screen = self._stdscr.subwin(1, 80, 23, 0) + screen.bkgd(' ', curses.color_pair(1)) + # Set local variable + normal = curses.A_NORMAL + # Copyright info + copyleft = self.__copyleft + screen.addstr(0, 0, copyleft.center(79), normal) + screen.refresh() + + def configure_settings_frame(self, pos=None): + """ + Draw `Configure Setting` frame with a index number (`pos`) of the item + selected. + + :param pos: Index of selected item in `Configure Setting` frame. The + default value of `pos` is `None`. + :type pos: int or None + + .. note:: None of the items in `Configure Setting` frame would be + selected if pos is `None`. + """ + self._stdscr.keypad(1) + screen = self._stdscr.subwin(8, 25, 2, 0) + screen.bkgd(' ', curses.color_pair(4)) + # Set local variable + normal = curses.A_NORMAL + select = curses.color_pair(5) + select += curses.A_BOLD + + for p, item in enumerate(self.settings): + item_str = item[0].ljust(12) + screen.addstr(3 + p, 2, item_str, select if p == pos else normal) + if p: + choice = "[%s]" % item[2][item[1]] + else: + choice = "[%s]" % item[2][item[1]]["label"] + screen.addstr(3 + p, 15, ''.ljust(10), normal) + screen.addstr(3 + p, 15, choice, select if p == pos else normal) + screen.refresh() + + def status(self): + """ + Draw `Status` frame and `Hosts File` frame in the TUI window. + """ + screen = self._stdscr.subwin(11, 25, 10, 0) + screen.bkgd(' ', curses.color_pair(4)) + # Set local variable + normal = curses.A_NORMAL + green = curses.color_pair(7) + red = curses.color_pair(9) + # Status info + for i, stat in enumerate(self.statusinfo): + screen.addstr(2 + i, 2, stat[0], normal) + stat_str = ''.join(['[', stat[1], ']']).ljust(9) + screen.addstr(2 + i, 15, stat_str, + green if stat[2] == "GREEN" else red) + # Hosts file info + i = 0 + for key, info in self.hostsinfo.items(): + screen.addstr(7 + i, 2, key, normal) + screen.addstr(7 + i, 15, info, normal) + i += 1 + screen.refresh() + + def show_funclist(self, pos, item_sup, item_inf): + """ + Draw `function selection list` frame with a index number of the item + selected and the range of items to be displayed. + + :param pos: Index of selected item in `function selection list`. + :type pos: int or None + :param item_sup: Upper bound of item index from `function selection + list`. + :type item_sup: int + :param item_inf: Lower bound of item index from `function selection + list`. + :type item_inf: int + """ + # Set UI variable + screen = self._stdscr.subwin(18, 26, 2, 26) + screen.bkgd(' ', curses.color_pair(4)) + normal = curses.A_NORMAL + select = curses.color_pair(5) + select += curses.A_BOLD + list_height = 15 + # Set local variable + ip = self.settings[1][1] + item_len = len(self.choice[ip]) + # Function list + items_show = self.choice[ip][item_sup:item_inf] + items_selec = self._funcs[ip][item_sup:item_inf] + for p, item in enumerate(items_show): + sel_ch = '+' if items_selec[p] else ' ' + item_str = ("[%s] %s" % (sel_ch, item[3])).ljust(23) + item_pos = pos - item_sup if pos is not None else None + highlight = select if p == item_pos else normal + if item_len > list_height: + if item_inf - item_sup == list_height - 2: + screen.addstr(4 + p, 2, item_str, highlight) + elif item_inf == item_len: + screen.addstr(4 + p, 2, item_str, highlight) + elif item_sup == 0: + screen.addstr(3 + p, 2, item_str, highlight) + else: + screen.addstr(3 + p, 2, item_str, highlight) + if item_len > list_height: + if item_inf - item_sup == list_height - 2: + screen.addstr(3, 2, " More ".center(23, '.'), normal) + screen.addch(3, 15, curses.ACS_UARROW) + screen.addstr(17, 2, " More ".center(23, '.'), normal) + screen.addch(17, 15, curses.ACS_DARROW) + elif item_inf == item_len: + screen.addstr(3, 2, " More ".center(23, '.'), normal) + screen.addch(3, 15, curses.ACS_UARROW) + elif item_sup == 0: + screen.addstr(17, 2, " More ".center(23, '.'), normal) + screen.addch(17, 15, curses.ACS_DARROW) + else: + for line_i in range(list_height - item_len): + screen.addstr(17 - line_i, 2, ' ' * 23, normal) + if not items_show: + screen.addstr(4, 2, "No data file!".center(23), normal) + screen.refresh() + self._item_sup, self._item_inf = item_sup, item_inf + + def info(self, pos, tab): + """ + Draw `Information` frame with a index number (`pos`) of the item + selected from the frame specified by `tab`. + + :param pos: Index of selected item in a specified frame. + :type pos: int + :param tab: Index of the frame to select items from. + :type tab: int + + .. warning:: Both of `pos` and `tab` in this method could not be + set to `None`. + """ + screen = self._stdscr.subwin(18, 24, 2, 52) + screen.bkgd(' ', curses.color_pair(4)) + normal = curses.A_NORMAL + if tab: + ip = self.settings[1][1] + info_str = self.choice[ip][pos][3] + else: + info_str = self.settings[pos][0] + # Clear Expired Infomotion + for i in range(6): + screen.addstr(1 + i, 2, ''.ljust(22), normal) + screen.addstr(1, 2, info_str, normal) + # Key Info Offset + k_info_y = 10 + k_info_x_key = 2 + k_info_x_text = 10 + # Arrow Keys + screen.addch(k_info_y, k_info_x_key, curses.ACS_UARROW, normal) + screen.addch(k_info_y, k_info_x_key + 1, curses.ACS_DARROW, normal) + # Show Key Info + for i, keyinfo in enumerate(self.funckeys): + screen.addstr(k_info_y + i, k_info_x_key, keyinfo[0], normal) + screen.addstr(k_info_y + i, k_info_x_text, keyinfo[1], normal) + screen.refresh() + + def process_bar(self, done, block, total, mode=1): + """ + Draw `Process Bar` at the bottom which is used to indicate progress of + downloading operation. + + .. note:: This method is a callback function responses to + :meth:`urllib.urlretrieve` method while fetching hosts data + file. + + :param done: Block count of packaged retrieved. + :type done: int + :param block: Block size of the data pack retrieved. + :type block: int + :param total: Total size of the hosts data file. + :type total: int + :param mode: A flag indicating the status of `Process Bar`. + The default value of `mode` is `1`. + + ==== ==================================== + mode `Process Bar` status + ==== ==================================== + 1 Downloading operation is processing. + 0 Not downloading. + ==== ==================================== + :type mode: int + """ + screen = self._stdscr.subwin(2, 80, 20, 0) + screen.bkgd(' ', curses.color_pair(4)) + normal = curses.A_NORMAL + line_width = 76 + prog_len = line_width - 20 + # Progress Bar + if mode: + done *= block + prog = 1.0 * prog_len * done / total + progress = ''.join(['=' * int(prog), '-' * int(2 * prog % 2)]) + progress = progress.ljust(prog_len) + total = CommonUtil.convert_size(total).ljust(7) + done = CommonUtil.convert_size(done).rjust(7) + else: + progress = ' ' * prog_len + done = total = 'N/A'.center(7) + # Show Progress + prog_bar = "[%s] %s | %s" % (progress, done, total) + screen.addstr(1, 2, prog_bar, normal) + screen.refresh() + + def sub_selection_dialog(self, pos): + """ + Draw a `Selection Dialog` on screen used to make configurations. + + :param pos: Index of selected item in `Configure Setting` frame. + :type pos: int + + .. warning:: The value of `pos` MUST NOT be `None`. + + :return: A **WindowObject** which represents the selection dialog. + :rtype: WindowObject + """ + i_len = len(self.settings[pos][2]) + # Draw Shadow + shadow = curses.newwin(i_len + 2, 18, 13 - i_len / 2, 31) + shadow.bkgd(' ', curses.color_pair(8)) + shadow.refresh() + # Draw Subwindow + screen = curses.newwin(i_len + 2, 18, 12 - i_len / 2, 30) + screen.box() + screen.bkgd(' ', curses.color_pair(1)) + screen.keypad(1) + # Set local variable + normal = curses.A_NORMAL + # Title of Subwindow + screen.addstr(0, 3, self.settings[pos][0].center(12), normal) + return screen + + def sub_selection_dialog_items(self, pos, i_pos, screen): + """ + Draw items in `Selection Dialog`. + + :param pos: Index of selected item in `Configure Setting` frame. + :type pos: int + :param i_pos: Index of selected item in `Selection Dialog`. + :type i_pos: int + :param screen: A **WindowObject** which represents the selection + dialog. + :type screen: WindowObject + """ + # Set local variable + normal = curses.A_NORMAL + select = normal + curses.A_BOLD + for p, item in enumerate(self.settings[pos][2]): + item_str = item if pos else item["tag"] + screen.addstr(1 + p, 2, item_str, + select if p == i_pos else normal) + screen.refresh() + + def setup_menu(self): + """ + Draw the main frame of `Setup` tab in the TUI window. + """ + screen = self._stdscr.subwin(21, 80, 2, 0) + screen.box() + screen.bkgd(' ', curses.color_pair(4)) + # Configuration Section + screen.addch(0, 26, curses.ACS_BSSS) + screen.vline(1, 26, curses.ACS_VLINE, 17) + # Status Section + screen.addch(7, 0, curses.ACS_SSSB) + screen.addch(7, 26, curses.ACS_SBSS) + screen.hline(7, 1, curses.ACS_HLINE, 25) + # Select Functions Section + screen.addch(0, 52, curses.ACS_BSSS) + screen.vline(1, 52, curses.ACS_VLINE, 17) + # Process Bar Section + screen.addch(18, 0, curses.ACS_SSSB) + screen.addch(18, 79, curses.ACS_SBSS) + screen.hline(18, 1, curses.ACS_HLINE, 78) + screen.addch(18, 26, curses.ACS_SSBS) + screen.addch(18, 52, curses.ACS_SSBS) + # Section Titles + title = curses.color_pair(6) + subtitles = [["Configure Settings", (1, 2)], ["Status", (8, 2)], + ["Hosts File", (13, 2)], ["Select Functions", (1, 28)]] + for s_title in subtitles: + cord = s_title[1] + screen.addstr(cord[0], cord[1], s_title[0], title) + screen.hline(cord[0] + 1, cord[1], curses.ACS_HLINE, 23) + screen.refresh() + + @staticmethod + def messagebox(msg, mode=0): + """ + Draw a `Message Box` with :attr:`msg` in a specified type defined by + :attr:`mode`. + + .. note:: This is a `static` method. + + :param msg: The information to be displayed in message box. + :type msg: str + :param mode: A flag indicating the type of message box to be + displayed. The default value of `mode` is `0`. + + ==== =========================================================== + mode Message Box Type + ==== =========================================================== + 0 A simple message box showing a message without any buttons. + 1 A message box with an `OK` button for user to confirm. + 2 A message box with `OK` and `Cancel` buttons for user to + choose. + ==== =========================================================== + :type mode: int + :return: A flag indicating the choice made by user. + + ====== ===================================================== + Return Condition + ====== ===================================================== + 0 #. No button is pressed while :attr:`mode` is `0`. + #. `OK` button pressed while :attr:`mode` is `1`. + #. `Cancel` button pressed while :attr:`mode` is `2`. + 1 `OK` button pressed while :attr:`mode` is `2`. + ====== ===================================================== + :rtype: int + """ + pos_x = 24 if mode == 0 else 20 + pos_y = 10 + width = 30 if mode == 0 else 40 + height = 2 + messages = CommonUtil.cut_message(msg, width - 4) + height += len(messages) + if mode: + height += 2 + # Draw Shadow + shadow = curses.newwin(height, width, pos_y + 1, pos_x + 1) + shadow.bkgd(' ', curses.color_pair(8)) + shadow.refresh() + # Draw Subwindow + screen = curses.newwin(height, width, pos_y, pos_x) + screen.box() + screen.bkgd(' ', curses.color_pair(2)) + screen.keypad(1) + # Set local variable + normal = curses.A_NORMAL + select = curses.A_REVERSE + # Insert messages + for i in range(len(messages)): + screen.addstr(1 + i, 2, messages[i].center(width - 4), normal) + if mode == 0: + screen.refresh() + else: + # Draw subwindow frame + line_height = 1 + len(messages) + screen.hline(line_height, 1, curses.ACS_HLINE, width - 2) + screen.addch(line_height, 0, curses.ACS_SSSB) + screen.addch(line_height, width - 1, curses.ACS_SBSS) + tab = 0 + key_in = None + while key_in != 27: + if mode == 1: + choices = ["OK"] + elif mode == 2: + choices = ["OK", "Cancel"] + else: + return 0 + for i, item in enumerate(choices): + item_str = ''.join(['[', item, ']']) + tab_pos_x = 6 + 20 * i if mode == 2 else 18 + screen.addstr(line_height + 1, tab_pos_x, item_str, + select if i == tab else normal) + screen.refresh() + key_in = screen.getch() + if mode == 2: + # OK or Cancel + if key_in in [9, curses.KEY_LEFT, curses.KEY_RIGHT]: + tab = [1, 0][tab] + if key_in in [ord('a'), ord('c')]: + key_in -= (ord('a') - ord('A')) + if key_in in [ord('C'), ord('O')]: + return [ord('C'), ord('O')].index(key_in) + if key_in in [10, 32]: + return not tab + return 0 diff --git a/tui/hostsutil.py b/tui/hostsutil.py new file mode 100644 index 0000000..063323b --- /dev/null +++ b/tui/hostsutil.py @@ -0,0 +1,177 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# hostsutil.py: Start a TUI session of `Hosts Setup Utility`. +# +# Copyleft (C) 2014 - huhamhire +# ===================================================================== +# Licensed under the GNU General Public License, version 3. You should +# have received a copy of the GNU General Public License along with +# this program. If not, see . +# ===================================================================== + +__author__ = "huhamhire " + +import os + +from zipfile import BadZipfile + +from curses_d import CursesDaemon + +import sys +sys.path.append("..") +from util import CommonUtil, RetrieveData + + +class HostsUtil(CursesDaemon): + """ + HostsUtil class in :mod:`tui` module is the main entrance to the + Text-based User Interface (TUI) mode of `Hosts Setup Utility`. This class + contains methods to start a TUI session of `Hosts Setup Utility`. + + .. note:: This class is subclass of :class:`~tui.curses_d.CursesDaemon` + class. + + .. inheritance-diagram:: tui.hostsutil.HostsUtil + :parts: 2 + + Typical usage to start a TUI session:: + + import tui + + util = tui.HostsUtil() + util.start() + + :ivar str platform: Platform of current operating system. The value could + be `Windows`, `Linux`, `Unix`, `OS X`, and of course `Unknown`. + :ivar str hostname: The hostname of current operating system. + + .. note:: This attribute would only be used on linux. + :ivar str hosts_path: The absolute path to the hosts file on current + operating system. + + .. seealso:: :attr:`platform`, :attr:`hostname`, :attr:`hosts_path` in + :class:`~tui.curses_d.CursesDaemon` class. + :ivar str sys_eol: The End-Of-Line marker. This maker could typically be + one of `CR`, `LF`, or `CRLF`. + + .. seealso:: :attr:`sys_eol` in :class:`~tui.curses_ui.CursesUI` + class. + """ + platform = "" + hostname = "" + hosts_path = "" + sys_eol = "" + + def __init__(self): + """ + Initialize a new TUI session. + + * Load server list from a configuration file under working directory. + * Try to load the hosts data file under working directory if it + exists. + + .. note:: IF hosts data file does not exists correctly in current + working directory, a warning message box would popup. And + operations to change the hosts file on current system could be + done only until a new data file has been downloaded. + + .. seealso:: :meth:`~tui.curses_d.CursesDaemon.session_daemon` method + in :class:`~tui.curses_d.CursesDaemon`. + + .. seealso:: :meth:`~gui.hostsutil.HostsUtil.init_main` in + :class:`~gui.hostsutil.HostsUtil` class. + """ + super(HostsUtil, self).__init__() + # Set mirrors + self.settings[0][2] = CommonUtil.set_network("network.conf") + # Read data file and set function list + try: + self.set_platform() + RetrieveData.unpack() + RetrieveData.connect_db() + self.set_info() + self.set_func_list() + except IOError: + self.messagebox("No data file found! Press F6 to get data file " + "first.", 1) + except BadZipfile: + self.messagebox("Incorrect Data file! Press F6 to get a new data " + "file first.", 1) + + def __del__(self): + """ + Reset the terminal and clear up the temporary data file while TUI + session is finished. + """ + super(HostsUtil, self).__del__() + try: + RetrieveData.clear() + except: + pass + + def start(self): + """ + Start the TUI session. + + .. note:: This method is the trigger to start a TUI session of + `Hosts Setup Utility`. + """ + while True: + # Reload + if self.session_daemon(): + self.__del__() + self.__init__() + else: + break + + def set_platform(self): + """ + Set the information about current operating system. + """ + system, hostname, path, encode, flag = CommonUtil.check_platform() + color = "GREEN" if flag else "RED" + self.platform = system + self.statusinfo[1][1] = system + self.hostname = hostname + self.hosts_path = path + self.statusinfo[1][2] = color + if encode == "win_ansi": + self.sys_eol = "\r\n" + else: + self.sys_eol = "\n" + + def set_func_list(self): + """ + Set the function selection list in TUI session. + """ + for ip in range(2): + choice, defaults, slices = RetrieveData.get_choice(ip) + if os.path.isfile(self.custom): + choice.insert(0, [4, 1, 0, "customize"]) + defaults[0x04] = [1] + for i in range(len(slices)): + slices[i] += 1 + slices.insert(0, 0) + self.choice[ip] = choice + self.slices[ip] = slices + funcs = [] + for func in choice: + if func[1] in defaults[func[0]]: + funcs.append(1) + else: + funcs.append(0) + self._funcs[ip] = funcs + + def set_info(self): + """ + Set the information of the current local data file. + """ + info = RetrieveData.get_info() + build = info["Buildtime"] + self.hostsinfo["Version"] = info["Version"] + self.hostsinfo["Release"] = CommonUtil.timestamp_to_date(build) + +if __name__ == "__main__": + main = HostsUtil() + main.start() diff --git a/util/__doc__.py b/util/__doc__.py new file mode 100644 index 0000000..7e87193 --- /dev/null +++ b/util/__doc__.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# __doc__.py : Document in reST format of util module. +# +# Copyleft (C) 2014 - huhamhire +# ===================================================================== +# Licensed under the GNU General Public License, version 3. You should +# have received a copy of the GNU General Public License along with +# this program. If not, see . +# ===================================================================== +""" +Shared Utilities +================ + +The module described in this chapter provides shared utilities + + +CommonUtil +---------- +.. autoclass:: util.common.CommonUtil + :members: + + +MakeHosts +--------- + +.. autoclass:: util.makehosts.MakeHosts + :members: + + .. automethod:: util.makehosts.MakeHosts.__init__ + + +RetrieveData +------------ +.. autoclass:: util.retrievedata.RetrieveData + :members: +""" \ No newline at end of file diff --git a/util/__init__.py b/util/__init__.py new file mode 100644 index 0000000..25acfea --- /dev/null +++ b/util/__init__.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# __init__.py: Declare modules to be called in util module. +# +# Copyleft (C) 2014 - huhamhire +# ===================================================================== +# Licensed under the GNU General Public License, version 3. You should +# have received a copy of the GNU General Public License along with +# this program. If not, see . +# ===================================================================== + +from common import CommonUtil +from makehosts import MakeHosts +from retrievedata import RetrieveData + +__all__ = ["CommonUtil", "MakeHosts", "RetrieveData"] \ No newline at end of file diff --git a/util/common.py b/util/common.py new file mode 100644 index 0000000..12e8115 --- /dev/null +++ b/util/common.py @@ -0,0 +1,256 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# common.py : Basic utilities used by Hosts Setup Utility. +# +# Copyleft (C) 2013 - huhamhire hosts team +# ===================================================================== +# Licensed under the GNU General Public License, version 3. You should +# have received a copy of the GNU General Public License along with +# this program. If not, see . +# +# This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING +# THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE. +# ===================================================================== + +__author__ = "huhamhire " + +import ConfigParser +import math +import os +import sys +import socket +import time + + +class CommonUtil(object): + """ + CommonUtil class contains a set of basic tools for Hosts Setup Utility to + use. + + .. note:: All methods from this class are declared as `classmethod`. + """ + @classmethod + def check_connection(cls, link): + """ + Check connect to a specified server by :attr:`link`. + + .. note:: This is a `classmethod`. + + :param link: The link to a specified server. This string could be a + domain name or the IP address of a server. + :type link: str + :return: A flag indicating whether the connection status is good or + not. + + ==== ====== + flag Status + ==== ====== + 1 OK + 0 Error + ==== ====== + :rtype: int + """ + try: + timeout = 3 + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(timeout) + sock.connect((link, 80)) + sock.close() + return 1 + except: + return 0 + + @classmethod + def check_platform(cls): + """ + Check information about current operating system. + + .. note:: This is a `classmethod`. + + :return: system, hostname, path, encode, flag + + * system(`str`): Operating system of current session. + * hostname(`str`): Hostname of current machine. + * path(`str`): Path to hosts on current operating system. + * encode(`str`): Default encoding of current OS. + * flag(`int`): A flag indicating whether the current OS is + supported or not. + + ==== =========== + flag Status + ==== =========== + 1 supported + 2 unsupported + ==== =========== + + :rtype: str, str, str, str, int + """ + hostname = socket.gethostname() + if os.name == "nt": + system = "Windows" + path = os.getenv("WINDIR") + "\\System32\\drivers\\etc\\hosts" + encode = "win_ansi" + elif os.name == "posix": + path = "/etc/hosts" + encode = "unix_utf8" + if sys.platform == "darwin": + system = "OS X" + # Remove the ".local" suffix + hostname = hostname[0:-6] + else: + system = ["Unix", "Linux"][sys.platform.startswith('linux')] + else: + return "Unknown", '', '', 0 + return system, hostname, path, encode, 1 + + @classmethod + def check_privileges(cls): + """ + Check whether the current session has privileges to change the hosts + file of current operating system. + + .. note:: This is a `classmethod`. + + :return: username, flag + + * username(`str`): Username of the user running current session. + * flag(`bool`): A flag indicating whether the current session has + write privileges to the hosts file or not. + :rtype: str, bool + """ + if os.name == 'nt': + try: + # Only windows users with admin privileges can read the + # C:\windows\temp + os.listdir(os.sep.join([ + os.environ.get('SystemRoot', 'C:\windows'), 'temp'])) + except: + return os.environ['USERNAME'], False + else: + return os.environ['USERNAME'], True + else: + # Check wirte privileges to the hosts file for current user + w_flag = os.access("/etc/hosts", os.W_OK) + try: + return os.environ['USERNAME'], w_flag + except KeyError: + return os.environ['USER'], w_flag + + @classmethod + def set_network(cls, conf_file="network.conf"): + """ + Get configurations for mirrors to connect to. + + .. note:: This is a `classmethod`. + + :param conf_file: Path to a configuration file containing which + contains the server list. + :type conf_file: str + :return: `tag`, `test url`, and `update url` of the servers listed in + the configuration file. + + Definition of the dictionary returned: + + ======== =================================================== + Key Value + ======== =================================================== + tag `Tag` string of a specified server. + label Name of a specified server. + test_url `URL` to test the connection to a server. + update `URL` containing the directory to get latest hosts\ + data file. + ======== =================================================== + + :rtype: dict + """ + conf = ConfigParser.ConfigParser() + conf.read(conf_file) + mirrors = [] + for sec in conf.sections(): + mirror = {"tag": sec, + "label": conf.get(sec, "label"), + "test_url": conf.get(sec, "server"), + "update": conf.get(sec, "update"), } + mirrors.append(mirror) + return mirrors + + @classmethod + def timestamp_to_date(cls, timestamp): + """ + Transform unix :attr:`timestamp` to a data string in ISO format. + + .. note:: This is a `classmethod`. + + :param timestamp: A unix timestamp indicating a specified time. + :type timestamp: number + + .. note:: The :attr:`timestamp` could be `int` or `float`. + + :return: Date in ISO format, which is `YY-mm-dd` in specific. + :rtype: str + """ + l_time = time.localtime(float(timestamp)) + iso_format = "%Y-%m-%d" + date = time.strftime(iso_format, l_time) + return date + + @classmethod + def convert_size(cls, bufferbytes): + """ + Convert byte size :attr:`bufferbytes` of a file into a size string. + + .. note:: This is a `classmethod`. + + :param bufferbytes: The size of a file counted in bytes. + :type bufferbytes: int + :return: A readable size string. + :rtype: str + """ + if bufferbytes == 0: + return "0 B" + l_unit = int(math.log(bufferbytes, 0x400)) + units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'] + formats = ['%.2f', '%.1f', '%d'] + size = bufferbytes / math.pow(0x400, l_unit) + l_num = int(math.log(size, 10)) + if l_unit >= len(units): + l_unit = len(units) - 1 + if l_num >= len(formats): + l_num = len(formats) - 1 + return ''.join([formats[l_num], ' ', units[l_unit]]) % size + + @classmethod + def cut_message(cls, msg, cut): + """ + Cut english message (:attr:`msg`) into lines with specified length + (:attr:`cut`). + + .. note:: This is a `classmethod`. + + :param msg: The message to be cut. + :type msg: str + :param cut: The length for each line of the message. + :type cut: int + :return: Lines cut from the message. + :rtype: list + """ + delimiter = [" ", "\n"] + msgs = [] + while len(msg) >= cut: + if "\n" in msg[:cut-1]: + [line, msg] = msg.split("\n", 1) + else: + if (msg[cut-1] not in delimiter) and \ + (msg[cut] not in delimiter): + cut_len = cut - 1 + hyphen = " " if msg[cut-2] in delimiter else "-" + else: + cut_len = cut + hyphen = " " + line = msg[:cut_len] + hyphen + msg = msg[cut_len:] + msgs.append(line) + msgs.append(msg) + return msgs diff --git a/util/makehosts.py b/util/makehosts.py new file mode 100644 index 0000000..c506f25 --- /dev/null +++ b/util/makehosts.py @@ -0,0 +1,208 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# makehosts.py: Make a hosts file. +# +# Copyleft (C) 2014 - huhamhire +# ===================================================================== +# Licensed under the GNU General Public License, version 3. You should +# have received a copy of the GNU General Public License along with +# this program. If not, see . +# ===================================================================== + +__author__ = "huhamhire " + +import os +import time + +from retrievedata import RetrieveData + + +class MakeHosts(object): + """ + MakeHosts class contains methods to make a hosts file with host entries + from a single data file. + + :ivar str make_mode: Operation mode for making hosts file. The valid value + could be one of `system`, `ansi`, and `utf-8`. + + .. seealso:: :attr:`make_mode` in + :class:`~gui.qdialog_d.QDialogDaemon` class. + + :ivar str custom: File name of User Customized Hosts file. + + .. seealso:: :ref:`User Customized Hosts`. + + :ivar str hostname: File Name of hosts file. + :ivar file hosts_file: The hosts file to write hosts to. + :ivar int mod_num: Total number of modules written to hosts file. + :ivar int count: Number of the module being processed currently in the + operation sequence. + :ivar dict make_cfg: Configuration to make a new hosts file. + :ivar str eol: End-of-Line used by the hosts file created. + :ivar time make_time: Timestamp of making hosts file. + + .. seealso:: :class:`gui._make.QSubMakeHosts` class and + :class:`tui.curses_d.CursesDaemon` class. + """ + make_mode = "" + custom = "" + hostname = "" + hosts_file = None + make_cfg = {} + mod_num = 0 + count = 0 + eol = "" + make_time = None + + def __init__(self, parent): + """ + Retrieve configuration from the main dialog to make a new hosts file. + + :param parent: An instance of :class:`~tui.curses_d.CursesDaemon` + class to retrieve configuration with. + :type parent: :class:`~tui.curses_d.CursesDaemon` + + .. warning:: :attr:`parent` MUST NOT be set as `None`. + """ + self.count = 0 + self.make_cfg = parent.make_cfg + self.hostname = parent.hostname + self.custom = parent.custom + make_path = parent.make_path + self.make_mode = parent.make_mode + if self.make_mode == "system": + self.eol = parent.sys_eol + self.hosts_file = open("hosts", "wb") + elif self.make_mode == "ansi": + self.eol = "\r\n" + self.hosts_file = open(unicode(make_path), "wb") + elif self.make_mode == "utf-8": + self.eol = "\n" + self.hosts_file = open(unicode(make_path), "wb") + + def make(self): + """ + Start operations to retrieve data from the data file and make the new + hosts file for the current operating system. + """ + RetrieveData.connect_db() + self.make_time = time.time() + self.write_head() + self.write_info() + self.get_hosts(self.make_cfg) + self.hosts_file.close() + RetrieveData.disconnect_db() + + def get_hosts(self, make_cfg): + """ + Make the new hosts file by the configuration defined by `make_cfg` + from function list on the main dialog. + + :param make_cfg: Module settings in byte word format. + :type make_cfg: dict + + .. seealso:: :attr:`make_cfg` in :class:`~tui.curses_d.CursesDaemon` + class. + """ + for part_id in sorted(make_cfg.keys()): + mod_cfg = make_cfg[part_id] + if not RetrieveData.chk_mutex(part_id, mod_cfg): + return + mods = RetrieveData.get_ids(mod_cfg) + for mod_id in mods: + self.mod_num += 1 + hosts, mod_name = RetrieveData.get_host(part_id, mod_id) + if part_id == 0x02: + self.write_localhost_mod(hosts) + elif part_id == 0x04: + self.write_customized() + else: + self.write_common_mod(hosts, mod_name) + + def write_head(self): + """ + Write the head part into the new hosts file. + """ + for head_str in RetrieveData.get_head(): + self.hosts_file.write("%s%s" % (head_str[0], self.eol)) + + def write_info(self): + """ + Write the information part into the new hosts file. + """ + info = RetrieveData.get_info() + info_lines = [ + "#", + "# %s: %s" % ("Version", info["Version"]), + "# %s: %s" % ("BuildTime", info["Buildtime"]), + "# %s: %s" % ("ApplyTime", int(self.make_time)), + "#" + ] + for line in info_lines: + self.hosts_file.write("%s%s" % (line, self.eol)) + + def write_common_mod(self, hosts, mod_name): + """ + Write hosts entries :attr:`hosts` from a module named `hosts` in the + hosts data file.. + + :param hosts: Hosts entries from a part in the data file. + :type hosts: list + :param mod_name: Name of a module from the data file. + :type mod_name: str + """ + self.hosts_file.write( + "%s# Section Start: %s%s" % (self.eol, mod_name, self.eol)) + for host in hosts: + ip = host[0] + if len(ip) < 16: + ip = ip.ljust(16) + self.hosts_file.write("%s %s%s" % (ip, host[1], self.eol)) + self.count += 1 + self.hosts_file.write("# Section End: %s%s" % (mod_name, self.eol)) + + def write_customized(self): + """ + Write user customized hosts list into the hosts file if the customized + hosts file exists. + """ + if os.path.isfile(self.custom): + custom_file = open(unicode(self.custom), "r") + lines = custom_file.readlines() + self.hosts_file.write( + "%s# Section Start: Customized%s" % (self.eol, self.eol)) + for line in lines: + line = line.strip("\n") + entry = line.split(" ", 1) + if line.startswith("#"): + self.hosts_file.write(line + self.eol) + elif len(entry) > 1: + ip = entry[0] + if len(ip) < 16: + ip = entry[0].ljust(16) + self.hosts_file.write( + "%s %s%s" % (ip, entry[1], self.eol) + ) + else: + pass + self.hosts_file.write("# Section End: Customized%s" % self.eol) + + def write_localhost_mod(self, hosts): + """ + Write localhost entries :attr:`hosts` into the hosts file. + + :param hosts: Hosts entries from a part in the data file. + :type hosts: list + """ + self.hosts_file.write( + "%s# Section Start: Localhost%s" % (self.eol, self.eol)) + for host in hosts: + if "#Replace" in host[1]: + host = (host[0], self.hostname) + ip = host[0] + if len(ip) < 16: + ip = ip.ljust(16) + self.hosts_file.write("%s %s%s" % (ip, host[1], self.eol)) + self.count += 1 + self.hosts_file.write("# Section End: Localhost%s" % self.eol) \ No newline at end of file diff --git a/util/retrievedata.py b/util/retrievedata.py new file mode 100644 index 0000000..bd15bd8 --- /dev/null +++ b/util/retrievedata.py @@ -0,0 +1,317 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# retrievedata.py : Retrieve data from the hosts data file. +# +# Copyleft (C) 2014 - huhamhire hosts team +# ===================================================================== +# Licensed under the GNU General Public License, version 3. You should +# have received a copy of the GNU General Public License along with +# this program. If not, see . +# +# This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING +# THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE. +# ===================================================================== + +__author__ = "huhamhire " + +import os +import sqlite3 +import zipfile + +DATAFILE = "./hostslist.data" +DATABASE = "./hostslist.s3db" + + +class RetrieveData(object): + """ + RetrieveData class contains a set of tools to retrieve information from + the hosts data file. + + .. note:: All methods from this class are declared as `classmethod`. + + :ivar sqlite3.connect conn: An instance of :class:`sqlite3.connect` + object to set up connection to a SQLite database. + :ivar sqlite3.connect.cursor _cur: An instance of + :class:`sqlite3.connect.cursor` object to process SQL queries in the + database. + :ivar str _db: Filename of a SQLite database file. + """ + conn = None + _cur = None + _database = None + + @classmethod + def db_exists(cls, database=DATABASE): + """ + Check whether the :attr:`database` file exists or not. + + .. note:: This is a `classmethod`. + + :param database: Path to a SQLite database file. + `./hostslist.s3db` by default. + :type database: str + :return: A flag indicating whether the database file exists or not. + :rtype: bool + """ + return os.path.isfile(database) + + @classmethod + def connect_db(cls, database=DATABASE): + """ + Set up connection with a SQLite :attr:`database`. + + .. note:: This is a `classmethod`. + + :param database: Path to a SQLite database file. + `./hostslist.s3db` by default. + :type database: str + """ + cls.conn = sqlite3.connect(database) + cls._cur = cls.conn.cursor() + cls._database = database + + @classmethod + def disconnect_db(cls): + """ + Close the connection with a SQLite database. + + .. note:: This is a `classmethod`. + """ + cls.conn.close() + + @classmethod + def get_info(cls): + """ + Retrieve the metadata of current data file. + + .. note:: This is a `classmethod`. + + :return: Metadata of current data file. The metadata here is a + dictionary while the `Keys` are made of `Section Name` and + `Values` are made of `Information` defined in the hosts data file. + :rtype: dict + """ + cls._cur.execute("SELECT sect, info FROM info;") + info = dict(cls._cur.fetchall()) + return info + + @classmethod + def get_head(cls): + """ + Retrieve the head information from hosts data file. + + .. note:: This is a `classmethod`. + + :return: Lines of hosts head information. + :rtype: list + """ + cls._cur.execute("SELECT str FROM hosts_head ORDER BY ln;") + head = cls._cur.fetchall() + return head + + @classmethod + def get_ids(cls, id_cfg): + """ + Calculate the id numbers covered by config word :attr:`id_cfg`. + + .. note:: This is a `classmethod`. + + :param id_cfg: A hexadecimal config word of id selections. + :type id_cfg: int + :return: ID numbers covered by config word. + :rtype: list + """ + cfg = bin(id_cfg)[:1:-1] + ids = [] + for i, id_en in enumerate(cfg): + if int(id_en): + ids.append(0b1 << i) + return ids + + @classmethod + def get_host(cls, part_id, mod_id): + """ + Retrieve the hosts module specified by :attr:`mod_id` from a part + specified by :attr:`part_id` in the data file. + + .. note:: This is a `classmethod`. + + :param part_id: ID number of a specified part from the hosts data + file. + + .. note:: ID number is usually an 8-bit control byte. + :type part_id: int + :param mod_id: ID number of a specified module from a specified part. + + .. note:: ID number is usually an 8-bit control byte. + :type mod_id: int + + .. seealso:: :attr:`make_cfg` in + :class:`~tui.curses_d.CursesDaemon` class. + + :return: hosts, mod_name + + * hosts(`list`): Hosts entries from a specified module. + * mod_name(`str`): Name of a specified module. + :rtype: list, str + """ + if part_id == 0x04: + return None, "customize" + cls._cur.execute(""" + SELECT part_name FROM parts + WHERE part_id=:part_id; + """, (part_id, )) + part_name = cls._cur.fetchone()[0] + cls._cur.execute(""" + SELECT ip, host FROM %s + WHERE cate=%s; + """ % (part_name, mod_id)) + hosts = cls._cur.fetchall() + cls._cur.execute(""" + SELECT mod_name FROM modules + WHERE part_id=:part_id AND mod_id=:mod_id; + """, (part_id, mod_id)) + mod_name = cls._cur.fetchone()[0] + return hosts, mod_name + + @classmethod + def get_choice(cls, flag_v6=False): + """ + Retrieve module selection items from the hosts data file with default + selection for users. + + .. note:: This is a `classmethod`. + + :param flag_v6: A flag indicating whether to receive IPv6 hosts + entries or the IPv4 ones. Default by `False`. + + =============== ======= + :attr:`flag_v6` hosts + =============== ======= + True IPv6 + False IPv4 + =============== ======= + :type flag_v6: bool + :return: modules, defaults, slices + + * modules(`list`): Information of modules for users to select. + * defaults(`dict`): Default selection config for selected parts. + * slices(`list`): Numbers of modules in each part. + :rtype: list, dict, list + """ + ch_parts = (0x08, 0x20 if flag_v6 else 0x10, 0x40) + cls._cur.execute(""" + SELECT * FROM modules + WHERE part_id IN (:id_shared, :id_ipv, :id_adblock); + """, ch_parts) + modules = cls._cur.fetchall() + cls._cur.execute(""" + SELECT part_id, part_default FROM parts + WHERE part_id IN (:id_shared, :id_ipv, :id_adblock); + """, ch_parts) + default_cfg = cls._cur.fetchall() + defaults = {} + for default in default_cfg: + defaults[default[0]] = cls.get_ids(default[1]) + slices = [0] + for ch_part in ch_parts: + cls._cur.execute(""" + SELECT COUNT(mod_id) FROM modules + WHERE part_id=:ch_part; + """, (ch_part, )) + slices.append(cls._cur.fetchone()[0]) + for s in range(1, len(slices)): + slices[s] += slices[s - 1] + return modules, defaults, slices + + @classmethod + def chk_mutex(cls, part_id, mod_cfg): + """ + Check if there is conflict in user selections :attr:`mod_cfg` from a + part specified by :attr:`part_id` in the data file. + + .. note:: A conflict may happen while one module selected is declared + in `mutex` word of ano module selected at the same time. + + .. note:: This is a `classmethod`. + + :param part_id: ID number of a specified part from the hosts data + file. + + .. note:: ID number is usually an 8-bit control byte. + + .. seealso:: :meth:`~util.retrievedata.get_host`. + + :type part_id: int + :param mod_cfg: A 16-bit config word indicating module selections of a + specified part. + + .. note:: + If modules in specified part whose IDs are `0x0002` and + `0x0010`, the value here should be `0x0002 + 0x0010 = 0x0012`, + which is `0b0000000000000010 + 0b0000000000010000 = + 0b0000000000010010` in binary. + + :type: int + + .. seealso:: :attr:`make_cfg` in + :class:`~tui.curses_d.CursesDaemon` class. + + :return: A flag indicating whether there is a conflict or not. + + ====== ============ + Return Status + ====== ============ + True Conflict + False No conflicts + ====== ============ + + :rtype: bool + """ + if part_id == 0x04: + return True + cls._cur.execute(""" + SELECT mod_id, mutex FROM modules + WHERE part_id=:part_id; + """, (part_id, )) + mutex_tuple = dict(cls._cur.fetchall()) + mutex_info = [] + mod_info = cls.get_ids(mod_cfg) + for mod_id in mod_info: + mutex_info.extend(cls.get_ids(mutex_tuple[mod_id])) + mutex_info = set(mutex_info) + for mod_id in mod_info: + if mod_id in mutex_info: + return False + return True + + @classmethod + def unpack(cls, datafile=DATAFILE, database=DATABASE): + """ + Unzip the archived :attr:`datafile` to a SQLite database file + :attr:`database`. + + .. note:: This is a `classmethod`. + + :param datafile: Path to the zipped data file. `./hostslist.data` by + default. + :type datafile: str + :param database: Path to a SQLite database file. `./hostslist.s3db` by + default. + :type database: str + """ + datafile = zipfile.ZipFile(datafile, "r") + path, filename = os.path.split(database) + datafile.extract(filename, path) + + @classmethod + def clear(cls): + """ + Close connection to the database and delete the database file. + + .. note:: This is a `classmethod`. + """ + cls.conn.close() + os.remove(cls._database) diff --git a/utilities.py b/utilities.py deleted file mode 100644 index e8a5f78..0000000 --- a/utilities.py +++ /dev/null @@ -1,262 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# utilities.py : Basic utilities used by Hosts Setup Utility -# -# Copyleft (C) 2013 - huhamhire hosts team -# ===================================================================== -# Licensed under the GNU General Public License, version 3. You should -# have received a copy of the GNU General Public License along with -# this program. If not, see . -# -# This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING -# THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR -# PURPOSE. -# ===================================================================== - -__version__ = "0.9" -__revision__ = "$Id$" -__author__ = "huhamhire " - -__all__ = ["Utilities", "LangUtilities"] - -import ConfigParser -import locale -import math -import os -import sys -import socket -import time - -class Utilities(object): - """Basic tools for Hosts Setup Utility - - Utilities class contains a set of basic tools for Hosts Setup Utility to - use. - """ - @classmethod - def check_connection(cls, link): - """ Check connect to a server - Class Method - - Check connect to a specified server by link ({link}). - - Args: - link (str): A string indicating the link to a specified server. - This string could be a domain name or the IP address of a - server. - - Returns: - A flag integer indicating whether the connection is good or not. - 1: OK, 0: Error. - """ - try: - timeout = 3 - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - sock.settimeout(timeout) - sock.connect((link, 80)) - sock.close() - return 1 - except: - return 0 - - @classmethod - def check_platform(cls): - """Check OS - Class Method - - Check information about current operating system. - - Returns: - (system, hostname, path, encode, flag) - system (str): A string indicating the platform of current OS. - hostname (str): A string indicating the hostname of current OS. - path (str): A string indicating the path to hosts on current - operating system. - encode (str): A string indicating the encoding of current OS. - flag (int): A flag integer indicating whether the current OS is - supported or not. 1: supported, 2, unsupported. - """ - hostname = socket.gethostname() - if os.name == "nt": - system = "Windows" - path = os.getenv("WINDIR") + "\\System32\\drivers\\etc\\hosts" - encode = "win_ansi" - elif os.name == "posix": - path = "/etc/hosts" - encode = "unix_utf8" - if sys.platform == "darwin": - system = "OS X" - # Remove the ".local" suffix - hostname = hostname[0:-6] - else: - system = ["Unix", "Linux"][sys.platform.startswith('linux')] - else: - return "Unknown", '', '', 0 - return system, hostname, path, encode, 1 - - @classmethod - def check_privileges(cls): - """Check user privileges - Class Method - - Check whether the current session has privileges to change the hosts - file of current operating system. - - Returns: - (username, flag) - username (str): A string indicating username of the user running - current session. - flag (bool): A bool flag indicating whether the current session - has write privileges to the hosts file or not. - """ - if os.name == 'nt': - try: - # Only windows users with admin privileges can read the - # C:\windows\temp - temp = os.listdir(os.sep.join([ - os.environ.get('SystemRoot', 'C:\windows'), 'temp'])) - except: - return (os.environ['USERNAME'], False) - else: - return (os.environ['USERNAME'], True) - else: - # Check wirte privileges to the hosts file for current user - w_flag = os.access("/etc/hosts", os.W_OK) - try: - return (os.environ['USERNAME'], w_flag) - except KeyError: - return (os.environ['USER'], w_flag) - - @classmethod - def set_network(cls, conf_file="network.conf"): - """Read network config file - - Get configurations for mirrors to connect to. - - Returns: - A dictionary containing tag, test url, and update url of mirrors. - """ - conf = ConfigParser.ConfigParser() - conf.read(conf_file) - mirrors = [] - for sec in conf.sections(): - mirror = {} - mirror["tag"] = sec - mirror["label"] = conf.get(sec, "label") - mirror["test_url"] = conf.get(sec, "server") - mirror["update"] = conf.get(sec, "update") - mirrors.append(mirror) - return mirrors - - @classmethod - def timestamp_to_date(cls, timestamp): - """Transform timestamp to readable string - Class Method - - Transform unix timestamp ({timestamp}) to a data string in ISO format. - - Args: - timestamp (int/float): A number indicating a unix timestamp. - - Returns: - A data string in ISO format. - """ - l_time = time.localtime(float(timestamp)) - iso_format = "%Y-%m-%d" - date = time.strftime(iso_format , l_time) - return date - - @classmethod - def convert_size(cls, bytes): - """Transform file size to readable string - Class Method - - Convert byte size ({bytes}) of a file into a size string. - - Args: - bytes (int): A integer indicating the size of a file counted by - byte. - - Returns: - A readable size string. - """ - if bytes == 0: - return "0 B" - l_unit = int(math.log(bytes, 0x400)) - units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'] - formats = ['%.2f', '%.1f', '%d'] - size = bytes / math.pow(0x400, l_unit) - l_num = int(math.log(size, 10)) - if l_unit >= len(units): - l_unit = len(units) - 1 - if l_num >= len(formats): - l_num = len(formats) - 1 - return ''.join([formats[l_num], ' ', units[l_unit]]) % (size) - - -class LangUtilities(object): - """language tools for Hosts Setup Utility - - LangUtilities contains a set of language tools for Hosts Setup Utility to - use. - - Attributes: - language (dict): A dictionary containing supported localized language - name for a specified locale. - """ - language = {"de_DE": u"Deutsch", - "en_US": u"English", - "ja_JP": u"日本語", - "ko_KR": u"한글", - "ru_RU": u"Русский", - "zh_CN": u"简体中文", - "zh_TW": u"正體中文", } - - @classmethod - def get_locale(cls): - """Get locale string - Class Method - - Get the default locale of current operating system. - - Returns: - locale (str): A string indicating the locale of current operating - system. If the locale is not in cls.dictionary dictionary, it - will return "en_US" as default. - """ - lc = locale.getdefaultlocale()[0] - if lc == None: - lc = "en_US" - return lc - - @classmethod - def get_language_by_locale(cls, l_locale): - """Get language name by locale - Class Method - - Return the name of a specified language by a locale string - ({l_locale}). - - Args: - l_locale (str): A string indicating a specified locale. - - Returns: - A string indicating the localized name of a language. - """ - try: - return cls.language[l_locale] - except KeyError: - return cls.language["en_US"] - - @classmethod - def get_locale_by_language(cls, l_lang): - """Get locale string by language name - Class Method - - Return the locale string connecting with a specified language - ({l_lang}). - - Args: - l_lang (str): A string indicating the localized name of a - language. - - Returns: - A string indicating a specified locale. - """ - for locl, lang in cls.language.items(): - if l_lang == lang: - return locl - return "en_US"