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.""" | |
| 7 | |
| 8 import _winreg | |
| 9 import ctypes | |
| 10 import httplib | |
| 11 import logging | |
| 12 import os | |
| 13 import platform | |
| 14 import shutil | |
| 15 import socket | |
| 16 import subprocess | |
| 17 import tempfile | |
| 18 import urllib | |
| 19 | |
| 20 | |
| 21 _CSIDL_COMMON_APPDATA = 0x1C | |
|
kkania
2012/07/23 16:46:23
how about move these 2 to class Installation?
nkang
2012/07/25 23:39:21
Moved both these variables to Installation or Chro
| |
| 22 _CSIDL_PROGRAM_FILESX86 = 0x2A | |
| 23 _PLATFORM = platform.system().lower() | |
|
kkania
2012/07/23 16:46:23
I see there's a few checks in this file that check
nkang
2012/07/25 23:39:21
I added this check because I was told that ultimat
kkania
2012/07/25 23:59:38
There's a few other places you need to update that
nkang
2012/08/02 21:05:42
Got rid of the _PLATFORM variable and removed all
| |
| 24 | |
| 25 # Import only on Windows. On other platforms it will invoke a ValueError. | |
| 26 if _PLATFORM == 'windows': | |
| 27 from ctypes import wintypes, windll | |
| 28 | |
| 29 | |
| 30 class InstallationType: | |
| 31 """Defines the Chrome installation types.""" | |
| 32 SYSTEM = 0 | |
| 33 USER = 1 | |
| 34 | |
| 35 | |
| 36 def _RemoveDuplicates(args): | |
| 37 """Removes duplicates options from a list. | |
| 38 | |
| 39 Args: | |
| 40 args: A list containing the options. | |
| 41 | |
| 42 Returns: | |
| 43 A string without any duplicate options. | |
| 44 """ | |
| 45 return ((lambda arg: arg if arg else '') | |
| 46 (' '.join(map(lambda opt: opt, list(frozenset(args.split(','))))))) | |
| 47 | |
| 48 | |
| 49 def Install(installer_path, chrome_type, build, options='', clean=True): | |
| 50 """Installs the specified Chrome build.""" | |
|
kkania
2012/07/23 16:46:23
Document the args and return
nkang
2012/07/25 23:39:21
Done.
| |
| 51 def DoPreliminaryChecks(chrome_install): | |
| 52 """Validates the test parameters and Chrome version.""" | |
| 53 assert(_PLATFORM == 'windows' and os.path.isfile(installer_path)) | |
| 54 # Chrome installation type specified by the user. | |
| 55 install_type = ('system-level' in options and InstallationType.SYSTEM | |
| 56 or InstallationType.USER) | |
| 57 cur_build = chrome_install.GetChromeVersion() | |
| 58 # Chrome already installed, make sure new build can be installed over it. | |
| 59 if cur_build: | |
| 60 current_type = chrome_install.GetChromeInstallationType() | |
| 61 if(current_type == InstallationType.SYSTEM and install_type == | |
| 62 InstallationType.USER): | |
| 63 raise RuntimeError('System level Chrome exists, aborting user level ' | |
| 64 'installation.') | |
| 65 # Installing a build that's older than the currently installed build. | |
| 66 elif current_type == install_type: | |
| 67 if cur_build >= build: | |
| 68 raise RuntimeError('Please specify a newer version of Chrome.') | |
| 69 | |
| 70 chrome_install = Installation(chrome_type) | |
| 71 DoPreliminaryChecks(chrome_install) | |
| 72 options += ' --install --do-not-launch-chrome' | |
| 73 options = _RemoveDuplicates(options) | |
|
kkania
2012/07/23 16:46:23
what's an example of options that might want to be
nkang
2012/07/25 23:39:21
Example of an option that needs to be passed in is
kkania
2012/07/25 23:59:38
Try running the installer/uninstaller manually and
nkang
2012/08/02 21:05:42
Checked with Prudhvi regarding duplicate options,
| |
| 74 logging.log(logging.INFO, 'Launching Chrome installer...') | |
| 75 cmd = '%s %s' % (installer_path, options) | |
| 76 ret = subprocess.Popen(cmd, shell=True).wait() | |
| 77 if ret == 0: | |
| 78 logging.log(logging.INFO, 'Installation complete.') | |
| 79 return chrome_install | |
| 80 logging.log(logging.ERROR, 'Installation failed.') | |
|
kkania
2012/07/23 16:46:23
How about throw an error here instead, which inclu
nkang
2012/07/25 23:39:21
Got rid of the logging.log message and the return
| |
| 81 return None | |
| 82 | |
| 83 | |
| 84 class Installation(object): | |
| 85 """Provides pertinent information about the installed Chrome version. | |
| 86 | |
| 87 The type of Chrome version must be passed as an argument to the constructor, | |
| 88 (i.e. - user or system level). | |
| 89 """ | |
| 90 | |
| 91 HKEY_LOCAL = (r'SOFTWARE\Wow6432Node\Google\Update\ClientState' | |
|
kkania
2012/07/23 16:46:23
make these private?
nkang
2012/07/25 23:39:21
Made both of them private. I had initially held of
| |
| 92 '\{8A69D345-D564-463C-AFF1-A69D9E530F96}') | |
| 93 HKEY_USER = HKEY_LOCAL.replace('\\Wow6432Node', '') | |
| 94 | |
| 95 def __init__(self, install_type): | |
| 96 assert(install_type == InstallationType.SYSTEM or | |
| 97 install_type == InstallationType.USER) | |
| 98 self._type = install_type | |
| 99 self._key_type = self._GetKeyType(self._type) | |
| 100 | |
| 101 def _OpenKey(self, key_type, key_name, key_access=_winreg.KEY_READ): | |
| 102 """Opens a registry key and returns the key handle. | |
| 103 | |
| 104 Args: | |
| 105 key_type: The type of key HKLM or HKCU. | |
| 106 key_name: Name of the key. | |
| 107 key_access: Type of access required. Be default its read only. | |
| 108 | |
| 109 Returns: | |
| 110 Key handle if successful, otherwise None. | |
| 111 """ | |
| 112 try: | |
| 113 key = _winreg.OpenKey(key_type, key_name, 0, key_access) | |
| 114 return key | |
| 115 except _winreg.error: | |
| 116 return None | |
|
kkania
2012/07/23 16:46:23
when could this occur? Should we throw an exceptio
nkang
2012/07/25 23:39:21
This could occur for a number of reasons. It could
kkania
2012/07/25 23:59:38
I don't think it's too important that we try to fo
nkang
2012/08/02 21:05:42
The reason I wrote this method was because I wante
| |
| 117 | |
| 118 def _GetWinLocalFolder(self, ftype=_CSIDL_COMMON_APPDATA): | |
| 119 """Returns full path of the 'Local' folder on Windows. | |
| 120 | |
| 121 Args: | |
| 122 ftype: Location to look up, which could vary based on installation type. | |
| 123 | |
| 124 Returns: | |
| 125 A String representing the folder path if successful, otherwise an empty | |
| 126 string. | |
| 127 """ | |
| 128 if _PLATFORM != 'windows': | |
| 129 return '' | |
| 130 SHGetFolderPathW = windll.shell32.SHGetFolderPathW | |
| 131 SHGetFolderPathW.argtypes = [wintypes.HWND, | |
| 132 ctypes.c_int, | |
| 133 wintypes.HANDLE, | |
| 134 wintypes.DWORD, | |
| 135 wintypes.LPCWSTR] | |
| 136 path_buf = wintypes.create_unicode_buffer(wintypes.MAX_PATH) | |
| 137 result = SHGetFolderPathW(0, ftype, 0, 0, path_buf) | |
| 138 return str(path_buf.value) | |
| 139 | |
| 140 def _GetKeyType(self, install_type): | |
| 141 """Determines the registry key to use based on installation type.""" | |
| 142 if install_type == InstallationType.SYSTEM: | |
| 143 return _winreg.HKEY_LOCAL_MACHINE | |
| 144 elif install_type == InstallationType.USER: | |
| 145 return _winreg.HKEY_CURRENT_USER | |
| 146 | |
| 147 def _GetKeyName(self, install_type, replace=True): | |
| 148 """Returns Chrome's registry key name based on installation type.""" | |
|
kkania
2012/07/23 16:46:23
The replace arg here is a bit strange. Can you fin
nkang
2012/07/25 23:39:21
Changed the replace arg with a 'value' arg. If the
kkania
2012/07/25 23:59:38
I see. I think this function is still confusing. S
nkang
2012/08/02 21:05:42
Got rid of this method altogether. Per our convers
| |
| 149 name = (install_type == InstallationType.SYSTEM and self.HKEY_LOCAL or | |
| 150 self.HKEY_USER) | |
| 151 return (replace and name.replace('ClientState', 'Clients') or name) | |
| 152 | |
| 153 def _DeleteChromeRegEntries(self): | |
| 154 """Deletes chrome registry settings.""" | |
| 155 if self._type == InstallationType.USER: | |
| 156 p_key = self.HKEY_USER[: self.HKEY_USER.rfind('\\')] | |
| 157 key_name = self.HKEY_USER[self.HKEY_USER.rfind('\\') + 1 :] | |
| 158 _type = _winreg.HKEY_CURRENT_USER | |
| 159 elif self._type == InstallationType.SYSTEM: | |
| 160 p_key = self.HKEY_LOCAL[: self.HKEY_LOCAL.rfind('\\')] | |
| 161 key_name = self.HKEY_LOCAL[self.HKEY_LOCAL.rfind('\\') + 1 :] | |
| 162 _type = _winreg.HKEY_LOCAL_MACHINE | |
| 163 try: | |
| 164 key = self._OpenKey(_type, p_key, _winreg.KEY_ALL_ACCESS) | |
| 165 _winreg.DeleteKey(key, key_name) | |
| 166 key.Close() | |
| 167 return 0 | |
| 168 except _winreg.error, err: | |
| 169 logging.log(logging.ERROR, 'Could not delete registry entries: %s' % err) | |
| 170 return -1 | |
| 171 | |
| 172 def _GetValueFromRegistry(self, reg_type, key, subkey): | |
| 173 """Gets value of the specified subkey from the registry. | |
| 174 | |
| 175 Args: | |
| 176 reg_type: Type of key to use HKLM or HKCU. | |
| 177 key: Name of the key. | |
| 178 subkey: Name of the subkey whose value will be returned. | |
| 179 | |
| 180 Returns: | |
| 181 A string representing the subkey value if successful, otherwise None. | |
| 182 """ | |
| 183 hkey = self._OpenKey(reg_type, key) | |
| 184 if hkey: | |
| 185 try: | |
| 186 value = str(_winreg.QueryValueEx(hkey, subkey)[0]) | |
| 187 except _winreg.error: | |
| 188 value = None | |
| 189 _winreg.CloseKey(hkey) | |
| 190 return value | |
| 191 return None | |
| 192 | |
| 193 def _GetUninstallString(self): | |
| 194 """Returns Chrome uninstall string from the registry.""" | |
| 195 key_name = self._GetKeyName(self._type, False) | |
| 196 return self._GetValueFromRegistry(self._key_type, key_name, | |
| 197 'UninstallString') | |
| 198 | |
| 199 def _GetUninstallArguments(self): | |
| 200 """Returns Chrome uninstall arguments from the registry.""" | |
| 201 key_name = self._GetKeyName(self._type, False) | |
| 202 return self._GetValueFromRegistry(self._key_type, key_name, | |
| 203 'UninstallArguments') | |
| 204 | |
| 205 def GetChromeVersion(self): | |
| 206 """Returns the installed version of Chrome.""" | |
| 207 key_name = self._GetKeyName(self._type, True) | |
| 208 return self._GetValueFromRegistry(self._key_type, key_name, 'pv') | |
| 209 | |
| 210 def _LaunchInstaller(self, installer_path, options): | |
| 211 """Launches the Chrome installer. | |
| 212 | |
| 213 Args: | |
| 214 installer_path: Path where the installer is located. | |
| 215 options: Any additional options to be used for installation. | |
| 216 """ | |
| 217 cmd = '%s %s' % (installer_path, options) | |
| 218 try: | |
| 219 subprocess.Popen(cmd, shell=True).wait() | |
| 220 except OSError, err: | |
| 221 raise err | |
| 222 | |
| 223 def _Delete(self, _path): | |
|
kkania
2012/07/23 16:46:23
i don't think this is used
nkang
2012/07/25 23:39:21
Yes, you are right. This method is currently not b
| |
| 224 """Deletes a file or folder.""" | |
| 225 try: | |
| 226 (lambda p: shutil.rmtree(p) if os.path.isdir(p) else os.remove(p))(_path) | |
| 227 return 0 | |
| 228 except(OSError, IOError, TypeError), err: | |
| 229 return -1 | |
| 230 | |
| 231 def GetChromeExePath(self): | |
| 232 """Returns Chrome binary location based on installation type.""" | |
| 233 chrome_path = '' | |
| 234 if self._type == InstallationType.USER: | |
| 235 folder_id = _CSIDL_COMMON_APPDATA | |
| 236 elif self._type == InstallationType.SYSTEM: | |
| 237 folder_id = _CSIDL_PROGRAM_FILESX86 | |
| 238 if _PLATFORM == 'windows': | |
| 239 chrome_path = os.path.join(self._GetWinLocalFolder(folder_id), 'Google', | |
| 240 'Chrome', 'Application', 'chrome.exe') | |
| 241 return chrome_path | |
| 242 | |
| 243 def GetChromeInstallationType(self): | |
|
kkania
2012/07/23 16:46:23
a lot of functions in this class have Chrome in th
nkang
2012/07/25 23:39:21
Changed the class name from Installation to Chrome
| |
| 244 """Determines Chrome installation type.""" | |
| 245 reg_type = _winreg.HKEY_CURRENT_USER | |
| 246 key_name = self.HKEY_USER.replace('ClientState', 'Clients') | |
| 247 key = self._OpenKey(reg_type, key_name) | |
| 248 if not key: | |
| 249 reg_type = _winreg.HKEY_LOCAL_MACHINE | |
| 250 key_name = self.HKEY_LOCAL.replace('ClientState', 'Clients') | |
| 251 key = self._OpenKey(reg_type, key_name) | |
| 252 if not key: | |
| 253 return None | |
| 254 version = self._GetValueFromRegistry(reg_type, key_name, 'pv') | |
| 255 key.Close() | |
| 256 if version: | |
| 257 return (reg_type == _winreg.HKEY_CURRENT_USER and | |
| 258 InstallationType.USER or InstallationType.SYSTEM) | |
| 259 return None | |
| 260 | |
| 261 def UninstallChrome(self): | |
| 262 """Uninstalls Chrome.""" | |
| 263 if not self.GetChromeVersion(): | |
| 264 raise RuntimeError('No Chrome version found on this system.') | |
| 265 chrome_path = self.GetChromeExePath() | |
| 266 install_type = self.GetChromeInstallationType() | |
| 267 reg_opts = self._GetUninstallArguments() | |
| 268 uninstall_str = self._GetUninstallString() | |
| 269 options = '%s --force-uninstall' % (reg_opts) | |
| 270 if self._type == InstallationType.SYSTEM: | |
| 271 options += ' --system-level' | |
| 272 if not os.path.exists(chrome_path): | |
| 273 logging.log(logging.ERROR, 'Could not find chrome, aborting uninstall.') | |
|
nkang
2012/07/25 23:39:21
Since we're now raising an exception instead of re
| |
| 274 return False | |
| 275 logging.log(logging.INFO, 'Launching Chrome installer...') | |
| 276 ret = self._LaunchInstaller(uninstall_str, options) | |
|
kkania
2012/07/23 16:46:23
this function doesn't look like it returns anythin
nkang
2012/07/25 23:39:21
LaunchInstaller did at one point return a value, b
| |
| 277 if not os.path.exists(chrome_path): | |
| 278 logging.log(logging.INFO, 'Chrome was uninstalled successfully...') | |
| 279 # TODO: add clean option like ChromeInstaller | |
|
kkania
2012/07/23 16:46:23
remove this TODO
nkang
2012/07/25 23:39:21
TODO: Remove the comment. Done!
| |
| 280 logging.log(logging.INFO, 'Deleting registry entries...') | |
| 281 self._DeleteChromeRegEntries() | |
| 282 logging.log(logging.INFO, 'Uninstall complete.') | |
| 283 return True | |
| 284 else: | |
| 285 logging.log(logging.ERROR, 'Uninstall failed.') | |
|
kkania
2012/07/23 16:46:23
how about throw error instead
nkang
2012/07/25 23:39:21
This method now raises a RuntimeError exception. U
| |
| 286 return False | |
| OLD | NEW |