diff options
author | initial.commit <initial.commit@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-07-26 23:55:29 +0000 |
---|---|---|
committer | initial.commit <initial.commit@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-07-26 23:55:29 +0000 |
commit | 09911bf300f1a419907a9412154760efd0b7abc3 (patch) | |
tree | f131325fb4e2ad12c6d3504ab75b16dd92facfed /chrome/tools | |
parent | 586acc5fe142f498261f52c66862fa417c3d52d2 (diff) | |
download | chromium_src-09911bf300f1a419907a9412154760efd0b7abc3.zip chromium_src-09911bf300f1a419907a9412154760efd0b7abc3.tar.gz chromium_src-09911bf300f1a419907a9412154760efd0b7abc3.tar.bz2 |
Add chrome to the repository.
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@15 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/tools')
70 files changed, 6648 insertions, 0 deletions
diff --git a/chrome/tools/README.google b/chrome/tools/README.google new file mode 100644 index 0000000..1c8057a --- /dev/null +++ b/chrome/tools/README.google @@ -0,0 +1,4 @@ +optipng.exe: v.0.5.5, under zlib/libpng license. Sources available at http://optipng.sourceforge.net/ + +Sample use: (while in trunk/) + for /r %a in (*.png) do @chrome\tools\optipng -o7 %a diff --git a/chrome/tools/automated_ui_test_tools/README.txt b/chrome/tools/automated_ui_test_tools/README.txt new file mode 100644 index 0000000..22ff797 --- /dev/null +++ b/chrome/tools/automated_ui_test_tools/README.txt @@ -0,0 +1,18 @@ +auto_ui_test_input_generator.py takes in a list of possible actions separated by new lines, +the number of commands per file, and the number of actions per command, and generate either +a single random file or a number of files containing all possible commands. This file is then +used as input to automated_ui_tests.exe (see chrome/test/automated_ui_tests/automated_ui_tests.cc/h) +which will run the commands, reporting on the success or failure of each. + +An example of typical use: + +$ python auto_ui_test_input_generator.py --action-list-file="possible_actions.txt" --output="automated_ui_tests.txt" --commands-per-file 100 --actions-per-commands 5 + +$ automated_ui_tests.exe --input="automated_ui_tests.txt" --output="automated_ui_test_report.txt" + + +This will generate a random sequence of 100 commands containing 5 actions each and write it, formatted in XML, +to automated_ui_tests.txt. Then automated_ui_tests.exe reads that file, runs the commands, and outputs an XML +file to automated_ui_test_report.txt. + +In the future we can write a script to parse automated_ui_test_report for failures and warnings.
\ No newline at end of file diff --git a/chrome/tools/automated_ui_test_tools/auto_ui_test_input_generator.py b/chrome/tools/automated_ui_test_tools/auto_ui_test_input_generator.py new file mode 100644 index 0000000..7756028 --- /dev/null +++ b/chrome/tools/automated_ui_test_tools/auto_ui_test_input_generator.py @@ -0,0 +1,468 @@ +#!/usr/bin/python +# +# Copyright 2008 Google Inc. All Rights Reserved. + +"""This generates the input file for automated_ui_tests.exe. + +We take in a list of possible actions separated by new lines, the number of +commands per file, and the number of actions per command, and generate +a single random file, or one or more files containing all possible commands, +or one file with a partial set of possible commands starting at a certain +command number. + +Example usage: + python auto_ui_test_input_generator.py --commands-per-file 50 + --actions-per-command 5 --action-list-file possible_actions.txt + --output random_file.txt + + Generates a file called random_file.txt with 50 commands containing 5 actions + each randomly chosen from the list of new line separated actions in + possible_actions.txt + + python auto_ui_test_input_generator.py --commands-per-file 200 + --actions-per-command 6 --partial-coverage-from 700 + --action-list-file possible_actions.txt + --output partial_coverage.txt + + Generates a file called partial_coverage.txt with 200 commands containing 6 + actions each starting at command #700 and ending at command #899 and chosen + from the list of new line separated actions possible_actions.txt + + +Options: + --action-list-file input_file_name + Name of file containing possible actions separated by new lines. You can + find supported actions in the automated_ui_tests.h documentation. + + --output output_file_name + Name of XML file that will be outputted for use as input for + automated_ui_tests.exe. + + --commands-per-file commands_per_file + Number of commands per file. + + --actions-per-command actions_per_command + Number of actions per command. + + --full-coverage + If full_coverage flag is set will output as many files as neccesary to cover + every combination of possible actions. Files will be named + output_file_name_1.txt, output_file_name_2.txt, etc... + If --full-coverage is true, --full-coverage-one-file and + --partial-coverage-from are ignored. + + --full-coverage-one-file + Just like --full_coverage, but outputs to just one file. + Ignores commands_per_file. This is very likely to cause memory errors on + large action sets. If --full coverage-one-file is true, + --partial-coverage-from is ignored. + + --partial-coverage-from command_to_start_at + Outputs a part of the full coverage, starting at command number + |command_to_start_at| and ending at command number |command_to_start_at| + + |commands_per_file|. Command numbering starts at 0, and the maximum + command number is number_of_actions_we_choose_from ^ actions_per_command - 1. + If |command_to_start_at| + |commands_per_file| is greater than the maximum + command number, then only the commands up to the maximum command number + are printed. + + --quiet + Silence progress messages. +""" + +import optparse +import os.path +import random +import xml.dom.minidom + +class ComprehensiveListGenerator: + """Generates a comprehensive list of all the ways to choose x combinations + from an input list, with replacement. + + Init takes |list_length|, which is the length of the of the combination and + |source_list|, which is the list we want to choose from. + GetNextPortionOfSourceList() returns a list of length |list_length| with a + portion of the complete list of all combinations or None once all possible + combinations have been returned. + + Example: + >>> list_gen = ComprehensiveListGenerator(2, ['a','b','c']) + >>> print list_gen.GetNextPortionOfSourceList() + ['a','a'] + >>> print list_gen.GetNextPortionOfSourceList() + ['a','b'] + >>> print list_gen.GetNextPortionOfSourceList() + ['a','c'] + >>> ...print list_gen.GetNextPortionOfSourceList() 6 more times... + >>> print list_gen.GetNextPortionOfSourceList() + None + >>> list_gen.SetIntegerListToCombinationNumber(2) + >>> print list_gen.GetCurrentPortionOfSourceList() + ['a','c'] + >>> print list_gen.GetNextPortionOfSourceList() + ['b','a'] + >>> list_gen.SetIntegerListToCombinationNumber(8) + >>> print list_gen.GetCurrentPortionOfSourceList() + ['c','c'] + >>> list_gen.SetIntegerListToCombinationNumber(9) + >>> print list_gen.GetCurrentPortionOfSourceList() + None + + Attributes: + __list_length: Length of the resulting combinations. + __source_list: The list we are pulling combinations from. + __integer_list: A list of integers representing which indices of + |source_list| to return. + """ + + def __init__(self, list_length, source_list): + self.__list_length = list_length + self.__integer_list = None + self.__source_list = source_list + + def SetIntegerListToCombinationNumber(self, combo_number): + """ Sets the integer list to represent the |combo_number|th number in the + sequence, counting from 0. + + Args: + combo_number: Number to set the integer list to represent. + + Returns: Sets integer_list to None and returns False if the combo_number is + out of range (bigger than the maximum number of combinations possible or + less than 0) + """ + if (combo_number < 0 or + combo_number >= len(self.__source_list) ** self.__list_length): + self.__integer_list = None + return False + if self.__integer_list == None: + self.__integer_list = [] + for i in range(self.__list_length): + self.__integer_list.append(0) + reversed_range = range(self.__list_length) + reversed_range.reverse() + index_max_value = len(self.__source_list) + quotient = 0 + for index in reversed_range: + quotient, remainder = divmod(combo_number, index_max_value) + combo_number = quotient + self.__integer_list[index] = remainder + + return True + + def __IncrementIntegerListIndex(self, index): + """ Increments the given index of integer_list, rolling over to 0 and + incrementing the a lower index if the index is incremented above the last + index of source_list + + Args: + index: The index integer_list to attempt to increment. + + Returns: False if it is impossible to incremement any index in the list + which is less than or equal to the given index. + """ + self.__integer_list[index] += 1 + if self.__integer_list[index] >= len(self.__source_list): + # We've incremented beyond the length of source_list, so reset to zero... + self.__integer_list[index] = 0 + # And if our index is high enough, increment the next index. Otherwise + # we can't increment any further and should return false. + if index > 0: + return self.__IncrementIntegerListIndex(index-1) + else: + # Restart the integer list at the beginning + self.__integer_list = None + return False + # Successfuly incremented the index, return true. + return True + + def __IncrementIntegerList(self): + """ Gets the next integer list in the series by attempting to increment the + final index of integer_list. + + Returns: False if we can't increment any index in the integer_list. + """ + + # If the list is empty we just started, so populate it with zeroes. + if self.__integer_list == None: + self.__integer_list = [] + for i in range(self.__list_length): + self.__integer_list.append(0) + return True + else: + return self.__IncrementIntegerListIndex(self.__list_length-1) + + def GetCurrentPortionOfSourceList(self): + """ Returns the current portion of source_list corresponding to the + integer_list + + For example, if our current state is: + integer_list = [0,1,0,2] + source_list = ['a','b','c','d'] + Then calling GetCurrentPortionOfSourceList() returns: + ['a','b','a','c'] + + Returns: None if the integer_list is empty, otherwise a list of length + list_length with a combination of elements from source_list + """ + portion_list = [] + if self.__integer_list == None: + return None + + for index in range(self.__list_length): + portion_list.append(self.__source_list[self.__integer_list[index]]) + + return portion_list + + def GetNextPortionOfSourceList(self): + """ Increments the integer_list and then returns the current portion of + source_list corresponding to the integer_list. + + This is the only function outside users should be calling. It will advance + to the next combination of elements from source_list, and return it. See + the class documentation for proper use. + + Returns: None if all possible combinations of source_list have previously + been returned. Otherwise a new list of length list_length with a combination + of elements from source_list. + """ + if self.__IncrementIntegerList(): + return self.GetCurrentPortionOfSourceList() + else: + return None + +class AutomatedTestInputGenerator: + """Creates a random file with with the name |file_name| with + the number of commands and actions specified in the command line. + + Attributes: + __commands_per_file: Number of commands per file. + __actions_per_command: Number of actions per command. + __actions_list: A list of strings representing the possible actions. + __is_verbose: If true, print progress messages + """ + def __init__(self): + (options,args) = ParseCommandLine() + input_file = open(options.input_file_name) + actions_list = input_file.readlines() + input_file.close() + + self.__commands_per_file = options.commands_per_file + self.__actions_per_command = options.actions_per_command + self.__actions_list = [action.strip() for action in actions_list] + self.__is_verbose = options.is_verbose + + def __CreateDocument(self): + """ Create the starter XML document. + + Returns: A tuple containing the XML document and its root element named + "Command List". + """ + doc = xml.dom.minidom.Document() + root_element = doc.createElement("CommandList") + doc.appendChild(root_element) + return doc, root_element + + def __WriteToOutputFile(self, file_name, output): + """Writes |output| to file with name |filename|. Overwriting any pre-existing + file. + + Args: + file_name: Name of the file to create. + output: The string to write to file. + """ + output_file = open(file_name, 'w') + output_file.write(output) + output_file.close() + + def CreateRandomFile(self, file_name): + """Creates a random file with with the name |file_name| with + the number of commands and actions specified in the command line. + + Args: + file_name - Name of the file to create. + + Return: + Nothing. + """ + output_doc, root_element = self.__CreateDocument() + for command_num in range(0, self.__commands_per_file): + command_element = output_doc.createElement("command") + for action_num in range(0,self.__actions_per_command): + action_element = output_doc.createElement( + random.choice(self.__actions_list)) + command_element.appendChild(action_element) + root_element.appendChild(command_element) + self.__WriteToOutputFile(file_name, output_doc.toprettyxml()) + + def __AddCommandToDoc(self, output_doc, root_element, command, command_num): + """Adds a given command to the output XML document + + Args: + output_doc - The output XML document. Used to create elements. + root_element - The root element of the XML document. (What we add the + command to) + command - The name of the command element we create and add to the doc. + command_num - The number of the command. + + Return: + Nothing. + """ + command_element = output_doc.createElement("command") + command_element.setAttribute("number", str(command_num)) + for action in command: + action_element = output_doc.createElement(action) + command_element.appendChild(action_element) + root_element.appendChild(command_element) + + def CreateComprehensiveFile(self, file_name, start_command, write_to_end): + """Creates one file containing all or part of the comprehensive list of + commands starting at a set command. + + Args: + file_name - Name of the file to create. + start_command - Command number to start at. + write_to_end - If true, writes all remaining commands, starting at + start_command. (If start_command is 0, this would write all + possible commands to one file.) If false, write only + commands_per_file commands starting at start_command + + Return: + Nothing. + """ + list_generator = ComprehensiveListGenerator(self.__actions_per_command, + self.__actions_list) + output_doc, root_element = self.__CreateDocument() + command_counter = start_command + end_command = start_command + self.__commands_per_file + is_complete = False + # Set the underlying integer representation of the command to the + # the starting command number. + list_generator.SetIntegerListToCombinationNumber(start_command) + command = list_generator.GetCurrentPortionOfSourceList() + while (command != None and + (write_to_end == True or command_counter < end_command)): + self.__AddCommandToDoc(output_doc, root_element, command, command_counter) + command_counter += 1; + command = list_generator.GetNextPortionOfSourceList() + + self.__WriteToOutputFile(file_name, output_doc.toprettyxml()) + + def CreateComprehensiveFiles(self, file_name): + """Creates a comprehensive coverage of all possible combinations from + action_list of length commands_per_file. Names them |file_name|_1, + |file_name|_2, and so on. + + Args: + file_name - Name of the file to create. + + Return: + Nothing. + """ + list_generator = ComprehensiveListGenerator(self.__actions_per_command, + self.__actions_list) + + is_complete = False + file_counter = 0 + # Split the file name so we can include the file number before the extension. + base_file_name, extension = os.path.splitext(file_name) + command_num = 0 + + while is_complete == False: + output_doc, root_element = self.__CreateDocument() + file_counter += 1 + if self.__is_verbose and file_counter % 200 == 0: + print "Created " + str(file_counter) + " files... " + + for i in range(self.__commands_per_file): + # Get the next sequence of actions as a list. + command = list_generator.GetNextPortionOfSourceList() + if command == None: + is_complete = True + break + + # Parse through the list and add them to the output document as children + # of a command element + self.__AddCommandToDoc(output_doc, root_element, command, command_num) + command_num += 1 + + # Finished the commands for this file, so write it and start on next file. + self.__WriteToOutputFile(base_file_name + "_" + str(file_counter) + + extension, output_doc.toprettyxml()) + +def ParseCommandLine(): + """Parses the command line. + + Return: List of options and their values and a list of arguments which were + unparsed. + """ + parser = optparse.OptionParser() + parser.set_defaults(full_coverage=False) + parser.add_option("-i", "--action-list-file", dest="input_file_name", + type="string", action="store", default="possible_actions.txt", + help="input file with a test of newline separated actions" + "which are possible. Default is 'possible_actions.txt'") + parser.add_option("-o", "--output", dest="output_file_name", type="string", + action="store", default="automated_ui_tests.txt", + help="the file to output the command list to") + parser.add_option("-c", "--commands-per-file", dest="commands_per_file", + type="int", action="store", default=500, + help="number of commands per file") + parser.add_option("-a", "--actions-per-command", dest="actions_per_command", + type="int", action="store", default=5, + help="number of actions per command") + parser.add_option("-f", "--full-coverage", dest="full_coverage", + action="store_true", help="Output files for every possible" + "combination. Default is to output just one random file.") + parser.add_option("-q", "--quiet", + action="store_false", dest="is_verbose", default=True, + help="don't print progress message while creating files") + parser.add_option("-1", "--full-coverage-one-file", + action="store_true", dest="full_coverage_one_file", + default=False, + help="complete coverage all outputted to one file") + parser.add_option("-p", "--partial-coverage-from", dest="start_at_command", + type="int", action="store", default=-1, + help="partial list from the complete coverage, starting at" + "command #start_at_command") + + return parser.parse_args() + +def main(): + (options,args) = ParseCommandLine() + test_generator = AutomatedTestInputGenerator() + if options.full_coverage == True: + if options.full_coverage_one_file and options.is_verbose == True: + print ("Error: Both --full-coverage and --full-coverage-one-file present," + " ignoring --full-coverage-one-file") + if options.start_at_command >= 0 and options.is_verbose == True: + print ("Error: Both --full-coverage and --partial-coverage-from present," + " ignoring --partial-coverage-from") + if options.is_verbose == True: + print "Starting to write comprehensive files:" + test_generator.CreateComprehensiveFiles(options.output_file_name) + if options.is_verbose == True: + print "Finished writing comprehensive files." + elif options.full_coverage_one_file: + if options.start_at_command >= 0 and options.is_verbose == True: + print ("Error: Both --full-coverage-one-file present and" + "--partial-coverage-from present, ignoring --partial-coverage-from") + if options.is_verbose == True: + print "Starting to write comprehensive file:" + test_generator.CreateComprehensiveFile(options.output_file_name, 0, True) + if options.is_verbose == True: + print "Finished writing comprehensive file." + elif options.start_at_command >= 0: + if options.is_verbose == True: + print "Starting to write partial file:" + test_generator.CreateComprehensiveFile(options.output_file_name, + options.start_at_command , False) + if options.is_verbose == True: + print "Finished writing partial file." + else: + test_generator.CreateRandomFile(options.output_file_name) + if options.is_verbose == True: + print "Output written to file: " + options.output_file_name + +if __name__ == '__main__': + main() diff --git a/chrome/tools/automated_ui_test_tools/possible_actions.txt b/chrome/tools/automated_ui_test_tools/possible_actions.txt new file mode 100644 index 0000000..4e0345b --- /dev/null +++ b/chrome/tools/automated_ui_test_tools/possible_actions.txt @@ -0,0 +1,17 @@ +Navigate +NewTab +Back +Forward +CloseTab +OpenWindow +Reload +FindInPage +SelectNextTab +SelectPrevTab +ZoomPlus +ZoomMinus +Sessions +Bookmarks +History +Downloads +Applications
\ No newline at end of file diff --git a/chrome/tools/build/win/FILES b/chrome/tools/build/win/FILES new file mode 100644 index 0000000..fa6dc0b --- /dev/null +++ b/chrome/tools/build/win/FILES @@ -0,0 +1,51 @@ +chrome.exe +chrome.dll +crash_service.exe +First Run +icudt38.dll +themes/default.dll +resources +rlz.dll +locales/ar.dll +locales/bg.dll +locales/ca.dll +locales/cs.dll +locales/da.dll +locales/de.dll +locales/el.dll +locales/en-GB.dll +locales/en-US.dll +locales/es.dll +locales/es-419.dll +locales/et.dll +locales/fi.dll +locales/fil.dll +locales/fr.dll +locales/he.dll +locales/hi.dll +locales/hr.dll +locales/hu.dll +locales/id.dll +locales/it.dll +locales/ja.dll +locales/ko.dll +locales/lt.dll +locales/lv.dll +locales/nl.dll +locales/nb.dll +locales/pl.dll +locales/pt-BR.dll +locales/pt-PT.dll +locales/ro.dll +locales/ru.dll +locales/sk.dll +locales/sl.dll +locales/sr.dll +locales/sv.dll +locales/th.dll +locales/tr.dll +locales/uk.dll +locales/vi.dll +locales/zh-CN.dll +locales/zh-TW.dll +plugins/gears/gears.dll diff --git a/chrome/tools/build/win/create_installer_archive.py b/chrome/tools/build/win/create_installer_archive.py new file mode 100644 index 0000000..5f32472 --- /dev/null +++ b/chrome/tools/build/win/create_installer_archive.py @@ -0,0 +1,273 @@ +#!/usr/bin/python +# Copyright 2008, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +"""Script to create Chrome Installer archive. + + This script is used to create an archive of all the files required for a + Chrome install in appropriate directory structure. It reads chrome.release + file as input, creates chrome.7z archive, compresses setup.exe and + generates packed_files.txt for mini_installer project. + +""" + +import ConfigParser +import glob +import md5 +import optparse +import os +import shutil +import sys + + +ARCHIVE_DIR = "installer_archive" +FULL_ARCHIVE_FILE = "chrome.7z" # uncompresed full archive file +C_FULL_ARCHIVE_FILE = "chrome.packed.7z" # compressed full archive file +PATCH_FILE_NAME = "patch" # patch archive file name +PATCH_FILE_EXT = ".packed.7z" # extension of patch archive file +CHROME_DIR = "Chrome-bin" +MINI_INSTALLER_INPUT_FILE = "packed_files.txt" +SETUP_EXEC = "setup.exe" +BSDIFF_EXEC = "bsdiff.exe" +VERSION_FILE = "VERSION" +PACKED_FILE_COMMENTS = """ +// This file is automatically generated by create_installer_archive.py. +// It contains the resource entries that are going to be linked inside +// mini_installer.exe. For each file to be linked there should be two +// lines: +// - The first line contains the output filename (without path) and the +// type of the resource ('BN' means the file is not compressed and +// 'BL' means the file is compressed. +// - The second line contains the path to the input file. Uses '/' to +// separate path components. +""" + +def BuildVersion(output_dir): + """Returns the full build version string constructed from information in + VERSION_FILE. Any segment not found in that file will default to '0'. + """ + major = 0 + minor = 0 + build = 0 + patch = 0 + # TODO(rahulk): find a better way to locate VERSION file + for line in open(os.path.join(output_dir, "..", VERSION_FILE), 'r'): + line = line.rstrip() + if line.startswith('MAJOR='): + major = line[6:] + elif line.startswith('MINOR='): + minor = line[6:] + elif line.startswith('BUILD='): + build = line[6:] + elif line.startswith('PATCH='): + patch = line[6:] + return '%s.%s.%s.%s' % (major, minor, build, patch) + + +def Readconfig(output_dir, input_file, current_version): + """Reads config information from input file after setting default value of + global variabes. + """ + variables = {} + variables['ChromeDir'] = CHROME_DIR + variables['VersionDir'] = os.path.join(variables['ChromeDir'], + current_version) + config = ConfigParser.SafeConfigParser(variables) + config.read(input_file) + return config + + +def MakeStagingDirectory(output_dir): + """Creates a staging path for installer archive. If directory exists already, + deletes the existing directory. + """ + file_path = os.path.join(output_dir, ARCHIVE_DIR) + if os.path.exists(file_path): + shutil.rmtree(file_path) + os.makedirs(file_path) + return file_path + + +def CopyFilesToStagingDir(config, staging_dir, output_dir): + """Copies files required for installer archive to staging dir. + """ + for option in config.options('FILES'): + if option.endswith('dir'): + continue + + dst = os.path.join(staging_dir, config.get('FILES', option)) + if not os.path.exists(dst): + os.makedirs(dst) + for file in glob.glob(os.path.join(output_dir, option)): + shutil.copy(file, dst) + + +def RunSystemCommand(cmd): + if (os.system(cmd) != 0): + raise "Error while running cmd: %s" % cmd + + +def CreateArchiveFile(output_dir, staging_dir, current_version, + prev_version_dir, prev_version, rebuild_archive): + """Creates a new installer archive file after deleting any existing old file. + """ + # First create an uncompressed archive file for the current build + # TODO(rahulk): find a better way to locate 7za.exe + lzma_exec = os.path.join(output_dir, "..", "..", "third_party", + "lzma_sdk", "Executable", "7za.exe") + archive_file = os.path.join(output_dir, FULL_ARCHIVE_FILE) + cmd = '%s a -t7z "%s" "%s" -mx0' % (lzma_exec, archive_file, + os.path.join(staging_dir, CHROME_DIR)) + # There doesnt seem to be any way in 7za.exe to override existing file so + # we always delete before creating a new one. + if not os.path.exists(archive_file): + RunSystemCommand(cmd) + elif rebuild_archive: + os.remove(archive_file) + RunSystemCommand(cmd) + + # If we are generating a patch, run bsdiff against previous build and + # compress the resulting patch file. If this is not a patch just compress the + # uncompressed archive file. + if (prev_version_dir): + prev_archive_file = os.path.join(prev_version_dir, FULL_ARCHIVE_FILE) + patch_file = os.path.join(output_dir, "patch.7z") + cmd = '%s "%s" "%s" "%s"' % (os.path.join(output_dir, BSDIFF_EXEC), + prev_archive_file, archive_file, patch_file) + RunSystemCommand(cmd) + + archive_file_name = PATCH_FILE_NAME + "-" + if prev_version: + archive_file_name += prev_version + "-" + archive_file_name += current_version + PATCH_FILE_EXT + orig_file = patch_file + else: + archive_file_name = C_FULL_ARCHIVE_FILE + orig_file = archive_file + + compressed_archive_file_path = os.path.join(output_dir, archive_file_name) + cmd = '%s a -t7z "%s" "%s" -mx9' % (lzma_exec, compressed_archive_file_path, + orig_file) + if os.path.exists(compressed_archive_file_path): + os.remove(compressed_archive_file_path) + RunSystemCommand(cmd) + + return archive_file_name + + +def CompressSetupExec(output_dir): + """Compresses setup.exe to reduce size.""" + cmd = 'makecab.exe /V1 /L "%s" "%s"' % (output_dir, + os.path.join(output_dir, SETUP_EXEC)) + RunSystemCommand(cmd) + + +def GetFileMD5Hash(file): + f = open(file, 'rb') + hash = md5.new(f.read()).hexdigest() + f.close() + return hash + + +def CreateResourceInputFile(output_dir, + prev_version_dir, archive_file_name): + """Creates resource input file (packed_files.txt) for mini_installer project. + + This method checks if we are generating a patch instead of full installer. In + case of patch it also checks if setup.exe has changed by comparing its + MD5 hash with the MD5 hash of previous setup.exe. If hash values are same + setup.exe is not included in packed_files.txt. + + In case of patch we include patch.7z and in case of full + installer we include chrome.7z in packed_files.txt. + """ + setup_exe_needed = 1 + if (prev_version_dir): + current_hash = GetFileMD5Hash(os.path.join(output_dir, SETUP_EXEC)) + prev_hash = GetFileMD5Hash(os.path.join(prev_version_dir, SETUP_EXEC)) + if (current_hash == prev_hash): + setup_exe_needed = 0 + + if (setup_exe_needed): + CompressSetupExec(output_dir) + c_setup_file = SETUP_EXEC[:len(SETUP_EXEC) - 1] + "_" + setup_file_entry = "%s\t\tBL\n\"%s\"" % (c_setup_file, + os.path.join(output_dir, c_setup_file).replace("\\","/")) + + archive_file_entry = "\n%s\t\tB7\n\"%s\"" % (archive_file_name, + os.path.join(output_dir, archive_file_name).replace("\\","/")) + output_file = os.path.join(output_dir, MINI_INSTALLER_INPUT_FILE) + f = open(output_file, 'w') + try: + f.write(PACKED_FILE_COMMENTS) + if (setup_exe_needed): + f.write(setup_file_entry) + f.write(archive_file_entry) + finally: + f.close() + + +def main(options): + """Main method that reads input file, creates archive file and write + resource input file. + """ + current_version = BuildVersion(options.output_dir) + + config = Readconfig(options.output_dir, options.input_file, current_version) + + staging_dir = MakeStagingDirectory(options.output_dir) + + CopyFilesToStagingDir(config, staging_dir, options.output_dir) + + # Name of the archive file built (for example - chrome.lz or + # patch-<old_version>-<new_version>.lz or patch-<new_version>.lz + archive_file_name = CreateArchiveFile(options.output_dir, staging_dir, + current_version, options.last_chrome_installer, + options.last_chrome_version, options.rebuild_archive) + + CreateResourceInputFile(options.output_dir, options.last_chrome_installer, + archive_file_name) + + +if '__main__' == __name__: + option_parser = optparse.OptionParser() + option_parser.add_option('-o', '--output_dir', help='Output directory') + option_parser.add_option('-i', '--input_file', help='Input file') + option_parser.add_option('-r', '--rebuild_archive', action='store_true', + default=False, help='Rebuild Chrome.7z archive, even if it exists.') + option_parser.add_option('-l', '--last_chrome_installer', + help='Generate differential installer. The value of this parameter ' + + 'specifies the directory that contains base versions of ' + + 'setup.exe & chrome.7z.') + option_parser.add_option('-v', '--last_chrome_version', + help='Version of the previous installer. ' + + 'Used only for the purpose of naming archive file. Optional.') + + options, args = option_parser.parse_args() + sys.exit(main(options)) diff --git a/chrome/tools/build/win/data_dll.vsprops b/chrome/tools/build/win/data_dll.vsprops new file mode 100644 index 0000000..9e6f840 --- /dev/null +++ b/chrome/tools/build/win/data_dll.vsprops @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioPropertySheet + ProjectType="Visual C++" + Version="8.00" + Name="data_dll" + > + <Tool + Name="VCCLCompilerTool" + PreprocessorDefinitions="_USRDLL;GENERATED_RESOURCES_DLL_EXPORTS" + /> + <Tool + Name="VCLinkerTool" + IgnoreImportLibrary="true" + LinkIncremental="1" + LinkTimeCodeGeneration="0" + ResourceOnlyDLL="true" + /> +</VisualStudioPropertySheet> diff --git a/chrome/tools/build/win/debugger_disabled.vsprops b/chrome/tools/build/win/debugger_disabled.vsprops new file mode 100644 index 0000000..b068039 --- /dev/null +++ b/chrome/tools/build/win/debugger_disabled.vsprops @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioPropertySheet + ProjectType="Visual C++" + Version="8.00" + Name="debugger" + > + <Tool + Name="VCCLCompilerTool" + PreprocessorDefinitions="CHROME_DEBUGGER_DISABLED" + /> + <Tool + Name="VCResourceCompilerTool" + PreprocessorDefinitions="CHROME_DEBUGGER_DISABLED" + /> +</VisualStudioPropertySheet> diff --git a/chrome/tools/build/win/dependencies.py b/chrome/tools/build/win/dependencies.py new file mode 100644 index 0000000..0ae3b8e --- /dev/null +++ b/chrome/tools/build/win/dependencies.py @@ -0,0 +1,197 @@ +#!/usr/bin/python +# Copyright 2008, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +"""Script to verify a Portable Executable's dependencies. + +Analyzes the input portable executable (a DLL or an EXE for example), extracts +its imports and confirms that its dependencies haven't changed. This is for +regression testing. + +Returns 0 if the list matches. + 1 if one or more dependencies has been removed. + 2 if one or more dependencies has been added. This preempts removal + result code. +""" + +import optparse +import os +import subprocess +import sys + +DUMPBIN = "dumpbin.exe" + + +class Error(Exception): + def __init__(self, message): + self.message = message + + +def RunSystemCommand(cmd): + return subprocess.Popen(cmd, stdout=subprocess.PIPE).communicate()[0] + + +def RunDumpbin(binary_file): + """Runs dumpbin and parses its output. + + Args: binary_file: the binary to analyze + Returns: a tuple of the dependencies and the delay-load dependencies + + The output of dumpbin that we will be parsing looks like this: + -- + <blah blah> + + Image has the following dependencies: + + foo.dll + bar.dll + + Image has the following delay load dependencies: + + foobar.dll + other.dll + + Summary + + <blah blah> + -- + The following parser extracts the dll names from the above format. + """ + cmd = DUMPBIN + " /dependents " + binary_file + output = RunSystemCommand(cmd) + dependents = [] + delay_loaded = [] + (START, DEPENDENCIES_HEADER, DEPENDENCIES, DELAY_LOAD_HEADER, DELAY_LOAD, + SUMMARY_HEADER, SUMMARY) = (0, 1, 2, 3, 4, 5, 6) + current_section = START + # Very basic scanning. + for line in output.splitlines(): + line = line.strip() + if len(line) > 1: + if line == "Image has the following dependencies:": + if current_section != START: + raise Error("Internal parsing error.") + current_section = DEPENDENCIES_HEADER; + elif line == "Image has the following delay load dependencies:": + if current_section != DEPENDENCIES: + raise Error("Internal parsing error.") + current_section = DELAY_LOAD_HEADER; + elif line == "Summary": + current_section = SUMMARY_HEADER; + elif current_section == DEPENDENCIES: + # Got a dependent + dependents.append(line) + elif current_section == DELAY_LOAD: + # Got a delay-loaded + delay_loaded.append(line) + else: + if current_section == DEPENDENCIES_HEADER: + current_section = DEPENDENCIES + elif current_section == DELAY_LOAD_HEADER: + current_section = DELAY_LOAD + elif current_section == SUMMARY_HEADER: + current_section = SUMMARY + return dependents, delay_loaded + + +def Diff(name, type, current, expected, deps_file): + """ + Args: name: Portable executable name being analysed. + type: Type of dependency. + current: List of current dependencies. + expected: List of dependencies that are expected. + deps_file: File name of the .deps file. + Returns 0 if the lists are equal + 1 if one entry in list1 is missing + 2 if one entry in list2 is missing. + """ + # Create sets of lower-case names. + set_expected = set([x.lower() for x in expected]) + set_current = set([x.lower() for x in current]) + only_in_expected = set_expected - set_current + only_in_current = set_current - set_expected + + # Find difference between the sets. + found_extra = 0 + name = os.path.basename(name).lower() + if len(only_in_expected) or len(only_in_current): + print name.upper() + " DEPENDENCIES MISMATCH\n" + + if len(only_in_current): + found_extra = 1 + print "%s is no longer dependent on these %s: %s." % (name, + type, + ' '.join(only_in_current)) + print "Please update \"%s\"." % deps_file + + if len(only_in_expected): + found_extra = 2 + string = "%s is now dependent on these %s, but shouldn't: %s." % (name, + type, + ' '.join(only_in_expected)) + stars = '*' * len(string) + print "**" + stars + "**" + print "* " + string + " *" + print "**" + stars + "**\n" + print "Please update \"%s\"." % deps_file + return found_extra + + +def VerifyDependents(pe_name, dependents, delay_loaded, list_file): + """Compare the actual dependents to the expected ones.""" + scope = {} + execfile(list_file, scope) + deps_result = Diff(pe_name, + "dll", + dependents, + scope["dependents"], + list_file) + delayed_result = Diff(pe_name, + "delay loaded dll", + delay_loaded, + scope["delay_loaded"], + list_file) + return max(deps_result, delayed_result) + + +def main(options, args): + # PE means portable executable. It's any .DLL, .EXE, .SYS, .AX, etc. + pe_name = args[0] + deps_file = args[1] + dependents, delay_loaded = RunDumpbin(pe_name) + return VerifyDependents(pe_name, dependents, delay_loaded, deps_file) + + +if '__main__' == __name__: + usage = "usage: %prog [options] input output" + option_parser = optparse.OptionParser(usage = usage) + options, args = option_parser.parse_args() + if len(args) != 2: + parser.error("Incorrect number of arguments") + sys.exit(main(options, args)) diff --git a/chrome/tools/build/win/flattened_html_file.bat b/chrome/tools/build/win/flattened_html_file.bat new file mode 100644 index 0000000..310d88d --- /dev/null +++ b/chrome/tools/build/win/flattened_html_file.bat @@ -0,0 +1,14 @@ +:: Batch file run as build command for flattening files +:: The custom build rule is set to expect (inputfile).html +@echo off + +setlocal + +set InFile=%~1 +set SolutionDir=%~2 +set OutFile=%~3 + +:: Use GNU tools +call %SolutionDir%\..\third_party\gnu\setup_env.bat + +%SolutionDir%\..\third_party\python_24\python.exe %SolutionDir%\tools\build\win\html_inline.py %InFile% %OutFile% diff --git a/chrome/tools/build/win/flattened_html_file.rules b/chrome/tools/build/win/flattened_html_file.rules new file mode 100644 index 0000000..8cc33ba --- /dev/null +++ b/chrome/tools/build/win/flattened_html_file.rules @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<VisualStudioToolFile + Name="Flattened HTML Resource" + Version="8.00" + > + <Rules> + <CustomBuildRule + Name="Flattened HTML Resource" + DisplayName="Flattened HTML Resource" + CommandLine="$(SolutionDir)\tools\build\win\flattened_html_file.bat $(InputPath) "$(SolutionDir)" "$(IntDir)\$(InputName)_flat.html"" + Outputs="$(IntDir)\$(InputName)_flat.html" + AdditionalDependencies="$(SolutionDir)\tools\build\win\flattened_html_file.bat" + FileExtensions="*.html" + ExecutionDescription="Generating resources..." + > + <Properties> + </Properties> + </CustomBuildRule> + </Rules> +</VisualStudioToolFile> diff --git a/chrome/tools/build/win/font_file_copy.rules b/chrome/tools/build/win/font_file_copy.rules new file mode 100644 index 0000000..912a79b --- /dev/null +++ b/chrome/tools/build/win/font_file_copy.rules @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<VisualStudioToolFile + Name="Font file copy" + Version="8.00" + > + <Rules> + <CustomBuildRule + Name="Font file copy" + DisplayName="Font file copy" + CommandLine="xcopy /Y /D /Q $(InputPath) $(OutDir)\fonts" + Outputs="$(OutDir)\fonts\$(InputFileName)" + FileExtensions="*.ttf;*.afm" + ExecutionDescription="Copying font file..." + > + <Properties> + </Properties> + </CustomBuildRule> + </Rules> +</VisualStudioToolFile> diff --git a/chrome/tools/build/win/hardlink_failsafe.bat b/chrome/tools/build/win/hardlink_failsafe.bat new file mode 100644 index 0000000..4113922 --- /dev/null +++ b/chrome/tools/build/win/hardlink_failsafe.bat @@ -0,0 +1,12 @@ +@echo off +:: %1 is source +:: %2 is output + +:: Delete it if it existed +if exist "%2" del "%2" + +:: Try to create a hardlink (argument are in reverse order). Hide errors if they occur, we have a fallback. +fsutil hardlink create "%2" "%1" > nul + +:: If it failed, copy it instead. Don't hide errors if it fails. +if errorlevel 1 copy "%1" "%2" diff --git a/chrome/tools/build/win/html_inline.py b/chrome/tools/build/win/html_inline.py new file mode 100644 index 0000000..17d24ca --- /dev/null +++ b/chrome/tools/build/win/html_inline.py @@ -0,0 +1,128 @@ +#!/usr/bin/python +# Copyright 2008, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +"""Flattens a HTML file by inlining its external resources. + +This is a small script that takes a HTML file, looks for src attributes +and inlines the specified file, producing one HTML file with no external +dependencies. + +This does not inline CSS styles, nor does it inline anything referenced +from an inlined file. +""" + +import re +import sys +import base64 +import mimetypes +from os import path + +def ReadFile(input_filename): + """Helper function that returns input_filename as a string. + + Args: + input_filename: name of file to be read + + Returns: + string + """ + f = open(input_filename, 'rb') + file_contents = f.read() + f.close() + return file_contents + +def SrcInline(src_match, base_path): + """regex replace function. + + Takes a regex match for src="filename", attempts to read the file + at 'filename' and returns the src attribute with the file inlined + as a data URI + + Args: + src_match: regex match object with 'filename' named capturing group + base_path: path that to look for files in + + Returns: + string + """ + filename = src_match.group('filename') + + if filename.find(':') != -1: + # filename is probably a URL, which we don't want to bother inlining + return src_match.group(0) + + filepath = path.join(base_path, filename) + mimetype = mimetypes.guess_type(filename)[0] or 'text/plain' + inline_data = base64.standard_b64encode(ReadFile(filepath)) + + prefix = src_match.string[src_match.start():src_match.start('filename')-1] + return "%s\"data:%s;base64,%s\"" % (prefix, mimetype, inline_data) + +def InlineFile(input_filename, output_filename): + """Inlines the resources in a specified file. + + Reads input_filename, finds all the src attributes and attempts to + inline the files they are referring to, then writes the result + to output_filename. + + Args: + input_filename: name of file to read in + output_filename: name of file to be written to + """ + print "inlining %s to %s" % (input_filename, output_filename) + input_filepath = path.dirname(input_filename) + + def SrcReplace(src_match): + """Helper function to provide SrcInline with the base file path""" + return SrcInline(src_match, input_filepath) + + # TODO(glen): Make this regex not match src="" text that is not inside a tag + flat_text = re.sub('src="(?P<filename>[^"\']*)"', + SrcReplace, + ReadFile(input_filename)) + + # TODO(glen): Make this regex not match url('') that is not inside a style + flat_text = re.sub('background:[ ]*url\(\'(?P<filename>[^"\']*)\'', + SrcReplace, + flat_text) + + out_file = open(output_filename, 'wb') + out_file.writelines(flat_text) + out_file.close() + +def main(): + if len(sys.argv) <= 2: + print "Flattens a HTML file by inlining its external resources.\n" + print "html_inline.py inputfile outputfile" + else: + InlineFile(sys.argv[1], sys.argv[2]) + +if __name__ == '__main__': + main() diff --git a/chrome/tools/build/win/inspector_copy.rules b/chrome/tools/build/win/inspector_copy.rules new file mode 100644 index 0000000..475b5b1 --- /dev/null +++ b/chrome/tools/build/win/inspector_copy.rules @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<VisualStudioToolFile + Name="Inspector file copy" + Version="8.00" + > + <Rules> + <CustomBuildRule + Name="Inspector file copy" + CommandLine="xcopy /Y /D /Q $(InputPath) $(OutDir)\Resources\Inspector\" + Outputs="$(OutDir)\Resources\Inspector\$(InputFileName)" + FileExtensions="*.html;*.js;*.gif;*.css" + ExecutionDescription="Copying resource file..." + > + <Properties> + </Properties> + </CustomBuildRule> + <CustomBuildRule + Name="Inspector image file copy" + CommandLine="xcopy /Y /D /Q $(InputPath) $(OutDir)\Resources\Inspector\Images\" + Outputs="$(OutDir)\Resources\Inspector\Images\$(InputFileName)" + FileExtensions="*.png" + ExecutionDescription="Copying resource file..." + > + <Properties> + </Properties> + </CustomBuildRule> + </Rules> +</VisualStudioToolFile> diff --git a/chrome/tools/build/win/js_engine.vsprops b/chrome/tools/build/win/js_engine.vsprops new file mode 100644 index 0000000..c329d6e --- /dev/null +++ b/chrome/tools/build/win/js_engine.vsprops @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioPropertySheet + ProjectType="Visual C++" + Version="8.00" + Name="js_engine" + InheritedPropertySheets="js_engine_impl$(JS_ENGINE_TYPE).vsprops" +> +</VisualStudioPropertySheet> diff --git a/chrome/tools/build/win/js_engine_impl.vsprops b/chrome/tools/build/win/js_engine_impl.vsprops new file mode 100644 index 0000000..a4f1996 --- /dev/null +++ b/chrome/tools/build/win/js_engine_impl.vsprops @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioPropertySheet + ProjectType="Visual C++" + Version="8.00" + Name="js_engine_impl" + > + <Tool + Name="VCCLCompilerTool" + PreprocessorDefinitions="CHROME_V8" + /> +</VisualStudioPropertySheet> diff --git a/chrome/tools/build/win/js_engine_impl_kjs.vsprops b/chrome/tools/build/win/js_engine_impl_kjs.vsprops new file mode 100644 index 0000000..20b24da --- /dev/null +++ b/chrome/tools/build/win/js_engine_impl_kjs.vsprops @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioPropertySheet + ProjectType="Visual C++" + Version="8.00" + Name="js_engine_impl_kjs" + > + <Tool + Name="VCCLCompilerTool" + PreprocessorDefinitions="CHROME_KJS" + /> +</VisualStudioPropertySheet> diff --git a/chrome/tools/build/win/language_dll.vsprops b/chrome/tools/build/win/language_dll.vsprops new file mode 100644 index 0000000..6da7f8e --- /dev/null +++ b/chrome/tools/build/win/language_dll.vsprops @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioPropertySheet + ProjectType="Visual C++" + Version="8.00" + Name="language_dll" + > + <Tool + Name="VCLinkerTool" + BaseAddress="0x3CF00000" + OutputFile="$(OutDir)\locales\$(ProjectName).dll" + /> +</VisualStudioPropertySheet> diff --git a/chrome/tools/build/win/make_zip.sh b/chrome/tools/build/win/make_zip.sh new file mode 100755 index 0000000..2fe8af12 --- /dev/null +++ b/chrome/tools/build/win/make_zip.sh @@ -0,0 +1,37 @@ +#!/bin/sh + +# A simple shell script for creating a chrome zip from an output directory. +# Pass the path to the output directory you wish to package. + +if [ $# = 0 ]; then + echo "usage: make_zip.sh path/to/release/dir [output-name]" + exit 1 +fi + +tools_dir=$(dirname "$0") +release_dir="$1" + +files=$(cat "$tools_dir/FILES") + +output=${2:-chrome-win32} +rm -fr $output $output.zip +mkdir $output + +# Get the absolute path of the output directory. We need it when copying +# files. +output_abs=`cygpath -a $output` + +# Use cp --parents to copy full relative directory. Since we need the +# relative directory for the zip, change into the release dir. +pushd "$release_dir" +# The file names in FILES may contain whitespace, e.g. 'First Run'. +# Change IFS setting so we only split words with '\n' +IFS_Default=$IFS +IFS=$'\n' +for f in ${files[@]}; do + cp -r --parents "$f" "$output_abs" +done +IFS=$IFS_Default +popd + +zip -r $output.zip $output diff --git a/chrome/tools/build/win/map_drive.bat b/chrome/tools/build/win/map_drive.bat new file mode 100644 index 0000000..c415d1e --- /dev/null +++ b/chrome/tools/build/win/map_drive.bat @@ -0,0 +1,30 @@ +@echo off + +set SHARE_NAME=\\chrome-dev\chrome +set DRIVE_LETTER=%1 +set PATH=%SystemRoot%;%SystemRoot%\system32 + +net use %DRIVE_LETTER% +if errorlevel 1 goto DRIVE_NOT_MAPPED + +net use %DRIVE_LETTER% | find "%SHARE_NAME%" > nul +if not errorlevel 1 goto DRIVE_ALREADY_MAPPED + +:WRONG_DRIVE_MAPPED +echo %DRIVE_LETTER% Drive mapped to wrong share, disconnecting.. +net use %DRIVE_LETTER% /DELETE +goto MAPDRIVE + +:DRIVE_ALREADY_MAPPED +echo %DRIVE_LETTER% Drive already mapped.. +goto END + +:DRIVE_NOT_MAPPED +echo %DRIVE_LETTER% Drive not mapped.. +goto MAPDRIVE + +:MAPDRIVE +echo Mapping %DRIVE_LETTER% to %SHARE_NAME% +net use %DRIVE_LETTER% %SHARE_NAME% + +:END
\ No newline at end of file diff --git a/chrome/tools/build/win/precompiled.cc b/chrome/tools/build/win/precompiled.cc new file mode 100644 index 0000000..b09ed13 --- /dev/null +++ b/chrome/tools/build/win/precompiled.cc @@ -0,0 +1,2 @@ +// This file exists purely as a means to generate precompiled.pch +#include "precompiled.h" diff --git a/chrome/tools/build/win/precompiled.h b/chrome/tools/build/win/precompiled.h new file mode 100644 index 0000000..50ff863 --- /dev/null +++ b/chrome/tools/build/win/precompiled.h @@ -0,0 +1,66 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// +// This header file is pre-compiled and added to the cl.exe command line for +// each source file. You should not include it explicitly. Instead, you +// should ensure that your source files specify all of their include files +// explicitly so they may be used without this pre-compiled header. +// +// To make use of this header file in a vcproj, just add precompiled.cc as a +// build target, and modify its build properties to enable /Yc for that file +// only. Then add the precompiled.vsprops property sheet to your vcproj. +// + +// windows headers +// TODO: when used, winsock2.h must be included before windows.h or +// the build blows up. The best fix is to define WIN32_LEAN_AND_MEAN. +// The best workaround is to define _WINSOCKAPI_. That's what winsock2.h does. +#include <winsock2.h> +#include <windows.h> +#include <shellapi.h> +#include <shlobj.h> +#include <tchar.h> + +// runtime headers +#include <cassert> +#include <climits> +#include <cstddef> +#include <cstdlib> +#include <cstring> +#include <memory.h> + +// Usual STL +#include <algorithm> +#include <list> +#include <map> +#include <string> +#include <strstream> +#include <vector> + diff --git a/chrome/tools/build/win/precompiled.vsprops b/chrome/tools/build/win/precompiled.vsprops new file mode 100644 index 0000000..464ea32 --- /dev/null +++ b/chrome/tools/build/win/precompiled.vsprops @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioPropertySheet + ProjectType="Visual C++" + Version="8.00" + Name="precompiled" + > + <Tool + Name="VCCLCompilerTool" + AdditionalIncludeDirectories=""$(SolutionDir)tools\build\win"" + UsePrecompiledHeader="2" + PrecompiledHeaderThrough="precompiled.h" + PrecompiledHeaderFile="$(IntDir)\precompiled.pch" + ForcedIncludeFiles="precompiled.h" + /> +</VisualStudioPropertySheet> diff --git a/chrome/tools/build/win/precompiled_wtl.cc b/chrome/tools/build/win/precompiled_wtl.cc new file mode 100644 index 0000000..0f6272d --- /dev/null +++ b/chrome/tools/build/win/precompiled_wtl.cc @@ -0,0 +1,2 @@ +// This file exists purely as a means to generate precompiled.pch +#include "precompiled_wtl.h" diff --git a/chrome/tools/build/win/precompiled_wtl.h b/chrome/tools/build/win/precompiled_wtl.h new file mode 100644 index 0000000..a44408a --- /dev/null +++ b/chrome/tools/build/win/precompiled_wtl.h @@ -0,0 +1,44 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// See precompiled.h for info on using this file, substituting references to +// "precompiled" with "precompiled_wtl" + +#include "chrome/tools/build/win/precompiled.h" + +// ATL/WTL headers +#include <atlbase.h> +#include <atlapp.h> +#include <atlwin.h> +#include <atldlgs.h> +#include <atlframe.h> +#include <atlmisc.h> +#include <atlctrls.h> +#include <atlcrack.h> +#include <atltheme.h> diff --git a/chrome/tools/build/win/precompiled_wtl.vsprops b/chrome/tools/build/win/precompiled_wtl.vsprops new file mode 100644 index 0000000..1e9a915 --- /dev/null +++ b/chrome/tools/build/win/precompiled_wtl.vsprops @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioPropertySheet + ProjectType="Visual C++" + Version="8.00" + Name="precompiled_wtl" + > + <Tool + Name="VCCLCompilerTool" + AdditionalIncludeDirectories=""$(SolutionDir)tools\build\win"" + UsePrecompiledHeader="2" + PrecompiledHeaderThrough="precompiled_wtl.h" + PrecompiledHeaderFile="$(IntDir)\precompiled_wtl.pch" + ForcedIncludeFiles="precompiled_wtl.h" + /> +</VisualStudioPropertySheet> diff --git a/chrome/tools/build/win/release.rules b/chrome/tools/build/win/release.rules new file mode 100644 index 0000000..2b2f683 --- /dev/null +++ b/chrome/tools/build/win/release.rules @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="utf-8"?> +<VisualStudioToolFile + Name="installer archive" + Version="8.00" + > + <Rules> + <CustomBuildRule + Name="create installer archive" + DisplayName="create installer archive" + CommandLine="$(SolutionDir)..\third_party\python_24\python.exe $(SolutionDir)tools\build\win\create_installer_archive.py --output_dir="$(OutDir)" --input_file="$(InputPath)" [LastChromeInstaller] [LastChromeVersion] [RebuildArchive]" + Outputs="$(OutDir)/$(InputName).7z;$(OutDir)/packed_files.txt;" + AdditionalDependencies="$(SolutionDir)\tools\build\win\create_installer_archive.py;$(OutDir)\chrome.exe;$(OutDir)\crash_reporter.exe;$(OutDir)\chrome.dll;$(OutDir)\locales\en-US.dll;$(OutDir)\icudt38.dll" + FileExtensions="*.release" + ExecutionDescription="create installer archive" + > + <Properties> + <StringProperty + Name="LastChromeInstaller" + DisplayName="Last Chrome Installer Directory" + Description="Directory where old version of installer is present (setup.exe and chrome.7z)" + Switch="--last_chrome_installer="[value]"" + /> + <StringProperty + Name="LastChromeVersion" + DisplayName="Last Chrome Version" + Description="Last released version of Chrome (used to name the patch file)" + Switch="--last_chrome_version="[value]"" + /> + <BooleanProperty + Name="RebuildArchive" + DisplayName="Rebuild Archive" + Description="Rebuilds chrome.7z even if already exists" + Switch="--rebuild_archive" + /> + </Properties> + </CustomBuildRule> + </Rules> +</VisualStudioToolFile> diff --git a/chrome/tools/build/win/reliability_test.vsprops b/chrome/tools/build/win/reliability_test.vsprops new file mode 100644 index 0000000..e3a76bf --- /dev/null +++ b/chrome/tools/build/win/reliability_test.vsprops @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioPropertySheet + ProjectType="Visual C++" + Version="8.00" + Name="reliability_test" + > + <Tool + Name="VCCLCompilerTool" + PreprocessorDefinitions="RELIABILITY_TEST" + /> +</VisualStudioPropertySheet> diff --git a/chrome/tools/build/win/resource_text_file_copy.rules b/chrome/tools/build/win/resource_text_file_copy.rules new file mode 100644 index 0000000..eb468f8 --- /dev/null +++ b/chrome/tools/build/win/resource_text_file_copy.rules @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="utf-8"?> +<VisualStudioToolFile + Name="Resource text file copy" + Version="8.00" + > + <Rules> + <CustomBuildRule + Name="Resource text file copy" + CommandLine="xcopy /Y /D /Q $(InputPath) $(OutDir)\resources" + Outputs="$(OutDir)\Resources\$(InputFileName)" + FileExtensions="*.html;*.js;*.gif" + ExecutionDescription="Copying resource file..." + > + <Properties> + </Properties> + </CustomBuildRule> + </Rules> +</VisualStudioToolFile> diff --git a/chrome/tools/build/win/sort_sln.py b/chrome/tools/build/win/sort_sln.py new file mode 100644 index 0000000..f679c1b --- /dev/null +++ b/chrome/tools/build/win/sort_sln.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python +# Copyright 2008, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import sys + +if len(sys.argv) != 2: + print """Usage: sort_sln.py <SOLUTIONNAME>.sln +to sort the solution file to a normalized scheme. Do this before checking in +changes to a solution file to avoid having a lot of unnecessary diffs.""" + sys.exit(1) + +filename = sys.argv[1] +print "Sorting " + filename; + +try: + sln = open(filename, "r"); +except IOError: + print "Unable to open " + filename + " for reading." + sys.exit(1) + +output = "" +seclines = None +while 1: + line = sln.readline() + if not line: + break + + if seclines is not None: + # Process the end of a section, dump the sorted lines + if line.lstrip().startswith('End'): + output = output + ''.join(sorted(seclines)) + seclines = None + # Process within a section + else: + seclines.append(line) + continue + + # Process the start of a section + if (line.lstrip().startswith('GlobalSection') or + line.lstrip().startswith('ProjectSection')): + if seclines: raise Exception('Already in a section') + seclines = [] + + output = output + line + +sln.close() +try: + sln = open(filename, "w") + sln.write(output) +except IOError: + print "Unable to write to " + filename + sys.exit(1); +print "Done." diff --git a/chrome/tools/build/win/test_memory_usage.vsprops b/chrome/tools/build/win/test_memory_usage.vsprops new file mode 100644 index 0000000..93c34cc --- /dev/null +++ b/chrome/tools/build/win/test_memory_usage.vsprops @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioPropertySheet + ProjectType="Visual C++" + Version="8.00" + Name="test_memory_usage" + > + <Tool + Name="VCLinkerTool" + AdditionalDependencies="psapi.lib" + /> +</VisualStudioPropertySheet> diff --git a/chrome/tools/build/win/test_shell_tests.vsprops b/chrome/tools/build/win/test_shell_tests.vsprops new file mode 100644 index 0000000..d008862 --- /dev/null +++ b/chrome/tools/build/win/test_shell_tests.vsprops @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioPropertySheet + ProjectType="Visual C++" + Version="8.00" + Name="test_shell_tests" + > + <Tool + Name="VCCLCompilerTool" + AdditionalIncludeDirectories="..\..\..\v8\public;" + PreprocessorDefinitions="_CRT_SECURE_NO_DEPRECATE;_SCL_SECURE_NO_DEPRECATE" + /> + <Tool + Name="VCLinkerTool" + AdditionalDependencies="winmm.lib" + /> +</VisualStudioPropertySheet> diff --git a/chrome/tools/build/win/ui_test.vsprops b/chrome/tools/build/win/ui_test.vsprops new file mode 100644 index 0000000..4a41137 --- /dev/null +++ b/chrome/tools/build/win/ui_test.vsprops @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioPropertySheet + ProjectType="Visual C++" + Version="8.00" + Name="ui_test" + > + <Tool + Name="VCCLCompilerTool" + PreprocessorDefinitions="UI_TEST" + /> +</VisualStudioPropertySheet> diff --git a/chrome/tools/build/win/unit_test.vsprops b/chrome/tools/build/win/unit_test.vsprops new file mode 100644 index 0000000..893ef30 --- /dev/null +++ b/chrome/tools/build/win/unit_test.vsprops @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioPropertySheet + ProjectType="Visual C++" + Version="8.00" + Name="unit_test" + InheritedPropertySheets="$(SolutionDir)third_party\wtl\using_wtl.vsprops" + > + <Tool + Name="VCCLCompilerTool" + AdditionalIncludeDirectories="$(SolutionDir).." + PreprocessorDefinitions="UNIT_TEST" + /> + <Tool + Name="VCLinkerTool" + AdditionalDependencies="rpcrt4.lib oleacc.lib comsupp.lib" + /> +</VisualStudioPropertySheet> diff --git a/chrome/tools/build/win/using_generated_strings.vsprops b/chrome/tools/build/win/using_generated_strings.vsprops new file mode 100644 index 0000000..da11cb0 --- /dev/null +++ b/chrome/tools/build/win/using_generated_strings.vsprops @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioPropertySheet + ProjectType="Visual C++" + Version="8.00" + Name="using_generated_strings" + > + <Tool + Name="VCCLCompilerTool" + AdditionalIncludeDirectories=""$(OutDir)\obj\generated_resources";"$(OutDir)\obj\localized_strings"" + /> +</VisualStudioPropertySheet> diff --git a/chrome/tools/build/win/using_javascriptcore.vsprops b/chrome/tools/build/win/using_javascriptcore.vsprops new file mode 100644 index 0000000..139d6f7 --- /dev/null +++ b/chrome/tools/build/win/using_javascriptcore.vsprops @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioPropertySheet + ProjectType="Visual C++" + Version="8.00" + Name="using_javascriptcore" + > + <Tool + Name="VCCLCompilerTool" + AdditionalIncludeDirectories=""$(SolutionDir)\third_party\webkit\src\JavaScriptCore\bindings";"$(SolutionDir)\third_party\webkit\src\JavaScriptCore";"$(SolutionDir)\third_party\webkit\src\JavaScriptCore\os-win32";"$(SolutionDir)webkit\pending";"$(SolutionDir)webkit\pending\wtf"" + /> +</VisualStudioPropertySheet> diff --git a/chrome/tools/build/win/version.bat b/chrome/tools/build/win/version.bat new file mode 100644 index 0000000..0cda045 --- /dev/null +++ b/chrome/tools/build/win/version.bat @@ -0,0 +1,44 @@ +:: Batch file run as build command for vers.vcproj +@echo off + +setlocal + +set InFile=%~1 +set SolutionDir=%~2 +set IntDir=%~3 +set OutFile=%~4 +set VarsBat=%IntDir%/vers-vars.bat + +:: Use GNU tools +call %SolutionDir%\..\third_party\gnu\setup_env.bat + +:: Load version digits as environment variables +cat %SolutionDir%\VERSION | sed "s/\(.*\)/set \1/" > %VarsBat% + +:: Load branding strings as environment variables +cat %SolutionDir%\BRANDING | sed "s/\(.*\)/set \1/" >> %VarsBat% + +if not "%OFFICIAL_BUILD%" == "1" set OFFICIAL_BUILD=0 + +:: Determine the current repository revision number +set PATH=%~dp0..\..\..\..\third_party\svn;%PATH% +svn.exe info | grep.exe "Revision:" | cut -d" " -f2- | sed "s/\(.*\)/set LASTCHANGE=\1/" >> %VarsBat% +call %VarsBat% + +::echo LastChange: %LASTCHANGE% + +:: output file +cat %InFile% | sed "s/@MAJOR@/%MAJOR%/" ^ + | sed "s/@MINOR@/%MINOR%/" ^ + | sed "s/@BUILD@/%BUILD%/" ^ + | sed "s/@PATCH@/%PATCH%/" ^ + | sed "s/@COMPANY_FULLNAME@/%COMPANY_FULLNAME%/" ^ + | sed "s/@COMPANY_SHORTNAME@/%COMPANY_SHORTNAME%/" ^ + | sed "s/@PRODUCT_FULLNAME@/%PRODUCT_FULLNAME%/" ^ + | sed "s/@PRODUCT_SHORTNAME@/%PRODUCT_SHORTNAME%/" ^ + | sed "s/@PRODUCT_EXE@/%PRODUCT_EXE%/" ^ + | sed "s/@COPYRIGHT@/%COPYRIGHT%/" ^ + | sed "s/@OFFICIAL_BUILD@/%OFFICIAL_BUILD%/" ^ + | sed "s/@LASTCHANGE@/%LASTCHANGE%/" > %OutFile% + +endlocal diff --git a/chrome/tools/build/win/version.rules b/chrome/tools/build/win/version.rules new file mode 100644 index 0000000..244de21 --- /dev/null +++ b/chrome/tools/build/win/version.rules @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<VisualStudioToolFile + Name="Version Template" + Version="8.00" + > + <Rules> + <CustomBuildRule + Name="Version" + DisplayName="Version" + CommandLine="$(SolutionDir)/tools/build/win/version.bat [inputs] "$(SolutionDir)" "$(IntDir)" "$(IntDir)/$(InputName)"" + Outputs="$(IntDir)/$(InputName)" + AdditionalDependencies="$(SolutionDir)/VERSION;$(SolutionDir)/BRANDING;$(SolutionDir)/tools/build/win/version.bat" + FileExtensions="*.version" + ExecutionDescription="Generating version template file" + > + <Properties> + </Properties> + </CustomBuildRule> + </Rules> +</VisualStudioToolFile> diff --git a/chrome/tools/convert_dict/aff_reader.cc b/chrome/tools/convert_dict/aff_reader.cc new file mode 100644 index 0000000..2279e02 --- /dev/null +++ b/chrome/tools/convert_dict/aff_reader.cc @@ -0,0 +1,301 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/tools/convert_dict/aff_reader.h" + +#include <algorithm> + +#include "base/string_util.h" +#include "chrome/tools/convert_dict/hunspell_reader.h" + +namespace convert_dict { + +namespace { + +// Returns true if the given line begins with the given case-sensitive +// NULL-terminated ASCII string. +bool StringBeginsWith(const std::string& str, const char* with) { + size_t cur = 0; + while (cur < str.size() && with[cur] != 0) { + if (str[cur] != with[cur]) + return false; + cur++; + } + return with[cur] == 0; +} + +// Collapses runs of spaces to only one space. +void CollapseDuplicateSpaces(std::string* str) { + int prev_space = false; + for (size_t i = 0; i < str->length(); i++) { + if ((*str)[i] == ' ') { + if (prev_space) { + str->erase(str->begin() + i); + i--; + } + prev_space = true; + } else { + prev_space = false; + } + } +} + +} // namespace + +AffReader::AffReader(const std::string& filename) { + fopen_s(&file_, filename.c_str(), "r"); + + // Default to Latin1 in case the file doesn't specify it. + encoding_ = "ISO8859-1"; +} + +AffReader::~AffReader() { + if (file_) + fclose(file_); +} + +bool AffReader::Read() { + if (!file_) + return false; + + // TODO(brettw) handle byte order mark. + + bool got_command = false; + bool got_first_af = false; + bool got_first_rep = false; + + has_indexed_affixes_ = false; + + while (!feof(file_)) { + std::string line = ReadLine(file_); + + // Save comment lines before any commands. + if (!got_command && !line.empty() && line[0] == '#') { + intro_comment_.append(line); + intro_comment_.push_back('\n'); + continue; + } + + StripComment(&line); + if (line.empty()) + continue; + got_command = true; + + if (StringBeginsWith(line, "SET ")) { + // Character set encoding. + encoding_ = line.substr(4); + TrimLine(&encoding_); + } else if (StringBeginsWith(line, "AF ")) { + // Affix. The first one is the number of ones following which we don't + // bother with. + has_indexed_affixes_ = true; + if (got_first_af) + AddAffixGroup(&line.substr(3)); + else + got_first_af = true; + } else if (StringBeginsWith(line, "SFX ") || + StringBeginsWith(line, "PFX ")) { + AddAffix(&line); + } else if (StringBeginsWith(line, "REP ")) { + // The first rep line is the number of ones following which we don't + // bother with. + if (got_first_rep) + AddReplacement(&line.substr(4)); + else + got_first_rep = true; + } else if (StringBeginsWith(line, "TRY ") || + StringBeginsWith(line, "MAP ")) { + HandleEncodedCommand(line); + } else if (StringBeginsWith(line, "IGNORE ")) { + printf("We don't support the IGNORE command yet. This would change how " + "we would insert things in our lookup table.\n"); + exit(1); + } else if (StringBeginsWith(line, "COMPLEXPREFIXES ")) { + printf("We don't support the COMPLEXPREFIXES command yet. This would " + "mean we have to insert words backwords as well (I think)\n"); + exit(1); + } else { + // All other commands get stored in the other commands list. + HandleRawCommand(line); + } + } + + return true; +} + +bool AffReader::EncodingToUTF8(const std::string& encoded, + std::string* utf8) const { + std::wstring wide_word; + if (!CodepageToWide(encoded, encoding(), + OnStringUtilConversionError::FAIL, &wide_word)) + return false; + *utf8 = WideToUTF8(wide_word); + return true; +} + +int AffReader::GetAFIndexForAFString(const std::string& af_string) { + std::map<std::string, int>::iterator found = affix_groups_.find(af_string); + if (found != affix_groups_.end()) + return found->second; + std::string my_string(af_string); + return AddAffixGroup(&my_string); +} + +// We convert the data from our map to an indexed list, and also prefix each +// line with "AF" for the parser to read later. +std::vector<std::string> AffReader::GetAffixGroups() const { + int max_id = 0; + for (std::map<std::string, int>::const_iterator i = affix_groups_.begin(); + i != affix_groups_.end(); ++i) { + if (i->second > max_id) + max_id = i->second; + } + + std::vector<std::string> ret; + + ret.resize(max_id); + for (std::map<std::string, int>::const_iterator i = affix_groups_.begin(); + i != affix_groups_.end(); ++i) { + // Convert the indices into 1-based. + ret[i->second - 1] = std::string("AF ") + i->first; + } + + return ret; +} + +int AffReader::AddAffixGroup(std::string* rule) { + TrimLine(rule); + + // We use the 1-based index of the rule. This matches the way Hunspell + // refers to the numbers. + int affix_id = static_cast<int>(affix_groups_.size()) + 1; + affix_groups_.insert(std::make_pair(*rule, affix_id)); + return affix_id; +} + +void AffReader::AddAffix(std::string* rule) { + TrimLine(rule); + CollapseDuplicateSpaces(rule); + + // These lines have two forms: + // AFX D Y 4 <- First line, lists how many affixes for "D" there are. + // AFX D 0 d e <- Following lines. + // We want to ensure the two last groups on the last line are encoded in + // UTF-8, and we want to make sure that the affix identifier "D" is *not* + // encoded, since that's basically an 8-bit identifier. + + // Count to the third space. Everything after that will be re-encoded. This + // will re-encode the number on the first line, but that will be a NOP. If + // there are not that many groups, we won't reencode it, but pass it through. + int found_spaces = 0; + for (size_t i = 0; i < rule->length(); i++) { + if ((*rule)[i] == ' ') { + found_spaces++; + if (found_spaces == 3) { + std::string part = rule->substr(i); // From here to end. + + size_t slash_index = part.find('/'); + if (slash_index != std::string::npos && !has_indexed_affixes()) { + // This can also have a rule string associated with it following a + // slash. For example: + // PFX P 0 foo/Y . + // The "Y" is a flag. For example, the aff file might have a line: + // COMPOUNDFLAG Y + // so that means that this prefix would be a compound one. + // + // It expects these rules to use the same alias rules as the .dic + // file. We've forced it to use aliases, which is a numberical index + // instead of these character flags, and this needs to be consistent. + + std::string before_flags = part.substr(0, slash_index + 1); + + // After the slash are both the flags, then whitespace, then the part + // that tells us what to strip. + std::vector<std::string> after_slash; + SplitString(part.substr(slash_index + 1), ' ', &after_slash); + if (after_slash.size() < 2) { + // Note that we may get a third term here which is the + // morphological description of this rule. This happens in the tests + // only, so we can just ignore it. + printf("ERROR: Didn't get enough after the slash\n"); + return; + } + + part = StringPrintf("%s%d %s", before_flags.c_str(), + GetAFIndexForAFString(after_slash[0]), + after_slash[1].c_str()); + } + + // Reencode from here + std::string reencoded; + if (!EncodingToUTF8(part, &reencoded)) + break; + + *rule = rule->substr(0, i) + reencoded; + break; + } + } + } + + affix_rules_.push_back(*rule); +} + +void AffReader::AddReplacement(std::string* rule) { + TrimLine(rule); + + std::string utf8rule; + if (!EncodingToUTF8(*rule, &utf8rule)) + return; + + std::vector<std::string> split; + SplitString(utf8rule, ' ', &split); + + // There should be two parts. + if (split.size() != 2) + return; + + // Underscores are used to represent spaces + // (since the line is parsed on spaces). + std::replace(split[0].begin(), split[0].end(), '_', ' '); + std::replace(split[1].begin(), split[1].end(), '_', ' '); + + replacements_.push_back(std::make_pair(split[0], split[1])); +} + +void AffReader::HandleRawCommand(const std::string& line) { + other_commands_.push_back(line); +} + +void AffReader::HandleEncodedCommand(const std::string& line) { + std::string utf8; + if (EncodingToUTF8(line, &utf8)) + other_commands_.push_back(utf8); +} + +} // namespace convert_dict diff --git a/chrome/tools/convert_dict/aff_reader.h b/chrome/tools/convert_dict/aff_reader.h new file mode 100644 index 0000000..0cfd40a --- /dev/null +++ b/chrome/tools/convert_dict/aff_reader.h @@ -0,0 +1,131 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_TOOLS_CONVERT_DICT_AFF_READER_H__ +#define CHROME_TOOLS_CONVERT_DICT_AFF_READER_H__ + +#include <map> +#include <stdio.h> +#include <string> +#include <vector> + +namespace convert_dict { + +class AffReader { + public: + AffReader(const std::string& filename); + ~AffReader(); + + bool Read(); + + // Returns whether this file uses indexed affixes, or, on false, whether the + // rule string will be specified literally in the .dic file. This must be + // called after Read(). + bool has_indexed_affixes() const { return has_indexed_affixes_; } + + // Returns a string representing the encoding of the dictionary. This will + // default to ISO-8859-1 if the .aff file does not specify it. + const char* encoding() const { return encoding_.c_str(); } + + // Converts the given string from the file encoding to UTF-8, returning true + // on success. + bool EncodingToUTF8(const std::string& encoded, std::string* utf8) const; + + // Adds a new affix string, returning the index. If it already exists, returns + // the index of the existing one. This is used to convert .dic files which + // list the + // You must not call this until after Read(); + int GetAFIndexForAFString(const std::string& af_string); + + // Getters for the computed data. + const std::string& comments() const { return intro_comment_; } + const std::vector<std::string>& affix_rules() const { return affix_rules_; } + const std::vector< std::pair<std::string, std::string> >& + replacements() const { + return replacements_; + } + const std::vector<std::string>& other_commands() const { + return other_commands_; + } + + // Returns the affix groups ("AF" lines) for this file. The indices into this + // are 1-based, but we don't use the 0th item, so lookups will have to + // subtract one to get the index. This is how hunspell stores this data. + std::vector<std::string> GetAffixGroups() const; + + private: + // Command-specific handlers. These are given the string folling the + // command. The input rule may be modified arbitrarily by the function. + int AddAffixGroup(std::string* rule); // Returns the new affix group ID. + void AddAffix(std::string* rule); // SFX/PFX + void AddReplacement(std::string* rule); + //void HandleFlag(std::string* rule); + + // Used to handle "other" commands. The "raw" just saves the line as-is. + // The "encoded" version converts the line to UTF-8 and saves it. + void HandleRawCommand(const std::string& line); + void HandleEncodedCommand(const std::string& line); + + FILE* file_; + + // Comments from the beginning of the file. This is everything before the + // first command. We want to store this since it often contains the copyright + // information. + std::string intro_comment_; + + // Encoding of the source words. + std::string encoding_; + + // Affix rules. These are populated by "AF" commands. The .dic file can refer + // to these by index. They are indexed by their string value (the list of + // characters representing rules), and map to the numeric affix IDs. + // + // These can also be added using GetAFIndexForAFString. + std::map<std::string, int> affix_groups_; + + // True when the affixes were specified in the .aff file using indices. The + // dictionary reader uses this to see how it should treat the stuff after the + // word on each line. + bool has_indexed_affixes_; + + // SFX and PFX commands. This is a list of each of those lines in the order + // they appear in the file. They have been re-encoded. + std::vector<std::string> affix_rules_; + + // Replacement commands. The first string is a possible input, and the second + // is the replacment. + std::vector< std::pair<std::string, std::string> > replacements_; + + // All other commands. + std::vector<std::string> other_commands_; +}; + +} // namespace convert_dict + +#endif // CHROME_TOOLS_CONVERT_DICT_AFF_READER_H__ diff --git a/chrome/tools/convert_dict/convert_dict.cc b/chrome/tools/convert_dict/convert_dict.cc new file mode 100644 index 0000000..c6918ce --- /dev/null +++ b/chrome/tools/convert_dict/convert_dict.cc @@ -0,0 +1,149 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// This tool converts Hunspell .aff/.dic pairs to a combined binary dictionary +// format (.bdic). This format is more compact, and can be more efficiently +// read by the client application. +// +// We do this conversion manually before publishing dictionary files. It is not +// part of any build process. +// +// See PrintHelp() below for usage. + +#include <stdio.h> + +#include "base/icu_util.h" +#include "base/string_util.h" +#include "chrome/third_party/hunspell/google/bdict_reader.h" +#include "chrome/third_party/hunspell/google/bdict_writer.h" +#include "chrome/tools/convert_dict/aff_reader.h" +#include "chrome/tools/convert_dict/dic_reader.h" + +namespace { + +// Compares the given word list with the serialized trie to make sure they +// are the same. +bool VerifyWords(const convert_dict::DicReader::WordList& org_words, + const std::string& serialized) { + hunspell::BDictReader reader; + if (!reader.Init(reinterpret_cast<const unsigned char*>(serialized.data()), + serialized.size())) { + printf("BDict is invalid\n"); + return false; + } + hunspell::WordIterator iter = reader.GetAllWordIterator(); + + int affix_ids[hunspell::BDict::MAX_AFFIXES_PER_WORD]; + + static const int buf_size = 128; + char buf[buf_size]; + for (size_t i = 0; i < org_words.size(); i++) { + int affix_matches = iter.Advance(buf, buf_size, affix_ids); + if (affix_matches == 0) + return false; // Found the end before we expectd. + if (org_words[i].first != buf) + return false; // Word doesn't match. + + if (affix_matches != static_cast<int>(org_words[i].second.size())) + return false; // Different number of affix indices. + + // Check the individual affix indices. + for (size_t affix_index = 0; affix_index < org_words[i].second.size(); + affix_index++) { + if (affix_ids[affix_index] != org_words[i].second[affix_index]) + return false; // Index doesn't match. + } + } + + return true; +} + +int PrintHelp() { + printf("Usage: convert_dict <dicfile base name>\n\n"); + printf("Example:\n convert_dict en-US\nwill read en-US.dic / en-US.aff and\n"); + printf("generate en-US.bdic\n\n"); + return 1; +} + +} // namespace + +int main(int argc, char* argv[]) { + if (argc != 2) + return PrintHelp(); + + icu_util::Initialize(); + + std::string file_base = argv[1]; + + std::string aff_name = file_base + ".aff"; + printf("Reading %s ...\n", aff_name.c_str()); + convert_dict::AffReader aff_reader(aff_name.c_str()); + if (!aff_reader.Read()) { + printf("Unable to read the aff file.\n"); + return 1; + } + + std::string dic_name = file_base + ".dic"; + printf("Reading %s ...\n", dic_name.c_str()); + convert_dict::DicReader dic_reader(dic_name.c_str()); + if (!dic_reader.Read(&aff_reader)) { + printf("Unable to read the dic file.\n"); + return 1; + } + + hunspell::BDictWriter writer; + writer.SetComment(aff_reader.comments()); + writer.SetAffixRules(aff_reader.affix_rules()); + writer.SetAffixGroups(aff_reader.GetAffixGroups()); + writer.SetReplacements(aff_reader.replacements()); + writer.SetOtherCommands(aff_reader.other_commands()); + writer.SetWords(dic_reader.words()); + + printf("Serializing...\n"); + std::string serialized = writer.GetBDict(); + + printf("Verifying...\n"); + if (!VerifyWords(dic_reader.words(), serialized)) { + printf("ERROR converting, the dictionary does not check out OK."); + return 1; + } + + std::string out_name = file_base + ".bdic"; + printf("Writing %s ...\n", out_name.c_str()); + FILE* out_file; + fopen_s(&out_file, out_name.c_str(), "wb"); + if (!out_file) { + printf("ERROR writing file\n"); + return 1; + } + fwrite(&serialized[0], 1, serialized.size(), out_file); + fclose(out_file); + + return 0; +} diff --git a/chrome/tools/convert_dict/convert_dict.vcproj b/chrome/tools/convert_dict/convert_dict.vcproj new file mode 100644 index 0000000..72e19be --- /dev/null +++ b/chrome/tools/convert_dict/convert_dict.vcproj @@ -0,0 +1,199 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioProject + ProjectType="Visual C++" + Version="8.00" + Name="convert_dict" + ProjectGUID="{42ECD5EC-722F-41DE-B6B8-83764C8016DF}" + RootNamespace="convert_dict" + Keyword="Win32Proj" + > + <Platforms> + <Platform + Name="Win32" + /> + </Platforms> + <ToolFiles> + </ToolFiles> + <Configurations> + <Configuration + Name="Debug|Win32" + OutputDirectory="$(SolutionDir)$(ConfigurationName)" + IntermediateDirectory="$(ConfigurationName)" + ConfigurationType="1" + InheritedPropertySheets="..\..\..\build\debug.vsprops" + CharacterSet="2" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLinkerTool" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCManifestTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCAppVerifierTool" + /> + <Tool + Name="VCWebDeploymentTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + <Configuration + Name="Release|Win32" + OutputDirectory="$(SolutionDir)$(ConfigurationName)" + IntermediateDirectory="$(ConfigurationName)" + ConfigurationType="1" + InheritedPropertySheets="..\..\..\build\release.vsprops" + CharacterSet="2" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLinkerTool" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCManifestTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCAppVerifierTool" + /> + <Tool + Name="VCWebDeploymentTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + </Configurations> + <References> + </References> + <Files> + <File + RelativePath=".\aff_reader.cc" + > + </File> + <File + RelativePath=".\aff_reader.h" + > + </File> + <File + RelativePath="..\..\third_party\hunspell\google\bdict.h" + > + </File> + <File + RelativePath="..\..\third_party\hunspell\google\bdict_reader.cc" + > + </File> + <File + RelativePath="..\..\third_party\hunspell\google\bdict_reader.h" + > + </File> + <File + RelativePath="..\..\third_party\hunspell\google\bdict_writer.cc" + > + </File> + <File + RelativePath="..\..\third_party\hunspell\google\bdict_writer.h" + > + </File> + <File + RelativePath=".\convert_dict.cc" + > + </File> + <File + RelativePath=".\dic_reader.cc" + > + </File> + <File + RelativePath=".\dic_reader.h" + > + </File> + <File + RelativePath=".\hunspell_reader.cc" + > + </File> + <File + RelativePath=".\hunspell_reader.h" + > + </File> + </Files> + <Globals> + </Globals> +</VisualStudioProject> diff --git a/chrome/tools/convert_dict/dic_reader.cc b/chrome/tools/convert_dict/dic_reader.cc new file mode 100644 index 0000000..8358e13 --- /dev/null +++ b/chrome/tools/convert_dict/dic_reader.cc @@ -0,0 +1,165 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/tools/convert_dict/dic_reader.h" + +#include <algorithm> +#include <set> + +#include "base/string_util.h" +#include "chrome/tools/convert_dict/aff_reader.h" +#include "chrome/tools/convert_dict/hunspell_reader.h" + +namespace convert_dict { + +namespace { + +// Maps each unique word to the unique affix group IDs associated with it. +typedef std::map<std::string, std::set<int> > WordSet; + +void SplitDicLine(const std::string& line, std::vector<std::string>* output) { + // We split the line on a slash not preceeded by a backslash. A slash at the + // beginning of the line is not a separator either. + size_t slash_index = line.size(); + for (size_t i = 0; i < line.size(); i++) { + if (line[i] == '/' && i > 0 && line[i - 1] != '\\') { + slash_index = i; + break; + } + } + + output->clear(); + + // Everything before the slash index is the first term. We also need to + // convert all escaped slashes ("\/" sequences) to regular slashes. + std::string word = line.substr(0, slash_index); + ReplaceSubstringsAfterOffset(&word, 0, "\\/", "/"); + output->push_back(word); + + // Everything (if anything) after the slash is the second. + if (slash_index < line.size() - 1) + output->push_back(line.substr(slash_index + 1)); +} + +} // namespace + +DicReader::DicReader(const std::string& filename) { + fopen_s(&file_, filename.c_str(), "r"); +} + +DicReader::~DicReader() { + if (file_) + fclose(file_); +} + +bool DicReader::Read(AffReader* aff_reader) { + if (!file_) + return false; + + bool got_count = false; + int line_number = 0; + + WordSet word_set; + while (!feof(file_)) { + std::string line = ReadLine(file_); + line_number++; + StripComment(&line); + if (line.empty()) + continue; + + if (!got_count) { + // Skip the first nonempty line, this is the line count. We don't bother + // with it and just read all the lines. + got_count = true; + continue; + } + + std::vector<std::string> split; + SplitDicLine(line, &split); + if (split.size() == 0 || split.size() > 2) { + printf("Line %d has extra slashes in the dic file\n", line_number); + return false; + } + + // The first part is the word, the second (optional) part is the affix. We + // always use UTF-8 as the encoding to simplify life. + std::string utf8word; + if (!aff_reader->EncodingToUTF8(split[0], &utf8word)) { + printf("Unable to convert line %d from %s to UTF-8 in the dic file\n", + line_number, aff_reader->encoding()); + return false; + } + + // We always convert the affix to an index. 0 means no affix. + int affix_index = 0; + if (split.size() == 2) { + // Got a rule, which is the stuff after the slash. The line may also have + // an optional term separated by a tab. This is the morphological + // description. We don't care about this (it is used in the tests to + // generate a nice dump), so we remove it. + size_t split1_tab_offset = split[1].find('\t'); + if (split1_tab_offset != std::string::npos) + split[1] = split[1].substr(0, split1_tab_offset); + + if (aff_reader->has_indexed_affixes()) + affix_index = atoi(split[1].c_str()); + else + affix_index = aff_reader->GetAFIndexForAFString(split[1]); + } + + WordSet::iterator found = word_set.find(utf8word); + if (found == word_set.end()) { + std::set<int> affix_vector; + affix_vector.insert(affix_index); + word_set.insert(std::make_pair(utf8word, affix_vector)); + } else { + found->second.insert(affix_index); + } + } + + // Make sure the words are sorted, they may be unsorted in the input. + for (WordSet::iterator word = word_set.begin(); word != word_set.end(); + ++word) { + std::vector<int> affixes; + for (std::set<int>::iterator aff = word->second.begin(); + aff != word->second.end(); ++aff) + affixes.push_back(*aff); + + // Double check that the affixes are sorted. This isn't strictly necessary + // but it's nice for the file to have a fixed layout. + std::sort(affixes.begin(), affixes.end()); + words_.push_back(std::make_pair(word->first, affixes)); + } + + // Double-check that the words are sorted. + std::sort(words_.begin(), words_.end()); + return true; +} + +} // namespace convert_dict diff --git a/chrome/tools/convert_dict/dic_reader.h b/chrome/tools/convert_dict/dic_reader.h new file mode 100644 index 0000000..4a9a059 --- /dev/null +++ b/chrome/tools/convert_dict/dic_reader.h @@ -0,0 +1,70 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_TOOLS_CONVERT_DICT_DIC_READER_H__ +#define CHROME_TOOLS_CONVERT_DICT_DIC_READER_H__ + +#include <stdio.h> +#include <map> +#include <string> +#include <vector> + +namespace convert_dict { + +class AffReader; + +// Reads Hunspell .dic files. +class DicReader { + public: + // Associated with each word is a list of affix group IDs. This will typically + // be only one long, but may be more if there are multiple groups of + // independent affix rules for a base word. + typedef std::pair<std::string, std::vector<int> > WordEntry; + typedef std::vector<WordEntry> WordList; + + DicReader(const std::string& filename); + ~DicReader(); + + // Non-numeric affixes will be added to the given AffReader and converted into + // indices. + bool Read(AffReader* aff_reader); + + // Returns the words read by Read(). These will be in order. + const WordList& words() const { return words_; } + + private: + FILE* file_; + + // Contains all words and their corresponding affix index. + WordList words_; +}; + +} // namespace convert_dict + +#endif // CHROME_TOOLS_CONVERT_DICT_DIC_READER_H__ diff --git a/chrome/tools/convert_dict/hunspell_reader.cc b/chrome/tools/convert_dict/hunspell_reader.cc new file mode 100644 index 0000000..7b44f76 --- /dev/null +++ b/chrome/tools/convert_dict/hunspell_reader.cc @@ -0,0 +1,72 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/tools/convert_dict/hunspell_reader.h" + +#include "base/string_util.h" + +namespace convert_dict { + +// This silly 64K buffer is just copied from Hunspell's way of parsing. +const int kLineBufferLen = 65535; +char line_buffer[kLineBufferLen]; + +// Shortcut for trimming whitespace from both ends of the line. +void TrimLine(std::string* line) { + if (line->size() > 3 && + static_cast<unsigned char>((*line)[0]) == 0xef && + static_cast<unsigned char>((*line)[1]) == 0xbb && + static_cast<unsigned char>((*line)[2]) == 0xbf) + *line = line->substr(3); + + TrimWhitespace(*line, TRIM_ALL, line); +} + +std::string ReadLine(FILE* file) { + const char* line = fgets(line_buffer, kLineBufferLen - 1, file); + if (!line) + return std::string(); + + std::string str = line; + TrimLine(&str); + return str; +} + +void StripComment(std::string* line) { + for (size_t i = 0; i < line->size(); i++) { + if ((*line)[i] == '#') { + line->resize(i); + TrimLine(line); + return; + } + } +} + +} // namespace convert_dict + diff --git a/chrome/tools/convert_dict/hunspell_reader.h b/chrome/tools/convert_dict/hunspell_reader.h new file mode 100644 index 0000000..2c0a1db --- /dev/null +++ b/chrome/tools/convert_dict/hunspell_reader.h @@ -0,0 +1,51 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_TOOLS_DIC_CONVERTER_HUNSPELL_READER_H__ +#define CHROME_TOOLS_DIC_CONVERTER_HUNSPELL_READER_H__ + +#include <string> + +// Common routines for reading hunspell files. +namespace convert_dict { + +// Reads one line and returns it. Whitespace will be trimmed. +std::string ReadLine(FILE* file); + +// Trims whitespace from the beginning and end of the given string. Also trims +// UTF-8 byte order markers from the beginning. +void TrimLine(std::string* line); + +// Strips any comments for the given line. +void StripComment(std::string* line); + +} // namespace convert_dict + +#endif // CHROME_TOOLS_DIC_CONVERTER_HUNSPELL_READER_H__ + diff --git a/chrome/tools/crash_service/SConscript b/chrome/tools/crash_service/SConscript new file mode 100644 index 0000000..b682986 --- /dev/null +++ b/chrome/tools/crash_service/SConscript @@ -0,0 +1,99 @@ +# Copyright 2008, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+Import('env')
+
+env = env.Clone()
+
+env.Prepend(
+ CPPPATH = [
+ '$BREAKPAD_DIR/src',
+ '#/..',
+ ],
+ LINKFLAGS = [
+ '/INCREMENTAL',
+
+ '/DELAYLOAD:"dwmapi.dll"',
+ '/DELAYLOAD:"uxtheme.dll"',
+
+ '/DEBUG',
+ '/MACHINE:X86',
+ '/FIXED:No',
+
+ '/safeseh',
+ '/dynamicbase',
+ '/ignore:4199',
+ '/nxcompat',
+
+ ],
+ LIBS = [
+ 'advapi32.lib',
+ 'comdlg32.lib',
+ 'gdi32.lib',
+ 'kernel32.lib',
+ 'msimg32.lib',
+ 'odbc32.lib',
+ 'odbccp32.lib',
+ 'ole32.lib',
+ 'oleaut32.lib',
+ 'psapi.lib',
+ 'shell32.lib',
+ 'user32.lib',
+ 'usp10.lib',
+ 'uuid.lib',
+ 'version.lib',
+ 'wininet.lib',
+ 'winspool.lib',
+ 'ws2_32.lib',
+
+ 'DelayImp.lib',
+ ],
+)
+
+libs = [
+ '$BASE_DIR/base.lib',
+ '$BASE_DIR/gfx/base_gfx.lib',
+ '$BREAKPAD_DIR/breakpad_handler.lib',
+ '$BREAKPAD_DIR/breakpad_sender.lib',
+ '$CHROME_DIR/common/common.lib',
+ '$ICU38_DIR/icuuc.lib',
+ '$SKIA_DIR/skia.lib',
+ '$ZLIB_DIR/zlib.lib',
+]
+
+
+input_files = [
+ 'main.cc',
+ 'crash_service.cc',
+]
+
+exe = env.Program('crash_service.exe', input_files + libs)
+
+i = env.Install('$TARGET_ROOT', exe)
+Alias('chrome', i)
diff --git a/chrome/tools/crash_service/crash_service.cc b/chrome/tools/crash_service/crash_service.cc new file mode 100644 index 0000000..172b66a --- /dev/null +++ b/chrome/tools/crash_service/crash_service.cc @@ -0,0 +1,454 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/tools/crash_service/crash_service.h" + +#include <windows.h> + +#include <iostream> +#include <fstream> +#include <map> + +#include "base/command_line.h" +#include "base/file_util.h" +#include "base/logging.h" +#include "base/path_service.h" +#include "breakpad/src/client/windows/crash_generation/crash_generation_server.h" +#include "breakpad/src/client/windows/sender/crash_report_sender.h" +#include "chrome/common/chrome_constants.h" +#include "chrome/common/chrome_paths.h" +#include "chrome/common/win_util.h" + +// TODO(cpu): Bug 1169078. There is a laundry list of things to do for this +// application. They will be addressed as they are required. + +namespace { + +const wchar_t kTestPipeName[] = L"\\\\.\\pipe\\ChromeCrashServices"; + +const wchar_t kCrashReportURL[] = L"https://www.google.com/cr/report"; +const wchar_t kCheckPointFile[] = L"crash_checkpoint.txt"; + +typedef std::map<std::wstring, std::wstring> CrashMap; + +bool CustomInfoToMap(const google_breakpad::ClientInfo* client_info, + const std::wstring& reporter_tag, CrashMap* map) { + google_breakpad::CustomClientInfo info = client_info->GetCustomInfo(); + + for (int i = 0; i < info.count; ++i) { + (*map)[info.entries[i].name] = info.entries[i].value; + } + + (*map)[L"rept"] = reporter_tag; + + return (map->size() > 0); +} + +bool WriteCustomInfoToFile(const std::wstring& dump_path, const CrashMap& map) { + std::wstring file_path(dump_path); + size_t last_dot = file_path.rfind(L'.'); + if (last_dot == std::wstring::npos) + return false; + file_path.resize(last_dot); + file_path += L".txt"; + + std::wofstream file(file_path.c_str(), + std::ios_base::out | std::ios_base::app | std::ios::binary); + if (!file.is_open()) + return false; + + CrashMap::const_iterator pos; + for (pos = map.begin(); pos != map.end(); ++pos) { + std::wstring line = pos->first; + line += L':'; + line += pos->second; + line += L'\n'; + file.write(line.c_str(), static_cast<std::streamsize>(line.length())); + } + return true; +} + +// The window procedure task is to handle when a) the user logs off. +// b) the system shuts down or c) when the user closes the window. +LRESULT __stdcall CrashSvcWndProc(HWND hwnd, UINT message, + WPARAM wparam, LPARAM lparam) { + switch (message) { + case WM_CLOSE: + case WM_ENDSESSION: + case WM_DESTROY: + PostQuitMessage(0); + break; + default: + return DefWindowProc(hwnd, message, wparam, lparam); + } + return 0; +} + +// This is the main and only application window. +HWND g_top_window = NULL; + +bool CreateTopWindow(HINSTANCE instance, bool visible) { + WNDCLASSEXW wcx = {0}; + wcx.cbSize = sizeof(wcx); + wcx.style = CS_HREDRAW | CS_VREDRAW; + wcx.lpfnWndProc = CrashSvcWndProc; + wcx.hInstance = instance; + wcx.lpszClassName = L"crash_svc_class"; + ATOM atom = ::RegisterClassExW(&wcx); + DWORD style = visible ? WS_POPUPWINDOW | WS_VISIBLE : WS_OVERLAPPED; + + // The window size is zero but being a popup window still shows in the + // task bar and can be closed using the system menu or using task manager. + HWND window = CreateWindowExW(0, wcx.lpszClassName, L"crash service", style, + CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, + NULL, NULL, instance, NULL); + if (!window) + return false; + + ::UpdateWindow(window); + LOG(INFO) << "window handle is " << window; + g_top_window = window; + return true; +} + +// Simple helper class to keep the process alive until the current request +// finishes. +class ProcessingLock { + public: + ProcessingLock() { + ::InterlockedIncrement(&op_count_); + } + ~ProcessingLock() { + ::InterlockedDecrement(&op_count_); + } + static bool IsWorking() { + return (op_count_ != 0); + } + private: + static volatile LONG op_count_; +}; + +volatile LONG ProcessingLock::op_count_ = 0; + +// This structure contains the information that the worker thread needs to +// send a crash dump to the server. +struct DumpJobInfo { + DWORD pid; + CrashService* self; + CrashMap map; + std::wstring dump_path; + + DumpJobInfo(DWORD process_id, CrashService* service, + const CrashMap& crash_map, const std::wstring& path) + : pid(process_id), self(service), map(crash_map), dump_path(path) { + } +}; + +} // namespace + +// Command line switches: +const wchar_t CrashService::kMaxReports[] = L"max-reports"; +const wchar_t CrashService::kNoWindow[] = L"no-window"; +const wchar_t CrashService::kReporterTag[]= L"reporter"; + +CrashService::CrashService(const std::wstring& report_dir) + : report_path_(report_dir), + sender_(NULL), + dumper_(NULL), + requests_handled_(0), + requests_sent_(0), + clients_connected_(0), + clients_terminated_(0) { + chrome::RegisterPathProvider(); +} + +CrashService::~CrashService() { + AutoLock lock(sending_); + delete dumper_; + delete sender_; +} + +bool CrashService::Initialize(const std::wstring& command_line) { + using google_breakpad::CrashReportSender; + using google_breakpad::CrashGenerationServer; + + const wchar_t* pipe_name = kTestPipeName; + std::wstring dumps_path; + int max_reports = -1; + + // The checkpoint file allows CrashReportSender to enforce the the maximum + // reports per day quota. Does not seem to serve any other purpose. + std::wstring checkpoint_path = report_path_; + file_util::AppendToPath(&checkpoint_path, kCheckPointFile); + + // The dumps path is typically : '<user profile>\Local settings\ + // Application data\Goggle\Chrome\Crash Reports' and the report path is + // Application data\Google\Chrome\Reported Crashes.txt + if (!PathService::Get(chrome::DIR_USER_DATA, &report_path_)) { + LOG(ERROR) << "could not get DIR_USER_DATA"; + return false; + } + file_util::AppendToPath(&report_path_, chrome::kCrashReportLog); + if (!PathService::Get(chrome::DIR_CRASH_DUMPS, &dumps_path)) { + LOG(ERROR) << "could not get DIR_CRASH_DUMPS"; + return false; + } + + CommandLine cmd_line(command_line); + + // We can override the send reports quota with a command line switch. + if (cmd_line.HasSwitch(kMaxReports)) + max_reports = _wtoi(cmd_line.GetSwitchValue(kMaxReports).c_str()); + + if (max_reports > 0) { + // Create the http sender object. + sender_ = new CrashReportSender(checkpoint_path); + if (!sender_) { + LOG(ERROR) << "could not create sender"; + return false; + } + sender_->set_max_reports_per_day(max_reports); + } + // Create the OOP crash generator object. + dumper_ = new CrashGenerationServer(pipe_name, NULL, + &CrashService::OnClientConnected, this, + &CrashService::OnClientDumpRequest, this, + &CrashService::OnClientExited, this, + true, &dumps_path); + if (!dumper_) { + LOG(ERROR) << "could not create dumper"; + return false; + } + + if (!CreateTopWindow(::GetModuleHandleW(NULL), + !cmd_line.HasSwitch(kNoWindow))) { + LOG(ERROR) << "could not create window"; + return false; + } + + reporter_tag_ = L"crash svc"; + if (cmd_line.HasSwitch(kReporterTag)) + reporter_tag_ = cmd_line.GetSwitchValue(kReporterTag); + + // Log basic information. + LOG(INFO) << "pipe name is " << pipe_name; + LOG(INFO) << "dumps at " << dumps_path; + LOG(INFO) << "reports at " << report_path_; + + if (sender_) { + LOG(INFO) << "checkpoint is " << checkpoint_path; + LOG(INFO) << "server is " << kCrashReportURL; + LOG(INFO) << "maximum " << sender_->max_reports_per_day() << " reports/day"; + LOG(INFO) << "reporter is " << reporter_tag_; + } + // Start servicing clients. + if (!dumper_->Start()) { + LOG(ERROR) << "could not start dumper"; + return false; + } + + // This is throwaway code. We don't need to sync with the browser process + // once Google Update is updated to a version supporting OOP crash handling. + // Create or open an event to signal the browser process that the crash + // service is initialized. + HANDLE running_event = + ::CreateEventW(NULL, TRUE, TRUE, L"g_chrome_crash_svc"); + // If the browser already had the event open, the CreateEvent call did not + // signal it. We need to do it manually. + ::SetEvent(running_event); + + return true; +} + +void CrashService::OnClientConnected(void* context, + const google_breakpad::ClientInfo* client_info) { + ProcessingLock lock; + LOG(INFO) << "client start. pid = " << client_info->pid(); + CrashService* self = static_cast<CrashService*>(context); + ::InterlockedIncrement(&self->clients_connected_); +} + +void CrashService::OnClientExited(void* context, + const google_breakpad::ClientInfo* client_info) { + ProcessingLock lock; + LOG(INFO) << "client end. pid = " << client_info->pid(); + CrashService* self = static_cast<CrashService*>(context); + ::InterlockedIncrement(&self->clients_terminated_); + + if (!self->sender_) + return; + + // When we are instructed to send reports we need to exit if there are + // no more clients to service. The next client that runs will start us. + // Only chrome.exe starts crash_service with a non-zero max_reports. + if (self->clients_connected_ > self->clients_terminated_) + return; + if (self->sender_->max_reports_per_day() > 0) { + // Wait for the other thread to send crashes, if applicable. The sender + // thread takes the sending_ lock, so the sleep is just to give it a + // chance to start. + ::Sleep(1000); + AutoLock lock(self->sending_); + // Some people can restart chrome very fast, check again if we have + // a new client before exiting for real. + if (self->clients_connected_ == self->clients_terminated_) { + LOG(INFO) << "zero clients. exiting"; + ::PostMessage(g_top_window, WM_CLOSE, 0, 0); + } + } +} + +void CrashService::OnClientDumpRequest(void* context, + const google_breakpad::ClientInfo* client_info, + const std::wstring* file_path) { + ProcessingLock lock; + + if (!file_path) { + LOG(ERROR) << "dump with no file path"; + return; + } + if (!client_info) { + LOG(ERROR) << "dump with no client info"; + return; + } + + DWORD pid = client_info->pid(); + LOG(INFO) << "dump for pid = " << pid << " is " << *file_path; + + CrashService* self = static_cast<CrashService*>(context); + if (!self) { + LOG(ERROR) << "dump with no context"; + return; + } + + CrashMap map; + CustomInfoToMap(client_info, self->reporter_tag_, &map); + + if (!WriteCustomInfoToFile(*file_path, map)) { + LOG(ERROR) << "could not write custom info file"; + } + + if (!self->sender_) + return; + + // Send the crash dump using a worker thread. This operation has retry + // logic in case there is no internet connection at the time. + DumpJobInfo* dump_job = new DumpJobInfo(pid, self, map, *file_path); + if (!::QueueUserWorkItem(&CrashService::AsyncSendDump, + dump_job, WT_EXECUTELONGFUNCTION)) { + LOG(ERROR) << "could not queue job"; + } +} + +// We are going to try sending the report several times. If we can't send, +// we sleep from one minute to several hours depending on the retry round. +unsigned long CrashService::AsyncSendDump(void* context) { + if (!context) + return 0; + + DumpJobInfo* info = static_cast<DumpJobInfo*>(context); + + std::wstring report_id = L"<unsent>"; + + const DWORD kOneMinute = 60*1000; + const DWORD kOneHour = 60*kOneMinute; + + const DWORD kSleepSchedule[] = { + 24*kOneHour, + 8*kOneHour, + 4*kOneHour, + kOneHour, + 15*kOneMinute, + 0}; + + int retry_round = arraysize(kSleepSchedule) - 1; + + do { + ::Sleep(kSleepSchedule[retry_round]); + { + // Take the server lock while sending. This also prevent early + // termination of the service object. + AutoLock lock(info->self->sending_); + LOG(INFO) << "trying to send report for pid = " << info->pid; + google_breakpad::ReportResult send_result + = info->self->sender_->SendCrashReport(kCrashReportURL, info->map, + info->dump_path, &report_id); + switch (send_result) { + case google_breakpad::RESULT_FAILED: + report_id = L"<network issue>"; + break; + case google_breakpad::RESULT_REJECTED: + report_id = L"<rejected>"; + ++info->self->requests_handled_; + retry_round = 0; + break; + case google_breakpad::RESULT_SUCCEEDED: + ++info->self->requests_sent_; + ++info->self->requests_handled_; + retry_round = 0; + break; + case google_breakpad::RESULT_THROTTLED: + report_id = L"<throttled>"; + break; + default: + report_id = L"<unknown>"; + break; + }; + } + + LOG(INFO) << "dump for pid =" << info->pid << " crash2 id =" << report_id; + --retry_round; + } while(retry_round >= 0); + + if (!::DeleteFileW(info->dump_path.c_str())) + LOG(WARNING) << "could not delete " << info->dump_path; + + delete info; + return 0; +} + +int CrashService::ProcessingLoop() { + MSG msg; + while (GetMessage(&msg, NULL, 0, 0)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + LOG(INFO) << "session ending.."; + while (ProcessingLock::IsWorking()) { + ::Sleep(50); + } + + LOG(INFO) << "clients connected :" << clients_connected_; + LOG(INFO) << "clients terminated :" << clients_terminated_; + LOG(INFO) << "dumps serviced :" << requests_handled_; + LOG(INFO) << "dumps reported :" << requests_sent_; + + return static_cast<int>(msg.wParam); +} diff --git a/chrome/tools/crash_service/crash_service.exe.manifest b/chrome/tools/crash_service/crash_service.exe.manifest new file mode 100644 index 0000000..28469a3 --- /dev/null +++ b/chrome/tools/crash_service/crash_service.exe.manifest @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+ <ms_asmv2:trustInfo xmlns:ms_asmv2="urn:schemas-microsoft-com:asm.v2">
+ <ms_asmv2:security>
+ <ms_asmv2:requestedPrivileges>
+ <ms_asmv2:requestedExecutionLevel level="asInvoker">
+ </ms_asmv2:requestedExecutionLevel>
+ </ms_asmv2:requestedPrivileges>
+ </ms_asmv2:security>
+ </ms_asmv2:trustInfo>
+</assembly>
diff --git a/chrome/tools/crash_service/crash_service.h b/chrome/tools/crash_service/crash_service.h new file mode 100644 index 0000000..d49f50c --- /dev/null +++ b/chrome/tools/crash_service/crash_service.h @@ -0,0 +1,138 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#ifndef CHROME_TOOLS_CRASH_SERVICE__ +#define CHROME_TOOLS_CRASH_SERVICE__ + +#include <string> + +#include "base/basictypes.h" +#include "base/lock.h" + +namespace google_breakpad { + +class CrashReportSender; +class CrashGenerationServer; +class ClientInfo; + +} + +// This class implements an out-of-process crash server. It uses breakpad's +// CrashGenerationServer and CrashReportSender to generate and then send the +// crash dumps. Internally, it uses OS specific pipe to allow applications to +// register for crash dumps and later on when a registered application crashes +// it will signal an event that causes this code to wake up and perform a +// crash dump on the signaling process. The dump is then stored on disk and +// possibly sent to the crash2 servers. +class CrashService { + public: + // The ctor takes a directory that needs to be writable and will create + // a subdirectory inside to keep logs, crashes and checkpoint files. + CrashService(const std::wstring& report_dir); + ~CrashService(); + + // Starts servicing crash dumps. The command_line specifies various behaviors, + // see below for more information. Returns false if it failed. Do not use + // other members in that case. + bool Initialize(const std::wstring& command_line); + + // Command line switches: + // + // --max-reports=<number> + // Allows to override the maximum number for reports per day. Normally + // the crash dumps are never sent so if you want to send any you must + // specify a positive number here. + static const wchar_t kMaxReports[]; + // --no-window + // Does not create a visible window on the desktop. The window does not have + // any other functionality other than allowing the crash service to be + // gracefully closed. + static const wchar_t kNoWindow[]; + // --reporter=<string> + // Allows to specify a custom string that appears on the detail crash report + // page in the crash server. This should be a 25 chars or less string. + // The default tag if not specified is 'crash svc'. + static const wchar_t kReporterTag[]; + + // Returns the actual report path. + std::wstring report_path() const { + return report_path_; + } + // Returns number of crash dumps handled. + int requests_handled() const { + return requests_handled_; + } + // Returns number of crash clients registered. + int clients_connected() const { + return clients_connected_; + } + // Returns number of crash clients terminated. + int clients_terminated() const { + return clients_terminated_; + } + + // Starts the processing loop. This function does not return unless the + // user is logging off or the user closes the crash service window. The + // return value is a good number to pass in ExitProcess(). + int ProcessingLoop(); + + private: + static void OnClientConnected(void* context, + const google_breakpad::ClientInfo* client_info); + + static void OnClientDumpRequest(void* context, + const google_breakpad::ClientInfo* client_info, + const std::wstring* file_path); + + static void OnClientExited(void* context, + const google_breakpad::ClientInfo* client_info); + + // This routine sends the crash dump to the server. It takes the sending_ + // lock when it is performing the send. + static unsigned long __stdcall AsyncSendDump(void* context); + + google_breakpad::CrashGenerationServer* dumper_; + google_breakpad::CrashReportSender* sender_; + + // the path to dumps and logs directory. + std::wstring report_path_; + // the extra tag sent to the server with each dump. + std::wstring reporter_tag_; + + // clients serviced statistics: + int requests_handled_; + int requests_sent_; + volatile long clients_connected_; + volatile long clients_terminated_; + Lock sending_; + + DISALLOW_EVIL_CONSTRUCTORS(CrashService); +}; + + +#endif // CHROME_TOOLS_CRASH_SERVICE__ diff --git a/chrome/tools/crash_service/crash_service.vcproj b/chrome/tools/crash_service/crash_service.vcproj new file mode 100644 index 0000000..5cbc496 --- /dev/null +++ b/chrome/tools/crash_service/crash_service.vcproj @@ -0,0 +1,157 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioProject + ProjectType="Visual C++" + Version="8.00" + Name="crash_service" + ProjectGUID="{89C1C190-A5D1-4EC4-BD6A-67FF2195C7CC}" + RootNamespace="crash_service" + Keyword="Win32Proj" + > + <Platforms> + <Platform + Name="Win32" + /> + </Platforms> + <ToolFiles> + </ToolFiles> + <Configurations> + <Configuration + Name="Debug|Win32" + ConfigurationType="1" + InheritedPropertySheets="$(SolutionDir)..\build\common.vsprops;$(SolutionDir)..\build\debug.vsprops;$(SolutionDir)..\breakpad\using_breakpad.vsprops" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLinkerTool" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCManifestTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCAppVerifierTool" + /> + <Tool + Name="VCWebDeploymentTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + <Configuration + Name="Release|Win32" + ConfigurationType="1" + InheritedPropertySheets="$(SolutionDir)..\build\common.vsprops;$(SolutionDir)..\build\release.vsprops;$(SolutionDir)..\breakpad\using_breakpad.vsprops" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLinkerTool" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCManifestTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCAppVerifierTool" + /> + <Tool + Name="VCWebDeploymentTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + </Configurations> + <References> + </References> + <Files> + <File + RelativePath=".\crash_service.cc" + > + </File> + <File + RelativePath=".\crash_service.h" + > + </File> + <File + RelativePath=".\main.cc" + > + </File> + </Files> + <Globals> + </Globals> +</VisualStudioProject> diff --git a/chrome/tools/crash_service/main.cc b/chrome/tools/crash_service/main.cc new file mode 100644 index 0000000..ab27ddd --- /dev/null +++ b/chrome/tools/crash_service/main.cc @@ -0,0 +1,84 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/tools/crash_service/crash_service.h" + +#include <windows.h> +#include <stdlib.h> +#include <tchar.h> + +#include "base/file_util.h" +#include "base/logging.h" + +namespace { + +const wchar_t kStandardLogFile[] = L"operation_log.txt"; + +bool GetCrashServiceDirectory(std::wstring* dir) { + std::wstring temp_dir; + if (!file_util::GetTempDir(&temp_dir)) + return false; + file_util::AppendToPath(&temp_dir, L"chrome_crashes"); + if (!file_util::PathExists(temp_dir)) { + if (!file_util::CreateDirectory(temp_dir)) + return false; + } + *dir = temp_dir; + return true; +} + +} // namespace. + +int __stdcall wWinMain(HINSTANCE instance, HINSTANCE, wchar_t* cmd_line, + int show_mode) { + // We use/create a directory under the user's temp folder, for logging. + std::wstring operating_dir; + GetCrashServiceDirectory(&operating_dir); + std::wstring log_file(operating_dir); + file_util::AppendToPath(&log_file, kStandardLogFile); + + // Logging to a file with pid, tid and timestamp. + logging::InitLogging(log_file.c_str(), logging::LOG_ONLY_TO_FILE, + logging::LOCK_LOG_FILE, logging::APPEND_TO_OLD_LOG_FILE); + logging::SetLogItems(true, true, true, false); + + LOG(INFO) << "session start. cmdline is [" << cmd_line << "]"; + + CrashService crash_service(operating_dir); + if (!crash_service.Initialize(::GetCommandLineW())) + return 1; + + LOG(INFO) << "ready to process crash requests"; + + // Enter the message loop. + int retv = crash_service.ProcessingLoop(); + // Time to exit. + LOG(INFO) << "session end. return code is " << retv; + return retv; +} diff --git a/chrome/tools/extract_actions.py b/chrome/tools/extract_actions.py new file mode 100644 index 0000000..8eefbb6 --- /dev/null +++ b/chrome/tools/extract_actions.py @@ -0,0 +1,124 @@ +#!/usr/bin/python +# Copyright 2007 Google Inc. All rights reserved. + +"""Extract UserMetrics "actions" strings from the Chrome source. + +This program generates the list of known actions we expect to see in the +user behavior logs. It walks the Chrome source, looking for calls to +UserMetrics functions, extracting actions and warning on improper calls, +as well as generating the lists of possible actions in situations where +there are many possible actions. + +See also: + chrome/browser/user_metrics.h + http://wiki.corp.google.com/twiki/bin/view/Main/ChromeUserExperienceMetrics + +Run it from the chrome/browser directory like: + extract_actions.py > actions_list +""" + +__author__ = 'evanm (Evan Martin)' + +import os +import re +import sys + +from google import path_utils + +# Files that are known to use UserMetrics::RecordComputedAction(), which means +# they require special handling code in this script. +# To add a new file, add it to this list and add the appropriate logic to +# generate the known actions to AddComputedActions() below. +KNOWN_COMPUTED_USERS = [ + 'back_forward_menu_model.cc', + 'options_page_view.cc', + 'render_view_host.cc', # called using webkit identifiers + 'user_metrics.cc', # method definition + 'new_tab_ui.cc', # most visited clicks 1-9 +] + +def AddComputedActions(actions): + """Add computed actions to the actions list. + + Arguments: + actions: set of actions to add to. + """ + + # Actions for back_forward_menu_model.cc. + for dir in ['BackMenu_', 'ForwardMenu_']: + actions.add(dir + 'ShowFullHistory') + actions.add(dir + 'Popup') + for i in range(1, 20): + actions.add(dir + 'HistoryClick' + str(i)) + actions.add(dir + 'ChapterClick' + str(i)) + + # Actions for new_tab_ui.cc. + for i in range(1, 10): + actions.add('MostVisited%d' % i) + +def AddWebKitEditorActions(actions): + """Add editor actions from editor_client_impl.cc. + + Arguments: + actions: set of actions to add to. + """ + action_re = re.compile(r'''\{ [\w']+, +\w+, +"(.*)" +\},''') + + editor_file = os.path.join(path_utils.ScriptDir(), '..', '..', 'webkit', + 'glue', 'editor_client_impl.cc') + for line in open(editor_file): + match = action_re.search(line) + if match: # Plain call to RecordAction + actions.add(match.group(1)) + +def GrepForActions(path, actions): + """Grep a source file for calls to UserMetrics functions. + + Arguments: + path: path to the file + actions: set of actions to add to + """ + + action_re = re.compile(r'[> ]UserMetrics:?:?RecordAction\(L"(.*)"') + other_action_re = re.compile(r'[> ]UserMetrics:?:?RecordAction\(') + computed_action_re = re.compile(r'UserMetrics::RecordComputedAction') + for line in open(path): + match = action_re.search(line) + if match: # Plain call to RecordAction + actions.add(match.group(1)) + elif other_action_re.search(line): + # Warn if this file shouldn't be mentioning RecordAction. + if os.path.basename(path) != 'user_metrics.cc': + print >>sys.stderr, 'WARNING: %s has funny RecordAction' % path + elif computed_action_re.search(line): + # Warn if this file shouldn't be calling RecordComputedAction. + if os.path.basename(path) not in KNOWN_COMPUTED_USERS: + print >>sys.stderr, 'WARNING: %s has RecordComputedAction' % path + +def WalkDirectory(root_path, actions): + for path, dirs, files in os.walk(root_path): + if '.svn' in dirs: + dirs.remove('.svn') + for file in files: + ext = os.path.splitext(file)[1] + if ext == '.cc': + GrepForActions(os.path.join(path, file), actions) + +def main(argv): + actions = set() + AddComputedActions(actions) + AddWebKitEditorActions(actions) + + # Walk the source tree to process all .cc files. + chrome_root = os.path.join(path_utils.ScriptDir(), '..') + WalkDirectory(chrome_root, actions) + webkit_root = os.path.join(path_utils.ScriptDir(), '..', '..', 'webkit') + WalkDirectory(os.path.join(webkit_root, 'glue'), actions) + WalkDirectory(os.path.join(webkit_root, 'port'), actions) + + # Print out the actions as a sorted list. + for action in sorted(actions): + print action + +if '__main__' == __name__: + main(sys.argv) diff --git a/chrome/tools/extract_actions.sh b/chrome/tools/extract_actions.sh new file mode 100755 index 0000000..a5316d4 --- /dev/null +++ b/chrome/tools/extract_actions.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +system_root=`cygpath "$SYSTEMROOT"` +export PATH="/usr/bin:$system_root/system32:$system_root:$system_root/system32/WBEM" + +exec_dir=$(dirname $0) + +"$exec_dir/../../third_party/python_24/python.exe" \ + "$exec_dir/extract_actions.py" "$@" diff --git a/chrome/tools/extract_histograms.py b/chrome/tools/extract_histograms.py new file mode 100644 index 0000000..97008a2 --- /dev/null +++ b/chrome/tools/extract_histograms.py @@ -0,0 +1,56 @@ +#!/usr/bin/python +# Copyright 2007 Google Inc. All rights reserved. + +"""Extract UMA histogram strings from the Chrome source. + +This program generates the list of known histograms we expect to see in +the user behavior logs. It walks the Chrome source, looking for calls +to UMA histogram macros. + +Run it from the chrome/browser directory like: + extract_histograms.py > histogram_list +""" + +# TODO(evanm): get all the jankometer histogram names. + +__author__ = 'evanm (Evan Martin)' + +import os +import re +import sys + +def GrepForHistograms(path, histograms): + """Grep a source file for calls to histogram macros functions. + + Arguments: + path: path to the file + histograms: set of histograms to add to + """ + + histogram_re = re.compile(r'HISTOGRAM_\w+\(L"(.*)"') + for line in open(path): + match = histogram_re.search(line) + if match: + histograms.add(match.group(1)) + +def WalkDirectory(root_path, histograms): + for path, dirs, files in os.walk(root_path): + if '.svn' in dirs: + dirs.remove('.svn') + for file in files: + ext = os.path.splitext(file)[1] + if ext == '.cc': + GrepForHistograms(os.path.join(path, file), histograms) + +def main(argv): + histograms = set() + + # Walk the source tree to process all .cc files. + WalkDirectory('..', histograms) + + # Print out the histograms as a sorted list. + for histogram in sorted(histograms): + print histogram + +if '__main__' == __name__: + main(sys.argv) diff --git a/chrome/tools/history-viz.py b/chrome/tools/history-viz.py new file mode 100644 index 0000000..2f94d9a --- /dev/null +++ b/chrome/tools/history-viz.py @@ -0,0 +1,240 @@ +#!/usr/bin/python +# +# Copyright 2008 Google Inc. All Rights Reserved. + +"""Process a History database and dump a .dot file suitable for GraphViz. + +This is useful for debugging history redirect flows. + +An example run of this program: + python /src/history-viz.py History > foo.dot + /c/Program\ Files/Graphviz2.18/bin/dot -Tpng foo.dot -o foo.png +""" + +import struct +import subprocess +import sys +import urlparse + +class URL: + """Represents a broken-down URL from our most visited database.""" + + def __init__(self, id, url): + """Initialize a new URL object. |id| is the database id of the URL.""" + self.id = id + self.url = url + scheme, loc, path, query, fragment = urlparse.urlsplit(url) + if scheme == 'http': + scheme = '' # Shorten for display purposes. + if len(scheme) > 0: + scheme += '://' + self.host = scheme + loc + self.path = path + + extra = '' + if len(query) > 0: + extra += '?' + query + if len(fragment) > 0 or url.find('#') > 0: + extra += '#' + fragment + self.extra = extra + + def PrettyPrint(self, include_host=True, include_path=True): + """Pretty-print this URL in a form more suitable for the graph. + + This will elide very long paths and potentially puts newlines between parts + of long components. include_host and include_path determine whether to + include the host and path in the output. + + Returns: the pretty-printed string.""" + MAX_LEN = 30 # Maximum length of a line in the output. + parts = [] + if include_host: + parts.append(self.host) + if include_path: + parts.append(self.path) + parts.append(self.extra) + lines = [] + line = '' + for part in parts: + if len(part) > MAX_LEN: + part = part[0:MAX_LEN-3] + '...' + if len(line)+len(part) > MAX_LEN: + lines.append(line) + line = '' + line += part + if len(line) > 0: + lines.append(line) + return '\n'.join(lines) + +class Edge: + """Represents an edge in the history graph, connecting two pages. + + If a link is traversed twice, it is one Edge with two entries in + the .transitions array.""" + def __init__(self, src, dst): + self.src = src + self.dst = dst + self.transitions = [] + + def Transitions(self): + """Return a dictionary mapping transition type -> occurences.""" + all = {} + for trans in self.transitions: + all[trans] = all.get(trans, 0) + 1 + # We currently don't use the chain type. + # TODO(evanm): make this a command-line option. + # if trans & 0x30000000 != 0: + # chain = '' + # if trans & 0x10000000: + # chain = 'start' + # if trans & 0x20000000: + # if len(chain) == 0: + # chain = 'end' + # else: + # chain = '' + # if len(chain) > 0: + # edge['chain'] = chain + return all + +def ClusterBy(objs, pred): + """Group a list of objects by a predicate. + + Given a list of objects and a predicate over the objects, return a + dictionary mapping pred(obj) -> all objs with the same pred(obj).""" + clusters = {} + for obj in objs: + cluster = pred(obj) + clusters[cluster] = clusters.get(cluster, []) + clusters[cluster].append(obj) + return clusters + +def EscapeDot(str): + """Escape a string suitable for embedding in a graphviz graph.""" + # TODO(evanm): this is likely not sufficient. + return str.replace('\n', '\\n') + +class SQLite: + """Trivial interface to executing SQLite queries. + Spawns a new process with each call.""" + def __init__(self, file=None): + self.file = file + + def Run(self, sql): + """Execute |sql|, yielding each row of results as an array.""" + subproc = subprocess.Popen(['sqlite', self.file], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE) + subproc.stdin.write('.mode tabs\n') + subproc.stdin.write(sql + ';') + subproc.stdin.close() + for line in subproc.stdout: + row = line.strip().split('\t') + yield row + +def LoadHistory(filename): + db = SQLite(filename) + + urls = {} # Map of urlid => url. + urls['0'] = URL('0', 'start') # Node name '0' is our special 'start' node. + for id, url in db.Run('SELECT id, url FROM urls'): + urls[id] = URL(id, url) + + visiturlids = {} # Map of visitid => urlid. + visiturlids['0'] = '0' # '0' is our special 'start' node. + edges = {} # Map of urlid->urlid->Edge. + for src, dst, url, trans in db.Run('SELECT from_visit, id, url, transition ' + 'FROM visits ORDER BY id'): + visiturlids[dst] = url + src = visiturlids[src] + dst = visiturlids[dst] + edges[src] = edges.get(src, {}) + edge = edges[src][dst] = edges[src].get(dst, Edge(src, dst)) + # SQLite outputs transition values as signed integers, but they're really + # a bitfield. Below does "unsigned trans = static_cast<unsigned>(trans)". + trans = struct.unpack('I', struct.pack('i', int(trans)))[0] + edge.transitions.append(trans) + + return urls, edges + +# Some transition types, copied from page_transition_types.h. +TRANS_TYPES = { + 0: 'link', + 1: 'typed', + 2: 'most-visited', + 3: 'auto subframe', + 7: 'form', +} + +urls, edges = LoadHistory(sys.argv[1]) + +print 'digraph G {' +print ' graph [rankdir=LR]' # Display left to right. +print ' node [shape=box]' # Display nodes as boxes. +print ' subgraph { rank=source; 0 [label="start"] }' + +# Output all the nodes within graph clusters. +hosts = ClusterBy(urls.values(), lambda url: url.host) +for i, (host, urls) in enumerate(hosts.items()): + # Cluster all URLs under this host if it has more than one entry. + host_clustered = len(urls) > 1 + if host_clustered: + print 'subgraph clusterhost%d {' % i + print ' label="%s"' % host + paths = ClusterBy(urls, lambda url: url.path) + for j, (path, urls) in enumerate(paths.items()): + # Cluster all URLs under this host if it has more than one entry. + path_clustered = host_clustered and len(urls) > 1 + if path_clustered: + print ' subgraph cluster%d%d {' % (i, j) + print ' label="%s"' % path + for url in urls: + if url.id == '0': continue # We already output the special start node. + pretty = url.PrettyPrint(include_host=not host_clustered, + include_path=not path_clustered) + print ' %s [label="%s"]' % (url.id, EscapeDot(pretty)) + if path_clustered: + print ' }' + if host_clustered: + print '}' + +# Output all the edges between nodes. +for src, dsts in edges.items(): + for dst, edge in dsts.items(): + # Gather up all the transitions into the label. + label = [] # Label for the edge. + transitions = edge.Transitions() + for trans, count in transitions.items(): + text = '' + if count > 1: + text = '%dx ' % count + base_type = trans & 0xFF + redir = (trans & 0xC0000000) != 0 + start = (trans & 0x10000000) != 0 + end = (trans & 0x20000000) != 0 + if start or end: + if start: + text += '<' + if end: + text += '>' + text += ' ' + if redir: + text += 'R ' + text += TRANS_TYPES.get(base_type, 'trans%d' % base_type) + label.append(text) + if len(label) == 0: + continue + + edgeattrs = [] # Graphviz attributes for the edge. + # If the edge is from the start and the transitions are fishy, make it + # display as a dotted line. + if src == '0' and len(transitions.keys()) == 1 and transitions.has_key(0): + edgeattrs.append('style=dashed') + if len(label) > 0: + edgeattrs.append('label="%s"' % EscapeDot('\n'.join(label))) + + out = '%s -> %s' % (src, dst) + if len(edgeattrs) > 0: + out += ' [%s]' % ','.join(edgeattrs) + print out +print '}' + diff --git a/chrome/tools/icudt38.dll b/chrome/tools/icudt38.dll Binary files differnew file mode 100644 index 0000000..b5b40f5 --- /dev/null +++ b/chrome/tools/icudt38.dll diff --git a/chrome/tools/inconsistent-eol.py b/chrome/tools/inconsistent-eol.py new file mode 100644 index 0000000..9f8ef64 --- /dev/null +++ b/chrome/tools/inconsistent-eol.py @@ -0,0 +1,130 @@ +#!/bin/env python +# Copyright 2008, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +"""Find and fix files with inconsistent line endings. + +This script requires 'dos2unix.exe' and 'unix2dos.exe' from Cygwin; they +must be in the user's PATH. + +Arg: a file containing a list of relative or absolute file paths. The + argument passed to this script, as well as the paths in the file, may be + relative paths or absolute Windows-style paths (with either type of + slash). The list might be generated with 'find -type f' or extracted from + a gvn changebranch listing, for example. +""" + +import errno +import logging +import subprocess +import sys + + +# Whether to produce excessive debugging output for each file in the list. +DEBUGGING = False + + +class Error(Exception): + """Local exception class.""" + pass + + +def CountChars(text, str): + """Count the number of instances of the given string in the text.""" + split = text.split(str) + logging.debug(len(split) - 1) + return len(split) - 1 + +def PrevailingEOLName(crlf, cr, lf): + """Describe the most common line ending. + + Args: + crlf: How many CRLF (\r\n) sequences are in the file. + cr: How many CR (\r) characters are in the file, excluding CRLF sequences. + lf: How many LF (\n) characters are in the file, excluding CRLF sequences. + + Returns: + A string describing the most common of the three line endings. + """ + most = max(crlf, cr, lf) + if most == cr: + return 'cr' + if most == crlf: + return 'crlf' + return 'lf' + +def FixEndings(file, crlf, cr, lf): + """Change the file's line endings to CRLF or LF, whichever is more common.""" + most = max(crlf, cr, lf) + if most == crlf: + result = subprocess.call('unix2dos.exe %s' % file, shell=True) + if result: + raise Error('Error running unix2dos.exe %s' % file) + else: + result = subprocess.call('dos2unix.exe %s' % file, shell=True) + if result: + raise Error('Error running dos2unix.exe %s' % file) + + +def main(argv=None): + """Process the list of files.""" + if not argv or len(argv) < 2: + raise Error('No file list given.') + + for filename in open(argv[1], 'r'): + filename = filename.strip() + logging.debug(filename) + try: + # Open in binary mode to preserve existing line endings. + text = open(filename, 'rb').read() + except IOError, e: + if e.errno != errno.ENOENT: + raise + logging.warning('File %s not found.' % filename) + continue + crlf = CountChars(text, '\r\n') + cr = CountChars(text, '\r') - crlf + lf = CountChars(text, '\n') - crlf + + if ((crlf > 0 and cr > 0) or + (crlf > 0 and lf > 0) or + ( lf > 0 and cr > 0)): + print ('%s: mostly %s' % (filename, PrevailingEOLName(crlf, cr, lf))) + FixEndings(filename, crlf, cr, lf) + +if '__main__' == __name__: + if DEBUGGING: + debug_level = logging.DEBUG + else: + debug_level = logging.INFO + logging.basicConfig(level=debug_level, + format='%(asctime)s %(levelname)-7s: %(message)s', + datefmt='%H:%M:%S') + + sys.exit(main(sys.argv)) diff --git a/chrome/tools/optipng.exe b/chrome/tools/optipng.exe Binary files differnew file mode 100644 index 0000000..85c993a --- /dev/null +++ b/chrome/tools/optipng.exe diff --git a/chrome/tools/profiles/generate_profile.cc b/chrome/tools/profiles/generate_profile.cc new file mode 100644 index 0000000..43927b67 --- /dev/null +++ b/chrome/tools/profiles/generate_profile.cc @@ -0,0 +1,238 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// This program generates a user profile and history by randomly generating +// data and feeding it to the history service. + +#include "chrome/tools/profiles/thumbnail-inl.h" + +#include "base/icu_util.h" +#include "base/message_loop.h" +#include "base/path_service.h" +#include "base/string_util.h" +#include "base/time.h" +#include "chrome/browser/history/history.h" +#include "chrome/common/jpeg_codec.h" +#include "chrome/common/thumbnail_score.h" +#include "SkBitmap.h" + +// Probabilities of different word lengths, as measured from Darin's profile. +// kWordLengthProbabilities[n-1] = P(word of length n) +const float kWordLengthProbabilities[] = { 0.069f, 0.132f, 0.199f, + 0.137f, 0.088f, 0.115f, 0.081f, 0.055f, 0.034f, 0.021f, 0.019f, 0.018f, + 0.007f, 0.007f, 0.005f, 0.004f, 0.003f, 0.003f, 0.003f }; + +// Return a float uniformly in [0,1]. +// Useful for making probabilistic decisions. +float RandomFloat() { + return rand() / static_cast<float>(RAND_MAX); +} + +// Return an integer uniformly in [min,max). +int RandomInt(int min, int max) { + return min + (rand() % (max-min)); +} + +// Return a string of |count| lowercase random characters. +std::wstring RandomChars(int count) { + std::wstring str; + for (int i = 0; i < count; ++i) + str += L'a' + rand() % 26; + return str; +} + +std::wstring RandomWord() { + // TODO(evanm): should we instead use the markov chain based + // version of this that I already wrote? + + // Sample a word length from kWordLengthProbabilities. + float sample = RandomFloat(); + int i; + for (i = 0; i < arraysize(kWordLengthProbabilities); ++i) { + sample -= kWordLengthProbabilities[i]; + if (sample < 0) break; + } + const int word_length = i + 1; + return RandomChars(word_length); +} + +// Return a string of |count| random words. +std::wstring RandomWords(int count) { + std::wstring str; + for (int i = 0; i < count; ++i) { + if (!str.empty()) + str += L' '; + str += RandomWord(); + } + return str; +} + +// Return a random URL-looking string. +GURL ConstructRandomURL() { + return GURL(std::wstring(L"http://") + RandomChars(3) + L".com/" + + RandomChars(RandomInt(5,20))); +} + +// Return a random page title-looking string. +std::wstring ConstructRandomTitle() { + return RandomWords(RandomInt(3, 15)); +} + +// Return a random string that could function as page contents. +std::wstring ConstructRandomPage() { + return RandomWords(RandomInt(10, 4000)); +} + +// Insert a batch of |batch_size| URLs, starting at pageid |page_id|. +// When history_only is set, we will not generate thumbnail or full text data. +void InsertURLBatch(const std::wstring& profile_dir, int page_id, + int batch_size, bool history_only) { + scoped_refptr<HistoryService> history_service(new HistoryService); + if (!history_service->Init(profile_dir)) { + printf("Could not init the history service\n"); + exit(1); + } + + // Probability of following a link on the current "page" + // (vs randomly jumping to a new page). + const float kFollowLinkProbability = 0.85f; + // Probability of visiting a page we've visited before. + const float kRevisitLinkProbability = 0.1f; + // Probability of a URL being "good enough" to revisit. + const float kRevisitableURLProbability = 0.05f; + // Probability of a URL being the end of a redirect chain. + const float kRedirectProbability = 0.05f; + + // A list of URLs that we sometimes revisit. + std::vector<GURL> revisit_urls; + + // Scoping value for page IDs (required by the history service). + void* id_scope = reinterpret_cast<void*>(1); + + printf("Inserting %d URLs...\n", batch_size); + GURL previous_url; + PageTransition::Type transition = PageTransition::TYPED; + const int end_page_id = page_id + batch_size; + for (; page_id < end_page_id; ++page_id) { + // Randomly decide whether this new URL simulates following a link or whether + // it's a jump to a new URL. + if (!previous_url.is_empty() && RandomFloat() < kFollowLinkProbability) { + transition = PageTransition::LINK; + } else { + previous_url = GURL(); + transition = PageTransition::TYPED; + } + + // Pick a URL, either newly at random or from our list of previously + // visited URLs. + GURL url; + if (!revisit_urls.empty() && RandomFloat() < kRevisitLinkProbability) { + // Draw a URL from revisit_urls at random. + url = revisit_urls[RandomInt(0, static_cast<int>(revisit_urls.size()))]; + } else { + url = ConstructRandomURL(); + } + + // Randomly construct a redirect chain. + HistoryService::RedirectList redirects; + if (RandomFloat() < kRedirectProbability) { + const int redir_count = RandomInt(1, 4); + for (int i = 0; i < redir_count; ++i) + redirects.push_back(ConstructRandomURL()); + redirects.push_back(url); + } + + // Add all of this information to the history service. + history_service->AddPage(url, + id_scope, page_id, + previous_url, transition, + redirects); + ThumbnailScore score(0.75, false, false); + history_service->SetPageTitle(url, ConstructRandomTitle()); + if (!history_only) { + history_service->SetPageContents(url, ConstructRandomPage()); + if (RandomInt(0, 2) == 0) { + scoped_ptr<SkBitmap> bitmap( + JPEGCodec::Decode(kGoogleThumbnail, sizeof(kGoogleThumbnail))); + history_service->SetPageThumbnail(url, *bitmap, score); + } else { + scoped_ptr<SkBitmap> bitmap( + JPEGCodec::Decode(kWeewarThumbnail, sizeof(kWeewarThumbnail))); + history_service->SetPageThumbnail(url, *bitmap, score); + } + } + + previous_url = url; + + if (revisit_urls.empty() || RandomFloat() < kRevisitableURLProbability) + revisit_urls.push_back(url); + } + + printf("Letting the history service catch up...\n"); + history_service->SetOnBackendDestroyTask(new MessageLoop::QuitTask); + history_service->Cleanup(); + history_service = NULL; + MessageLoop::current()->Run(); +} + +int main(int argc, const char* argv[]) { + int next_arg = 1; + bool history_only = false; + if (strcmp(argv[next_arg], "--history-only") == 0) { + history_only = true; + next_arg++; + } + + // We require two arguments: urlcount and profiledir. + if (argc - next_arg < 2) { + printf("usage: %s [--history-only] <urlcount> <profiledir>\n", argv[0]); + printf("\n --history-only Generate only history, not thumbnails or full"); + printf("\n text index data.\n\n"); + return -1; + } + + const int url_count = atoi(argv[next_arg++]); + std::wstring profile_dir = UTF8ToWide(argv[next_arg++]); + + MessageLoop main_message_loop; + + srand(static_cast<unsigned int>(Time::Now().ToInternalValue())); + icu_util::Initialize(); + + // The maximum number of URLs to insert into history in one batch. + const int kBatchSize = 2000; + int page_id = 0; + while (page_id < url_count) { + const int batch_size = std::min(kBatchSize, url_count - page_id); + InsertURLBatch(profile_dir, page_id, batch_size, history_only); + page_id += batch_size; + } + + return 0; +} diff --git a/chrome/tools/profiles/generate_profile.vcproj b/chrome/tools/profiles/generate_profile.vcproj new file mode 100644 index 0000000..078e4c1 --- /dev/null +++ b/chrome/tools/profiles/generate_profile.vcproj @@ -0,0 +1,161 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioProject + ProjectType="Visual C++" + Version="8.00" + Name="generate_profile" + ProjectGUID="{2E969AE9-7B12-4EDB-8E8B-48C7AE7BE357}" + RootNamespace="generate_profile" + Keyword="Win32Proj" + > + <Platforms> + <Platform + Name="Win32" + /> + </Platforms> + <ToolFiles> + </ToolFiles> + <Configurations> + <Configuration + Name="Debug|Win32" + ConfigurationType="1" + InheritedPropertySheets="$(SolutionDir)..\build\common.vsprops;$(SolutionDir)..\build\debug.vsprops;$(SolutionDir)..\third_party\icu38\build\using_icu.vsprops;..\..\..\skia\using_skia.vsprops" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + AdditionalIncludeDirectories="..\..\.." + PreprocessorDefinitions="PERF_TEST" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLinkerTool" + AdditionalDependencies="shlwapi.lib rpcrt4.lib winmm.lib" + SubSystem="1" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCManifestTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCAppVerifierTool" + /> + <Tool + Name="VCWebDeploymentTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + <Configuration + Name="Release|Win32" + ConfigurationType="1" + InheritedPropertySheets="$(SolutionDir)..\build\common.vsprops;$(SolutionDir)..\build\release.vsprops;$(SolutionDir)..\third_party\icu38\build\using_icu.vsprops;..\..\..\skia\using_skia.vsprops" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + AdditionalIncludeDirectories="..\..\.." + PreprocessorDefinitions="PERF_TEST" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLinkerTool" + AdditionalDependencies="shlwapi.lib rpcrt4.lib winmm.lib" + SubSystem="1" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCManifestTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCAppVerifierTool" + /> + <Tool + Name="VCWebDeploymentTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + </Configurations> + <References> + </References> + <Files> + <File + RelativePath=".\generate_profile.cc" + > + </File> + <File + RelativePath=".\thumbnail-inl.h" + > + </File> + </Files> + <Globals> + </Globals> +</VisualStudioProject> diff --git a/chrome/tools/profiles/thumbnail-inl.h b/chrome/tools/profiles/thumbnail-inl.h new file mode 100644 index 0000000..18ec4a8 --- /dev/null +++ b/chrome/tools/profiles/thumbnail-inl.h @@ -0,0 +1,807 @@ +const unsigned char kGoogleThumbnail[] = +"\xff\xd8\xff\xe0\x00\x10\x4a\x46\x49\x46\x00\x01\x01\x00\x00\x01\x00\x01\x00" +"\x00\xff\xdb\x00\x43\x00\x03\x02\x02\x03\x02\x02\x03\x03\x03\x03\x04\x03\x03" +"\x04\x05\x08\x05\x05\x04\x04\x05\x0a\x07\x07\x06\x08\x0c\x0a\x0c\x0c\x0b\x0a" +"\x0b\x0b\x0d\x0e\x12\x10\x0d\x0e\x11\x0e\x0b\x0b\x10\x16\x10\x11\x13\x14\x15" +"\x15\x15\x0c\x0f\x17\x18\x16\x14\x18\x12\x14\x15\x14\xff\xdb\x00\x43\x01\x03" +"\x04\x04\x05\x04\x05\x09\x05\x05\x09\x14\x0d\x0b\x0d\x14\x14\x14\x14\x14\x14" +"\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14" +"\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14" +"\x14\x14\x14\x14\x14\x14\xff\xc0\x00\x11\x08\x00\x88\x00\xc4\x03\x01\x22\x00" +"\x02\x11\x01\x03\x11\x01\xff\xc4\x00\x1f\x00\x00\x01\x05\x01\x01\x01\x01\x01" +"\x01\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a" +"\x0b\xff\xc4\x00\xb5\x10\x00\x02\x01\x03\x03\x02\x04\x03\x05\x05\x04\x04\x00" +"\x00\x01\x7d\x01\x02\x03\x00\x04\x11\x05\x12\x21\x31\x41\x06\x13\x51\x61\x07" +"\x22\x71\x14\x32\x81\x91\xa1\x08\x23\x42\xb1\xc1\x15\x52\xd1\xf0\x24\x33\x62" +"\x72\x82\x09\x0a\x16\x17\x18\x19\x1a\x25\x26\x27\x28\x29\x2a\x34\x35\x36\x37" +"\x38\x39\x3a\x43\x44\x45\x46\x47\x48\x49\x4a\x53\x54\x55\x56\x57\x58\x59\x5a" +"\x63\x64\x65\x66\x67\x68\x69\x6a\x73\x74\x75\x76\x77\x78\x79\x7a\x83\x84\x85" +"\x86\x87\x88\x89\x8a\x92\x93\x94\x95\x96\x97\x98\x99\x9a\xa2\xa3\xa4\xa5\xa6" +"\xa7\xa8\xa9\xaa\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xc2\xc3\xc4\xc5\xc6\xc7" +"\xc8\xc9\xca\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xe1\xe2\xe3\xe4\xe5\xe6\xe7" +"\xe8\xe9\xea\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xff\xc4\x00\x1f\x01\x00" +"\x03\x01\x01\x01\x01\x01\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x01\x02\x03" +"\x04\x05\x06\x07\x08\x09\x0a\x0b\xff\xc4\x00\xb5\x11\x00\x02\x01\x02\x04\x04" +"\x03\x04\x07\x05\x04\x04\x00\x01\x02\x77\x00\x01\x02\x03\x11\x04\x05\x21\x31" +"\x06\x12\x41\x51\x07\x61\x71\x13\x22\x32\x81\x08\x14\x42\x91\xa1\xb1\xc1\x09" +"\x23\x33\x52\xf0\x15\x62\x72\xd1\x0a\x16\x24\x34\xe1\x25\xf1\x17\x18\x19\x1a" +"\x26\x27\x28\x29\x2a\x35\x36\x37\x38\x39\x3a\x43\x44\x45\x46\x47\x48\x49\x4a" +"\x53\x54\x55\x56\x57\x58\x59\x5a\x63\x64\x65\x66\x67\x68\x69\x6a\x73\x74\x75" +"\x76\x77\x78\x79\x7a\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x92\x93\x94\x95\x96" +"\x97\x98\x99\x9a\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xb2\xb3\xb4\xb5\xb6\xb7" +"\xb8\xb9\xba\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xd2\xd3\xd4\xd5\xd6\xd7\xd8" +"\xd9\xda\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9" +"\xfa\xff\xda\x00\x0c\x03\x01\x00\x02\x11\x03\x11\x00\x3f\x00\xfd\x26\x9b\xc1" +"\x57\xf2\x78\x99\xf5\x64\xf1\x46\xb3\x14\x0d\x32\x4b\xfd\x98\xb2\xc4\x6d\x80" +"\x50\x99\x40\x0c\x65\x82\x9d\xac\x4f\xcd\x9f\x9c\xf2\x00\x18\x87\x50\xf0\x6e" +"\xbd\x75\xa8\x49\x3d\xbf\x8b\xaf\x6c\xe1\x79\x1d\xc5\xba\xc7\x1b\x2a\x82\x72" +"\x14\x12\x33\x81\xd3\xe9\xf9\xd7\x67\x45\x6d\x4e\xac\xa9\x3b\xc6\xdf\x34\x9f" +"\xe6\x26\xae\x72\xfa\xdf\x86\x35\x6d\x52\x78\x24\xb5\xf1\x15\xde\x98\x23\x8d" +"\x11\x96\x15\x56\x0e\x54\xe4\xb1\x04\x63\x27\xa1\xe3\x18\xa6\xeb\x9e\x15\xd6" +"\x35\x4b\x98\x25\xb4\xf1\x2d\xe6\x96\x12\x21\x1b\xc7\x02\xa3\x2c\x87\x8f\x98" +"\xee\x53\x83\xc7\x6f\x53\x5d\x55\x15\x6b\x11\x38\xda\xd6\xd3\xc9\x7f\x90\xac" +"\x72\x5a\xd7\x84\x75\x9d\x46\xfd\x6e\x6d\x3c\x4f\x7b\xa6\xa8\x45\x46\x82\x25" +"\x56\x8d\x88\x18\x27\x04\x70\x4f\x5e\x2a\x4d\x6b\xc2\xfa\xbe\xa5\x72\x65\xb6" +"\xf1\x25\xde\x9c\x86\x15\x8b\xcb\x85\x51\x86\xe1\x9c\xbf\x2b\xd4\xe7\xe9\xc7" +"\x4a\xea\x68\xa1\x62\x2a\x2b\x6d\xa7\x92\xff\x00\x20\xe5\x47\x35\xaa\x78\x6f" +"\x56\xbe\xb8\x8e\x4b\x7f\x10\xdd\x58\xa2\xc6\xa8\x63\x8d\x51\x83\x11\xd5\xb9" +"\x1d\x4d\x6d\xd9\x5b\x4b\x67\x01\x8d\xa5\x7b\x83\xbd\xdf\x7c\xcf\x96\xf9\x98" +"\xb6\xde\x00\xe0\x67\x03\xd8\x0e\xbd\x6a\xd5\x15\x9c\xaa\x4a\x49\x45\xf4\xf2" +"\x43\xb5\x86\x66\x4f\xee\xaf\xfd\xf5\xff\x00\xd6\xa3\x32\x7f\x75\x7f\xef\xaf" +"\xfe\xb5\x3e\x8a\xcc\x63\x33\x27\xf7\x57\xfe\xfa\xff\x00\xeb\x51\x99\x3f\xba" +"\xbf\xf7\xd7\xff\x00\x5a\x9f\x45\x00\x33\x32\x7f\x75\x7f\xef\xaf\xfe\xb5\x19" +"\x93\xfb\xab\xff\x00\x7d\x7f\xf5\xab\x90\xf8\xbb\xf1\x1a\x0f\x85\x3f\x0f\xf5" +"\x4f\x12\x4d\x01\xbb\x7b\x60\x91\xc1\x6c\x1b\x6f\x9b\x33\xb8\x48\xd7\x3d\x81" +"\x66\x19\x3e\x99\xac\x9b\xcf\x0b\x7c\x44\xb8\xf0\xfc\x33\xda\x78\xe2\x3b\x6d" +"\x77\x0b\x23\xc1\x26\x9b\x03\xd9\x13\xc1\x64\xc6\xdf\x33\x1d\x46\x77\x66\xb7" +"\x8d\x26\xe2\xa7\x26\x92\x6e\xda\xdf\xa6\xfb\x5f\xba\x33\x73\xd5\xc5\x6a\xd1" +"\xe8\xb9\x93\xfb\xab\xff\x00\x7d\x7f\xf5\xa8\xcc\x9f\xdd\x5f\xfb\xeb\xff\x00" +"\xad\x5c\x37\x89\xbe\x33\xf8\x77\xc2\x4f\x7e\x2f\x5a\xea\xe2\xdf\x4b\x64\x8f" +"\x54\xbd\xb1\xb7\x33\x41\x60\xcc\x01\xfd\xf1\x5e\x57\x00\x86\x38\x07\x68\x20" +"\x9c\x66\xb5\xf5\x3f\x88\x5a\x3e\x99\x2c\x88\x5e\x6b\xb5\x86\x24\x9e\x79\x6c" +"\xe2\x33\x24\x31\xb8\xca\xb3\x15\xec\x47\x3c\x67\x8e\x7a\x11\x5e\x7d\x4c\x4d" +"\x1a\x49\xb9\xc9\x2b\x7f\xc1\xff\x00\x27\xf7\x3e\xc7\x63\xc3\x56\x49\x49\xc5" +"\xd9\xed\xf8\x7f\x9a\xfb\xd7\x73\xa2\xcc\x9f\xdd\x5f\xfb\xeb\xff\x00\xad\x46" +"\x64\xfe\xea\xff\x00\xdf\x5f\xfd\x6a\xf3\xff\x00\x19\xfc\x77\xf0\x8f\x81\xec" +"\x65\xbc\xbc\xbc\x96\xf2\xd2\x04\x85\xe7\xb8\xb0\x8f\xce\x8a\x15\x97\xfd\x51" +"\x91\xc1\xda\x81\xb8\x39\x24\x00\x08\x24\x80\x46\x79\xff\x00\x0c\x7c\x55\x96" +"\xcb\xc6\x3e\x3e\x8b\x59\xbd\xb8\xbb\xb4\xb7\xd5\x2d\x2c\xb4\x9d\x3d\x62\x4f" +"\x3b\x74\x96\x8b\x33\x46\xa0\x63\x27\x97\x62\x58\xf0\x14\xf3\xc5\x74\xd4\xbd" +"\x28\xc2\x72\x5a\x4b\x67\xf2\xbf\xf9\x7d\xe8\xe3\x53\x52\xa8\xa9\x47\x56\xff" +"\x00\xc9\xbf\xd1\x9e\xc1\x99\x3f\xba\xbf\xf7\xd7\xff\x00\x5a\x8c\xc9\xfd\xd5" +"\xff\x00\xbe\xbf\xfa\xd5\xce\x78\x1b\xe2\x0e\x97\xf1\x0f\x4e\x37\xda\x38\xb8" +"\x7b\x65\x79\x21\x95\xa6\x8f\x61\x8a\x64\x6d\xaf\x13\x02\x72\x18\x11\x9f\x42" +"\x08\x20\x9c\xd7\x4d\x52\xa4\xa4\xae\xb6\x37\x9c\x25\x4e\x4e\x13\x56\x68\x66" +"\x64\xfe\xea\xff\x00\xdf\x5f\xfd\x6a\x33\x27\xf7\x57\xfe\xfa\xff\x00\xeb\x53" +"\xe8\xaa\x20\x66\x64\xfe\xea\xff\x00\xdf\x5f\xfd\x6a\xe7\xfc\x4b\xe1\x9d\x47" +"\x5e\x9a\x27\xb5\xf1\x16\xa5\xa1\x84\x00\x14\xb0\x30\x95\x7c\x12\x79\xf3\x22" +"\x63\xdc\x74\x23\xa0\xf7\xcf\x47\x45\x20\x38\x99\x3c\x07\xad\x3b\x96\x4f\x1c" +"\x6b\xb1\x03\x8f\x95\x45\xa9\x1d\x31\xfc\x56\xe6\x8a\xed\xa8\xa7\x71\x58\x28" +"\xa2\x8a\x43\x0a\x28\xa2\x80\x0a\x28\xa2\x80\x0a\x28\xa2\x80\x0a\x28\xa2\x80" +"\x0a\x2a\x87\xfc\x24\x1a\x5e\x58\x7f\x69\x5a\x65\x49\x52\x3c\xf5\xe0\x83\x82" +"\x3a\xf5\x06\x8f\xed\xfd\x2f\xfe\x82\x56\x9f\xf7\xfd\x7f\xc6\xa7\x99\x77\x1d" +"\x99\xcd\x7c\x61\xf8\x63\x65\xf1\x83\xe1\xe6\xad\xe1\x5b\xdb\x89\x2c\xd6\xf1" +"\x55\xa2\xba\x88\x65\xe0\x95\x18\x3c\x6e\x07\x7c\x32\x8c\x8e\xe3\x23\x8c\xd7" +"\x09\xae\x69\x5f\x17\xbc\x43\xe0\x3b\xaf\x0f\x4f\x6d\x61\xa7\x6b\xc9\x6c\xd0" +"\xdb\x78\x97\x48\xd6\xa4\x85\x1e\x5d\xa5\x56\x66\x87\xcb\xdc\x3f\xbc\x53\x71" +"\x19\xef\x5e\xc1\xfd\xbf\xa5\xff\x00\xd0\x4a\xd3\xfe\xff\x00\xaf\xf8\xd1\xfd" +"\xbf\xa5\xff\x00\xd0\x4a\xd3\xfe\xff\x00\xaf\xf8\xd7\x5d\x3c\x5c\xa9\xc5\x42" +"\xe9\xa4\xee\xaf\xd1\xe9\xf9\xd9\x5d\x6c\x63\x2a\x2a\x4d\xcb\xbe\x87\x8c\x69" +"\x7f\x06\xbc\x53\xe1\x7b\x5f\x1e\xe9\x56\x37\x76\x5a\xa6\x9d\xe3\x3d\xd7\x13" +"\xdc\xdf\xbb\x79\x96\x57\x12\x5b\xac\x12\xe5\x71\xfb\xd4\x21\x03\x0e\x54\xf6" +"\x39\xeb\x5d\x1e\x83\xf0\xe3\x59\xf0\x14\x37\x9a\x76\x88\x6d\xb5\x1d\x32\xf3" +"\x4f\xb6\xb4\x06\xf2\x42\xaf\x04\x91\x40\x21\xde\x46\x0e\xf5\x65\x0a\x71\x90" +"\x46\x2b\xd1\x3f\xb7\xf4\xbf\xfa\x09\x5a\x7f\xdf\xf5\xff\x00\x1a\x3f\xb7\xf4" +"\xbf\xfa\x09\x5a\x7f\xdf\xf5\xff\x00\x1a\xf1\xea\x61\x68\x54\x49\x4b\xa7\x9f" +"\x74\xd3\xfc\x1b\xfe\x92\x3d\x3a\x98\xba\xf5\x7e\x27\xf8\x7a\x6b\xeb\xa2\xfe" +"\x9b\x3e\x39\xf1\x77\x82\xa5\xd2\x3c\x2b\xe3\x2b\x7f\x0a\xda\x5e\x78\x9b\xc3" +"\xfa\x60\x83\x4d\xf1\xad\xac\xf7\x51\xc0\x97\xff\x00\x66\x89\x5e\x63\x6f\xb8" +"\x12\x8e\x10\xa0\x6e\x40\x21\x48\xda\x7a\x9f\x41\xd6\x7e\x06\xeb\x9e\x2e\xb9" +"\xd4\x3c\x4f\xa3\xcb\xa6\x32\xdd\x6a\xb6\x7a\xfe\x97\x67\xa8\xa3\x34\x53\xc4" +"\x2c\xc4\x0f\x6f\x70\xb8\xf9\x77\x23\x13\x91\x9c\x1c\x71\x5e\xaf\x7f\xe0\x4f" +"\x87\xba\x96\xbf\x71\xad\x5c\x5a\x69\x6f\xa9\xdc\x10\x67\x9b\xcf\x03\xce\x20" +"\x60\x17\x50\xd8\x63\x80\x39\x20\xf4\xae\xb1\x35\xcd\x2a\x34\x55\x5d\x46\xcd" +"\x55\x46\x00\x13\xa6\x00\xfc\xeb\xd4\xc5\x4e\x85\x7a\x34\xe8\xa5\x65\x0d\xb5" +"\xf2\x49\x2d\xfa\x5b\x47\xa6\x96\x5b\x24\x79\xf8\x75\x53\x0f\x5f\xeb\x09\xfb" +"\xca\xff\x00\x8d\xd3\xfb\xd3\x7a\x7c\xf7\x2b\x78\x46\x1b\xe8\x74\x38\x57\x51" +"\xd3\x6c\xf4\x9b\xb3\xcb\xda\xd8\xc9\xe6\x46\x9f\xf0\x2d\xab\x9f\xae\x2b\x6a" +"\xa8\x7f\x6f\xe9\x7f\xf4\x12\xb4\xff\x00\xbf\xeb\xfe\x34\x7f\x6f\xe9\x7f\xf4" +"\x12\xb4\xff\x00\xbf\xeb\xfe\x35\x82\x92\xee\x68\xee\xdd\xec\x5f\xa2\xa8\x7f" +"\x6f\xe9\x7f\xf4\x12\xb4\xff\x00\xbf\xeb\xfe\x34\xb2\xeb\xba\x6c\x0c\x16\x4d" +"\x46\xd2\x36\x2a\x1c\x06\x9d\x41\x2a\x7a\x1e\xbd\x3d\xea\xa3\xef\x3b\x47\x52" +"\x76\x2f\x51\x59\xdf\xf0\x91\x69\x3c\x7f\xc4\xce\xcf\x9e\x9f\xe9\x09\xcf\xeb" +"\x57\xa1\x9a\x3b\x88\x92\x58\x9d\x65\x8d\xc6\x55\xd0\xe4\x30\xf5\x06\xa9\xc6" +"\x51\xdd\x00\xfa\x28\xa2\xa4\x02\x8a\x28\xa0\x02\x8a\x28\xa0\x02\x8a\x28\xa0" +"\x02\x8a\x28\xa0\x02\x8a\x28\xa0\x0c\xdb\x1d\x36\xe3\x4e\xb7\xf2\x21\xb8\x88" +"\xc6\x1d\xd8\x79\x90\x92\x7e\x66\x2c\x79\x0c\x3d\x6a\xc7\x97\x7b\xff\x00\x3f" +"\x10\x7f\xdf\x86\xff\x00\xe2\xea\xd5\x15\x1c\x8b\xfa\x6c\xae\x66\x63\xeb\x9a" +"\x8d\xde\x89\xa4\xdc\xdf\x3c\x90\xca\xb0\xae\xe2\x8b\x03\x64\xf3\x8f\xef\xd7" +"\x93\xcf\xfb\x4f\x68\xf6\x90\x07\xba\x59\xad\x98\xbf\x96\x23\x7b\x09\x49\x2d" +"\x9c\x60\x15\x24\x37\xaf\xca\x4f\x1c\xd7\xb4\x5d\x5b\x5b\xea\x16\xcd\x0c\xe8" +"\x93\xc1\x20\xc9\x46\xe4\x30\x07\x3f\xe1\x59\xbf\xf0\x86\xe8\x59\xc7\xf6\x5d" +"\xb6\x7f\xdc\x15\x2e\x1d\x9f\xe2\x35\x23\xcb\xcf\xed\x25\xa7\x47\x34\x91\xcd" +"\x05\xc4\x0c\xb2\x79\x6b\x9d\x3e\x57\xdf\xe8\x46\xc2\x70\x38\x3d\x70\x78\xe9" +"\xd2\xaf\x0f\x8f\x11\x1d\x1e\x3d\x4c\x59\xcc\x6d\x9d\xc2\x60\x5a\x3f\x98\xbf" +"\x2b\x31\x25\x37\x67\x00\x29\xe8\x0e\x78\xc6\x72\x2b\xd0\x5b\xc2\x1a\x0a\x0c" +"\xb6\x9b\x6a\xa3\xdd\x05\x3c\x78\x43\x44\x31\xec\x1a\x65\xb6\xdc\xee\xc0\x41" +"\xd6\x97\x23\xee\x1c\xde\x47\x97\x4f\xfb\x47\xd8\xdb\x24\x6d\x2d\xa5\xd2\x79" +"\xa4\x79\x43\xfb\x3e\x52\x64\x53\xfc\x43\x07\x81\xc1\xe1\xb0\x78\xe9\xd2\x9a" +"\x9f\xb4\xae\x93\x34\x12\x4d\x1e\xf9\x23\x43\xb7\xe5\xb2\x97\x71\x6c\xb0\x2b" +"\xb4\x9c\xe4\x15\xe7\x20\x01\x91\xcd\x7a\x87\xfc\x21\xfa\x0e\x14\xff\x00\x66" +"\xda\xfc\xdd\x3e\x41\xcd\x1f\xf0\x87\xe8\x43\xfe\x61\x96\xbd\x71\xf7\x05\x1c" +"\x8f\xb8\x73\x79\x18\x7e\x02\xf8\x80\x7e\x20\x2d\xdc\x96\x45\x23\x86\x05\x8d" +"\xb7\x4b\x6c\xe8\x49\x62\xe3\x1b\x59\x81\x18\xd9\xdc\x77\xae\xb7\xcb\xbd\xff" +"\x00\x9f\x88\x3f\xef\xc3\x7f\xf1\x75\x1e\x9d\xa2\xd8\x68\xe6\x43\x65\x69\x15" +"\xa9\x97\x01\xcc\x6b\x82\xd8\xce\x33\xf4\xc9\xfc\xea\xf5\x52\x82\xeb\xf9\xb1" +"\x73\x15\x7c\xbb\xdf\xf9\xf8\x83\xfe\xfc\x37\xff\x00\x17\x5e\x69\xe3\xef\x04" +"\x6a\x9a\xae\xb9\x1d\xc5\x9e\x8d\x6b\xa8\xa0\x8d\x10\xcf\x2a\xc2\x4e\x46\x73" +"\xc4\x84\x90\x39\xed\xfa\xe2\xbd\x56\x8a\xeb\xc3\xd6\x78\x69\xfb\x48\x6a\xfc" +"\xef\xfe\x64\xcb\xde\x56\x67\x8e\xe8\x7e\x08\xd4\x2c\x60\x91\xaf\x3c\x0d\xa5" +"\x5f\x3b\x33\x05\xb7\x96\x3b\x55\x8e\x30\x08\xc3\x29\x00\xfd\xee\x49\xe3\xd3" +"\xf0\xf5\xeb\x7b\x78\xad\x20\x48\x60\x89\x21\x85\x06\xd4\x8e\x35\x0a\xaa\x3d" +"\x00\x1d\x2a\x4a\x2b\x4c\x46\x26\x78\x87\x79\x2b\x7a\x5f\xf5\x6c\x95\x1b\x05" +"\x14\x51\x5c\x85\x05\x14\x51\x40\x05\x14\x51\x40\x05\x14\x51\x40\x05\x14\x51" +"\x40\x05\x14\x51\x40\x05\x21\x19\x18\x3d\x29\x69\x92\xab\x3c\x4e\xa8\xfe\x5b" +"\x95\x21\x5f\x19\xda\x7b\x1c\x77\xa0\x0e\x42\x6f\x84\x3e\x13\xb8\x50\xaf\xa6" +"\xc8\x54\x63\x81\x79\x38\xe0\x10\x71\xc3\xf4\xc8\x19\x1d\x0e\x39\xa9\xd3\xe1" +"\x77\x86\x91\xb7\x7f\x67\xb3\x1d\xd9\x05\xae\x24\xf9\x7d\x87\xcd\xd3\xbe\x2a" +"\xa4\x3e\x08\xf1\x14\x32\x96\x1e\x38\xbf\x92\x3c\x60\x47\x25\xac\x27\x07\x18" +"\x07\x38\xc9\x3f\xa6\x49\x38\xe9\x89\x0f\x83\x7c\x43\xe4\xe0\x78\xce\xef\xce" +"\x0a\x54\x48\x6d\x23\xc6\x37\x12\x32\xbd\x09\x00\x81\x9e\x38\x51\x9e\xf9\xeb" +"\xf6\x34\xff\x00\xe7\xea\xfb\xa5\xfe\x44\xdd\xf6\x34\x07\xc3\xfd\x07\xed\x97" +"\xb7\x46\xc9\x9e\x7b\xc0\xe2\x66\x7b\x89\x5b\x76\xfc\xee\xc0\x2d\x85\xea\x71" +"\x8c\x63\x27\x18\xa8\x62\xf8\x69\xe1\xe8\x27\xb7\x95\x6d\x25\xdd\x6e\xc1\xa2" +"\x06\xea\x52\x15\x81\x27\x3f\x7b\x9c\xe7\x9c\xe4\x60\x01\x50\xc5\xe1\xed\x57" +"\x4a\x82\xfa\xe3\x50\xf1\x85\xc3\xdb\x7d\x96\x50\x64\x9a\x18\xa2\x58\x0f\x5f" +"\x37\x77\x6d\xa0\x1e\xbf\x5a\xc1\x83\xcc\x96\x28\x64\x8f\xe2\x9c\x12\xc6\x87" +"\xcb\x2c\xa2\xd8\xac\x87\x6f\x00\x9c\xfd\xec\x60\xf0\x47\xd2\x92\xa5\x4e\xf6" +"\xf6\x8b\xee\x97\xf9\x0e\xef\xb1\xbe\xff\x00\x0b\x7c\x3a\x6d\x7e\xcf\x15\xa4" +"\xb6\xf0\xe4\x1d\x91\x5c\x49\x8e\x33\x81\x82\x4e\x07\x3d\x07\xb7\xa0\xa8\x97" +"\xe1\x17\x86\x0c\x16\xd1\x4d\x67\x3d\xca\xdb\xb3\x3c\x66\x6b\xc9\x89\x0c\x58" +"\xb1\x3f\x7f\x93\x93\xde\xab\x5a\x68\x7a\xa6\xb1\x23\xdc\xe9\xfe\x3d\x7b\x88" +"\x14\xec\x75\xb6\x86\x29\x10\x36\x17\xb8\x27\x07\x80\x71\x9f\xe2\x3c\x72\x31" +"\xa5\x37\x85\xb5\xa9\xb4\xa8\xed\xc7\x8a\xef\x12\xed\x26\xf3\x3e\xd6\xb6\xf1" +"\x02\xcb\xcf\xc8\x57\x18\x23\x9f\xd0\x7e\x3a\x34\xb9\x15\x3f\x6c\xb9\x7b\x7b" +"\xda\x7e\x02\xf3\xb0\xd6\xf8\x5f\xe1\xa6\x80\x44\x6c\x65\x28\x1f\x78\xff\x00" +"\x4c\x9f\x21\xb0\x06\x41\xdf\x9e\x80\x57\x41\xa5\x69\x90\x68\xda\x7c\x16\x56" +"\xa1\x96\x08\x57\x6a\x07\x72\xed\x8f\x72\x79\x3f\x8d\x73\x6d\xe1\x2f\x10\x34" +"\x68\x07\x8b\xee\x52\x40\x5b\x32\x0b\x48\xce\xe0\x76\xe0\x60\xe4\x71\x8f\x4e" +"\xe6\xb6\xbc\x35\xa5\x5f\x68\xfa\x60\xb7\xd4\x35\x69\x75\x9b\x80\xc5\xbe\xd3" +"\x34\x4b\x1b\x63\x03\x8c\x2f\x1e\xbf\x9d\x61\x3a\x70\x8c\x6e\xa6\x9b\xed\xaf" +"\xea\x87\x7f\x23\x56\x8a\x28\xac\x06\x14\x51\x45\x00\x14\x51\x45\x00\x14\x51" +"\x45\x00\x14\x51\x45\x00\x14\x51\x45\x00\x14\x51\x45\x00\x14\x51\x45\x00\x14" +"\x51\x45\x00\x14\x51\x45\x00\x36\x48\xd2\x68\xd9\x1d\x43\xa3\x02\xac\xac\x32" +"\x08\x3d\x41\x15\x59\x74\x9b\x15\x8b\xcb\x16\x56\xe2\x3c\x83\xb0\x44\xb8\xcf" +"\xd3\x15\x6e\x8a\x00\xaf\x67\x61\x6b\xa7\x46\xd1\xda\xdb\x43\x6c\x8c\x77\x15" +"\x86\x30\x80\x9c\x01\x9c\x0e\xf8\x03\xf2\xab\x14\x51\x40\x05\x14\x51\x40\x05" +"\x14\x51\x40\x05\x14\x51\x40\x05\x14\x51\x40\x05\x14\x51\x40\x05\x14\x51\x40" +"\x05\x14\x51\x40\x05\x14\x51\x40\x05\x14\x51\x40\x05\x14\x51\x40\x05\x14\x51" +"\x40\x05\x14\x51\x40\x05\x14\x51\x40\x05\x14\x51\x40\x05\x14\x51\x40\x05\x14" +"\x51\x40\x05\x14\x51\x40\x05\x14\x51\x40\x05\x14\x51\x40\x05\x14\x51\x40\x05" +"\x14\x51\x40\x05\x14\x51\x40\x05\x14\x51\x40\x05\x14\x51\x40\x05\x14\x51\x40" +"\x05\x14\x51\x40\x05\x14\x51\x40\x05\x14\x51\x40\x05\x14\x51\x40\x05\x14\x51" +"\x40\x05\x14\x51\x40\x05\x14\x51\x40\x05\x14\x51\x40\x05\x14\x51\x40\x05\x14" +"\x51\x40\x05\x14\x51\x40\x05\x14\x51\x40\x05\x14\x51\x40\x05\x14\x51\x40\x05" +"\x14\x51\x40\x05\x14\x51\x40\x05\x14\x51\x40\x05\x14\x51\x40\x05\x14\x51\x40" +"\x05\x14\x51\x40\x05\x14\x51\x40\x05\x14\x51\x40\x05\x14\x51\x40\x05\x14\x51" +"\x40\x05\x14\x51\x40\x05\x14\x51\x40\x05\x14\x51\x40\x05\x14\x51\x40\x05\x14" +"\x51\x40\x05\x14\x51\x40\x05\x14\x51\x40\x1f\xff\xd9"; + +const unsigned char kWeewarThumbnail[] = +"\xff\xd8\xff\xe0\x00\x10\x4a\x46\x49\x46\x00\x01\x01\x00\x00\x01\x00" +"\x01\x00\x00\xff\xdb\x00\x43\x00\x03\x02\x02\x03\x02\x02\x03\x03\x03" +"\x03\x04\x03\x03\x04\x05\x08\x05\x05\x04\x04\x05\x0a\x07\x07\x06\x08" +"\x0c\x0a\x0c\x0c\x0b\x0a\x0b\x0b\x0d\x0e\x12\x10\x0d\x0e\x11\x0e\x0b" +"\x0b\x10\x16\x10\x11\x13\x14\x15\x15\x15\x0c\x0f\x17\x18\x16\x14\x18" +"\x12\x14\x15\x14\xff\xdb\x00\x43\x01\x03\x04\x04\x05\x04\x05\x09\x05" +"\x05\x09\x14\x0d\x0b\x0d\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14" +"\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14" +"\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14" +"\x14\x14\x14\x14\x14\xff\xc0\x00\x11\x08\x00\x88\x00\xc4\x03\x01\x22" +"\x00\x02\x11\x01\x03\x11\x01\xff\xc4\x00\x1f\x00\x00\x01\x05\x01\x01" +"\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x03\x04\x05" +"\x06\x07\x08\x09\x0a\x0b\xff\xc4\x00\xb5\x10\x00\x02\x01\x03\x03\x02" +"\x04\x03\x05\x05\x04\x04\x00\x00\x01\x7d\x01\x02\x03\x00\x04\x11\x05" +"\x12\x21\x31\x41\x06\x13\x51\x61\x07\x22\x71\x14\x32\x81\x91\xa1\x08" +"\x23\x42\xb1\xc1\x15\x52\xd1\xf0\x24\x33\x62\x72\x82\x09\x0a\x16\x17" +"\x18\x19\x1a\x25\x26\x27\x28\x29\x2a\x34\x35\x36\x37\x38\x39\x3a\x43" +"\x44\x45\x46\x47\x48\x49\x4a\x53\x54\x55\x56\x57\x58\x59\x5a\x63\x64" +"\x65\x66\x67\x68\x69\x6a\x73\x74\x75\x76\x77\x78\x79\x7a\x83\x84\x85" +"\x86\x87\x88\x89\x8a\x92\x93\x94\x95\x96\x97\x98\x99\x9a\xa2\xa3\xa4" +"\xa5\xa6\xa7\xa8\xa9\xaa\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xc2\xc3" +"\xc4\xc5\xc6\xc7\xc8\xc9\xca\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xe1" +"\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8" +"\xf9\xfa\xff\xc4\x00\x1f\x01\x00\x03\x01\x01\x01\x01\x01\x01\x01\x01" +"\x01\x00\x00\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a" +"\x0b\xff\xc4\x00\xb5\x11\x00\x02\x01\x02\x04\x04\x03\x04\x07\x05\x04" +"\x04\x00\x01\x02\x77\x00\x01\x02\x03\x11\x04\x05\x21\x31\x06\x12\x41" +"\x51\x07\x61\x71\x13\x22\x32\x81\x08\x14\x42\x91\xa1\xb1\xc1\x09\x23" +"\x33\x52\xf0\x15\x62\x72\xd1\x0a\x16\x24\x34\xe1\x25\xf1\x17\x18\x19" +"\x1a\x26\x27\x28\x29\x2a\x35\x36\x37\x38\x39\x3a\x43\x44\x45\x46\x47" +"\x48\x49\x4a\x53\x54\x55\x56\x57\x58\x59\x5a\x63\x64\x65\x66\x67\x68" +"\x69\x6a\x73\x74\x75\x76\x77\x78\x79\x7a\x82\x83\x84\x85\x86\x87\x88" +"\x89\x8a\x92\x93\x94\x95\x96\x97\x98\x99\x9a\xa2\xa3\xa4\xa5\xa6\xa7" +"\xa8\xa9\xaa\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xc2\xc3\xc4\xc5\xc6" +"\xc7\xc8\xc9\xca\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xe2\xe3\xe4\xe5" +"\xe6\xe7\xe8\xe9\xea\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xff\xda\x00" +"\x0c\x03\x01\x00\x02\x11\x03\x11\x00\x3f\x00\xfa\x18\x8f\x9b\x39\x60" +"\x58\xff\x00\x9c\x53\x98\x28\x62\x06\x48\x07\x8a\xe4\x3c\x65\xe3\xc9" +"\x7c\x2b\xaa\xd9\xdb\x8b\x41\x73\x1c\xe4\x17\x72\xe4\x6c\x04\x90\x4e" +"\x02\xb1\x38\x0a\x5b\x00\x16\x38\xc0\x1c\xd6\xed\x9e\xbb\x05\xce\xad" +"\xac\x69\x8e\x24\x86\xeb\x4e\x96\x1f\xde\x11\x98\xa7\x8a\x58\xf7\x21" +"\x56\xfe\xf0\x21\x81\x1e\xe0\x76\x39\xfe\x0b\xa9\x94\x62\xa9\x61\x61" +"\x8c\x95\xb9\x24\x9c\x96\xaa\xf6\x52\x8c\x76\xf5\x92\xfe\x91\xad\x6c" +"\xb3\x11\x42\x85\x3c\x4c\xd2\xe5\x9a\x6d\x6a\xb6\x4d\x2f\xcd\xec\x74" +"\x72\x78\xc6\xee\xc2\xc9\x9e\x49\xe3\xb7\xb7\x86\x3d\xf2\x4b\x22\x80" +"\x36\x28\x39\x2c\x4f\x00\x00\x3a\xfa\x75\xaa\x53\x78\x85\x7c\x4d\x14" +"\x37\x91\xdc\xc5\x77\x6e\xc9\x98\xa4\xb6\x60\xe8\xcb\xec\x41\x39\x1d" +"\x7b\x9a\xcd\xd6\x74\xb8\x35\xfd\x22\xff\x00\x4b\xb9\x69\x05\xb5\xed" +"\xbc\x96\xf2\x34\x2e\x51\xb6\x3a\x95\x6d\xac\x3a\x1c\x1e\x0f\x6a\xa1" +"\xe1\x0f\x0b\xdb\xf8\x2f\x42\x8b\x4c\xb6\xbb\xb8\xba\x82\x0c\xed\x6b" +"\x8d\xa5\xc0\xc9\x24\x7c\xaa\x3b\x92\x7d\x79\x35\xe6\x46\x86\x19\x51" +"\x75\x2f\x6a\xb7\x56\x56\xd1\xa6\x9d\xdd\xfb\xde\xda\x1c\xf2\xc4\x4e" +"\xa5\x27\x19\xd4\x6d\xdd\x69\xd2\xdf\xd5\x8d\x4b\xeb\x58\x6f\x6d\x66" +"\x86\xe1\x4b\x5b\xc8\x9b\x59\x77\x11\x90\x7d\xfb\x51\xff\x00\x0a\x93" +"\xc2\xfa\x97\x82\xef\x3f\xb2\x96\x7b\xad\x5e\x1b\x77\x95\x5c\x4a\x4c" +"\x9e\x76\x1b\x6c\x65\x48\xda\x06\xe0\x57\xee\xe4\xe3\x83\x91\x9a\x75" +"\xdc\x0b\x77\x6f\x2c\x0e\xc5\x51\xd7\x6e\x47\x5a\xc3\xf0\x97\x89\xee" +"\x3c\x0b\xaa\x5c\xdb\x5a\xc2\x2f\xcd\xe4\xfb\x25\x8c\x9d\x8d\x1a\x21" +"\x7d\xac\xa3\x91\xb7\xe6\x03\x18\xe7\x24\xf1\x8c\x9f\xaa\xc9\x38\x8f" +"\x3d\xc8\xe1\xff\x00\x09\x18\xb9\xd3\x4a\x51\x9b\x84\x64\xe3\x19\xb8" +"\xff\x00\x36\xbb\x35\xa3\x5d\x55\x93\xe9\x6f\x16\xae\x5f\x96\xe3\x6a" +"\x5b\x30\xa1\x19\xa9\x45\xc7\x99\xc5\x37\x1b\xf5\x5e\x77\xdb\xb7\x42" +"\xff\x00\xc2\x8b\xdb\xb8\xed\xe4\xb0\xf1\x2c\x77\xa9\xa6\x59\xba\x98" +"\x6d\xd9\x0c\x2e\xc4\xe4\xc8\x9d\x33\x8c\xa8\x19\xf7\xeb\xc7\x1a\xd2" +"\x95\xcb\xac\x41\xfc\xb2\xdb\x97\x77\xde\xef\x8c\xfb\xd7\x18\xdf\x12" +"\xb5\x4d\x63\x46\x5d\x51\xfc\x37\x79\x16\xa7\x23\x86\x9e\xc8\x0f\x96" +"\x2c\x99\x01\x5d\xe7\x01\x9b\x31\xec\x2a\xbb\x88\x67\x18\x0c\x36\x96" +"\xb1\x6d\xe3\xa1\x76\xb6\x85\x20\x92\xdd\x6e\x0c\x65\xad\xee\x00\x59" +"\xa3\xdc\xb9\xf9\xc6\xef\x94\xa6\x76\xb0\xe7\x96\x18\xcf\xcc\x57\xd5" +"\xe2\xd9\xe3\xf3\xac\xc2\xbe\x69\x57\x0b\x4e\x8a\x9c\xdd\xe3\x4d\xc5" +"\xa5\x25\x18\xdf\x54\xdb\x6a\x5f\x12\x7f\x0c\x9b\x93\x8f\x53\x3c\x15" +"\x05\x96\xe1\x29\xe0\xa5\x51\xcf\x92\xe9\x4a\x5f\x13\x57\x76\x5f\x25" +"\xa2\x5d\x15\x8e\x98\x28\xda\x77\xf2\x30\x7b\x60\x01\xff\x00\xea\xa2" +"\x52\x44\x6d\xb1\x4b\x3e\xd2\x54\x1f\x5a\x19\x95\xe3\xc1\x6f\x91\x81" +"\x19\x1c\xd0\xf2\x15\x57\x2b\x97\x65\x19\x0a\x3b\xfb\x57\xe7\x7a\xdc" +"\xf4\x48\x27\xbf\xb3\xb6\xbb\x82\xd2\x6b\x98\xa3\xb9\xb8\x0d\xe4\xc0" +"\xee\x03\xcb\xb4\x65\xb6\xae\x72\x76\x8c\x13\x8e\x99\xad\xcd\x36\x5b" +"\x6b\x4b\x79\xee\x3c\xe9\x61\xd4\x15\x81\x80\xa6\x78\xf9\x5b\x91\xe8" +"\x73\xb7\xae\x46\x37\x71\x92\x08\xe8\x6f\x7e\x04\xf8\x57\x59\xf0\xce" +"\x8d\xe2\xaf\x10\x6b\xba\xf5\xad\xb0\x92\xd9\x9e\xd2\xc9\xa4\x92\xd9" +"\xe4\x13\x8f\x24\xbc\x08\xad\x9d\xb2\x14\x6d\xf8\xf9\x76\x06\x24\x2a" +"\x9c\x57\xf1\x05\xdf\xc2\xdf\x0b\xce\x53\x52\xf1\x07\x89\x20\x1b\x24" +"\x94\xca\xbe\x1e\xbe\x92\x15\x8e\x39\x1a\x37\x73\x22\x5b\x14\x0a\x19" +"\x09\xdc\x4e\x0a\x95\x60\x4a\xb2\x93\xfa\x5c\x78\x2f\x30\xa7\x46\x86" +"\x22\x8c\xa1\xcd\x52\x1c\xcd\x4e\x50\x8d\xb9\xaf\x66\xaf\x2b\xbd\x1a" +"\x77\xb2\xb4\xb4\x5b\x1d\x54\xa9\x55\xbf\x3c\x63\x7f\xbf\xfa\xb9\xcb" +"\x36\xb2\xd6\x6d\x2a\x1b\xa9\x23\xf3\x48\xdf\x92\x7e\x60\x32\x46\x7d" +"\xb8\x3f\x91\xa8\xdb\x2c\xfb\xb9\xc9\x39\x20\x8f\xf3\x8a\xde\xb9\xd0" +"\xfe\x11\xf8\xa2\xd7\x4f\xd5\x60\xf1\xbe\xaa\xf6\x57\x4a\xc2\x3b\x8b" +"\x5b\x19\x4a\x18\xd2\x63\x13\x3b\x3f\x92\x7c\xb8\xf7\x6e\x3b\xd8\x85" +"\x28\xac\xf9\x31\x86\x6a\xef\xfc\x23\xf0\xd7\xc2\x1f\x10\x74\xbf\xed" +"\x4d\x0b\x5e\xd4\x2e\xec\x8c\x85\x44\x8f\x6e\x61\xdd\xe8\xc1\x64\x45" +"\x25\x0f\x05\x5c\x0d\xac\x30\x54\x90\x73\x4a\x3c\x03\x9c\x56\x71\x85" +"\x37\x09\x4b\x5d\x3d\xa4\x5d\xbf\x1f\x51\x3a\x35\xef\xfb\xc5\x64\xbb" +"\xdc\xf2\x59\x15\x04\x8c\x14\x12\x06\x70\x7d\x45\x5d\x93\xc6\x37\x7a" +"\x65\x94\x92\x49\x71\x15\xb5\xac\x11\xef\x92\x69\x00\x03\x62\x83\x9d" +"\xc4\xf0\x00\x00\x73\xe8\x39\x3c\x71\xce\xc7\xae\x20\xf1\x0e\xa7\xa4" +"\xcd\x1b\xa4\xb6\x82\x29\x92\xe5\x7e\x68\x66\x8a\x45\xf9\x40\x23\xf8" +"\xc3\x89\x01\x5f\xf7\x70\x0f\x53\x2e\xb7\xa5\xc3\xaf\xe9\x17\xfa\x55" +"\xd3\x4a\xb6\xb7\xb6\xcf\x6d\x23\xc3\x21\x8d\xf6\xba\x95\x6d\xac\x39" +"\x0d\x83\xc1\x1c\x8a\xfc\xf2\xae\x15\x53\x9c\x61\x88\xd9\xa8\xbd\x2c" +"\xf4\x69\x3f\xbe\xcf\x6e\x9d\x47\x19\x55\xc3\x4a\xca\x5c\xb7\xb5\xec" +"\xfa\x3d\x7a\x1a\x93\x78\x91\x7c\x53\x0c\x57\x71\xdc\x43\x75\x6c\xcb" +"\x98\xa4\xb6\x60\xd1\x95\x27\xaa\x90\x4e\x47\x1e\xb5\x4e\xf2\xde\x1b" +"\xab\x5b\x88\xae\x14\x9b\x79\x10\xa3\xae\xec\x64\x1e\xbc\xf6\xac\x8f" +"\x07\x78\x4e\xd7\xc1\x5a\x30\xd3\x6c\xee\xae\x6e\x20\x46\x2c\x0d\xcb" +"\x86\x65\x07\xb0\xc0\x1c\x75\xfc\x49\x35\xb3\x75\x0a\xdc\xdb\xcb\x0b" +"\xb1\x55\x91\x76\x92\x3a\xf3\xe9\x56\xf9\x30\xb8\x8b\xe0\xe6\xf9\x62" +"\xef\x19\x7c\x2f\x47\x74\xfc\x9f\x5d\xf7\x31\xc4\xf2\xd6\x94\xee\xf9" +"\x93\xbe\xfd\x57\x9a\xd7\xa7\x42\x28\xfe\x14\x78\x6b\x53\xf0\x4d\xe8" +"\xd3\x12\x6b\x9d\x5a\x38\x24\x95\x58\x4a\x4c\x9e\x70\xdc\x11\x76\x9f" +"\x94\x0c\x82\xbf\x77\x27\x1d\x72\x33\x54\x7e\x15\xdc\xde\xc3\x6b\x2e" +"\x9d\xe2\x08\xee\xe0\xd3\xad\x64\x06\x38\x65\x8c\xc4\xd9\x20\x97\x07" +"\x8c\xf5\x55\x1e\xbe\xe3\xb3\x3c\x25\xe2\x39\xbc\x0b\xaa\xcd\x69\x08" +"\xfb\x67\xdb\xa7\x58\xdd\x1c\x05\x31\xaa\xe7\x6b\x28\x07\x85\x1b\xf1" +"\xd3\x92\x7d\x89\xac\x84\xf8\xa5\xa8\x78\x83\x4b\xbc\xd4\xa4\xf0\xd5" +"\xfd\xae\xa5\x14\xeb\x1d\xcd\x9b\x2f\xee\xe3\xc8\x62\xc4\x48\x70\x24" +"\xda\x53\x61\x11\xef\x21\x98\x60\x30\x20\x9f\xd6\x61\xc5\x9c\x45\x9b" +"\xe5\x78\x9c\xa3\x1c\xd6\x2e\x35\x2a\x53\x9c\x2a\x56\x9a\x72\xa7\x26" +"\xf6\x8a\x9b\x49\x42\x56\x71\x93\xd2\x31\x4d\xde\xdc\xc8\xf0\xa9\xe4" +"\xb9\x7d\x1c\x4e\x1f\x30\xc0\x47\xd9\x4e\x9c\x5c\x67\x08\x46\xd1\x9a" +"\xd6\xdc\xd6\xdd\xf5\xee\xda\xf2\x3b\x39\xf6\x79\x8f\xe4\xab\xec\x24" +"\x94\xf3\x3e\xf0\x1d\xb3\xef\x51\xb2\xa9\x53\xbf\xee\x63\x9c\xf4\xc5" +"\x72\xb1\xf8\xf0\x5d\x45\x69\x34\x56\xd2\xc5\xe7\xaa\x31\xb5\xb9\x50" +"\x93\xc7\xb9\x03\x61\xd7\x3f\x21\x5f\xba\xc3\x92\x18\xa8\xc1\xf9\x8a" +"\xf5\x4f\xb2\x48\x88\x2d\xfb\xb7\x04\x13\xed\x5f\x92\xd7\xc3\x55\xc3" +"\x4b\x96\xb4\x6c\xcf\x5a\x35\x23\x52\xee\x22\xb0\xe0\xe0\x12\x48\xa8" +"\x66\xbd\xb5\xb6\x9e\x0b\x79\xa7\x8e\x39\xa7\xca\xc3\x1c\x8e\x03\x4a" +"\x40\xdc\x76\x8e\xf8\x00\x93\x8e\x83\x35\x31\x7c\x6e\x00\x92\x40\xcf" +"\x4a\xe7\xbc\x49\xe0\x7d\x3f\xc5\x1a\xa6\x9b\x7d\x79\x73\x76\xad\x63" +"\x2c\x72\x2c\x11\x4d\x88\xa5\x29\x22\xca\x81\xd4\x82\x0e\x24\x8e\x37" +"\xe3\x07\x31\xaf\x3c\x51\x86\x8d\x19\xd4\xb6\x22\x4e\x31\xb3\xd5\x2b" +"\xbb\xdb\x4d\x2e\xb4\x6e\xc9\xeb\xa2\xd7\x52\xce\xf8\xe9\x52\x41\xb2" +"\x68\xb5\x2b\xe8\xfe\xd3\x1c\x72\xb4\x66\x45\x74\x43\xb1\x57\x08\x19" +"\x4e\xc5\xc2\x83\xb5\x70\x32\x59\xba\xb1\x24\xad\x09\xb9\xb7\xb1\xff" +"\x00\xaf\x68\xff\x00\x95\x15\xfd\xc3\x93\xff\x00\xc8\xb7\x0d\xff\x00" +"\x5e\xe1\xff\x00\xa4\xa3\xd3\x8e\xc8\xf3\x1b\x7f\x1f\x5b\x43\x73\x0c" +"\xfe\x5a\x92\x92\xe5\x73\xc0\x62\x38\xf9\x4f\xde\x24\xf6\x23\x1c\x1c" +"\xe4\x01\xba\xa6\xf8\x8d\xe1\x69\x3c\x7f\xac\xe9\x9a\x84\xda\x94\xd6" +"\x52\x58\x5d\xfd\xa1\xa0\x88\x29\x86\x73\x80\x30\xe3\xae\x70\x08\xdc" +"\x08\x38\x77\x1c\xee\xae\x5b\xc2\x3a\xa5\x94\x5a\xe4\x17\xba\x85\xac" +"\x32\x69\xb6\xf2\xa9\x30\x4c\x01\xdd\xc2\x81\xc6\x00\x51\xd8\x2e\x5b" +"\x71\x6c\xf5\x24\x57\xad\xeb\x76\x57\x0c\x13\x52\x36\xf1\x5a\x43\x76" +"\xe4\x24\x51\xf4\x50\x30\x33\x81\xc0\x04\xe7\xf1\x0d\xc7\x15\xfc\x89" +"\x8d\xc2\x63\x72\x1a\xf2\xa3\x69\x53\xab\x49\xbe\x68\xb5\x7b\x7d\x9b" +"\xbb\xe9\xd6\xdd\x77\x3c\x8c\xbf\x16\xf1\x18\x7f\x6f\x42\xa7\x34\x5e" +"\xaa\xdd\x9a\xb5\xff\x00\x1b\x19\x39\xf9\xb0\x0f\x41\x48\x17\x07\x76" +"\x41\x38\x03\x1d\x87\x5f\xf1\xa7\x0e\x5b\x18\xc0\x14\xd0\xaf\x9c\xfc" +"\xa3\x20\x00\x3b\x0e\xbc\xfb\xf6\xaf\x8f\x34\x14\x7d\xe3\xce\x00\x1d" +"\x2b\xcb\xc6\x8f\x63\x3d\xe4\x1e\x06\xfe\xd4\xd4\x52\xea\x39\x8d\xcb" +"\xcb\x15\xab\xa0\xf2\xc8\xde\xea\xb2\x0f\xba\x1b\x79\xcc\x8c\xcc\x37" +"\x3c\x8a\x0a\xb7\x96\x17\xd4\x01\x05\xfa\x0e\x07\x5a\xe7\xfc\x61\xe1" +"\xab\xff\x00\x10\xd9\x2a\xe9\xba\x9c\xba\x3d\xf2\x3e\x44\xd0\x2f\xfa" +"\xc5\xf2\xdd\x44\x6e\x41\x0c\x54\x33\x87\x01\x48\xf9\x91\x73\x95\xdc" +"\x1b\xe9\xb2\x2c\x7c\xb0\x95\xfd\x9f\x3f\x27\x33\x4d\x4b\x6e\x49\xab" +"\xa8\xcd\xbe\x59\x3b\x45\x4a\x57\x4a\xd7\xbe\xbb\x21\x34\x9e\xe5\x6f" +"\x88\x56\x17\xbe\x07\x9b\x64\x9a\xb9\xd4\xad\xa6\xd3\x06\x9a\x96\xfb" +"\x63\x3b\x1f\x2c\x5e\x66\x93\x6e\xf2\xcc\x36\x64\x71\x9d\x8b\xf4\x0f" +"\xf0\x87\xc2\xab\xad\x43\xc1\x4f\xaa\xa6\xa3\x1c\x53\xcf\x18\x9a\x18" +"\xfc\xb6\x0a\xaa\x9b\x86\x0b\x29\x07\x39\xe7\x03\x23\x81\x9c\x83\x5e" +"\xad\xa8\xf8\x55\x75\x6f\x0c\x5e\xe8\xb2\xd8\xda\x8b\x91\x6c\xc0\x49" +"\x3e\x19\x52\x76\x0c\x23\x71\x80\x73\x82\x1b\x9e\xa3\x6f\x19\x06\xbc" +"\xb6\x0f\x84\x7e\x37\xb5\xf0\xe5\xed\x8a\x5e\x20\x8f\xcf\x42\xb6\x49" +"\x76\xc1\x65\x55\x0c\x0b\x0e\x80\x03\x90\x70\x48\x27\x1c\x80\x40\xaf" +"\xdd\xb2\x4c\xe3\x2d\xce\xb8\x4a\xaf\x0f\x56\xc7\xd3\xcb\xab\xfb\x68" +"\x2a\x8e\xad\xa5\xed\x29\xa9\x37\x1e\x54\xed\xcb\xc8\xed\x77\xd7\x97" +"\xde\x92\x52\xd3\xe4\xb3\x7c\xb7\x1b\x84\xcf\x21\x99\xfd\x56\x78\xaa" +"\x5e\xce\x5c\x91\x85\xd7\xb3\x95\xb5\x4d\xfd\xab\xeb\x6d\x3a\xe8\x9b" +"\x5a\xeb\x69\x7a\x7b\xd8\xe9\x70\xda\x3c\xed\x3b\x2a\x15\x32\xb9\xcb" +"\x31\x3d\xf3\xde\xad\xc8\xfe\x52\x33\xf0\x76\xa9\x38\x15\xd9\xf8\x43" +"\xc2\xb7\x3a\x3f\x83\xed\x74\xcd\x48\xc1\x75\x79\xe5\xb8\x96\xe3\x71" +"\x6c\xb1\x63\xb3\x92\x32\x70\xa4\x0c\xf6\xc7\x1d\x68\xd1\x3c\x3b\x73" +"\xa3\xc9\x73\x75\x3c\x76\xf2\xb4\x48\xde\x4a\x33\x1c\x6e\x1c\x86\xce" +"\x0e\x01\xfa\x64\x73\xc5\x7f\x33\x63\xea\x51\xc3\xe3\x2b\xd1\xa7\x55" +"\x54\x8c\x25\x24\xa4\xb6\x9a\x52\x69\x4a\x3e\x52\xb5\xd5\xfa\x33\xf4" +"\x2c\x3e\x5b\x5e\xa5\x2a\x33\x94\x5c\x5c\xd2\x6d\x3d\xe0\xda\x4e\xcf" +"\xcd\x6d\xea\x7a\x75\x86\xbb\xa0\xe9\x7f\x0f\x2c\xf4\x8d\x4f\x57\x92" +"\xcc\x88\x84\x0f\x34\x70\x12\xea\xf9\x27\x80\x51\x86\x78\xe0\x91\xef" +"\xe9\x5c\x17\x88\xfc\x1f\xf0\xc7\xc5\x1a\x46\xad\xa6\xdf\xf8\xaf\x52" +"\xfb\x3e\xa9\x0c\x90\x5e\x34\x36\x50\x45\x24\xa8\xe8\x11\xb2\xeb\x6a" +"\x0e\x4a\xa2\x0d\xd9\xcf\xc8\xbc\xf0\x2b\xe7\x2f\x89\x3e\x3e\x9b\xc4" +"\xde\x28\x92\xeb\x4d\xba\xbb\x86\xca\x30\x16\xda\x36\x72\xb8\x20\x60" +"\xb2\xa8\x38\xe4\x83\xee\x73\xcd\x73\x07\x57\xd4\x40\x9b\xfd\x2e\xe4" +"\xf9\xa3\x74\xc3\x7b\x72\x0f\x73\xcf\x4c\x11\xf9\xe2\xbf\xbf\xb2\x2e" +"\x00\xe2\x8a\xf9\x56\x12\xa6\x2e\x78\x78\x4f\xd9\xc3\xdd\x9d\x19\xb9" +"\x43\x6f\x76\x4f\xda\x6e\x96\xae\xd6\x5c\xda\x59\x2d\x4f\xc7\xb3\x0f" +"\x12\x70\x58\x5c\x5d\x4a\x38\x4a\x52\x94\x21\x26\x94\x94\xf4\x76\xd9" +"\xab\x74\x6f\x6f\x2d\x7c\x8f\xa4\x7e\x1c\xf8\x77\xe0\xaf\xc3\x8d\x26" +"\x0d\x17\x47\xf1\xc6\xad\x74\x90\xbb\x90\xf7\xf0\x2d\xcc\xa8\xed\xb7" +"\x8d\xed\x6b\x94\xc1\x48\x98\x28\xda\x3e\x44\x6c\x70\x0d\x7a\x47\x87" +"\xbe\x32\x7c\x33\xf0\xe3\xde\x5c\x43\xe2\xe7\xb8\x17\x45\x77\x09\x2c" +"\x59\x43\x3a\xb3\xe5\xd3\xcb\x81\x4b\x16\xdd\x9e\xe3\x0a\x08\x00\x64" +"\x9f\x87\xb7\xcf\x11\x77\x04\x8d\xa4\x02\x47\x63\xf5\xfc\x0f\x5a\xec" +"\xfc\x37\xab\x59\x26\xaf\x6f\x79\x79\x6f\x0b\x69\x96\xae\x19\xad\xe7" +"\x55\x04\xf1\xc7\x60\x00\x03\x03\x04\x92\x4b\x77\xe0\x57\xcf\xf1\xae" +"\x0b\x3d\xe0\x8a\x14\xb1\xbc\x94\x6a\xc2\x4d\xa9\x4a\x34\xe5\x1e\x47" +"\xa7\x2d\xdf\x3b\x6d\xce\xef\xe6\x9a\xd7\x46\xfd\x4c\x8b\x8d\x21\x9f" +"\x57\x9d\x16\xb9\x25\xa3\xf7\x9b\x77\xbe\xf6\xf4\xfc\x8e\xaf\xe2\x87" +"\x85\x5b\xe2\x3d\xf5\x9d\xc5\xcd\xf1\xb3\x92\xd2\xed\x6e\x1a\x24\x40" +"\xf0\xce\x14\x6d\x29\x20\x38\x38\x61\x90\x59\x4a\xb6\x19\xb0\x46\xe3" +"\x5b\x5b\xbe\x7d\xa3\xb2\x83\x8f\xaf\xff\x00\xaa\xb6\x35\xbb\x1b\x87" +"\xc6\xa6\x6d\xe2\xb3\x82\xe9\xb6\xa4\x51\x91\x81\x8e\x33\x81\xc6\x09" +"\x07\xf1\x07\x81\xc5\x64\x02\x19\xf6\xf4\xc0\x07\xf3\xff\x00\xf5\x1a" +"\xfe\x2f\xc5\xd6\xc4\x35\x1a\x15\xa5\x78\xc6\xee\x37\x56\xf8\xac\xef" +"\xdf\x5d\x1e\xad\x9f\xa0\xd6\x94\xdb\xb4\xdd\xed\xb7\xcd\xdc\x6e\xcc" +"\x16\x6d\xc3\x38\x03\xe9\x8a\x7f\x73\xc8\xc6\x07\x18\xa6\x01\x20\xc9" +"\xc0\xe4\x00\x07\x61\xd7\xf3\xa7\xe4\x16\x3c\x0e\x3a\xd7\x9c\xce\x73" +"\xca\xee\x2d\xb4\x76\xbf\x4f\x03\x5c\x6a\xda\x9f\xdb\x1e\xeb\xcf\xcc" +"\x36\xed\x10\x0a\xe0\xbb\x22\xba\x80\xdb\x58\x79\x84\xc9\xb9\x80\x66" +"\x93\xe6\x05\x42\xa7\x5f\xe3\x9b\x0b\xbf\x00\xc2\xbe\x6e\xae\xda\xaa" +"\xdc\xe9\x89\xa7\x6c\x78\xe3\xde\x1c\x16\x66\x95\xe4\xd8\x1b\x73\x7c" +"\x84\x80\x14\x7c\xa3\x81\xd0\x4f\xe3\x1d\x02\xff\x00\xc4\x1a\x15\xcd" +"\xb6\x9b\xa8\x36\x93\xa8\xbe\x16\x2b\xc8\x57\x2c\x8b\xdc\x71\xc9\xf5" +"\xc0\xc7\x20\x76\xce\x7d\x3e\xf3\xc2\x6b\xa9\xf8\x5e\xeb\x48\x9e\xca" +"\xd4\xcc\xd6\xec\x0c\x92\x90\xc1\x26\x6d\xc2\x36\xe0\x1c\xe0\x86\xe4" +"\x1c\x8c\x0c\x67\x35\xfa\xde\x49\xc4\xb8\x7c\x1e\x2f\x09\x8e\xc7\x53" +"\x75\x28\x29\xb5\x56\x0a\x56\xb3\x92\x5c\xf5\x34\x8a\x51\x8c\x9b\x4f" +"\x95\x5d\xfe\xee\x49\x35\x7b\x99\x62\x30\x38\x8c\x7e\x0e\xbd\x0c\x1c" +"\xb9\x2a\xb8\xfb\xb2\x6a\xff\x00\x2f\x3b\xea\xb5\xdb\xcc\xf2\x1f\x04" +"\xfc\x28\xb9\xd6\x3c\x23\xfd\xb6\x9a\x92\x45\x2c\x90\xb3\x41\x01\x47" +"\x0b\x85\x66\x07\x73\xab\x29\xea\x09\xc0\xc8\x23\x83\x90\x71\x5b\xfa" +"\x66\x9a\xf6\x3a\x54\x56\x72\x5c\x34\xcc\xa8\x55\xa6\x63\x96\x62\x49" +"\x39\xcf\x7e\xb5\x8d\x0f\xc2\x1f\x1c\x58\xe8\xd7\x96\xc9\x7f\x10\xb5" +"\x59\x79\xb1\x8e\xed\xc2\xdc\x28\xe0\xb0\x1c\x0c\x1c\xf4\x62\x09\x03" +"\x90\x30\x2b\xd6\x3c\x19\xe1\x5b\x8d\x1b\xc1\xd6\x9a\x6e\xa5\xe4\x5c" +"\xde\x14\x71\x3c\xdb\xcb\x72\x58\x94\xe4\x8e\x70\x08\x1e\xd8\xe2\xbd" +"\x7f\x15\x6a\x60\xf1\x78\xca\xf9\xe5\x1c\xe6\x8e\x32\x55\x2a\xf2\xc6" +"\x9d\x34\x94\xa1\x4d\x45\xb8\xb6\xd3\xb3\x51\x56\x83\x7f\x69\xd9\xdd" +"\xbb\xa8\xf8\x1c\x23\x81\xc4\x2a\x34\xb2\xe9\x60\x27\x87\x50\xa7\x79" +"\x4e\x6d\xb5\x29\xde\xce\xcb\xa5\xf5\x76\xe8\xba\x6c\xdf\x1a\xc7\x01" +"\xba\x70\x2b\x6b\x5c\xf0\xd9\xd2\x6c\x23\x9b\xed\x5e\x66\x5c\x44\xf9" +"\x4d\xa7\x77\x3d\x33\xdb\x8c\x76\xcf\x51\x5b\x3a\x07\x86\x6e\x34\xdb" +"\xb9\x6e\x2e\x92\x07\x08\xa4\x44\xb9\x24\xee\x04\x1d\xdd\x38\x1f\xa8" +"\xf4\xaf\x06\xf8\x9b\xf1\x02\x4f\x14\x78\x93\xed\x3a\x55\xcd\xdc\x56" +"\x10\xa8\x48\x55\x98\xa0\x0c\x33\x97\x55\x07\x1c\xfb\xf2\x6b\xe4\x38" +"\x0b\x82\x31\xde\x20\x66\x55\x30\x78\x2a\x8a\x9d\x3a\x51\xe6\x9d\x46" +"\xb9\xa2\x9b\xf8\x23\xa3\x56\x72\xb3\xb7\x92\x6f\x5b\x58\xf4\xb8\x87" +"\x36\xc3\x70\xc6\x01\x62\x31\x90\xe6\xa9\x51\xda\x31\xbd\x9d\x97\xc5" +"\x2f\x34\xb4\xfb\xd7\x73\xdc\xa7\xff\x00\x8f\x7b\x1f\xfa\xf6\x8f\xf9" +"\x51\x59\x9e\x1f\x9d\xee\x7c\x31\xa2\x4b\x2b\x97\x91\xec\xa3\x2c\xcc" +"\x72\x49\xc5\x15\xfd\x73\x43\x05\x2c\xb6\x94\x30\x53\x77\x74\x92\x83" +"\x6b\x66\xe2\xad\x7f\xc0\xf7\x70\x95\xd6\x27\x0f\x4e\xbc\x55\x94\xa2" +"\x9f\xde\xae\x7d\x33\x71\xe2\x3d\x24\xdb\xca\x20\xd5\x34\xe5\x9c\xa9" +"\xf2\xda\x49\x15\x94\x36\x38\x24\x02\x09\x19\xf7\x15\x4f\xfb\x7a\xdc" +"\x2b\xa8\xd6\xf4\x72\x47\xdc\x66\xc6\x4f\x4f\xbc\x03\x8f\x7e\x9f\xa5" +"\x63\xeb\xd7\xcd\xa2\xe8\x7a\x8e\xa1\x0e\x9d\x71\xaa\x4b\x69\x6d\x24" +"\xe9\x63\x64\xaa\x67\xb9\x65\x52\xc2\x38\xc3\x10\x0b\xb6\x30\x32\x40" +"\xc9\x19\x22\xb8\x59\x3e\x2b\x6a\xf0\x49\x3c\x52\x7c\x30\xf1\x54\xb2" +"\x42\xce\x86\x4b\x54\xb4\x68\xa4\x2a\x40\xdd\x1b\x34\xea\x4a\x92\x78" +"\x25\x54\x90\x33\x80\x33\x8f\x4d\x68\x68\xd5\xcf\x45\x7d\x76\xe4\x49" +"\x26\xcd\x77\xc3\xa5\x0b\xa9\x5d\xc8\x72\x17\x0b\xb9\x78\x93\xaf\x0f" +"\x83\xfe\xd0\xe3\x8e\x75\xed\x3c\x45\xa5\xad\xba\x8b\xad\x53\x4d\x79" +"\xf9\xdc\xd0\xc8\xaa\xa7\x9e\x30\x0b\x13\xfa\xd7\x95\x69\xbf\x13\xf5" +"\x2d\x46\xdc\x4e\xdf\x0e\xbc\x4d\x62\x3e\xdd\x35\x97\x95\x77\x15\xb8" +"\x94\x84\xd8\x12\x60\x16\x56\x1e\x53\x97\x20\x31\x60\x40\x46\x2c\x00" +"\x15\xa3\x67\xe3\x7b\xfb\xab\x0b\x4b\x99\x3c\x19\xac\x5a\x35\xc4\xb6" +"\xe8\x20\x9d\x62\xf3\x22\x49\x02\x17\x79\x02\xbb\x05\xf2\xf7\x3e\x46" +"\x49\x3e\x59\xc6\x72\xb9\x72\x95\xd2\x4d\x21\x25\xab\x67\xa5\x7f\xc2" +"\x49\xa2\x7f\xd0\x4a\xc7\xfe\xff\x00\x27\xf8\xd1\xff\x00\x09\x26\x89" +"\xff\x00\x41\x2b\x1f\xfb\xfc\x9f\xe3\x5e\x7b\xa3\xf8\xc6\x7d\x4b\x40" +"\x8b\x52\xb9\xf0\xa6\xb5\xa6\x4b\x24\xc2\x11\xa7\xdc\xc3\x11\xb8\x50" +"\x40\x3b\xc8\x49\x18\x05\xfc\x73\xc7\x20\x55\xad\x3b\xc4\xb2\x5f\xc9" +"\x3a\xbf\x87\xb5\x5b\x31\x13\x05\x0d\x71\x0c\x60\x48\x37\x28\xca\xe1" +"\xcf\x1f\x36\x79\xc7\x0a\x7b\xf1\x59\xf7\x28\xee\x3f\xe1\x24\xd1\x3f" +"\xe8\x25\x63\xff\x00\x7f\x93\xfc\x68\xff\x00\x84\x93\x44\xff\x00\xa0" +"\x95\x8f\xfd\xfe\x4f\xf1\xae\x11\xbc\x52\xea\xd0\x03\xe1\xbd\x5f\x12" +"\xab\xb1\x61\x04\x64\x26\xd2\x38\x6c\x3e\x41\x39\xe3\x8e\x71\x53\xe9" +"\xfe\x22\xfb\x76\xb4\x74\xe6\xd1\x75\x2b\x5f\xdd\x4b\x28\xba\x9e\x05" +"\x10\x10\x8e\xa9\x8d\xc1\x8e\x19\xb7\x6e\x50\x46\x4a\x82\x78\xc6\x29" +"\xda\xe0\x76\x9f\xf0\x92\x68\x9f\xf4\x12\xb1\xff\x00\xbf\xc9\xfe\x34" +"\x7f\xc2\x49\xa2\x7f\xd0\x4a\xc7\xfe\xff\x00\x27\xf8\xd6\x2f\x96\x9f" +"\xdd\x5f\xca\x8f\x2d\x3f\xba\xbf\x95\x00\x6d\x7f\xc2\x49\xa2\x7f\xd0" +"\x4a\xc7\xfe\xff\x00\x27\xf8\xd1\xff\x00\x09\x26\x89\xff\x00\x41\x2b" +"\x1f\xfb\xfc\x9f\xe3\x58\xbe\x5a\x7f\x75\x7f\x2a\x3c\xb4\xfe\xea\xfe" +"\x54\x01\xb5\xff\x00\x09\x26\x89\xff\x00\x41\x2b\x1f\xfb\xfc\x9f\xe3" +"\x47\xfc\x24\x9a\x27\xfd\x04\xac\x7f\xef\xf2\x7f\x8d\x62\xf9\x69\xfd" +"\xd5\xfc\xa8\xf2\xd3\xfb\xab\xf9\x50\x06\xd7\xfc\x24\x9a\x27\xfd\x04" +"\xac\x7f\xef\xf2\x7f\x8d\x1f\xf0\x92\x68\x9f\xf4\x12\xb1\xff\x00\xbf" +"\xc9\xfe\x35\x8b\xe5\xa7\xf7\x57\xf2\xa3\xcb\x4f\xee\xaf\xe5\x40\x1b" +"\x5f\xf0\x92\x68\x9f\xf4\x12\xb1\xff\x00\xbf\xc9\xfe\x34\x7f\xc2\x49" +"\xa2\x7f\xd0\x4a\xc7\xfe\xff\x00\x27\xf8\xd6\x2f\x96\x9f\xdd\x5f\xca" +"\x8f\x2d\x3f\xba\xbf\x95\x00\x6d\x7f\xc2\x49\xa2\x7f\xd0\x4a\xc7\xfe" +"\xff\x00\x27\xf8\xd1\xff\x00\x09\x26\x89\xff\x00\x41\x2b\x1f\xfb\xfc" +"\x9f\xe3\x58\xbe\x5a\x7f\x75\x7f\x2a\x3c\xb4\xfe\xea\xfe\x54\x01\xb5" +"\xff\x00\x09\x26\x89\xff\x00\x41\x2b\x1f\xfb\xfc\x9f\xe3\x47\xfc\x24" +"\x9a\x27\xfd\x04\xac\x7f\xef\xf2\x7f\x8d\x62\xf9\x69\xfd\xd5\xfc\xa8" +"\xf2\xd3\xfb\xab\xf9\x50\x05\xcd\x5b\xc4\x56\xcd\x0a\x7f\x66\x6a\xba" +"\x32\x4a\x18\xef\x37\x6f\xb9\x48\xda\x70\x06\xd6\x18\x3b\xb6\x9c\xfa" +"\x02\x31\xce\x45\x9b\x6f\x11\xe9\x42\x32\x2e\x35\x4d\x35\x9f\x71\xc1" +"\x8e\x55\x03\x6e\x78\xe0\x93\xce\x2b\x2b\xcb\x4f\xee\xaf\xe5\x47\x96" +"\x9f\xdd\x5f\xca\x95\xb5\xb9\x6e\x77\x8a\x8d\x97\xea\x79\x87\xc4\x69" +"\xa2\xb8\xf1\x65\xd4\xb0\x3a\x49\x13\x05\x2a\xe8\x72\xa4\x63\xb1\xa2" +"\xa2\xf1\xf0\x03\xc4\x93\x80\x30\x36\x27\xf2\xa2\xb9\x9e\xe5\x1e\xac" +"\x46\x41\xe7\x1e\xf5\xe6\x1e\x12\xf8\xd5\x17\x8b\x75\x4d\x62\xda\xdb" +"\x47\xd4\x65\xb7\xd3\xd2\xd5\xbe\xd3\x68\x04\xe2\x43\x2b\x5c\x06\xc0" +"\x0a\x3e\xe7\xd9\xf9\x2a\x5b\x3e\x62\xe3\xad\x7a\x7d\x79\x1f\x80\xbe" +"\x2e\xd9\xf8\x93\x57\xd7\xe2\xd3\x3c\x25\x70\xb6\xf6\x51\xd9\x91\x75" +"\xa6\x95\x91\xae\x04\x86\xe0\x72\x30\x83\x6a\x79\x3c\x10\xcd\x9f\x34" +"\x60\x01\xc9\xea\x8e\xb3\x4b\xf0\x12\x5f\xbb\x93\xb7\x6d\x7b\x1d\x8c" +"\x9e\x39\x48\xac\x20\xbb\x3a\x4e\xbe\x63\x95\x88\xf2\xd7\x4e\x63\x22" +"\xe0\xe3\x2c\x80\x64\x02\x7d\xbd\xfa\x73\x56\x6e\x3c\x58\xb0\x69\xf3" +"\x5d\x2e\x9d\xac\xcd\xe5\x67\xf7\x31\xd8\x9f\x31\xfe\x4d\xdf\x2a\x90" +"\x33\xd4\x0f\xaf\xd0\xe2\x93\x7c\x40\x64\xb2\x82\xe5\xbc\x2d\xe2\x40" +"\x25\x66\x5f\x2c\x59\xab\x48\xa0\x30\x5d\xcc\xa1\xc9\x00\xe7\x23\xbe" +"\x01\x38\xab\xb7\x3e\x2e\x36\xf6\x73\x4e\xba\x0e\xb5\x3b\x46\x4f\xee" +"\x62\xb7\x1b\xdc\x04\x0f\x95\x05\x80\x3d\x42\xf5\xce\xef\x60\x48\xef" +"\x95\x2b\x35\xee\x75\xee\x71\x42\x77\xfb\x57\xf9\x12\xd8\x78\x91\x2f" +"\xb4\xab\x6b\xe6\xb3\xd5\x6d\x7c\xe8\xcc\x9f\x67\x9e\xcd\x84\xd1\xe1" +"\x82\x90\xca\x01\xc1\xc9\xce\x3b\x80\x48\xe0\x53\xa2\xf1\x24\x33\x49" +"\x6a\xa2\x1d\x49\x45\xc1\x50\xae\xf6\x6c\xaa\xb9\x0c\x46\xe2\x57\xe5" +"\xfb\xa7\xaf\x72\xa3\xab\x0c\xc5\x6b\xe2\xb1\x73\x0e\x97\x21\xd1\xb5" +"\x88\x1a\xff\x00\x3f\xbb\x96\xd8\x86\xb7\xc3\x05\xfd\xee\x09\x09\xd7" +"\x3d\x79\x00\x9a\x22\xf1\x58\x94\x31\x3a\x36\xb1\x1e\xd1\x2f\x0f\x6a" +"\x79\x31\x82\x48\x1c\xf3\xbb\x18\x5e\xcd\xda\xb9\x26\xad\x26\xad\x6d" +"\x4d\xe2\xef\x14\xcb\x16\xba\xf4\x77\x77\x4b\x02\xc3\xa9\x46\xc4\x29" +"\xdf\x2d\x9b\xa2\x72\xa0\x8f\x98\xae\x3b\xf3\xe8\x41\x07\x91\x5e\x7d" +"\x69\xf1\xe6\xd6\xe7\xe2\x53\x78\x4b\xfb\x2e\xf4\x03\x33\xdb\xad\xea" +"\x32\x32\x6f\x42\x41\x0c\x31\xc6\x7b\x00\x49\xc0\x24\x81\x5d\xfc\xde" +"\x22\xf2\xee\xde\x04\xd2\xb5\x49\xf6\x01\x99\x12\x1c\x21\x25\x9c\x60" +"\x16\x61\x9f\xb9\x9c\xf4\xc3\x29\xcf\xcc\x2a\xae\x93\x0e\x9f\x71\xae" +"\x49\x7d\x1f\x86\xe6\xb2\xbf\x95\x48\x96\xfe\x6b\x78\xd1\x8e\xde\x30" +"\x5b\x76\xe3\x9f\x6c\xf1\xd7\x8c\x56\x68\xa7\xa1\xb9\x04\x86\x71\x90" +"\xd3\x27\x00\xfc\xe8\x17\xaf\xd4\x54\xbe\x5b\x7f\xcf\x57\xfc\x87\xf8" +"\x57\x0d\xf0\xbb\xe2\xc5\x87\xc5\x49\x3c\x41\xfd\x9f\x69\x3d\xb4\x3a" +"\x4d\xfc\xd6\x3e\x6c\xac\x48\x9f\xcb\x9a\x48\xc4\x8a\x71\xb4\xab\x79" +"\x5b\xd4\xa9\x6f\x95\xd7\x38\x6c\xa8\xee\xf6\x8f\x7f\xcc\xd3\x69\xad" +"\xc4\x9a\x7b\x0d\xf2\xdb\xfe\x7a\xbf\xe4\x3f\xc2\x8f\x2d\xbf\xe7\xab" +"\xfe\x43\xfc\x29\xdb\x47\xbf\xe6\x68\xda\x3d\xff\x00\x33\x48\x63\x7c" +"\xb6\xff\x00\x9e\xaf\xf9\x0f\xf0\xa3\xcb\x6f\xf9\xea\xff\x00\x90\xff" +"\x00\x0a\x76\xd1\xef\xf9\x9a\x36\x8f\x7f\xcc\xd0\x03\x7c\xb6\xff\x00" +"\x9e\xaf\xf9\x0f\xf0\xa3\xcb\x6f\xf9\xea\xff\x00\x90\xff\x00\x0a\x76" +"\xd1\xef\xf9\x9a\x36\x8f\x7f\xcc\xd0\x03\x7c\xb6\xff\x00\x9e\xaf\xf9" +"\x0f\xf0\xa3\xcb\x6f\xf9\xea\xff\x00\x90\xff\x00\x0a\x76\xd1\xef\xf9" +"\x9a\x36\x8f\x7f\xcc\xd0\x03\x7c\xb6\xff\x00\x9e\xaf\xf9\x0f\xf0\xa3" +"\xcb\x6f\xf9\xea\xff\x00\x90\xff\x00\x0a\x76\xd1\xef\xf9\x9a\x36\x8f" +"\x7f\xcc\xd0\x03\x7c\xb6\xff\x00\x9e\xaf\xf9\x0f\xf0\xac\xbd\x73\x59" +"\x97\x46\x58\xfc\xbb\x0d\x47\x53\x79\x08\x01\x2c\xa3\x46\x23\x90\x39" +"\x2c\x54\x0e\xbd\xc8\xe8\x6b\x5b\x68\xf7\xfc\xcd\x65\x6b\xba\xd4\x9a" +"\x32\xc4\x62\xd2\xaf\xf5\x46\x91\x82\xec\xb2\x55\x24\x64\x81\xc9\x66" +"\x50\x39\x23\xa9\x03\x19\x39\xe2\xb4\xa6\xaf\x24\xad\x72\x64\xec\xb7" +"\x39\xfb\xaf\x89\x02\xd3\x53\x36\x0d\xe1\xdf\x12\xcb\x38\x85\x66\x66" +"\x86\xc1\x64\x8d\x77\x67\xe5\x2e\x18\xae\xee\x09\xc6\x7d\x3b\xf1\x5d" +"\x76\x9d\x72\x6f\x6c\x2d\xee\x1a\x29\xad\xcc\xa8\xb2\x18\x6e\x14\x2c" +"\x91\xe4\x67\x6b\x01\xc0\x23\xbd\x72\xb7\xbf\x10\xe5\xb1\xd4\x05\x9b" +"\x78\x47\xc4\xb3\x48\x62\x59\x77\xc1\x6b\x1b\xc6\x33\xbb\xe5\xde\x24" +"\xdb\xb8\x6d\xe4\x67\xb8\xf5\xae\xab\x4e\xbb\xfb\x7d\x85\xbd\xc9\x82" +"\x6b\x5f\x3a\x35\x93\xc8\xb9\x5d\xb2\x47\x91\x9d\xac\x32\x70\x47\x71" +"\x9a\xe8\xad\x0e\x58\xa7\xc9\x6f\x9d\xee\x63\x4e\x57\x93\x5c\xd7\xf9" +"\x58\xf3\x2f\x1f\x7f\xc8\xcb\x3f\xfb\x89\xfc\xa8\xa3\xc7\xdf\xf2\x32" +"\xcf\xfe\xe2\x7f\x2a\x2b\xc9\x7b\x9d\xa7\xaa\x91\xb8\x11\xeb\xe8\x71" +"\x5e\x23\xf0\x77\xe3\xa5\x8f\xc4\xdd\x35\xee\xb4\xdf\x0e\xdc\xb5\xe0" +"\xfb\x32\xdc\xc3\xa7\x39\x22\x23\x20\x9c\x8d\xcf\x21\x8f\x2a\x82\x1c" +"\x12\x70\x37\x36\x17\x70\x2a\x5b\xdb\x99\x77\x29\x07\x38\x23\x1c\x1c" +"\x57\xc9\xff\x00\xb2\x37\xed\x37\xe2\xbf\x8f\x2d\xe2\x91\xaa\xf8\x62" +"\xd5\x7f\xb3\x20\xd2\xa7\x8b\xfb\x1d\xe4\x83\x70\xb9\x17\x26\x5d\xde" +"\x74\xc4\x36\xcf\x21\x40\xc1\x07\x2d\xc8\x1d\xb7\x57\xf6\xd0\xd7\x4d" +"\x74\xef\xf3\xe9\x6f\xc4\xe9\x82\x87\xd5\x2a\xb7\x0b\xca\xf1\xb4\xaf" +"\xf0\xef\x75\xcb\xd7\x9b\x4d\x7a\x5b\xcc\xfa\x5c\xea\x13\xaf\x9f\x9d" +"\x1f\x51\x3b\x06\x53\x6c\xd1\x9f\x33\xe4\x56\xc0\xcc\x83\x07\x24\xa7" +"\x38\xe5\x4f\x38\xc1\xa6\xdc\x6a\x57\x31\x5b\x5d\x49\x1e\x89\xa9\x4b" +"\x24\x4a\xc6\x38\x84\xf1\x83\x31\x00\x10\x14\xf9\xb8\x19\x27\x1f\x36" +"\x3a\x1f\x6a\x86\xf3\x50\xbd\x84\xaa\xdb\xe8\x1a\x95\xc3\x19\xa2\x8c" +"\xb3\x5e\x44\x88\x11\x88\xdf\x26\x7c\xd2\x70\x80\x9c\x8c\x64\x91\x80" +"\x08\x39\xa7\x9b\xeb\xb8\xb4\xe5\x9e\x4d\x0f\x51\x92\xe7\x6f\xcd\x6b" +"\x6f\x77\x1b\x10\x7d\x03\x34\x8a\x08\xf7\x38\xfc\x2b\xa7\xd9\xcb\x7d" +"\x35\xf3\x5f\xe6\x79\xdc\xeb\x6f\xd1\x8f\x4d\x4e\x59\x62\x9e\x44\xd2" +"\x35\x22\x23\x97\xcb\x55\x69\x15\x5a\x4f\x9d\x94\xb2\x83\x20\xf9\x46" +"\xdd\xd9\x38\xc8\x23\x19\xa1\xb5\x88\xed\xac\x35\x1b\xcb\xdb\x2b\xfb" +"\x08\x6c\xa2\x33\xb9\x99\xb7\xef\x40\x85\x89\x51\x1b\x31\x24\x60\x82" +"\x3a\xe4\x70\x0e\x46\x64\x79\xe5\x51\x19\x1a\x65\xfb\x07\x38\x21\x6e" +"\x13\x72\x7d\xfe\x5b\x32\x63\x1f\x2a\xf4\x24\xfc\xe3\x8e\x0e\x2b\xcf" +"\x79\x75\xf6\x98\xa1\x4d\x0b\x51\x92\x16\x62\x25\x95\xae\xe3\x0a\xab" +"\xe5\x33\x64\x0f\x34\x96\x25\x82\xa6\x38\xe5\xb3\x9c\x0c\xd6\x36\xb3" +"\xb1\x6b\x55\x73\x33\xe1\x47\x8d\x17\xe2\x77\x80\x34\xaf\x13\x7d\x8d" +"\xf4\xdf\xb7\x09\x4f\xd9\x7e\xd2\xf2\x79\x7b\x25\x78\xf1\xb8\xaa\x92" +"\x7e\x4c\xfd\xd1\x8c\xe3\x9c\x64\xf5\xbf\x66\x4f\x59\x3f\xef\xe3\x7f" +"\x8d\x64\xc7\x73\x70\xca\xc8\x74\x9b\xd8\xe5\xe4\x26\xeb\x95\xf2\xc9" +"\xc1\xc7\xcc\x1c\x90\x0e\x07\x3b\x78\xcf\x7a\xa7\xe2\x2f\x12\x43\xe1" +"\x8f\x0d\x6a\xfa\xcd\xfd\x8d\xf4\x10\x69\xf0\xb4\xa1\x0c\xdb\x9a\x62" +"\x09\x01\x57\x63\x37\xde\x21\x71\x9c\x7d\xf1\x9c\x60\xe1\xbb\x37\xa0" +"\x95\xd2\xd4\xde\xb7\xd3\x6d\xed\x23\x11\xc1\x19\x86\x30\x49\x09\x1b" +"\x15\x1f\x90\x35\x27\xd9\x93\xd6\x4f\xfb\xf8\xdf\xe3\x5c\x1f\xc1\x9f" +"\x88\xab\xf1\x77\xc2\x12\x6b\x8d\xa1\x6a\xbe\x1e\x29\x79\x35\xa0\xb7" +"\xbf\x77\x1e\x68\x42\x31\x2c\x6c\x70\x59\x08\x20\x67\x03\xe6\x57\x03" +"\x20\x06\x3d\xdf\xd8\xe3\xfe\xf4\xbf\xf7\xf9\xff\x00\xc6\x86\xac\xec" +"\xc1\x3b\xab\x8b\xf6\x64\xf5\x93\xfe\xfe\x37\xf8\xd1\xf6\x64\xf5\x93" +"\xfe\xfe\x37\xf8\xd2\x7d\x8e\x3f\xef\x4b\xff\x00\x7f\x9f\xfc\x68\xfb" +"\x1c\x7f\xde\x97\xfe\xff\x00\x3f\xf8\xd2\x18\xbf\x66\x4f\x59\x3f\xef" +"\xe3\x7f\x8d\x1f\x66\x4f\x59\x3f\xef\xe3\x7f\x8d\x27\xd8\xe3\xfe\xf4" +"\xbf\xf7\xf9\xff\x00\xc6\x8f\xb1\xc7\xfd\xe9\x7f\xef\xf3\xff\x00\x8d" +"\x00\x2f\xd9\x93\xd6\x4f\xfb\xf8\xdf\xe3\x47\xd9\x93\xd6\x4f\xfb\xf8" +"\xdf\xe3\x49\xf6\x38\xff\x00\xbd\x2f\xfd\xfe\x7f\xf1\xa3\xec\x71\xff" +"\x00\x7a\x5f\xfb\xfc\xff\x00\xe3\x40\x0b\xf6\x64\xf5\x93\xfe\xfe\x37" +"\xf8\xd1\xf6\x64\xf5\x93\xfe\xfe\x37\xf8\xd2\x7d\x8e\x3f\xef\x4b\xff" +"\x00\x7f\x9f\xfc\x68\xfb\x1c\x7f\xde\x97\xfe\xff\x00\x3f\xf8\xd0\x02" +"\xfd\x99\x3d\x64\xff\x00\xbf\x8d\xfe\x34\x7d\x99\x3d\x64\xff\x00\xbf" +"\x8d\xfe\x34\x9f\x63\x8f\xfb\xd2\xff\x00\xdf\xe7\xff\x00\x1a\x3e\xc7" +"\x1f\xf7\xa5\xff\x00\xbf\xcf\xfe\x34\x00\xbf\x66\x4f\x59\x3f\xef\xe3" +"\x7f\x8d\x67\xea\xd3\xdc\x58\x2c\x66\xd7\x4e\xba\xd4\x4b\x1c\x11\x15" +"\xc0\x4d\xbf\xf7\xd3\x0a\xbf\xf6\x38\xff\x00\xbd\x2f\xfd\xfe\x7f\xf1" +"\xaa\xba\x98\x7b\x2b\x39\x25\xb7\xb4\xba\xd4\x26\x55\x25\x2d\xe1\xb8" +"\xda\xce\x40\x24\x0c\xbb\x80\x32\x46\x33\x9e\xf5\x51\x69\x3d\x55\xc4" +"\xd5\xcc\x19\x7c\x51\xa8\x26\xa5\x25\x9a\x78\x4f\x5a\x98\xc7\x1a\x48" +"\xd3\xac\xd1\x08\xb2\xdb\xbe\x50\xcd\x28\xdc\x46\xde\x71\xd3\x23\xd6" +"\xba\x7b\x09\x1e\x6b\x38\x64\x92\x09\x2d\xa4\x75\x0c\xd0\xca\xc1\x99" +"\x0f\xa1\x20\x91\xf9\x1a\xa1\x1c\xd2\xbc\x45\x9b\x4d\xbf\x8d\xb7\x60" +"\x46\x6e\x10\xb6\x3e\x5e\x78\x93\x1f\xc4\x7b\xe7\xe5\x3e\xd9\xbf\x62" +"\xc5\xed\x22\x76\x86\x5b\x77\x75\x0c\xd0\xce\xe1\x9d\x09\xfe\x12\x43" +"\x30\xc8\xf6\x24\x7a\x1a\xb9\xca\x32\x4b\x96\x29\x7d\xff\x00\xe6\x44" +"\x62\xd3\xd6\x57\xfb\x8f\x3f\xf1\x6f\x86\x75\xad\x5b\x5d\xb8\xb8\xb7" +"\x8f\x4f\x10\x1c\x2a\x79\xb7\x4e\xac\x40\x1d\x48\x11\x9c\x7e\x66\x8a" +"\xed\xa7\xff\x00\x5c\xf8\x6d\xbc\xfa\xe3\x3f\xa8\xa2\xb9\x1a\x46\xf7" +"\x34\x6b\xc7\x7e\x10\xfc\x5a\xf1\x67\xc4\x0b\x5d\x68\xea\x1e\x16\x4b" +"\x4b\x9d\x39\xad\xe3\x54\xf3\x1e\xdc\x4e\x58\xc8\x24\x65\xdf\xbb\x18" +"\xd8\xb8\x52\x73\xc9\xc9\xe9\x5e\xc4\x46\x46\x0d\x79\x1f\xc2\x7f\x88" +"\x9e\x36\xf1\xa5\xb6\xb6\x75\xcf\x08\x47\xa5\x5c\xd8\x98\x16\x04\x74" +"\x96\xdd\x6e\x4b\x79\x9e\x61\x06\x40\x78\x1b\x53\x81\x9c\x67\x92\x72" +"\x2a\x5b\xfd\xfd\x38\xdf\x7b\xe9\xd1\xfa\xbe\x84\xb5\xee\x36\x77\xf2" +"\xea\xfa\xb2\x2b\x14\xf0\xf5\xc4\x84\x79\x7c\x7d\xae\x20\x4e\xe0\x0b" +"\x63\xe6\xfe\x12\x48\x39\xc6\x70\x71\x9e\x33\x25\x8e\xa9\xa9\xdc\xbc" +"\x4b\x3e\x87\x35\xa6\xe8\xd9\xdd\x9e\xea\x36\x08\xc1\x88\x0b\xf2\xb1" +"\x24\x90\x01\xce\x31\xcf\x3c\xf1\x55\xe5\xd4\xb5\xb5\x52\x53\xc3\xa8" +"\xe4\x2a\x1c\x1b\xd4\x19\x25\x41\x60\x38\xfe\x12\x71\x9e\xf8\x24\x76" +"\x06\x7b\x2b\xed\x52\x76\x85\x6e\x34\x35\xb5\xdd\x1b\x34\x8c\x6e\x51" +"\x82\x30\x24\x05\x18\xe4\xe4\x00\x73\xd3\x9f\x6a\xf4\x65\x1b\x41\xfb" +"\xab\xef\xd7\x6e\xd7\xfd\x37\x30\x4f\xde\x5a\xbf\xbb\xfe\x01\x1a\xeb" +"\x3a\xab\x5b\x47\x27\xfc\x23\xb7\x4b\x23\x06\xcc\x2d\x75\x0e\xe4\x21" +"\x54\x80\x48\x72\x39\x24\xaf\x04\xe3\x6f\xa1\x15\x32\xea\xd7\xe7\x78" +"\x3a\x1d\xd8\x28\xc1\x7f\xd7\xc5\x86\xcb\xa8\x24\x7c\xfd\x02\xb1\x6e" +"\xdf\x74\x8e\xb8\x06\x38\xaf\xf5\x57\x82\x07\x6d\x05\x63\x91\xdf\x6c" +"\x91\x9b\xb4\x3e\x58\xd8\x0e\xec\x81\xcf\xcd\x95\xc0\xfa\xf4\xa9\x5a" +"\xf3\x51\x10\x23\x8d\x1d\x4b\x9d\x9b\xe3\x17\x09\x91\x92\x43\x60\xf4" +"\x3b\x46\x0f\xb8\x27\x1c\x8c\x1e\x6e\xa6\xe6\x0f\xc4\x2f\x18\x6b\x1e" +"\x14\xf8\x7f\xe2\x5d\x76\xcf\x43\x92\x5b\xcd\x33\x4c\xbb\xbd\x86\x19" +"\x64\x12\x07\x78\xa1\x95\xd0\x15\x46\xdc\xd9\x28\x83\x6a\xf2\x77\x8c" +"\x74\x38\xf2\x0f\xd9\x6f\xf6\x85\xf1\xef\xc6\x69\xbc\x4e\x3c\x47\xe1" +"\xab\x2b\x78\xb4\xe8\xb4\xd9\xad\xce\x9f\x0c\xf6\xac\x7e\xd3\x1c\xcf" +"\x22\x37\x9e\xe4\x33\xc6\x63\x89\x48\x52\x00\xdc\xdc\x92\x31\x5e\xbf" +"\xe2\xaf\x17\xeb\x5e\x1d\xf0\x4e\xab\xaa\x45\xe1\x49\xaf\xb5\xb8\x87" +"\x97\x61\xa4\xdb\xca\x24\x37\x72\x94\x05\x15\x9d\x54\x88\xd4\xb9\x2a" +"\x59\xb8\x1b\x73\xdc\x0a\xd3\xf0\x1e\xb7\x7f\xe2\x9f\x0b\xda\xea\x7a" +"\xb6\x83\x37\x87\x6f\x27\x69\x3f\xe2\x5d\x74\x41\x96\x34\x12\x32\xa1" +"\x70\x38\x52\xca\x03\x6d\xc9\xc6\xec\x66\x84\xec\xf5\x2b\x4e\x47\xa7" +"\xcc\xa9\x77\xe2\x5f\x11\xc5\x67\x6b\x24\x1e\x0c\xbb\x9e\x79\x24\x55" +"\x96\x13\xa8\xdb\xa7\x94\x85\xc8\x2d\x9d\xe4\x12\x14\x6e\xc0\xeb\x90" +"\x33\xd7\x1a\x96\xba\x9e\xa7\x35\xe5\x94\x72\xe8\x92\xdb\xc1\x34\x65" +"\xe6\x9d\xae\xa3\x6f\x21\xbe\x6c\x21\x50\x72\xc7\x81\xc8\xe3\xe6\xef" +"\x83\x5a\xfe\x52\x7f\x74\x7e\x54\x79\x49\xfd\xd1\xf9\x56\xee\xa4\x5a" +"\xb2\x82\x5f\x7f\xf9\xf4\xff\x00\x87\x30\x51\x69\xdf\x99\xfe\x1f\xe4" +"\x79\x3e\x91\xf1\xba\xff\x00\x56\xf8\x89\xe2\x0d\x15\x7c\x1d\xa8\xc7" +"\xe1\xad\x1a\x39\x5e\x5f\x12\x17\x63\x1c\x86\x36\x85\x4a\xac\x41\x0b" +"\x16\xcb\xcc\x71\xfd\xdb\x72\x46\xed\xc0\x0d\x3f\x83\x1f\x14\xb5\x2f" +"\x8a\xba\x6d\xf5\xed\xff\x00\x84\x75\x0f\x0a\x45\x09\x8b\xc9\x17\xee" +"\x49\x9f\x7a\x96\x3b\x48\x50\xa7\x68\xdb\x92\xac\xc3\x2c\x47\x05\x48" +"\xaf\x45\xf2\x93\xfb\xa3\xf2\xa3\xca\x4f\xee\x8f\xca\xb9\x92\x69\xea" +"\xcd\x9b\x4d\x68\x85\xda\x3d\xff\x00\x33\x46\xd1\xef\xf9\x9a\x4f\x29" +"\x3f\xba\x3f\x2a\x3c\xa4\xfe\xe8\xfc\xaa\x89\x17\x68\xf7\xfc\xcd\x1b" +"\x47\xbf\xe6\x69\x3c\xa4\xfe\xe8\xfc\xa8\xf2\x93\xfb\xa3\xf2\xa0\x05" +"\xda\x3d\xff\x00\x33\x46\xd1\xef\xf9\x9a\x4f\x29\x3f\xba\x3f\x2a\x3c" +"\xa4\xfe\xe8\xfc\xa8\x01\x76\x8f\x7f\xcc\xd1\xb4\x7b\xfe\x66\x93\xca" +"\x4f\xee\x8f\xca\x8f\x29\x3f\xba\x3f\x2a\x00\x5d\xa3\xdf\xf3\x35\x97" +"\xad\xde\xea\x76\x42\x11\xa6\xe9\x63\x52\x67\x60\x18\xbd\xd0\x85\x63" +"\x04\x81\x92\x48\x27\x00\x12\x78\x04\xf0\x78\xce\x2b\x4f\xca\x4f\xee" +"\x8f\xca\xb2\xf5\xa9\xf5\x2b\x5f\x24\x69\x9a\x64\x37\xec\xcc\x03\xf9" +"\xb7\x02\x15\x41\x90\x32\x4e\x09\xe3\x39\xe0\x1e\x87\x8c\xe2\xb4\xa7" +"\xac\x96\x89\xfa\xe8\xbf\x34\x4c\xb6\x31\x6f\xbc\x4b\xe2\x7b\x6d\x48" +"\x5a\xc3\xe0\xd9\x2f\x21\xf2\x55\xcd\xdc\x5a\x9c\x4b\x18\x73\x9c\xa6" +"\x1f\x0c\x71\x81\xc8\x18\xe7\xb5\x75\x1a\x6c\xd3\xdc\x69\xf6\xd2\xdd" +"\x5b\x1b\x3b\x99\x23\x57\x96\xd8\xc8\x24\x31\x31\x19\x2b\xb8\x70\x70" +"\x78\xc8\xe2\xb9\x5b\xdd\x77\xc5\xb0\x6a\x86\xda\x0f\x07\x5b\x5d\x5a" +"\x88\x55\xfe\xd8\x35\x54\x55\xde\x73\x94\x0a\x53\x77\x18\x1c\xe3\x1c" +"\xfb\x57\x55\xa6\xc9\x3c\xd6\x16\xf2\x5d\x5b\x0b\x3b\x97\x8d\x5a\x5b" +"\x75\x70\xe2\x36\x23\x95\xdc\x38\x38\xe9\x9a\xe8\xad\x1b\x45\x7b\xb1" +"\x5e\x8e\xef\xe7\xef\x3b\x7d\xc8\xc6\x9b\xbc\x9e\xad\xfa\xab\x7e\x88" +"\xad\x70\xc4\x4c\xdb\x7d\x79\xa2\x89\xd8\xac\xcf\x8c\x72\x73\xc9\xc7" +"\xf5\x14\x57\x9a\x75\xa3\x45\x94\x30\x20\x80\x41\xe0\x83\xde\xbc\x53" +"\xe1\x37\x8f\x7c\x6d\xe2\xed\x21\x9f\x53\xf0\x4c\xd6\xda\x84\x62\xd8" +"\x4c\xba\xad\xb9\xd3\x63\xcb\xf9\xe6\x56\x8f\x2b\x21\x65\x4d\x91\x28" +"\x1c\x93\xb8\x31\x09\x9d\xab\xed\x64\x02\x08\x23\x20\xf5\x06\xbc\x2f" +"\xe0\xaf\x89\x3e\x28\xf8\x94\xeb\xcd\xe2\x7d\x22\xca\xdb\xc8\xba\x85" +"\x6d\x92\xff\x00\x4f\x92\xc0\x18\x88\x9b\xcc\xd8\x70\xdb\xcf\x10\x10" +"\x7a\x7c\xcd\x9c\x1f\x94\x74\x42\x94\xa5\x35\x51\x35\x65\xba\x7d\x6f" +"\xb7\x55\xb5\x8b\x55\xe1\x0a\x33\xa2\xe1\x79\x4a\xcd\x4b\xaa\xb5\xee" +"\xb6\x7a\x3b\xfe\x08\xf5\x39\xbf\xb6\x04\x77\x1e\x56\x89\xa6\xb3\x84" +"\xcc\x3b\xee\x88\x05\xbc\xb5\x38\x6f\xdd\xff\x00\x7f\x70\xc8\xec\x01" +"\xef\x81\x2b\x1d\x45\x44\x18\xd0\xec\xd8\xb1\x22\x50\x2e\x87\xc9\xf3" +"\x0e\x9f\xbb\xf9\xbe\x5c\x9e\xdc\xe0\x74\x3b\x84\xb6\xb0\xea\x46\x61" +"\xf6\x9b\x7d\x35\x62\xcf\x26\x22\xc5\xb1\xb3\xa7\x20\x7f\x17\x19\xee" +"\x06\x70\x09\xc0\x8e\x44\xd5\xc4\x65\x92\xcb\x4a\x2e\x19\xc0\x46\x99" +"\xc0\x2b\x8f\x90\xee\xf2\xf8\x24\xf5\x18\x38\xcf\x04\xe3\x05\xc9\x59" +"\x9c\xe9\xdd\x12\x3a\x5e\x09\x50\x2e\x93\x66\x62\xdc\xc1\x98\xcd\xce" +"\xdf\xde\x6d\x20\x6c\xea\x71\x1f\x7e\x37\xb7\x5d\xbc\xf8\x7d\x8f\x8f" +"\x3e\x28\x4f\xfb\x46\xea\x5e\x1c\x93\xc3\x50\x27\x82\xad\xae\x12\x28" +"\xa6\x3a\x34\xeb\x1c\xc8\xda\x50\x9c\xb8\xbe\x3f\xbb\xc0\xbb\xdd\x11" +"\xf9\x09\xe4\x0c\x64\x1a\xf6\xf2\x9a\x98\x57\x22\xc7\x4c\x2d\x92\x14" +"\x19\xd8\x02\x37\x8c\x12\x7c\xbe\x3e\x4d\xc7\x1d\x88\x03\x24\x1c\x8b" +"\x56\xb0\xca\xd2\x49\xf6\x9b\x7b\x54\x4c\x0d\x9e\x51\x2c\x49\xe7\x39" +"\xc8\x1e\xd8\xfc\x6a\x0b\x4e\xdd\x0c\x88\x1b\x5d\x66\xbc\xf3\x74\x0d" +"\x29\x42\x85\x36\xc0\x5f\x37\xce\x76\xfc\xc1\xcf\x93\xf2\x80\x7b\x80" +"\x78\x3d\x38\xab\xfa\x4a\x5d\xdc\x09\x3f\xb4\x74\x9b\x4b\x22\x31\xb3" +"\xc8\x9f\xce\x0d\xc7\x39\xca\x2e\x2b\x47\xec\xf0\x7f\xcf\x38\xff\x00" +"\xef\x91\x47\xd9\xe0\xff\x00\x9e\x71\xff\x00\xdf\x22\xb5\x94\xd3\x5a" +"\x45\x2f\xbf\xfc\xcc\xd4\x5a\xea\x79\x17\x86\x7e\x28\xf8\xa3\x5c\xd0" +"\xbc\x53\x77\x37\xc3\x5b\xab\x3b\xbd\x36\x2b\x99\xf4\xf8\x6e\x73\x08" +"\xbe\x0a\xae\xf0\x26\x36\x33\x23\xb6\x11\x4e\x03\x10\x58\x9d\xa3\x85" +"\xae\x97\xe0\xee\xbf\xe2\x0f\x19\x78\x31\x35\x1f\x17\xf8\x51\x3c\x2f" +"\xab\xfd\xa2\x48\xfe\xc4\x47\x2d\x18\x3f\x2c\x9b\x4e\x4a\xe7\x91\x82" +"\x4f\x4c\xe7\x07\x03\xb8\xfb\x3c\x1f\xf3\xce\x3f\xfb\xe4\x51\xf6\x78" +"\x3f\xe7\x9c\x7f\xf7\xc8\xac\x2c\xf9\xaf\x7f\x97\xf5\xaf\x9f\xcf\xb6" +"\x86\xca\x49\x43\x92\xd7\x7d\xfa\xff\x00\x97\xe1\xd0\x4f\xb1\x5b\xff" +"\x00\xcf\x08\xbf\xef\x81\x47\xd8\xad\xff\x00\xe7\x84\x5f\xf7\xc0\xa5" +"\xfb\x3c\x1f\xf3\xce\x3f\xfb\xe4\x51\xf6\x78\x3f\xe7\x9c\x7f\xf7\xc8" +"\xaa\x20\x4f\xb1\x5b\xff\x00\xcf\x08\xbf\xef\x81\x47\xd8\xad\xff\x00" +"\xe7\x84\x5f\xf7\xc0\xa5\xfb\x3c\x1f\xf3\xce\x3f\xfb\xe4\x51\xf6\x78" +"\x3f\xe7\x9c\x7f\xf7\xc8\xa0\x04\xfb\x15\xbf\xfc\xf0\x8b\xfe\xf8\x14" +"\x7d\x8a\xdf\xfe\x78\x45\xff\x00\x7c\x0a\x5f\xb3\xc1\xff\x00\x3c\xe3" +"\xff\x00\xbe\x45\x1f\x67\x83\xfe\x79\xc7\xff\x00\x7c\x8a\x00\x4f\xb1" +"\x5b\xff\x00\xcf\x08\xbf\xef\x81\x47\xd8\xad\xff\x00\xe7\x84\x5f\xf7" +"\xc0\xa5\xfb\x3c\x1f\xf3\xce\x3f\xfb\xe4\x51\xf6\x78\x3f\xe7\x9c\x7f" +"\xf7\xc8\xa0\x04\xfb\x15\xbf\xfc\xf0\x8b\xfe\xf8\x14\x7d\x8a\xdf\xfe" +"\x78\x45\xff\x00\x7c\x0a\x5f\xb3\xc1\xff\x00\x3c\xe3\xff\x00\xbe\x45" +"\x1f\x67\x83\xfe\x79\xc7\xff\x00\x7c\x8a\x00\x4f\xb1\x5b\xff\x00\xcf" +"\x08\xbf\xef\x81\x59\xfa\xb4\x17\x91\xf9\x5f\xd9\xb6\x16\x53\x92\xd8" +"\x73\x70\xdb\x30\x3d\x46\x14\xfd\x7f\x0a\xd1\xfb\x3c\x1f\xf3\xce\x3f" +"\xfb\xe4\x56\x7e\xad\x05\xff\x00\xee\xc6\x99\x16\x9f\xc9\xf9\xda\xec" +"\x37\x03\xd8\x28\xe7\xf3\x15\x70\xf8\xbf\xcc\x99\x6c\x62\x4f\x71\xe2" +"\x75\xd5\x64\x82\x2f\x0c\xe9\x52\x59\x2c\x48\xc2\xed\xef\xf6\xef\x73" +"\xbb\x72\x84\xf2\x89\xe3\x0b\xd7\x1f\x7b\x8c\xe0\x8a\xea\x34\xf1\x28" +"\xb2\x87\xcf\x86\x3b\x79\xca\x83\x24\x71\x1c\xaa\xb7\x70\x0e\x06\x6b" +"\x96\xb9\x3e\x30\xfe\xd3\x78\xad\xb4\xdf\x0f\xb5\x8a\xc4\xa7\xed\x13" +"\xcf\x22\xb3\xc8\x73\xb8\x04\x08\xdc\x0e\x07\x24\x75\xcf\x3d\x2b\xaa" +"\xb0\x59\x96\xca\x11\x70\x90\xa5\xc6\xc1\xe6\xad\xbe\x7c\xbd\xd8\xe7" +"\x6e\x70\x71\x9f\x5a\xde\xb2\x4a\x2a\xca\x2b\xd1\xdf\xef\xd5\x99\x53" +"\x6d\xb7\xab\xf9\xaf\xf8\x05\x59\xf3\xe7\x3e\x09\x1c\xf6\x19\xa2\x89" +"\xc0\x33\x3e\x71\xd7\xb9\xc5\x15\xe7\x9d\x26\x89\xc6\x0e\x7a\x77\xcd" +"\x72\x2c\xde\x31\x0b\x2a\xa8\xf0\xf3\x38\x99\x3c\xb7\x3e\x70\x56\x8b" +"\x07\x7e\x47\x24\x3e\x71\x8e\x48\xe6\xba\xe2\x70\x32\x78\x15\xe4\x1f" +"\x09\x2e\xfe\x28\x5d\x5b\xeb\x91\xf8\xc1\xad\x6d\x67\x84\xc0\x9a\x7c" +"\xb3\x47\x19\x59\x40\xf3\x3c\xd6\x61\x1b\x64\x93\xf2\x7f\x77\x8c\x63" +"\xbd\x6f\x1a\xde\xce\xa4\x61\xcb\x7e\x6e\xb6\xd1\x5b\xbf\x63\x39\x43" +"\x99\x5e\xfb\x1d\xae\x80\xfe\x2b\x33\xcc\xba\xda\xe8\x29\x09\x56\xf2" +"\x9f\x4f\x69\x58\x83\x93\xb7\x70\x7c\x76\x23\xa7\xa1\xe7\x91\x8b\x80" +"\xeb\xa4\xdb\x67\xfb\x21\x41\x8b\xf7\xf8\xf3\x0e\xc9\x37\x8f\xb9\xd3" +"\x72\xec\xdd\xd7\x07\x20\x76\x3c\x24\x92\x78\x8b\x69\xf2\xe6\xd1\xc1" +"\xc2\x00\x59\x64\xe4\xe0\x6f\x3d\x78\x00\xe7\x03\xb8\x03\x24\x67\x22" +"\x6b\x49\x75\xad\xf0\x8b\xa9\x74\xdd\x82\x36\xf3\x1e\x22\xf9\x2f\x93" +"\xb7\x68\x3d\x06\x36\xe7\x24\xf7\xfa\xd7\x45\x4f\x7a\xf3\xd3\xd1\x19" +"\xc3\xdd\xb2\xd7\xe6\x46\xaf\xad\x79\x30\x13\x1e\x92\x26\x2e\xbe\x6a" +"\x89\x1f\x68\x5d\xad\xbb\x69\xdb\xc9\xdd\xb7\x19\x03\x82\x7d\x00\x37" +"\x34\xb3\x78\x52\x6f\xed\x21\x64\x1f\x78\xf2\x85\xa9\x24\x6c\xd8\xb9" +"\xdd\xbb\xbe\xfd\xfd\x3b\x6d\xef\x9a\xa9\x14\x9a\xf9\x82\x0f\x36\x6d" +"\x24\x4d\xe6\x7e\xf7\x60\x90\xae\xcd\xa3\xee\xf3\x9c\xee\xcf\x5e\xd8" +"\xef\x52\xa4\x9a\xbb\x40\xbb\xe6\xd3\x92\x70\x13\x76\xd0\xec\xa4\xee" +"\x3b\xf1\xc8\x23\xe5\xdb\x8e\xbc\xe7\x39\xae\x63\x73\x4f\x30\xff\x00" +"\xb1\xfa\x51\x98\x7f\xd8\xfd\x2b\x29\x9b\x59\xd9\x1e\xdb\xad\x34\xbe" +"\x46\xfc\xc6\xe0\x63\x3c\xe3\xe6\xf4\xff\x00\x22\xb0\x7e\x21\x6b\x9e" +"\x2b\xd2\x7c\x1c\x4f\x87\xf4\xc8\xf5\x4f\x10\xdc\x4a\xb0\x20\xb6\x91" +"\x16\x3b\x75\x20\x96\x95\x8c\x84\x74\x0a\x40\xe0\xfc\xcc\x99\x1b\x77" +"\x10\x9b\xb2\x6c\x71\x8f\x34\x94\x7b\x9d\x9e\x61\xff\x00\x63\xf4\xa3" +"\x30\xff\x00\xb1\xfa\x56\x7f\x86\xae\x6f\xa4\xf0\xe6\x94\xfa\xc7\x97" +"\x16\xae\xd6\x91\x1b\xc8\xd4\xae\x16\x6d\x83\xcc\x03\x04\x8f\xbd\x9e" +"\x84\x8f\x73\x5a\x5e\x74\x7f\xdf\x5f\xce\x99\x23\x73\x0f\xfb\x1f\xa5" +"\x19\x87\xfd\x8f\xd2\x9d\xe7\x47\xfd\xf5\xfc\xe8\xf3\xa3\xfe\xfa\xfe" +"\x74\x00\xdc\xc3\xfe\xc7\xe9\x46\x61\xff\x00\x63\xf4\xa7\x79\xd1\xff" +"\x00\x7d\x7f\x3a\x3c\xe8\xff\x00\xbe\xbf\x9d\x00\x37\x30\xff\x00\xb1" +"\xfa\x51\x98\x7f\xd8\xfd\x29\xde\x74\x7f\xdf\x5f\xce\x8f\x3a\x3f\xef" +"\xaf\xe7\x40\x0d\xcc\x3f\xec\x7e\x94\x66\x1f\xf6\x3f\x4a\x77\x9d\x1f" +"\xf7\xd7\xf3\xa3\xce\x8f\xfb\xeb\xf9\xd0\x03\x73\x0f\xfb\x1f\xa5\x19" +"\x87\xfd\x8f\xd2\x9d\xe7\x47\xfd\xf5\xfc\xe8\xf3\xa3\xfe\xfa\xfe\x74" +"\x00\xdc\xc3\xfe\xc7\xe9\x59\x7a\xd8\xd5\x18\x46\x34\x89\x34\xd8\xc9" +"\x3f\x3b\xde\xa3\x3e\xd1\x91\xc8\x55\x23\x3c\x67\x8c\x8e\x71\xcd\x6b" +"\x79\xd1\xff\x00\x7d\x7f\x3a\xcb\xd6\xce\xab\x22\xc4\x34\x9b\xab\x08" +"\x1b\x70\xf3\x1a\xf1\x19\xc0\x19\x19\xc0\x56\x19\xe3\x3c\x64\x72\x07" +"\x35\xa5\x3f\x89\x6d\xf3\xd8\x99\x6c\x73\xb7\xa7\xc7\xa3\x55\x31\xd9" +"\x9f\x0b\xb6\x9e\x21\x5f\xdf\x5c\x2c\xeb\x23\x4b\xce\xec\x20\x62\x02" +"\xf4\xea\x73\xd7\xad\x76\x1a\x6f\xda\x3e\xc1\x6f\xf6\xbf\x23\xed\x7b" +"\x07\x9d\xf6\x6c\xf9\x7b\xf1\xf3\x6d\xcf\x38\xcf\x4c\xd7\x2f\x78\x3c" +"\x6e\x35\x3c\x5a\xdf\x78\x74\xe9\xde\x4a\xfc\xf3\x41\x30\x94\xc9\xf3" +"\x6e\xf9\x43\xe3\x6f\xdd\xc7\x39\xeb\xed\x5d\x3e\x9a\x6e\x4e\x9f\x6f" +"\xf6\xd7\x85\xef\x3c\xb5\xf3\x9a\xd8\x11\x1e\xfc\x7c\xdb\x73\xce\x33" +"\xd3\x35\xd1\x59\xde\x2b\xe1\xf9\x6f\xf3\x31\xa6\xbd\xe7\xbf\xcc\xaf" +"\x39\x22\x67\xc7\xaf\xa6\x68\xa4\xb8\x00\xcc\xd9\xc7\x5e\xf4\x57\x9a" +"\x75\x9a\x44\x80\x09\x27\x00\x77\xaf\x19\xf8\x4d\x73\xf1\x62\xeb\x49" +"\x31\x78\xb5\x2c\x74\x9b\xd8\xbe\xcf\x99\xef\x24\x8a\xeb\xcf\x1f\xbf" +"\x33\x60\x40\xe0\x03\x9f\x20\x0f\xba\x00\xe3\xe7\x20\xb3\x14\x56\x96" +"\x6a\xa4\x6a\x27\xb5\xf4\xe8\xef\xdf\xd3\xa1\xac\x6b\x72\xd0\x9d\x0e" +"\x54\xf9\x9a\x77\xfb\x4a\xd7\xd9\xdf\x44\xef\xaf\x7b\x2e\xc7\xa6\x01" +"\xab\x81\x3f\xfc\x4c\xf4\xf6\x66\x5f\xdd\x66\xdd\xb0\x8d\xe5\xa8\xe7" +"\xf7\x9c\x8d\xe1\x9b\x1c\x1c\x10\x33\xc6\x6a\x5c\xea\x1b\xd3\xfd\x3e" +"\xcc\x20\x70\x5c\x79\x47\x25\x73\xc8\x1f\x37\x04\x8c\x8c\xfe\x94\x51" +"\x54\xf5\x77\x39\xcf\x9d\x3f\x67\x0b\x5f\xda\x09\x35\x5b\x49\x3e\x26" +"\x6a\x13\x43\x6e\xda\x13\x25\xca\x6a\x0d\xa7\x4d\x1a\xea\x22\x68\xb0" +"\xc8\x2d\x18\x31\x06\x3f\x33\xa9\xc0\xfa\xe0\x9f\xa0\x6d\x63\xd7\x23" +"\x86\xe8\x4f\xab\xe9\xd3\x48\xc5\xbe\xce\xcb\x68\xca\x10\x67\xe5\xdd" +"\xfb\xc3\xbb\x03\x23\x8c\x67\xda\x8a\x2a\xe3\x37\x15\x65\xf9\x04\xbd" +"\xe7\xcc\x50\xf1\x5d\xd7\x89\x6d\x3c\x0f\xae\x9d\x2d\xe0\xd4\x7c\x44" +"\xd0\xba\xe9\xc2\xc8\x47\x01\x46\x65\x0a\xad\xfb\xe6\x64\x25\x58\xb3" +"\xfc\xd8\x04\x00\x31\xeb\xe7\xda\xee\xbd\xf1\xaa\xef\xe1\x2c\xcf\x67" +"\xa2\xe8\xd6\x5e\x3b\x7b\x98\xe1\x48\xec\xef\x22\x68\x92\x21\x93\x24" +"\xc3\xcd\x25\x70\x48\x0a\x14\x92\x70\xd9\x3c\xf0\x0a\x2a\x26\xf9\xf4" +"\x7f\x86\x83\xa6\xfd\x9c\x94\x96\xb6\xef\xaa\xfb\x99\xeb\x1e\x17\xb8" +"\xd4\x4f\x86\x74\x8f\xed\xf9\x2d\x06\xbb\xf6\x38\x7f\xb4\x05\x9b\x7e" +"\xe7\xed\x1b\x07\x9b\xe5\xe7\x9d\xbb\xf7\x63\x3d\xb1\x5a\x9f\x68\x8b" +"\xfe\x7a\x27\xfd\xf4\x28\xa2\x80\x7a\xbb\x87\xda\x22\xff\x00\x9e\x89" +"\xff\x00\x7d\x0a\x3e\xd1\x17\xfc\xf4\x4f\xfb\xe8\x51\x45\x02\x0f\xb4" +"\x45\xff\x00\x3d\x13\xfe\xfa\x14\x7d\xa2\x2f\xf9\xe8\x9f\xf7\xd0\xa2" +"\x8a\x00\x3e\xd1\x17\xfc\xf4\x4f\xfb\xe8\x51\xf6\x88\xbf\xe7\xa2\x7f" +"\xdf\x42\x8a\x28\x00\xfb\x44\x5f\xf3\xd1\x3f\xef\xa1\x47\xda\x22\xff" +"\x00\x9e\x89\xff\x00\x7d\x0a\x28\xa0\x03\xed\x11\x7f\xcf\x44\xff\x00" +"\xbe\x85\x1f\x68\x8b\xfe\x7a\x27\xfd\xf4\x28\xa2\x80\x0f\xb4\x45\xff" +"\x00\x3d\x13\xfe\xfa\x15\x9f\xaa\x8b\xdb\x8f\x2b\xec\x1a\x8d\xb5\x9e" +"\x0f\xce\x65\x8b\xcc\xdc\x3d\xbe\x61\x8e\x28\xa2\xa9\x3e\x57\x74\x26" +"\xae\x63\x5c\x5b\xf8\xb1\xf5\x37\x30\x6b\x9a\x3c\x5a\x78\x89\x42\x87" +"\xb2\x77\x95\x9f\x9d\xc4\xfe\xf4\x00\x3e\xee\x3a\xf7\xae\x96\xc3\xce" +"\x16\x70\x8b\x89\xa3\x9e\xe0\x28\x12\x49\x12\xed\x56\x6e\xe4\x0c\x9c" +"\x7e\x74\x51\x57\x3a\x8e\x69\x26\x97\xc9\x24\x44\x60\xa2\xef\x77\xf7" +"\x95\x67\x19\x99\xf9\xc7\x3d\x86\x68\xa2\x8a\xe5\x36\x3f\xff\xd9";
\ No newline at end of file diff --git a/chrome/tools/sqlite.exe b/chrome/tools/sqlite.exe Binary files differnew file mode 100644 index 0000000..908b607 --- /dev/null +++ b/chrome/tools/sqlite.exe diff --git a/chrome/tools/sqlite3_analyzer.exe b/chrome/tools/sqlite3_analyzer.exe Binary files differnew file mode 100644 index 0000000..9a7e7b4 --- /dev/null +++ b/chrome/tools/sqlite3_analyzer.exe diff --git a/chrome/tools/test/generate_mime_tests.pl b/chrome/tools/test/generate_mime_tests.pl new file mode 100644 index 0000000..1446b85 --- /dev/null +++ b/chrome/tools/test/generate_mime_tests.pl @@ -0,0 +1,270 @@ +#!/usr/bin/perl -w +# Copyright 2008, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +# Generate html files that will be used test MIME type handling using +# the layout test framework. + +use strict; +use Switch; # used for switch-case program structure + +my $arg_count = $#ARGV + 1; + +# Make sure that only one command line argument is provided +if ($arg_count ne 1) { + print "Usage:\n generate_mime_tests.pl < target_path >\n target_path". + " - path where the generated tests are to be placed"; + exit; +} + +# Obtain target path from the command line +my $target_path = $ARGV[0]; + +# Set relative path of the page to be requested +my $root = "resources/getpage.pl?"; + +# Variables used in the script +my $content_type; +my $parameter; +my $source; # Temporary variable to hold source path +my $count; # Used to generate number appended to filenames +my $current_expected = ""; # Temporary storage for expected result +my $query_description; + +# Number of tests each content type goes through, also determines the half +# size of the expected results matrix +my $test_set_size = 14; + +# List of HTTP content types to be used in generating test HTMLs +my @content_type = ("NULL", + "text/plain", + "text/html", + "image/gif", + "image/bmp", + "image/tif", + "image/png", + "image/jpg", + "application/x-shockwave-flash"); + +# List of URL query parameters to be used in generating test HTMLs +my @parameter = ("tag=img&content=type_gif.gif", + "tag=img&content=type_bmp.bmp", + "tag=img&content=type_tif.tif", + "tag=img&content=type_png.png", + "tag=img&content=type_jpg.jpg", + "tag=img&content=type_txt.txt", + "tag=embed&content=type_swf.swf", + "switch=nohtml&content=type_gif.gif", + "switch=nohtml&content=type_bmp.bmp", + "switch=nohtml&content=type_tif.tif", + "switch=nohtml&content=type_png.png", + "switch=nohtml&content=type_jpg.jpg", + "switch=nohtml&content=type_txt.txt", + "switch=nohtml&content=type_swf.swf"); + +# Matrix with expected results of all tests. +# The matrix rows have four parts +# 1. iframe with a html +# 2. iframe without a html +# 3. content within a html page +# 4. content without a html +# Each part has the same sequence of column headers +my @expected = ( +# gif bmp tif png jpg txt swf +# gif bmp tif png jpg txt swf +# gif bmp tif png jpg txt swf +# gif bmp tif png jpg txt swf + # NULL + "html","html","html","html","html","html","html", # iframe html + "void","void","image","void","void", "text","text", # iframe no html + "html","html","html","html","html","html","html", # html + "image","void","image","image","image","text","text", # no html + + # text/plain + "html","html","html","html","html","html","html", # iframe html + "void","void","image","void","void", "text","text", # iframe no html + "html","html","html","html","html","html","html", # html + "image","void","image","image","image","text","text", # no html + + # text/html + "rs", "rf", "rf", "rs", "rs", "rf", "rs", # iframe html + "text","text","text","text", "text", "text","text", # iframe no html + "rs", "rf", "rf", "rs", "rs", "rf", "rs", # html + "text", "text","text", "text", "text", "text","text", # no html + + # image/gif + "void","void","void","void","void","void","void", # iframe html + "void","void","void","void", "void", "void","void", # iframe no html + "void","void","void","void","void","void","void", # html + "image","void","void", "image","image","void","void", # no html + + # image/bmp + "void","void","void","void","void","void","void", # iframe html + "void","void","void","void", "void", "void","void", # iframe no html + "void","void","void","void","void","void","void", # html + "image","void","void", "image","image","void","void", # no html + + # image/tif + "void","void","void","void","void","void","void", # iframe html + "void","void","void","void", "void", "void","void", # iframe no html + "void","void","void","void","void","void","void", # html + "void", "void","void", "void", "void", "void","void", # no html + + # image/png + "void","void","void","void","void","void","void", # iframe html + "void","void","void","void", "void", "void","void", # iframe no html + "void","void","void","void","void","void","void", # html + "image","void","void", "image","image","void","void", # no html + + # image/jpg + "void","void","void","void","void","void","void", # iframe html + "void","void","void","void", "void", "void","void", # iframe no html + "void","void","void","void","void","void","void", # html + "image","void","void", "image","void", "void","void", # no html + + # application/x-shockwave-flash + "void","void","void","void","void","void","void", # iframe html + "flash","void","void","flash","flash","void","flash", # iframe no html + "void","void","void","void","void","void","void", # html + "flash","void","void", "flash","flash","void","flash");# no html + +# get_description() +# Maps the expected word to an appropriate phrase. +# Used to generated verbal descriptions for expected results of every test. +# Input : expected result from the matrix +# Output : returns the associated description +sub get_result_description +{ + switch ($_[0]) { + case "void" { return "NOTHING";} + case "image" { return "an IMAGE";} + case "text" { return "simple TEXT";} + case "html" { return "an HTML as text";} + case "flash" { return "a FLASH object"} + case "rs" { return "been RENDERED CORRECTLY";} + case "rf" { return "been RENDERED INCORRECTLY";} + else { return "UNKNOWN";} + } +} + +# get_query_description() +# Maps the URL query to an appropriate phrase. +# Used to generated verbal descriptions for URL queries of every test. +# Input : URL query +# Output : returns the associated description +sub get_query_description +{ + switch ($_[0]) { + case "tag=img&content=type_gif.gif" { return "HTML page with a GIF image";} + case "tag=img&content=type_bmp.bmp" { return "HTML page with a BMP image";} + case "tag=img&content=type_tif.tif" { return "HTML page with a TIF image";} + case "tag=img&content=type_png.png" { return "HTML page with a PNG image";} + case "tag=img&content=type_jpg.jpg" { return "HTML page with a JPEG image"} + case "tag=img&content=type_txt.txt" { return "HTML page";} + case "tag=embed&content=type_swf.swf" { return "an HTML page with a FLASH object";} + case "switch=nohtml&content=type_gif.gif" { return "GIF image and no HTML";} + case "switch=nohtml&content=type_bmp.bmp" { return "BMP image and no HTML";} + case "switch=nohtml&content=type_tif.tif" { return "TIF image and no HTML";} + case "switch=nohtml&content=type_png.png" { return "PNG image and no HTML";} + case "switch=nohtml&content=type_jpg.jpg" { return "JPEG image and no HTML"} + case "switch=nohtml&content=type_txt.txt" { return "simple TEXT and no HTML";} + case "switch=nohtml&content=type_swf.swf" { return "FLASH object and no HTML";} + else { return "UNKNOWN TYPE";} + } +} + +# This loop generates one HTML page with multiple iframes in it. +# Generated one iframe for every parameter of every content type. +my $iframe_index = 0; +foreach $content_type ( @content_type) { + my $infile = join "", "iframe/", $content_type, ".html"; + $infile =~ tr/\//_/; + $infile = $target_path.$infile; + + open OUT, "> $infile" or die "Failed to open file $infile"; + print OUT "This HTML is used to test HTTP content-type \"$content_type\"". + " by having multiple iframes render different types of content for the". + " same HTTP content-type header.\n"; + print OUT "<script>\n if(window.layoutTestController)\n " . + "window.layoutTestController.waitUntilDone();\n</script>\n"; + print OUT "<html>\n<body>\n<br>Well here are the frames !<br>\n"; + + foreach $parameter ( @parameter ) { + + # Make sure to iterate only through the first half of the expected + # results matrix + if (($iframe_index > 0) && (0 == ($iframe_index % $test_set_size))) { + $iframe_index += $test_set_size; + } + $current_expected = get_result_description($expected[$iframe_index++]); + + $source = join "", $root, "type=", $content_type, "&", $parameter; + $query_description = get_query_description($parameter); + print OUT "<br><br>This frame tests loading of a $query_description when the ". + "HTTP content-type is set to \"$content_type\" .<br> Expected : This ", + "iframe should have $current_expected .<br>\n<iframe src=\"$source\" ". + "height=\"300\" width=\"500\"></iframe>\n\n"; + } + + print OUT "</body>\n</html>\n"; + print OUT "<script>\n if(window.layoutTestController)\n ". + "layoutTestController.notifyDone();\n</script>"; + close OUT; +} + +# This loop generates one HTML for every combination of content-type and +# parameter. +my $main_index = 0; +foreach $content_type ( @content_type) { + $count = 0; + foreach $parameter ( @parameter ) { + $count++; + + # Make sure to iterate only through the second half of the expected + # results matrix + if (0 == ($main_index % $test_set_size)) { + $main_index += $test_set_size; + } + + $current_expected = get_result_description($expected[$main_index++]); + + my $infile = join "", "main/", $content_type, $count, ".html"; + $infile =~ tr/\//_/; + $source = join "", $root, "type=", $content_type, "&", $parameter; + $infile = $target_path.$infile; + $query_description = get_query_description($parameter); + + open OUT, "> $infile" or die "Failed to open file $infile"; + print OUT "This tests loading of a $query_description when the HTTP content-type". + " is set to \"$content_type\" .\n Expected : This page should have ". + "$current_expected .\n\n"; + print OUT "<script>\n window.location=\"$source\";\n</script>\n"; + close OUT; + } +}
\ No newline at end of file diff --git a/chrome/tools/test/image_diff/SConscript b/chrome/tools/test/image_diff/SConscript new file mode 100644 index 0000000..7a6b825 --- /dev/null +++ b/chrome/tools/test/image_diff/SConscript @@ -0,0 +1,106 @@ +# Copyright 2008, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+Import('env_test')
+
+env_test = env_test.Clone()
+
+env_test.Prepend(
+ CPPDEFINES = [
+ 'PNG_USER_CONFIG',
+ 'CHROME_PNG_WRITE_SUPPORT',
+ '_DEBUG',
+ 'CERT_CHAIN_PARA_HAS_EXTRA_FIELDS',
+ 'WIN32_LEAN_AND_MEAN',
+ ],
+ CPPPATH = [
+ '$ZLIB_DIR',
+ '$LIBPNG_DIR',
+ '#/..',
+ ],
+ LINKFLAGS = [
+ '/INCREMENTAL',
+ '/DEBUG',
+
+ '/DELAYLOAD:"dwmapi.dll"',
+ '/DELAYLOAD:"uxtheme.dll"',
+
+ '/SUBSYSTEM:CONSOLE',
+
+ '/MACHINE:X86',
+ '/FIXED:No',
+
+ '/safeseh',
+ '/dynamicbase',
+ '/ignore:4199',
+ '/nxcompat',
+ ],
+ LIBS = [
+ 'wininet.lib',
+ 'version.lib',
+ 'msimg32.lib',
+ 'ws2_32.lib',
+ 'usp10.lib',
+ 'psapi.lib',
+ 'kernel32.lib',
+ 'user32.lib',
+ 'gdi32.lib',
+ 'winspool.lib',
+ 'comdlg32.lib',
+ 'advapi32.lib',
+ 'shell32.lib',
+ 'ole32.lib',
+ 'oleaut32.lib',
+ 'uuid.lib',
+ 'odbc32.lib',
+ 'odbccp32.lib',
+
+ 'DelayImp.lib',
+ ],
+)
+
+input_files = [
+ 'image_diff.cc',
+]
+
+libs = [
+ '$SKIA_DIR/skia.lib',
+ '$LIBPNG_DIR/libpng.lib',
+ '$BASE_DIR/gfx/base_gfx.lib',
+ '$ICU38_DIR/icuuc.lib',
+ '$ZLIB_DIR/zlib.lib',
+ '$BASE_DIR/base.lib',
+]
+
+exe = env_test.Program(['image_diff',
+ 'image_diff.pdb'],
+ input_files + libs)
+i = env_test.Install('$TARGET_ROOT', exe)
+
+env_test.Alias('chrome', i)
diff --git a/chrome/tools/test/image_diff/image_diff.cc b/chrome/tools/test/image_diff/image_diff.cc new file mode 100644 index 0000000..a2135e9 --- /dev/null +++ b/chrome/tools/test/image_diff/image_diff.cc @@ -0,0 +1,291 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// This file input format is based loosely on +// WebKitTools/DumpRenderTree/ImageDiff.m + +// The exact format of this tool's output to stdout is important, to match +// what the run-webkit-tests script expects. + +#include <algorithm> +#include <vector> + +#include "base/basictypes.h" +#include "base/command_line.h" +#include "base/gfx/png_decoder.h" +#include "base/logging.h" +#include "base/scoped_ptr.h" + +// Causes the app to remain open, waiting for pairs of filenames on stdin. +// The caller is then responsible for terminating this app. +static const wchar_t kOptionPollStdin[] = L"use-stdin"; + +// Return codes used by this utility. +static const int kStatusSame = 0; +static const int kStatusDifferent = 1; +static const int kStatusError = 2; + +class Image { + public: + Image() : w_(0), h_(0) { + } + + bool has_image() const { + return w_ > 0 && h_ > 0; + } + + int w() const { + return w_; + } + + int h() const { + return h_; + } + + // Creates the image from stdin with the given data length. On success, it + // will return true. On failure, no other methods should be accessed. + bool CreateFromStdin(int byte_length) { + if (byte_length <= 0) + return false; + + scoped_array<unsigned char> source(new unsigned char[byte_length]); + if (fread(source.get(), 1, byte_length, stdin) != byte_length) + return false; + + if (!PNGDecoder::Decode(source.get(), byte_length, PNGDecoder::FORMAT_RGBA, + &data_, &w_, &h_)) { + Clear(); + return false; + } + return true; + } + + // Creates the image from the given filename on disk, and returns true on + // success. + bool CreateFromFilename(const char* filename) { + FILE* f; + if (fopen_s(&f, filename, "rb") != 0) + return false; + + std::vector<unsigned char> compressed; + const int buf_size = 1024; + unsigned char buf[buf_size]; + size_t num_read = 0; + while ((num_read = fread(buf, 1, buf_size, f)) > 0) { + std::copy(buf, &buf[num_read], std::back_inserter(compressed)); + } + + fclose(f); + + if (!PNGDecoder::Decode(&compressed[0], compressed.size(), + PNGDecoder::FORMAT_RGBA, &data_, &w_, &h_)) { + Clear(); + return false; + } + return true; + } + + void Clear() { + w_ = h_ = 0; + data_.clear(); + } + + // Returns the RGBA value of the pixel at the given location + uint32 pixel_at(int x, int y) const { + DCHECK(x >= 0 && x < w_); + DCHECK(y >= 0 && y < h_); + return data_[(y * w_ + x) * 4]; + } + + private: + // pixel dimensions of the image + int w_, h_; + + std::vector<unsigned char> data_; +}; + +float PercentageDifferent(const Image& baseline, const Image& actual) { + int w = std::min(baseline.w(), actual.w()); + int h = std::min(baseline.h(), actual.h()); + + // compute pixels different in the overlap + int pixels_different = 0; + for (int y = 0; y < h; y++) { + for (int x = 0; x < w; x++) { + if (baseline.pixel_at(x, y) != actual.pixel_at(x, y)) + pixels_different++; + } + } + + // count pixels that are a difference in size as also being different + int max_w = std::max(baseline.w(), actual.w()); + int max_h = std::max(baseline.h(), actual.h()); + + // ...pixels off the right side, but not including the lower right corner + pixels_different += (max_w - w) * h; + + // ...pixels along the bottom, including the lower right corner + pixels_different += (max_h - h) * max_w; + + // Like the WebKit ImageDiff tool, we define percentage different in terms + // of the size of the 'actual' bitmap. + float total_pixels = static_cast<float>(actual.w()) * + static_cast<float>(actual.h()); + if (total_pixels == 0) + return 100.0f; // when the bitmap is empty, they are 100% different + return static_cast<float>(pixels_different) / total_pixels * 100; +} + +void PrintHelp() { + fprintf(stderr, + "Usage:\n" + " image_diff <compare file> <reference file>\n" + " Compares two files on disk, returning 0 when they are the same\n" + " image_diff --use-stdin\n" + " Stays open reading pairs of filenames from stdin, comparing them,\n" + " and sending 0 to stdout when they are the same\n"); + /* For unfinished webkit-like-mode (see below) + "\n" + " image_diff -s\n" + " Reads stream input from stdin, should be EXACTLY of the format\n" + " \"Content-length: <byte length> <data>Content-length: ...\n" + " it will take as many file pairs as given, and will compare them as\n" + " (cmp_file, reference_file) pairs\n"); + */ +} + +int CompareImages(const char* file1, const char* file2) { + Image actual_image; + Image baseline_image; + + if (!actual_image.CreateFromFilename(file1)) { + fprintf(stderr, "image_diff: Unable to open file \"%s\"\n", file1); + return kStatusError; + } + if (!baseline_image.CreateFromFilename(file2)) { + fprintf(stderr, "image_diff: Unable to open file \"%s\"\n", file2); + return kStatusError; + } + + float percent = PercentageDifferent(actual_image, baseline_image); + if (percent > 0.0) { + // failure: The WebKit version also writes the difference image to + // stdout, which seems excessive for our needs. + printf("diff: %01.2f%% failed\n", percent); + return kStatusDifferent; + } + + // success + printf("diff: %01.2f%% passed\n", percent); + return kStatusSame; + +/* Untested mode that acts like WebKit's image comparator. I wrote this but + decided it's too complicated. We may use it in the future if it looks useful + + char buffer[2048]; + while (fgets(buffer, sizeof(buffer), stdin)) { + + if (strncmp("Content-length: ", buffer, 16) == 0) { + char* context; + strtok_s(buffer, " ", &context); + int image_size = strtol(strtok_s(NULL, " ", &context), NULL, 10); + + bool success = false; + if (image_size > 0 && actual_image.has_image() == 0) { + if (!actual_image.CreateFromStdin(image_size)) { + fputs("Error, input image can't be decoded.\n", stderr); + return 1; + } + } else if (image_size > 0 && baseline_image.has_image() == 0) { + if (!baseline_image.CreateFromStdin(image_size)) { + fputs("Error, baseline image can't be decoded.\n", stderr); + return 1; + } + } else { + fputs("Error, image size must be specified.\n", stderr); + return 1; + } + } + + if (actual_image.has_image() && baseline_image.has_image()) { + float percent = PercentageDifferent(actual_image, baseline_image); + if (percent > 0.0) { + // failure: The WebKit version also writes the difference image to + // stdout, which seems excessive for our needs. + printf("diff: %01.2f%% failed\n", percent); + } else { + // success + printf("diff: %01.2f%% passed\n", percent); + } + actual_image.Clear(); + baseline_image.Clear(); + } + + fflush(stdout); + } +*/ +} + +int main(int argc, const char* argv[]) { + CommandLine parsed_command_line; + if (parsed_command_line.HasSwitch(kOptionPollStdin)) { + // Watch stdin for filenames. + char stdin_buffer[2048]; + char filename1_buffer[2048]; + bool have_filename1 = false; + while (fgets(stdin_buffer, sizeof(stdin_buffer), stdin)) { + char *newLine = strchr(stdin_buffer, '\n'); + if (newLine) + *newLine = '\0'; + if (!*stdin_buffer) + continue; + + if (have_filename1) { + // CompareImages writes results to stdout unless an error occurred. + if (CompareImages(filename1_buffer, stdin_buffer) == kStatusError) + printf("error\n"); + fflush(stdout); + have_filename1 = false; + } else { + // Save the first filename in another buffer and wait for the second + // filename to arrive via stdin. + strcpy_s(filename1_buffer, sizeof(filename1_buffer), stdin_buffer); + have_filename1 = true; + } + } + return 0; + } + + if (argc == 3) { + return CompareImages(argv[1], argv[2]); + } + + PrintHelp(); + return kStatusError; +} diff --git a/chrome/tools/test/image_diff/image_diff.vcproj b/chrome/tools/test/image_diff/image_diff.vcproj new file mode 100644 index 0000000..77f36ac --- /dev/null +++ b/chrome/tools/test/image_diff/image_diff.vcproj @@ -0,0 +1,49 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioProject + ProjectType="Visual C++" + Version="8.00" + Name="image_diff" + ProjectGUID="{50B079C7-CD01-42D3-B8C4-9F8D9322E822}" + RootNamespace="image_diff" + Keyword="Win32Proj" + > + <Platforms> + <Platform + Name="Win32" + /> + </Platforms> + <Configurations> + <Configuration + Name="Debug|Win32" + ConfigurationType="1" + InheritedPropertySheets="$(SolutionDir)..\build\common.vsprops;$(SolutionDir)..\build\debug.vsprops;$(SolutionDir)..\third_party\libpng\using_libpng.vsprops;$(SolutionDir)..\third_party\zlib\using_zlib.vsprops" + > + <Tool + Name="VCLinkerTool" + SubSystem="1" + /> + <Tool + Name="VCCLCompilerTool" + /> + </Configuration> + <Configuration + Name="Release|Win32" + ConfigurationType="1" + InheritedPropertySheets="$(SolutionDir)..\build\common.vsprops;$(SolutionDir)..\build\release.vsprops;$(SolutionDir)..\third_party\libpng\using_libpng.vsprops;$(SolutionDir)..\third_party\zlib\using_zlib.vsprops" + > + <Tool + Name="VCLinkerTool" + SubSystem="1" + /> + <Tool + Name="VCCLCompilerTool" + /> + </Configuration> + </Configurations> + <Files> + <File + RelativePath=".\image_diff.cc" + > + </File> + </Files> +</VisualStudioProject> diff --git a/chrome/tools/test/smoketests.py b/chrome/tools/test/smoketests.py new file mode 100644 index 0000000..0d31ec4 --- /dev/null +++ b/chrome/tools/test/smoketests.py @@ -0,0 +1,263 @@ +#!/bin/env python +# Copyright 2008, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +""" +Runs all the available unit tests, layout tests, page-cycler tests, etc. +for a build of Chrome, imitating a buildbot. + +Usage examples: + smoketests.py + smoketests.py --target=debug --build-type=kjs + smoketests.py --nopage-cycler + smoketests.py --tests=unit,ui --verbose + +For a full list of options, pass the '--help' switch. + +[Alternatively, this script will kill all the tests' executables, in case one +got orphaned during a previous run of this script. (This generally only +happens during script development.)] + +""" + +import errno +import optparse +import os +import subprocess +import sys +import time + +import google.httpd_utils +import google.path_utils +import google.process_utils + + +# Keep a global httpd object so it can be killed in the event of errors. +_httpd = None + +# All the available commands, by name. Items in the command-line lists may +# contain various keywords, listed in the "Substitutions" section below. +# The target build directory will be prepended to the first item in each +# command list. +COMMANDS = {'ipc': ['ipc_tests.exe'], + 'unit': ['unit_tests.exe'], + 'ui': ['ui_tests.exe', '%(page_heap)s'], + 'ui-single': ['ui_tests.exe', '--single-process'], + 'test_shell': ['test_shell_tests.exe'], + 'page-cycler-moz': ['page_cycler_tests.exe', + '--gtest_filter=PageCycler*.MozFile'], + 'page-cycler-moz-http': ['page_cycler_tests.exe', + '--gtest_filter=PageCycler*.MozHttp'], + 'page-cycler-intl1': ['page_cycler_tests.exe', + '--gtest_filter=PageCycler*.Intl1File'], + 'page-cycler-intl2': ['page_cycler_tests.exe', + '--gtest_filter=PageCycler*.Intl2File'], + 'page-cycler-bloat-http': ['page_cycler_tests.exe', + '--gtest_filter=PageCycler*.BloatHttp'], + 'startup': ['startup_tests.exe', + '--gtest_filter=Startup*.*'], + 'dest-startup': ['startup_tests.exe', + '--gtest_filter=DestinationsStartupTest.*'], + 'selenium': ['selenium_tests.exe'], + 'plugin': ['plugin_tests.exe'], + 'installer': ['installer_unittests.exe'], + 'webkit': ['%(python)s', + '%(slave_scripts)s/layout_test_wrapper.py', + '--build-type', '%(build_type)s', + '--target', '%(target)s', + '%(page_heap)s'], + } + +# Certain tests are not run for each build type. +SKIPPED = {'Release': ['plugin'], + 'Debug': ['selenium', 'webkit']} + +def _BuildbotScriptPath(sub_dir): + """Returns the full path to the given subdir of tools/buildbot/scripts.""" + this_script_dir = google.path_utils.ScriptDir() + return google.path_utils.FindUpward(this_script_dir, 'tools', 'buildbot', + 'scripts', sub_dir) + + +def _MakeSubstitutions(list, options): + """Makes substitutions in each item of a list and returns the resulting list. + + Args: + list: a list of strings, optionally containing certain %()s substitution + tags listed below + options: options as returned by optparse + """ + this_script_dir = google.path_utils.ScriptDir() + python_path = google.path_utils.FindUpward(this_script_dir, + 'third_party', + 'python_24', + 'python_slave.exe') + + substitutions = {'target': options.target, + 'build_type': options.build_type, + 'page_heap': '', + 'python': python_path, + 'slave_scripts': _BuildbotScriptPath('slave'), + } + if options.build_type == 'kjs': + substitutions['page_heap'] = '--enable-pageheap' + return [word % substitutions for word in list] + + +def main(options, args): + """Runs all the selected tests for the given build type and target.""" + options.build_type = options.build_type.lower() + options.target = options.target.title() + + this_script_dir = google.path_utils.ScriptDir() + test_path = google.path_utils.FindUpward(this_script_dir, + 'chrome', options.target) + + # Add the buildbot script paths to the module search path. + sys.path.insert(0, _BuildbotScriptPath('slave')) + sys.path.insert(0, _BuildbotScriptPath('common')) + + # Collect list of tests to run. + if options.tests == '': + tests = sorted(COMMANDS.keys()) + else: + tests = set() + requested_tests = options.tests.lower().split(',') + for test in requested_tests: + if test in COMMANDS: + tests.add(test) + else: + print 'Ignoring unknown test "%s"' % test + + # Check page-cycler data, since the tests choke if it isn't available. + try: + page_cycler_data = google.path_utils.FindUpward(this_script_dir, + 'data', + 'page_cycler') + except google.path_utils.PathNotFound: + # Were we going to run any page-cycler tests? + if (not options.nopage_cycler and + len([x for x in tests if x.startswith('page-cycler')])): + print 'Skipping page-cycler tests (no data)' + options.nopage_cycler = True + + # Start an httpd if needed. + http_tests = [x for x in tests if x.endswith('-http')] + if http_tests and not options.nopage_cycler and not options.nohttp: + try: + _httpd = google.httpd_utils.StartServer(document_root=page_cycler_data) + except google.httpd_utils.HttpdNotStarted: + print 'Skipping http tests (httpd failed to start)' + options.nohttp = True + + # Remove tests not desired. + if options.nopage_cycler: + tests = [x for x in tests if not x.startswith('page-cycler')] + if options.nowebkit and 'webkit' in tests: + tests.remove('webkit') + if options.nohttp: + tests = [x for x in tests if not x.endswith('-http')] + + # Remove tests skipped for this build target. + for skip in SKIPPED[options.target]: + if skip in tests: + print 'Skipping %s for %s build' % (skip, options.target) + tests.remove(skip) + + if not len(tests): + print 'No tests to run.' + return 0 + + # Run each test, substituting strings as needed. + failures = [] + start_time = time.time() + for test in tests: + test_start_time = time.time() + command = _MakeSubstitutions(COMMANDS[test], options) + command[0] = os.path.join(test_path, command[0]) + if options.verbose: + print + print 'Running %s:' % test, + try: + result = google.process_utils.RunCommand(command, options.verbose) + except google.process_utils.CommandNotFound: + print '%s' % e + if options.verbose: + print test, + print '(%ds)' % (time.time() - test_start_time), + if result: + print 'FAIL' + failures.append(test) + else: + print 'PASS' + + print 'Total time: %ds' % (time.time() - start_time) + if len(failures): + print 'Failed tests:' + print os.linesep.join(failures) + else: + print 'All tests passed. Hurrah!' + + return len(failures) + +if '__main__' == __name__: + option_parser = optparse.OptionParser() + option_parser.add_option('', '--target', default='Release', + help='build target (Debug or Release)') + option_parser.add_option('', '--build-type', default='v8', + help='build type (V8 or KJS), used by webkit tests') + option_parser.add_option('', '--verbose', action='store_true', default=False, + help='show full output from every command') + option_parser.add_option('', '--nopage-cycler', action='store_true', + default=False, help='disable page-cycler tests') + option_parser.add_option('', '--nowebkit', action='store_true', + default=False, help='disable webkit (layout) tests') + option_parser.add_option('', '--nohttp', action='store_true', + default=False, + help="don't run tests (e.g. page_cycler) with http") + option_parser.add_option('', '--tests', default='', + help='comma-separated list of tests to run, from ' + '{%s}' % ', '.join(sorted(COMMANDS.keys()))) + option_parser.add_option('', '--killall', action='store_true', default=False, + help='kill all test executables (and run no tests)') + options, args = option_parser.parse_args() + + if options.killall: + kill_list = _MakeSubstitutions([COMMANDS[x][0] for x in COMMANDS.keys()], + options) + kill_list = set([os.path.basename(x) for x in kill_list]) + sys.exit(google.process_utils.KillAll(kill_list)) + + try: + result = main(options, args) + finally: + # Kill the httpd. + if _httpd: + _httpd.StopServer(force=True) + sys.exit(result) |