OLD | NEW |
(Empty) | |
| 1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. |
| 4 |
| 5 import download |
| 6 import logging |
| 7 import os |
| 8 from sdk_update_common import Error |
| 9 import sdk_update_common |
| 10 import sys |
| 11 import urlparse |
| 12 import urllib2 |
| 13 |
| 14 SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) |
| 15 PARENT_DIR = os.path.dirname(SCRIPT_DIR) |
| 16 sys.path.append(PARENT_DIR) |
| 17 try: |
| 18 import cygtar |
| 19 except ImportError: |
| 20 # Try to find this in the Chromium repo. |
| 21 CHROME_SRC_DIR = os.path.abspath( |
| 22 os.path.join(PARENT_DIR, '..', '..', '..', '..')) |
| 23 sys.path.append(os.path.join(CHROME_SRC_DIR, 'native_client', 'build')) |
| 24 import cygtar |
| 25 |
| 26 __all__ = ['RECOMMENDED', 'SDK_TOOLS', 'UpdateDelegate', 'RealUpdateDelegate', |
| 27 'Update', 'UpdateBundleIfNeeded'] |
| 28 |
| 29 |
| 30 RECOMMENDED = 'recommended' |
| 31 SDK_TOOLS = 'sdk_tools' |
| 32 HTTP_CONTENT_LENGTH = 'Content-Length' # HTTP Header field for content length |
| 33 |
| 34 |
| 35 class UpdateDelegate(object): |
| 36 def BundleDirectoryExists(self, bundle_name): |
| 37 raise NotImplementedError() |
| 38 |
| 39 def DownloadToFile(self, url, dest_filename): |
| 40 raise NotImplementedError() |
| 41 |
| 42 def ExtractArchive(self, archive, extract_dir, rename_from_dir, |
| 43 rename_to_dir): |
| 44 raise NotImplementedError() |
| 45 |
| 46 |
| 47 class RealUpdateDelegate(UpdateDelegate): |
| 48 def __init__(self, user_data_dir, install_dir): |
| 49 UpdateDelegate.__init__(self) |
| 50 self.user_data_dir = user_data_dir |
| 51 self.install_dir = install_dir |
| 52 |
| 53 def BundleDirectoryExists(self, bundle_name): |
| 54 bundle_path = os.path.join(self.install_dir, bundle_name) |
| 55 return os.path.isdir(bundle_path) |
| 56 |
| 57 def DownloadToFile(self, url, dest_filename): |
| 58 sdk_update_common.MakeDirs(self.user_data_dir) |
| 59 dest_path = os.path.join(self.user_data_dir, dest_filename) |
| 60 out_stream = None |
| 61 url_stream = None |
| 62 try: |
| 63 out_stream = open(dest_path, 'wb') |
| 64 url_stream = download.UrlOpen(url) |
| 65 content_length = int(url_stream.info()[HTTP_CONTENT_LENGTH]) |
| 66 progress = download.MakeProgressFunction(content_length) |
| 67 sha1, size = download.DownloadAndComputeHash(url_stream, out_stream, |
| 68 progress) |
| 69 return sha1, size |
| 70 except urllib2.URLError as e: |
| 71 raise Error('Unable to read from URL "%s".\n %s' % (url, e)) |
| 72 except IOError as e: |
| 73 raise Error('Unable to write to file "%s".\n %s' % (dest_filename, e)) |
| 74 finally: |
| 75 if url_stream: |
| 76 url_stream.close() |
| 77 if out_stream: |
| 78 out_stream.close() |
| 79 |
| 80 def ExtractArchive(self, archive, extract_dir, rename_from_dir, |
| 81 rename_to_dir): |
| 82 tar_file = None |
| 83 |
| 84 archive_path = os.path.join(self.user_data_dir, archive) |
| 85 extract_path = os.path.join(self.install_dir, extract_dir) |
| 86 rename_from_path = os.path.join(self.install_dir, rename_from_dir) |
| 87 rename_to_path = os.path.join(self.install_dir, rename_to_dir) |
| 88 |
| 89 # Extract to extract_dir, usually "<bundle name>_update". |
| 90 # This way if the extraction fails, we haven't blown away the old bundle |
| 91 # (if it exists). |
| 92 sdk_update_common.RemoveDir(extract_path) |
| 93 sdk_update_common.MakeDirs(extract_path) |
| 94 curpath = os.getcwd() |
| 95 tar_file = None |
| 96 |
| 97 try: |
| 98 try: |
| 99 tar_file = cygtar.CygTar(archive_path, 'r', verbose=True) |
| 100 except Exception as e: |
| 101 raise Error('Can\'t open archive "%s".\n %s' % (archive_path, e)) |
| 102 |
| 103 try: |
| 104 logging.info('Changing the directory to %s' % (extract_path,)) |
| 105 os.chdir(extract_path) |
| 106 except Exception as e: |
| 107 raise Error('Unable to chdir into "%s".\n %s' % (extract_path, e)) |
| 108 |
| 109 logging.info('Extracting to %s' % (extract_path,)) |
| 110 tar_file.Extract() |
| 111 |
| 112 logging.info('Changing the directory to %s' % (curpath,)) |
| 113 os.chdir(curpath) |
| 114 |
| 115 logging.info('Renaming %s->%s' % (rename_from_path, rename_to_path)) |
| 116 sdk_update_common.RenameDir(rename_from_path, rename_to_path) |
| 117 finally: |
| 118 # Change the directory back so we can remove the update directory. |
| 119 os.chdir(curpath) |
| 120 |
| 121 # Clean up the ..._update directory. |
| 122 try: |
| 123 sdk_update_common.RemoveDir(extract_path) |
| 124 except Exception as e: |
| 125 logging.error('Failed to remove directory \"%s\". %s' % ( |
| 126 extract_path, e)) |
| 127 |
| 128 if tar_file: |
| 129 tar_file.Close() |
| 130 |
| 131 # Remove the archive. |
| 132 os.remove(archive_path) |
| 133 |
| 134 |
| 135 def Update(delegate, remote_manifest, local_manifest, bundle_names, force): |
| 136 valid_bundles = set([bundle.name for bundle in remote_manifest.GetBundles()]) |
| 137 requested_bundles = _GetRequestedBundlesFromArgs(remote_manifest, |
| 138 bundle_names) |
| 139 invalid_bundles = requested_bundles - valid_bundles |
| 140 if invalid_bundles: |
| 141 logging.warn('Ignoring unknown bundle(s): %s' % ( |
| 142 ', '.join(invalid_bundles))) |
| 143 requested_bundles -= invalid_bundles |
| 144 |
| 145 if SDK_TOOLS in requested_bundles: |
| 146 logging.warn('Updating sdk_tools happens automatically. ' |
| 147 'Ignoring manual update request.') |
| 148 requested_bundles.discard(SDK_TOOLS) |
| 149 |
| 150 if requested_bundles: |
| 151 for bundle_name in requested_bundles: |
| 152 logging.info('Trying to update %s' % (bundle_name,)) |
| 153 UpdateBundleIfNeeded(delegate, remote_manifest, local_manifest, |
| 154 bundle_name, force) |
| 155 else: |
| 156 logging.warn('No bundles to update.') |
| 157 |
| 158 |
| 159 def UpdateBundleIfNeeded(delegate, remote_manifest, local_manifest, |
| 160 bundle_name, force): |
| 161 bundle = remote_manifest.GetBundle(bundle_name) |
| 162 if bundle: |
| 163 if _BundleNeedsUpdate(delegate, local_manifest, bundle): |
| 164 _UpdateBundle(delegate, bundle, local_manifest, force) |
| 165 else: |
| 166 print '%s is already up-to-date.' % (bundle.name,) |
| 167 else: |
| 168 logging.error('Bundle %s does not exist.' % (bundle_name,)) |
| 169 |
| 170 |
| 171 def _GetRequestedBundlesFromArgs(remote_manifest, requested_bundles): |
| 172 requested_bundles = set(requested_bundles) |
| 173 if RECOMMENDED in requested_bundles: |
| 174 requested_bundles.discard(RECOMMENDED) |
| 175 requested_bundles |= set(_GetRecommendedBundles(remote_manifest)) |
| 176 |
| 177 return requested_bundles |
| 178 |
| 179 |
| 180 def _GetRecommendedBundles(remote_manifest): |
| 181 return [bundle for bundle in remote_manifest.GetBundles() if |
| 182 bundle.recommended] |
| 183 |
| 184 |
| 185 def _BundleNeedsUpdate(delegate, local_manifest, bundle): |
| 186 # Always update the bundle if the directory doesn't exist; |
| 187 # the user may have deleted it. |
| 188 if not delegate.BundleDirectoryExists(bundle.name): |
| 189 return True |
| 190 |
| 191 return local_manifest.BundleNeedsUpdate(bundle) |
| 192 |
| 193 |
| 194 def _UpdateBundle(delegate, bundle, local_manifest, force): |
| 195 archive = bundle.GetHostOSArchive() |
| 196 if not archive: |
| 197 logging.warn('Bundle %s does not exist for this platform.' % (bundle.name,)) |
| 198 return |
| 199 |
| 200 print 'Downloading bundle %s' % (bundle.name,) |
| 201 dest_filename = _GetFilenameFromURL(archive.url) |
| 202 sha1, size = delegate.DownloadToFile(archive.url, dest_filename) |
| 203 _ValidateArchive(archive, sha1, size) |
| 204 |
| 205 print 'Updating bundle %s to version %s, revision %s' % ( |
| 206 bundle.name, bundle.version, bundle.revision) |
| 207 extract_dir = bundle.name + '_update' |
| 208 |
| 209 repath_dir = bundle.get('repath', None) |
| 210 if repath_dir: |
| 211 # If repath is specified: |
| 212 # The files are extracted to nacl_sdk/<bundle.name>_update/<repath>/... |
| 213 # The destination directory is nacl_sdk/<repath>/... |
| 214 rename_from_dir = os.path.join(extract_dir, repath_dir) |
| 215 rename_to_dir = repath_dir |
| 216 else: |
| 217 # If no repath is specified: |
| 218 # The files are extracted to nacl_sdk/<bundle.name>_update/... |
| 219 # The destination directory is nacl_sdk/<bundle.name>/... |
| 220 rename_from_dir = extract_dir |
| 221 rename_to_dir = bundle.name |
| 222 |
| 223 delegate.ExtractArchive(dest_filename, extract_dir, rename_from_dir, |
| 224 rename_to_dir) |
| 225 |
| 226 logging.info('Updating local manifest to include bundle %s' % (bundle.name)) |
| 227 local_manifest.MergeBundle(bundle) |
| 228 |
| 229 |
| 230 def _GetFilenameFromURL(url): |
| 231 _, _, path, _, _, _ = urlparse.urlparse(url) |
| 232 return path.split('/')[-1] |
| 233 |
| 234 |
| 235 def _ValidateArchive(archive, actual_sha1, actual_size): |
| 236 if actual_sha1 != archive.GetChecksum(): |
| 237 raise Error('SHA1 checksum mismatch on "%s". Expected %s but got %s' % ( |
| 238 archive.name, archive.GetChecksum(), actual_sha1)) |
| 239 if actual_size != archive.size: |
| 240 raise Error('Size mismatch on "%s". Expected %s but got %s bytes' % ( |
| 241 archive.name, archive.size, actual_size)) |
OLD | NEW |