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 import _winreg | |
| 7 import ctypes | |
| 8 import httplib | |
| 9 import os | |
| 10 import platform | |
| 11 import shutil | |
| 12 import socket | |
| 13 import subprocess | |
| 14 import tempfile | |
| 15 import urllib | |
| 16 import urlparse | |
| 17 | |
| 18 _PLATFORM = platform.system().lower() | |
| 19 _CSIDL_COMMON_APPDATA = 28 | |
| 20 _CSIDL_PROGRAM_FILESX86 = 42 | |
| 21 | |
| 22 # Import only on Windows. On other platforms it will invoke a ValueError. | |
| 23 if _PLATFORM == 'windows': | |
| 24 from ctypes import wintypes, windll | |
| 25 | |
| 26 | |
| 27 class ChromeInstaller: | |
| 28 """Provides an interface to install/uninstall Chrome.""" | |
| 29 | |
| 30 def __init__(self, url, build, dest, opts='', clean=True): | |
|
kkania
2012/06/14 17:20:33
we should make the installer not know about the UR
nkang
2012/06/28 19:02:39
Done. ChromeInstaller is no more, which makes Inst
| |
| 31 self._url = (lambda u: u if self._DoesUrlExist(u) else '')(url) | |
| 32 self._dest = dest | |
| 33 self._opts = opts | |
| 34 self._clean = clean | |
| 35 self._type = (lambda o: 'system' if o.find('system-level') != -1 else | |
| 36 'user')(self._opts) | |
| 37 self._plat = 'win' | |
| 38 self._installer = 'mini_installer.exe' | |
| 39 self.SetBuild(build) | |
|
kkania
2012/06/14 17:20:33
i don't like SetBuild. Remove it and GetBuild. Thi
nkang
2012/06/28 19:02:39
SetBuild and GetBuild are gone. In fact, so is the
| |
| 40 self.c_install = ChromeVersion(self._type) | |
| 41 self._c_path = self.c_install.GetChromeExePath() | |
| 42 | |
| 43 def SetBuild(self, bld): | |
| 44 """Sets the build number that is to be installed.""" | |
| 45 if type(bld) == str: | |
| 46 self._build = (lambda b: (all(map(lambda x: x.isdigit(), b.split('.'))) | |
| 47 and len(b.split('.')) == 4) and b or '')(bld) | |
| 48 else: | |
| 49 self._build = '' | |
| 50 | |
| 51 def GetBuild(self): | |
| 52 return self._build | |
| 53 | |
| 54 def _DoesUrlExist(self, url): | |
| 55 """Checks if a url exists. | |
| 56 | |
| 57 Args: | |
| 58 url: URL to be verified. | |
| 59 | |
| 60 Returns: | |
| 61 Boolean - True if the URL exists, otherwise False. | |
| 62 """ | |
| 63 parse = urlparse.urlparse(url) | |
| 64 if parse[0] == '' or parse[1] == '': | |
| 65 return False | |
| 66 try: | |
| 67 conn = httplib.HTTPConnection(parse.netloc) | |
| 68 conn.request('HEAD', parse.path) | |
| 69 res = conn.getresponse() | |
| 70 except socket.gaierror: | |
| 71 return False | |
| 72 finally: | |
| 73 conn.close() | |
| 74 # Follow both permanent (301) and temporary (302) redirects. | |
| 75 if res.status == 302 or res.status == 301: | |
| 76 return self._DoesUrlExist(res.getheader('location')) | |
| 77 return res.status == 200 | |
| 78 | |
| 79 def _Download(self, _file, url, dest=None): | |
| 80 """Downloads a file from the specified URL. | |
| 81 | |
| 82 Args: | |
| 83 _file: Name of the file to download. | |
| 84 url: URL where the file is located. | |
| 85 dest: Where file will be downloaded(optional). Default location is CWD. | |
| 86 | |
| 87 Returns: | |
| 88 Integer - zero if successful, otherwise a non-zero value. | |
| 89 """ | |
| 90 f_name = (lambda d, f: os.path.join(d, f) if(d and os.path.exists(d)) else | |
| 91 os.path.join(tempfile.gettempdir(), f))(dest, _file) | |
| 92 file_url = '%s/%s' % (url, _file) | |
| 93 # Check link before downloading, urlretrieve *WILL NOT* report | |
| 94 # an error if url doesn't exist. | |
| 95 if not self._DoesUrlExist(file_url): | |
| 96 print "Could not locate the file specified." | |
| 97 return -1 | |
| 98 try: | |
| 99 d = urllib.urlretrieve(file_url, f_name) | |
| 100 except IOError, err: | |
| 101 print 'CChromeInstaller._Download: ', err | |
| 102 return -1 | |
| 103 if os.path.isfile(d[0]): | |
| 104 return 0 | |
| 105 return 1 | |
| 106 | |
| 107 def _Delete(self, _path): | |
| 108 """Deletes a file or folder""" | |
| 109 try: | |
| 110 (lambda p: shutil.rmtree(p) if os.path.isdir(p) else os.remove(p))(_path) | |
| 111 return 0 | |
| 112 except(OSError, IOError, TypeError), err: | |
| 113 print 'CChromeInstaller._Delete: ', err | |
| 114 return -1 | |
| 115 | |
| 116 def _LaunchInstaller(self, opts=''): | |
| 117 """Launches the Chrome installer using specified options. | |
| 118 | |
| 119 Args: | |
| 120 opts: Any options that are to be passed to the installer. | |
| 121 | |
| 122 Returns: | |
| 123 Integer - zero if successful, otherwise a non-zero value. | |
| 124 """ | |
| 125 print 'Downloading installer for build %s ...' % self._build | |
| 126 build_url = '%s/%s/%s' % (self._url, self._build, self._plat) | |
| 127 if self._Download(self._installer, build_url, self._dest) != 0: | |
| 128 print 'Failed to download installer, exiting test' | |
| 129 return -1 | |
| 130 print 'Launching installer...' | |
| 131 cmd = '%s %s' % (os.path.join(self._dest, self._installer), opts) | |
| 132 try: | |
| 133 return subprocess.Popen.wait(subprocess.Popen(cmd)) | |
| 134 except OSError, err: | |
| 135 print 'CChromeInstaller._LaunchInstaller: ', err | |
| 136 return -2 | |
| 137 | |
| 138 def _RemoveDuplicates(self, args): | |
| 139 """Removes duplicates options from a list. | |
| 140 | |
| 141 Args: | |
| 142 args: A list containing the options. | |
| 143 | |
| 144 Returns: | |
| 145 A string without any duplicate options. | |
| 146 """ | |
| 147 return ((lambda arg: arg if arg else '') | |
| 148 (' '.join(map(lambda opt: opt, list(frozenset(args.split(','))))))) | |
| 149 | |
| 150 def _DoPreliminaryChecks(self, op='install'): | |
| 151 """Checks test parameters for validity and performs version checking.""" | |
| 152 if _PLATFORM != 'windows': | |
| 153 print 'Unsupported platform, aborting operation...' | |
| 154 return False | |
| 155 if self._url == '': | |
| 156 print 'URL specified is not valid. Please check the URL and try again.' | |
| 157 return False | |
| 158 ver = self.c_install.GetChromeVersion() | |
| 159 c_type = self.c_install.GetInstallTypeString() | |
| 160 if op == 'install': | |
| 161 if c_type == 'system' and self._type == 'user': | |
| 162 print 'System level Chrome exists, aborting user level installation.' | |
| 163 return False | |
| 164 # Version installed is higher than or same as the one we're installing. | |
| 165 elif self._type == c_type: | |
| 166 if self._build <= ver: | |
| 167 print 'Please specify a version higher than the one installed.' | |
| 168 return False | |
| 169 elif op == 'uninstall': | |
| 170 if not ver: | |
| 171 print 'No version of Chrome found, aborting uninstall...' | |
| 172 return False | |
| 173 if self._type != c_type: | |
| 174 print ('Invalid args passed. Please specify --opts=--system-level if ' | |
| 175 'uninstalling system version of Chrome, or omit it otherwise.') | |
| 176 return False | |
| 177 return True | |
| 178 | |
| 179 def InstallChrome(self): | |
|
kkania
2012/06/14 17:20:33
change this name to just Install, and return an in
nkang
2012/06/28 19:02:39
Changed name to install. Although, since ChromeIns
| |
| 180 """Installs specified chrome build.""" | |
| 181 if not self._DoPreliminaryChecks('install'): | |
| 182 return None | |
| 183 opts = '--install --do-not-launch-chrome %s' % self._opts | |
| 184 opts = self._RemoveDuplicates(opts) | |
| 185 ret = self._LaunchInstaller(opts) | |
| 186 self._Delete(os.path.join(self._dest, self._installer)) | |
| 187 if ret == 0 and os.path.exists(self._c_path): | |
| 188 print 'Installation complete...' | |
| 189 return self.c_install | |
| 190 else: | |
| 191 print 'Installation failed.' | |
| 192 return None | |
| 193 | |
| 194 def UninstallChrome(self): | |
| 195 """Uninstalls Chrome.""" | |
| 196 if not self._DoPreliminaryChecks('uninstall'): | |
| 197 return False | |
| 198 install_type = self.c_install.GetChromeInstallType() | |
| 199 reg_opts = self.c_install.GetUninstallArgs(install_type) | |
| 200 opts = '%s --force-uninstall %s' % (reg_opts, self._opts) | |
| 201 opts = self._RemoveDuplicates(opts) | |
| 202 if not os.path.exists(self._c_path): | |
| 203 print 'Couldn\'t find Chrome. Verify Chrome is installed and try again.' | |
| 204 return False | |
| 205 ret = self._LaunchInstaller(opts) | |
| 206 if os.path.exists(os.path.join(self._dest, self._installer)): | |
| 207 self._Delete(os.path.join(self._dest, self._installer)) | |
| 208 if not os.path.exists(self._c_path): | |
| 209 print 'Chrome was successfully uninstalled...' | |
| 210 if self._clean: | |
| 211 print 'Removing registry settings...' | |
| 212 self.c_install.DeleteChromeRegEntries() | |
| 213 print 'Uninstall complete.' | |
| 214 return True | |
| 215 else: | |
| 216 print 'Uninstall failed.' | |
| 217 return False | |
| 218 | |
| 219 | |
| 220 class ChromeVersion(): | |
|
kkania
2012/06/14 17:20:33
Change this class name from ChromeVersion to just
nkang
2012/06/28 19:02:39
Changed class name to Installation and moved the U
| |
| 221 """Contains utility functions that provide information about installed | |
| 222 | |
| 223 version of Chrome. The type of Chrome version is passed as an argument | |
| 224 to the constructor (i.e. - user or system level version). | |
| 225 """ | |
| 226 | |
|
kkania
2012/06/14 17:20:33
add a static method GetInstallations() which retur
nkang
2012/06/28 19:02:39
I am assuming you mean user and system level insta
| |
| 227 def __init__(self, install_type): | |
| 228 self._type = install_type | |
|
kkania
2012/06/14 17:20:33
is this just a string. change it to a simple enum.
nkang
2012/06/28 19:02:39
Done.
| |
| 229 self.HKEY_LOCAL = (r'SOFTWARE\Wow6432Node\Google\Update\ClientState' | |
| 230 '\{8A69D345-D564-463C-AFF1-A69D9E530F96}') | |
| 231 self.HKEY_USER = self.HKEY_LOCAL.replace('\\Wow6432Node', '') | |
| 232 | |
| 233 def _OpenKey(self, key_type, key_name, key_access=_winreg.KEY_READ): | |
| 234 """Opens a registry key. | |
| 235 | |
| 236 Args: | |
| 237 key_type: HKEY_CURRENT_USER or HKEY_LOCAL_MACHINE. | |
| 238 key_name: Name of the key that is to be opened. | |
| 239 key_access: Type of access required (i.e. - KEY_READ or KEY_ALL_ACCESS). | |
| 240 | |
| 241 Returns: | |
| 242 Handle - handle to the key if successful, otherwise zero. | |
| 243 """ | |
| 244 try: | |
| 245 key = _winreg.OpenKey(key_type, key_name, 0, key_access) | |
| 246 return key | |
| 247 except(OSError, TypeError), err: | |
| 248 return 0 | |
| 249 | |
| 250 def _GetKeyValue(self, key, name): | |
| 251 """Gets the value of the specified key. | |
| 252 | |
| 253 Args: | |
| 254 key: Handle to the key. | |
| 255 name: Name of the key. | |
| 256 | |
| 257 Returns: | |
| 258 String - key value if successful, otherwise empty string. | |
| 259 """ | |
| 260 try: | |
| 261 val = str(_winreg.QueryValueEx(key, name)[0]) | |
| 262 return val | |
| 263 except OSError, err: | |
| 264 print 'ChromeVersion._GetKeyValue: %s' % err | |
| 265 return '' | |
| 266 | |
| 267 def _GetKeyName(self, install_type, b_replace=True): | |
| 268 """Returns Chrome registry key name. """ | |
| 269 if install_type == _winreg.HKEY_CURRENT_USER: | |
| 270 if b_replace: | |
| 271 return self.HKEY_USER.replace('ClientState', 'Clients') | |
| 272 else: | |
| 273 return self.HKEY_USER | |
| 274 elif install_type == _winreg.HKEY_LOCAL_MACHINE: | |
| 275 if b_replace: | |
| 276 return self.HKEY_LOCAL.replace('ClientState', 'Clients') | |
| 277 else: | |
| 278 return self.HKEY_LOCAL | |
| 279 | |
| 280 def _GetWinLocalFolder(self, ftype=_CSIDL_COMMON_APPDATA): | |
| 281 """Returns the full path of the 'Local' folder on Windows. | |
| 282 | |
| 283 Args: | |
| 284 ftype: Location to look up, which could vary based on installation type. | |
| 285 | |
| 286 Returns: | |
| 287 String - folder path if successful, otherwise an empty string. | |
| 288 """ | |
| 289 if _PLATFORM != 'windows': | |
| 290 return '' | |
| 291 SHGetFolderPathW = windll.shell32.SHGetFolderPathW | |
| 292 SHGetFolderPathW.argtypes = [wintypes.HWND, | |
| 293 ctypes.c_int, | |
| 294 wintypes.HANDLE, | |
| 295 wintypes.DWORD, | |
| 296 wintypes.LPCWSTR] | |
| 297 path_buf = wintypes.create_unicode_buffer(wintypes.MAX_PATH) | |
| 298 result = SHGetFolderPathW(0, ftype, 0, 0, path_buf) | |
| 299 return str(path_buf.value) | |
| 300 | |
| 301 def GetInstallTypeString(self): | |
| 302 """Returns a string representing the type of Chrome installation.""" | |
| 303 install_type = ({_winreg.HKEY_CURRENT_USER: 'user', | |
| 304 _winreg.HKEY_LOCAL_MACHINE: 'system'}).get( | |
| 305 self.GetChromeInstallType()) | |
| 306 return install_type | |
| 307 | |
| 308 def GetChromeInstallType(self): | |
| 309 """Determines Chrome installation type.""" | |
| 310 c_type = None | |
| 311 key = self._OpenKey(_winreg.HKEY_CURRENT_USER, | |
| 312 self.HKEY_USER.replace('ClientState', 'Clients')) | |
| 313 if key: | |
| 314 if self._GetKeyValue(key, 'pv'): | |
| 315 c_type = _winreg.HKEY_CURRENT_USER | |
| 316 else: | |
| 317 key = self._OpenKey(_winreg.HKEY_LOCAL_MACHINE, | |
| 318 self.HKEY_USER.replace('ClientState', 'Clients')) | |
| 319 if key: | |
| 320 if self._GetKeyValue(key, 'pv'): | |
| 321 c_type = _winreg.HKEY_LOCAL_MACHINE | |
| 322 if key: | |
| 323 _winreg.CloseKey(key) | |
| 324 return c_type | |
| 325 | |
| 326 def GetChromeVersion(self): | |
| 327 """Returns current Chrome version.""" | |
| 328 chrome_ver = '' | |
| 329 key_type = ({'user': _winreg.HKEY_CURRENT_USER, | |
| 330 'system' : _winreg.HKEY_LOCAL_MACHINE}).get(self._type) | |
| 331 key = self._OpenKey(key_type, self._GetKeyName(key_type)) | |
| 332 if key: | |
| 333 chrome_ver = self._GetKeyValue(key, 'pv') | |
| 334 _winreg.CloseKey(key) | |
| 335 return chrome_ver | |
| 336 | |
| 337 def GetUninstallArgs(self, install_type=_winreg.HKEY_CURRENT_USER): | |
| 338 """Gets chrome's uninstall arguments from the registry. | |
| 339 | |
| 340 Args: | |
| 341 install_type: Type of chrome installation (user or system). | |
| 342 | |
| 343 Returns: | |
| 344 String - uninstall arguments if successful, otherwise empty string. | |
| 345 """ | |
| 346 c_args = '' | |
| 347 key = self._OpenKey(install_type, self._GetKeyName(install_type, False)) | |
| 348 if key: | |
| 349 c_args = self._GetKeyValue(key, 'UninstallArguments') | |
| 350 _winreg.CloseKey(key) | |
| 351 return c_args | |
| 352 | |
| 353 def GetChromeExePath(self): | |
| 354 """Gets Chrome's location, based on install type (i.e. user or system).""" | |
| 355 c_path = '' | |
| 356 f_id = (lambda _type: _type == 'user' and _CSIDL_COMMON_APPDATA or | |
| 357 _CSIDL_PROGRAM_FILESX86)(self._type) | |
| 358 if _PLATFORM == 'windows': | |
| 359 c_path = os.path.join(self._GetWinLocalFolder(f_id), | |
| 360 'Google', 'Chrome', | |
| 361 'Application', 'chrome.exe') | |
| 362 return c_path | |
| 363 | |
| 364 def DeleteChromeRegEntries(self): | |
| 365 """Deletes chrome registry settings.""" | |
| 366 if self._type == 'user': | |
| 367 p_key = self.HKEY_USER[: self.HKEY_USER.rfind('\\')] | |
| 368 key_name = self.HKEY_USER[self.HKEY_USER.rfind('\\') + 1 :] | |
| 369 _type = _winreg.HKEY_CURRENT_USER | |
| 370 else: | |
| 371 p_key = self.HKEY_LOCAL[: self.HKEY_LOCAL.rfind('\\')] | |
| 372 key_name = self.HKEY_LOCAL[self.HKEY_LOCAL.rfind('\\')+1:] | |
| 373 _type = _winreg.HKEY_LOCAL_MACHINE | |
| 374 key = self._OpenKey(_type, p_key, _winreg.KEY_ALL_ACCESS) | |
| 375 try: | |
| 376 _winreg.DeleteKey(key, key_name) | |
| 377 key.Close() | |
| 378 return 0 | |
| 379 except _winreg.error, err: | |
| 380 print "Warning: could not delete registry settings - ", err | |
| 381 return -1 | |
| OLD | NEW |