Chromium Code Reviews
|
| OLD | NEW |
|---|---|
| (Empty) | |
| 1 #!/usr/bin/python | |
| 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
| 3 # Use of this source code is governed by a BSD-style license that can be | |
| 4 # found in the LICENSE file. | |
| 5 | |
| 6 """Provides an interface for installing Chrome.""" | |
|
Nirnimesh
2012/08/09 21:32:48
Is this supposed to be for win only? If so, mentio
nkang
2012/08/16 23:46:24
Added a line that mentions that currently the only
| |
| 7 | |
| 8 import _winreg | |
|
Nirnimesh
2012/08/09 21:32:48
will fail on non-win
nkang
2012/08/16 23:46:24
Previously I had a check in here that only importe
Nirnimesh
2012/08/22 07:06:34
You know that it's supposed to be used on win beca
| |
| 9 import ctypes | |
| 10 from ctypes import wintypes, windll | |
| 11 import httplib | |
| 12 import logging | |
| 13 import os | |
| 14 import shutil | |
| 15 import socket | |
| 16 import subprocess | |
| 17 import tempfile | |
| 18 import urllib | |
| 19 | |
| 20 | |
| 21 class InstallationType: | |
|
Nirnimesh
2012/08/09 21:32:48
inherit from object
nkang
2012/08/16 23:46:24
Done.
| |
| 22 """Defines the Chrome installation types.""" | |
| 23 SYSTEM = 0 | |
| 24 USER = 1 | |
| 25 | |
| 26 | |
| 27 class ChromeRegistryValues: | |
|
Nirnimesh
2012/08/09 21:32:48
inherit from object
nkang
2012/08/16 23:46:24
Done.
| |
| 28 """Defines the Chrome registry key values.""" | |
| 29 PRODUCT_VERSION = 'pv' | |
| 30 UNINSTALL_STRING = 'UninstallString' | |
| 31 UNINSTALL_ARGUMENTS = 'UninstallArguments' | |
| 32 | |
| 33 | |
| 34 def Install(installer_path, install_type, build, options=''): | |
| 35 """Installs the specified Chrome build. | |
| 36 | |
| 37 Args: | |
| 38 installer_path: Path to the Chrome installer. | |
| 39 install_type: Type of installation (i.e., system or user). | |
| 40 build: Chrome build number. | |
| 41 options: Any additional installation options. | |
| 42 | |
| 43 Returns: | |
| 44 A ChromeInstallation object. | |
|
Nirnimesh
2012/08/09 21:32:48
an instance of ChromeInstallation.
nkang
2012/08/16 23:46:24
Done.
| |
| 45 """ | |
| 46 def DoPreliminaryChecks(regedit): | |
|
Nirnimesh
2012/08/09 21:32:48
prefix _
nkang
2012/08/16 23:46:24
Done.
| |
| 47 """Validates the test parameters and Chrome version. | |
|
Nirnimesh
2012/08/09 21:32:48
Be more specific. Call out exactly what it does.
nkang
2012/08/16 23:46:24
Updated the docstring, so its more descriptive.
| |
| 48 | |
| 49 Args: | |
| 50 regedit: ChromeRegistryKeys object. | |
| 51 """ | |
| 52 assert(os.path.isfile(installer_path)) | |
|
Nirnimesh
2012/08/09 21:32:48
why?
nkang
2012/08/16 23:46:24
The installer was initially downloaded by a class
| |
| 53 current_type = None | |
| 54 # Check if Chrome is already installed on the system. | |
| 55 if regedit.DoesKeyExist(InstallationType.USER, | |
| 56 ChromeRegistryValues.PRODUCT_VERSION): | |
| 57 current_type = InstallationType.USER | |
| 58 elif regedit.DoesKeyExist(InstallationType.SYSTEM, | |
| 59 ChromeRegistryValues.PRODUCT_VERSION): | |
| 60 current_type = InstallationType.SYSTEM | |
| 61 if current_type != None: | |
|
Nirnimesh
2012/08/09 21:32:48
remove "!= None"
nkang
2012/08/16 23:46:24
I explicitly added '!= None' because if a Chrome v
Nirnimesh
2012/08/22 07:06:34
That sounds bizarre. Why not add another installat
nkang
2012/08/24 22:45:26
Added a third variable called NOT_INSTALLED and se
| |
| 62 # Make sure new build can be installed over existing Chrome build. | |
| 63 if (current_type == InstallationType.SYSTEM and install_type == | |
| 64 InstallationType.USER): | |
| 65 raise RuntimeError('System level Chrome exists, aborting user level ' | |
| 66 'installation.') | |
| 67 build_num = regedit.GetKeyValue(current_type, | |
| 68 ChromeRegistryValues.PRODUCT_VERSION) | |
| 69 # Confirm that the new Chrome build is higher than the installed build. | |
| 70 if build_num >= build: | |
| 71 raise RuntimeError('Please specify a version higher than the one ' | |
|
Nirnimesh
2012/08/09 21:32:48
Remove "Please"
nkang
2012/08/16 23:46:24
Sorry, I was just trying to be courteous. But, res
| |
| 72 'already installed.') | |
| 73 | |
| 74 regedit = ChromeRegistryKeys() | |
| 75 DoPreliminaryChecks(regedit) | |
| 76 options += ' --install --do-not-launch-chrome' | |
| 77 logging.log(logging.INFO, 'Launching Chrome installer...') | |
| 78 cmd = '%s %s' % (installer_path, options) | |
|
Nirnimesh
2012/08/09 21:32:48
either use + to join strings (line 76), or use thi
nkang
2012/08/16 23:46:24
Got rid of the +=. I now use string formatting to
| |
| 79 ret = subprocess.Popen(cmd, shell=True).wait() | |
|
Nirnimesh
2012/08/09 21:32:48
make cmd a list. remove shell=True
nkang
2012/08/16 23:46:24
Made cmd a list and got rid of shell=True.
| |
| 80 if ret == 0: | |
|
Nirnimesh
2012/08/09 21:32:48
Invert the logic. Raise first if ret != 0
nkang
2012/08/16 23:46:24
Inverted the logic. An exception is now raised if
| |
| 81 logging.log(logging.INFO, 'Installation complete.') | |
| 82 return ChromeInstallation.GetCurrent() | |
| 83 raise RuntimeError('Chrome installation for build %s failed.' % build) | |
| 84 | |
| 85 | |
| 86 class ChromeRegistryKeys(object): | |
| 87 """An interface for accessing and manipulating Chrome registry keys.""" | |
| 88 | |
| 89 _HKEY_LOCAL = r'SOFTWARE\Wow6432Node\Google\Update' | |
| 90 _HKEY_USER = _HKEY_LOCAL.replace('\\Wow6432Node', '') | |
|
Nirnimesh
2012/08/09 21:32:48
either use r'..' or \\ thoughout.
nkang
2012/08/16 23:46:24
Changed to r'..'.
| |
| 91 _chrome_version = r'Clients\{8A69D345-D564-463C-AFF1-A69D9E530F96}' | |
| 92 _chrome_args = r'ClientState\{8A69D345-D564-463C-AFF1-A69D9E530F96}' | |
| 93 | |
| 94 def _GetKeyName(self, install_type, value): | |
| 95 """Generates the registry key name for the specified value. | |
|
Nirnimesh
2012/08/09 21:32:48
Generate -> Get
nkang
2012/08/16 23:46:24
Done.
| |
| 96 | |
| 97 Args: | |
| 98 install_type: Type of installation, must be InstallationType type. | |
| 99 value: ChromeRegistryValues type for which the key name is required. | |
| 100 | |
| 101 Returns: | |
| 102 A string representing the full key name of the specified key value. | |
| 103 """ | |
| 104 key_name = None | |
| 105 if install_type == InstallationType.USER: | |
| 106 key_name = self._HKEY_USER | |
| 107 elif install_type == InstallationType.SYSTEM: | |
| 108 key_name = self._HKEY_LOCAL | |
| 109 if value == ChromeRegistryValues.PRODUCT_VERSION: | |
| 110 return '%s\%s' % (key_name, self._chrome_version) | |
| 111 elif value == ChromeRegistryValues.UNINSTALL_ARGUMENTS: | |
| 112 return '%s\%s' % (key_name, self._chrome_args) | |
| 113 elif value == ChromeRegistryValues.UNINSTALL_STRING: | |
| 114 return '%s\%s' % (key_name, self._chrome_args) | |
| 115 raise RuntimeError('Invalid registry value.') | |
| 116 | |
| 117 def _GetRegistryType(self, install_type): | |
| 118 """Determines the registry key to use based on installation type. | |
| 119 | |
| 120 Args: | |
| 121 install_type: Type of installation, must be InstallationType type. | |
| 122 | |
| 123 Returns: | |
| 124 A long representing HKLM or HKCU, depending on installation type. | |
| 125 """ | |
| 126 if install_type == InstallationType.SYSTEM: | |
| 127 return _winreg.HKEY_LOCAL_MACHINE | |
| 128 elif install_type == InstallationType.USER: | |
| 129 return _winreg.HKEY_CURRENT_USER | |
| 130 raise RuntimeError('Invalid installation type.') | |
| 131 | |
| 132 def DoesKeyExist(self, install_type, subkey): | |
| 133 """Determines if a particular key exists in the registry. | |
| 134 | |
| 135 Args: | |
| 136 install_type: Type of installation, must be InstallationType type. | |
| 137 subkey: Subkey to look up. It must be a ChromeRegistryValues type. | |
| 138 | |
| 139 Returns: | |
| 140 True if the key exists, otherwise False. | |
| 141 """ | |
| 142 b_exists = False | |
|
Nirnimesh
2012/08/09 21:32:48
remove "b_"
nkang
2012/08/16 23:46:24
Got rid of the boolean var, so this no longer appl
| |
| 143 key = self._GetRegistryType(install_type) | |
| 144 key_name = self._GetKeyName(install_type, subkey) | |
| 145 try: | |
| 146 hkey = _winreg.OpenKey(key, key_name) | |
| 147 if hkey.handle: | |
| 148 b_exists = True | |
| 149 hkey.Close() | |
|
Nirnimesh
2012/08/09 21:32:48
If you just return True from here, you don't even
nkang
2012/08/16 23:46:24
Done. Got rid of the boolean var.
| |
| 150 return b_exists | |
| 151 except _winreg.error: | |
| 152 return False | |
| 153 | |
| 154 def GetKeyValue(self, install_type, subkey): | |
| 155 """Gets value of the specified subkey from the registry. | |
| 156 | |
| 157 Args: | |
| 158 install_type: Type of installation, must be InstallationType type. | |
| 159 subkey: ChromeRegistryValue type representing the value to be returned. | |
| 160 | |
| 161 Returns: | |
| 162 A string representing the subkey value. | |
| 163 """ | |
| 164 reg_value = '' | |
| 165 key = self._GetRegistryType(install_type) | |
| 166 key_name = self._GetKeyName(install_type, subkey) | |
| 167 hkey = _winreg.OpenKey(key, key_name) | |
| 168 if hkey.handle: | |
| 169 reg_value = str(_winreg.QueryValueEx(hkey, subkey)[0]) | |
| 170 hkey.Close() | |
| 171 return reg_value | |
| 172 | |
| 173 def DeleteRegistryEntries(self, install_type): | |
| 174 """Deletes chrome registry settings. | |
| 175 | |
| 176 Args: | |
| 177 install_type: Type of installation, must be InstallationType type. | |
| 178 """ | |
| 179 reg_type = self._GetRegistryType(install_type) | |
| 180 key_name = self._GetKeyName(install_type, | |
| 181 ChromeRegistryValues.UNINSTALL_ARGUMENTS) | |
| 182 root = key_name[:key_name.rfind('\\')] | |
| 183 child = key_name[key_name.rfind('\\') + 1:] | |
| 184 key = _winreg.OpenKey(reg_type, root, 0, _winreg.KEY_ALL_ACCESS) | |
| 185 _winreg.DeleteKey(key, child) | |
| 186 key.Close() | |
| 187 | |
| 188 | |
| 189 class ChromeInstallation(object): | |
| 190 """Provides pertinent information about the installed Chrome version. | |
| 191 | |
| 192 The type of Chrome version must be passed as an argument to the constructor, | |
| 193 (i.e. - user or system level). | |
| 194 """ | |
| 195 | |
| 196 _CSIDL_COMMON_APPDATA = 0x1C | |
| 197 _CSIDL_PROGRAM_FILESX86 = 0x2A | |
| 198 | |
| 199 def __init__(self, install_type): | |
| 200 assert(install_type == InstallationType.SYSTEM or | |
| 201 install_type == InstallationType.USER) | |
| 202 self._type = install_type | |
| 203 self._regedit = ChromeRegistryKeys() | |
| 204 | |
| 205 def _GetWinLocalFolder(self, ftype=_CSIDL_COMMON_APPDATA): | |
| 206 """Returns full path of the 'Local' folder on Windows. | |
| 207 | |
| 208 Args: | |
| 209 ftype: Location to look up, which could vary based on installation type. | |
| 210 | |
| 211 Returns: | |
| 212 A String representing the folder path if successful, otherwise an empty | |
| 213 string. | |
| 214 """ | |
| 215 SHGetFolderPathW = windll.shell32.SHGetFolderPathW | |
| 216 SHGetFolderPathW.argtypes = [wintypes.HWND, | |
| 217 ctypes.c_int, | |
| 218 wintypes.HANDLE, | |
| 219 wintypes.DWORD, | |
| 220 wintypes.LPCWSTR] | |
| 221 path_buf = wintypes.create_unicode_buffer(wintypes.MAX_PATH) | |
| 222 result = SHGetFolderPathW(0, ftype, 0, 0, path_buf) | |
| 223 return str(path_buf.value) | |
| 224 | |
| 225 def _GetUninstallString(self): | |
| 226 """Returns the Chrome uninstall string from the registry.""" | |
| 227 return self._regedit.GetKeyValue(self._type, | |
| 228 ChromeRegistryValues.UNINSTALL_STRING) | |
| 229 | |
| 230 def _GetUninstallArguments(self): | |
| 231 """Returns the Chrome uninstall arguments from the registry.""" | |
| 232 return self._regedit.GetKeyValue(self._type, | |
| 233 ChromeRegistryValues.UNINSTALL_ARGUMENTS) | |
| 234 | |
| 235 def GetExePath(self): | |
| 236 """Returns Chrome binary location based on installation type.""" | |
| 237 if self._type == InstallationType.USER: | |
| 238 folder_id = self._CSIDL_COMMON_APPDATA | |
| 239 elif self._type == InstallationType.SYSTEM: | |
| 240 folder_id = self._CSIDL_PROGRAM_FILESX86 | |
| 241 chrome_path = os.path.join(self._GetWinLocalFolder(folder_id), 'Google', | |
|
Nirnimesh
2012/08/09 21:32:48
What about Chromium?
nkang
2012/08/16 23:46:24
Per Anantha, we will not be using Chromium.
Nirnimesh
2012/08/22 07:06:34
Do you declare this somewhere in the docs?
nkang
2012/08/24 22:45:26
I checked with Ken on this, and he advised to decl
| |
| 242 'Chrome', 'Application', 'chrome.exe') | |
| 243 return (chrome_path if os.path.exists(chrome_path) else '') | |
| 244 | |
| 245 @staticmethod | |
| 246 def GetCurrent(): | |
| 247 """Determines Chrome installation type. | |
| 248 | |
| 249 Returns: | |
| 250 ChromeInstallation object if Chrome is present, otherwise None. | |
| 251 """ | |
| 252 registry = ChromeRegistryKeys() | |
| 253 if registry.DoesKeyExist(InstallationType.SYSTEM, | |
| 254 ChromeRegistryValues.PRODUCT_VERSION): | |
| 255 return ChromeInstallation(InstallationType.SYSTEM) | |
| 256 elif registry.DoesKeyExist(InstallationType.USER, | |
| 257 ChromeRegistryValues.PRODUCT_VERSION): | |
| 258 return ChromeInstallation(InstallationType.USER) | |
| 259 return None | |
| 260 | |
| 261 def Uninstall(self): | |
| 262 """Uninstalls Chrome.""" | |
| 263 chrome_path = self.GetExePath() | |
| 264 reg_opts = self._GetUninstallArguments() | |
| 265 uninstall_str = self._GetUninstallString() | |
| 266 options = '%s --force-uninstall' % (reg_opts) | |
| 267 if self._type == InstallationType.SYSTEM: | |
| 268 options += ' --system-level' | |
| 269 if not os.path.exists(chrome_path): | |
| 270 raise RuntimeError('Could not find chrome, aborting uninstall.') | |
| 271 logging.log(logging.INFO, 'Launching Chrome installer...') | |
| 272 cmd = '"%s" %s' % (uninstall_str, options) | |
| 273 subprocess.Popen(cmd, shell=True).wait() | |
| 274 if not os.path.exists(chrome_path): | |
| 275 logging.log(logging.INFO, 'Chrome was uninstalled successfully...') | |
| 276 logging.log(logging.INFO, 'Deleting registry entries...') | |
| 277 self._regedit.DeleteRegistryEntries(self._type) | |
| 278 logging.log(logging.INFO, 'Uninstall complete.') | |
| 279 else: | |
| 280 raise RuntimeError('Uninstall failed.') | |
| OLD | NEW |