16320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org#!/usr/bin/python
26320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org# Copyright (c) 2012 The Native Client Authors. All rights reserved.
36320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org# Use of this source code is governed by a BSD-style license that can be
46320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org# found in the LICENSE file.
56320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org
66320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org"""A library to assist automatically downloading files.
76320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org
86320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.orgThis library is used by scripts that download tarballs, zipfiles, etc. as part
96320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.orgof the build process.
106320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org"""
116320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org
126320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.orgimport hashlib
136320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.orgimport http_download
146320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.orgimport os.path
156320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.orgimport re
166320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.orgimport shutil
176320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.orgimport sys
186320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.orgimport time
196320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.orgimport urllib2
206320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org
216320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.orgSOURCE_STAMP = 'SOURCE_URL'
226320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.orgHASH_STAMP = 'SOURCE_SHA1'
236320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org
246320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org
256320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org# Designed to handle more general inputs than sys.platform because the platform
266320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org# name may come from the command line.
276320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.orgPLATFORM_COLLAPSE = {
286320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org    'windows': 'windows',
296320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org    'win32': 'windows',
306320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org    'cygwin': 'windows',
316320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org    'linux': 'linux',
326320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org    'linux2': 'linux',
336320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org    'linux3': 'linux',
346320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org    'darwin': 'mac',
356320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org    'mac': 'mac',
366320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org}
376320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org
386320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.orgARCH_COLLAPSE = {
396320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org    'i386'  : 'x86',
406320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org    'i686'  : 'x86',
416320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org    'x86_64': 'x86',
426320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org    'armv7l': 'arm',
436320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org}
446320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org
456320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org
466320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.orgclass HashError(Exception):
476320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  def __init__(self, download_url, expected_hash, actual_hash):
486320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org    self.download_url = download_url
496320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org    self.expected_hash = expected_hash
506320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org    self.actual_hash = actual_hash
516320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org
526320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  def __str__(self):
536320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org    return 'Got hash "%s" but expected hash "%s" for "%s"' % (
546320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org        self.actual_hash, self.expected_hash, self.download_url)
556320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org
566320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org
576320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.orgdef PlatformName(name=None):
586320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  if name is None:
596320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org    name = sys.platform
606320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  return PLATFORM_COLLAPSE[name]
616320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org
626320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.orgdef ArchName(name=None):
636320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  if name is None:
646320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org    if PlatformName() == 'windows':
656320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org      # TODO(pdox): Figure out how to auto-detect 32-bit vs 64-bit Windows.
666320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org      name = 'i386'
676320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org    else:
686320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org      import platform
696320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org      name = platform.machine()
706320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  return ARCH_COLLAPSE[name]
716320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org
726320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.orgdef EnsureFileCanBeWritten(filename):
736320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  directory = os.path.dirname(filename)
746320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  if not os.path.exists(directory):
756320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org    os.makedirs(directory)
766320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org
776320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org
786320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.orgdef WriteData(filename, data):
796320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  EnsureFileCanBeWritten(filename)
806320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  f = open(filename, 'wb')
816320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  f.write(data)
826320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  f.close()
836320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org
846320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org
856320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.orgdef WriteDataFromStream(filename, stream, chunk_size, verbose=True):
866320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  EnsureFileCanBeWritten(filename)
876320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  dst = open(filename, 'wb')
886320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  try:
896320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org    while True:
906320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org      data = stream.read(chunk_size)
916320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org      if len(data) == 0:
926320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org        break
936320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org      dst.write(data)
946320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org      if verbose:
956320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org        # Indicate that we're still writing.
966320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org        sys.stdout.write('.')
976320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org        sys.stdout.flush()
986320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  finally:
996320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org    if verbose:
1006320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org      sys.stdout.write('\n')
1016320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org    dst.close()
1026320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org
1036320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org
1046320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.orgdef DoesStampMatch(stampfile, expected, index):
1056320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  try:
1066320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org    f = open(stampfile, 'r')
1076320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org    stamp = f.read()
1086320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org    f.close()
1096320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org    if stamp.split('\n')[index] == expected:
1106320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org      return "already up-to-date."
1116320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org    elif stamp.startswith('manual'):
1126320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org      return "manual override."
1136320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org    return False
1146320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  except IOError:
1156320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org    return False
1166320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org
1176320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org
1186320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.orgdef WriteStamp(stampfile, data):
1196320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  EnsureFileCanBeWritten(stampfile)
1206320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  f = open(stampfile, 'w')
1216320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  f.write(data)
1226320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  f.close()
1236320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org
1246320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org
1256320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.orgdef StampIsCurrent(path, stamp_name, stamp_contents, min_time=None, index=0):
1266320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  stampfile = os.path.join(path, stamp_name)
1276320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org
1286320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  # Check if the stampfile is older than the minimum last mod time
1296320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  if min_time:
1306320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org    try:
1316320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org      stamp_time = os.stat(stampfile).st_mtime
1326320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org      if stamp_time <= min_time:
1336320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org        return False
1346320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org    except OSError:
1356320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org      return False
1366320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org
1376320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  return DoesStampMatch(stampfile, stamp_contents, index)
1386320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org
1396320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org
1406320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.orgdef WriteSourceStamp(path, url):
1416320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  stampfile = os.path.join(path, SOURCE_STAMP)
1426320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  WriteStamp(stampfile, url)
1436320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org
1446320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.orgdef WriteHashStamp(path, hash_val):
1456320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  hash_stampfile = os.path.join(path, HASH_STAMP)
1466320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  WriteStamp(hash_stampfile, hash_val)
1476320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org
1486320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org
1496320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.orgdef Retry(op, *args):
1506320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  # Windows seems to be prone to having commands that delete files or
1516320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  # directories fail.  We currently do not have a complete understanding why,
1526320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  # and as a workaround we simply retry the command a few times.
1536320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  # It appears that file locks are hanging around longer than they should.  This
1546320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  # may be a secondary effect of processes hanging around longer than they
1556320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  # should.  This may be because when we kill a browser sel_ldr does not exit
1566320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  # immediately, etc.
1576320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  # Virus checkers can also accidently prevent files from being deleted, but
1586320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  # that shouldn't be a problem on the bots.
1596320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  if sys.platform in ('win32', 'cygwin'):
1606320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org    count = 0
1616320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org    while True:
1626320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org      try:
1636320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org        op(*args)
1646320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org        break
1656320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org      except Exception:
1666320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org        sys.stdout.write("FAILED: %s %s\n" % (op.__name__, repr(args)))
1676320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org        count += 1
1686320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org        if count < 5:
1696320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org          sys.stdout.write("RETRY: %s %s\n" % (op.__name__, repr(args)))
1706320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org          time.sleep(pow(2, count))
1716320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org        else:
1726320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org          # Don't mask the exception.
1736320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org          raise
1746320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  else:
1756320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org    op(*args)
1766320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org
1776320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org
1786320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.orgdef MoveDirCleanly(src, dst):
1796320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  RemoveDir(dst)
1806320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  MoveDir(src, dst)
1816320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org
1826320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org
1836320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.orgdef MoveDir(src, dst):
1846320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  Retry(shutil.move, src, dst)
1856320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org
1866320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org
1876320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.orgdef RemoveDir(path):
1886320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  if os.path.exists(path):
1896320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org    Retry(shutil.rmtree, path)
1906320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org
1916320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org
1926320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.orgdef RemoveFile(path):
1936320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  if os.path.exists(path):
1946320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org    Retry(os.unlink, path)
1956320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org
1966320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org
1976320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.orgdef _HashFileHandle(fh):
1986320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  """sha1 of a file like object.
1996320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org
2006320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  Arguments:
2016320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org    fh: file handle like object to hash.
2026320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  Returns:
2036320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org    sha1 as a string.
2046320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  """
2056320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  hasher = hashlib.sha1()
2066320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  try:
2076320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org    while True:
2086320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org      data = fh.read(4096)
2096320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org      if not data:
2106320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org        break
2116320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org      hasher.update(data)
2126320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  finally:
2136320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org    fh.close()
2146320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  return hasher.hexdigest()
2156320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org
2166320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org
2176320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.orgdef HashFile(filename):
2186320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  """sha1 a file on disk.
2196320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org
2206320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  Arguments:
2216320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org    filename: filename to hash.
2226320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  Returns:
2236320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org    sha1 as a string.
2246320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  """
2256320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  fh = open(filename, 'rb')
2266320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  return _HashFileHandle(fh)
2276320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org
2286320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org
2296320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.orgdef HashUrlByDownloading(url):
2306320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  """sha1 the data at an url.
2316320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org
2326320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  Arguments:
2336320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org    url: url to download from.
2346320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  Returns:
2356320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org    sha1 of the data at the url.
2366320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  """
2376320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  try:
2386320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org    fh = urllib2.urlopen(url)
2396320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  except:
2406320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org    sys.stderr.write("Failed fetching URL: %s\n" % url)
2416320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org    raise
2426320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  return _HashFileHandle(fh)
2436320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org
2446320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org
2456320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org# Attempts to get the SHA1 hash of a file given a URL by looking for
2466320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org# an adjacent file with a ".sha1hash" suffix.  This saves having to
2476320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org# download a large tarball just to get its hash.  Otherwise, we fall
2486320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org# back to downloading the main file.
2496320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.orgdef HashUrl(url):
2506320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  hash_url = '%s.sha1hash' % url
2516320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  try:
2526320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org    fh = urllib2.urlopen(hash_url)
2536320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org    data = fh.read(100)
2546320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org    fh.close()
2556320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  except urllib2.HTTPError, exn:
2566320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org    if exn.code == 404:
2576320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org      return HashUrlByDownloading(url)
2586320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org    raise
2596320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  else:
2606320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org    if not re.match('[0-9a-f]{40}\n?$', data):
2616320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org      raise AssertionError('Bad SHA1 hash file: %r' % data)
2626320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org    return data.strip()
2636320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org
2646320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org
2656320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.orgdef SyncURL(url, filename=None, stamp_dir=None, min_time=None,
2666320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org            hash_val=None, keep=False, verbose=False, stamp_index=0):
2676320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  """Synchronize a destination file with a URL
2686320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org
2696320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  if the URL does not match the URL stamp, then we must re-download it.
2706320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org
2716320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  Arugments:
2726320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org    url: the url which will to compare against and download
2736320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org    filename: the file to create on download
2746320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org    path: the download path
2756320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org    stamp_dir: the filename containing the URL stamp to check against
2766320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org    hash_val: if set, the expected hash which must be matched
2776320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org    verbose: prints out status as it runs
2786320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org    stamp_index: index within the stamp file to check.
2796320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  Returns:
2806320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org    True if the file is replaced
2816320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org    False if the file is not replaced
2826320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  Exception:
2836320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org    HashError: if the hash does not match
2846320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  """
2856320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org
2866320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  assert url and filename
2876320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org
2886320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  # If we are not keeping the tarball, or we already have it, we can
2896320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  # skip downloading it for this reason. If we are keeping it,
2906320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  # it must exist.
2916320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  if keep:
2926320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org    tarball_ok = os.path.isfile(filename)
2936320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  else:
2946320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org    tarball_ok = True
2956320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org
2966320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  # If we don't need the tarball and the stamp_file matches the url, then
2976320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  # we must be up to date.  If the URL differs but the recorded hash matches
2986320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  # the one we'll insist the tarball has, then that's good enough too.
2996320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  # TODO(mcgrathr): Download the .sha1sum file first to compare with
3006320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  # the cached hash, in case --file-hash options weren't used.
3016320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  if tarball_ok and stamp_dir is not None:
3026320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org    if StampIsCurrent(stamp_dir, SOURCE_STAMP, url, min_time):
3036320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org      if verbose:
3046320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org        print '%s is already up to date.' % filename
3056320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org      return False
3066320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org    if (hash_val is not None and
3076320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org        StampIsCurrent(stamp_dir, HASH_STAMP, hash_val, min_time, stamp_index)):
3086320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org      if verbose:
3096320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org        print '%s is identical to the up to date file.' % filename
3106320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org      return False
3116320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org
3126320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  if verbose:
3136320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org    print 'Updating %s\n\tfrom %s.' % (filename, url)
3146320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  EnsureFileCanBeWritten(filename)
3156320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  http_download.HttpDownload(url, filename)
3166320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org
3176320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  if hash_val:
3186320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org    tar_hash = HashFile(filename)
3196320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org    if hash_val != tar_hash:
3206320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org      raise HashError(actual_hash=tar_hash, expected_hash=hash_val,
3216320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org                      download_url=url)
3226320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org
3236320e8f393539b7a536b32b5a072a474709da5ffcommit-bot@chromium.org  return True
324