Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / native_client / pynacl / download_utils.py
1 #!/usr/bin/python
2 # Copyright (c) 2012 The Native Client 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 """A library to assist automatically downloading files.
7
8 This library is used by scripts that download tarballs, zipfiles, etc. as part
9 of the build process.
10 """
11
12 import hashlib
13 import os.path
14 import re
15 import sys
16 import urllib2
17
18 import http_download
19
20 SOURCE_STAMP = 'SOURCE_URL'
21 HASH_STAMP = 'SOURCE_SHA1'
22
23 class HashError(Exception):
24   def __init__(self, download_url, expected_hash, actual_hash):
25     self.download_url = download_url
26     self.expected_hash = expected_hash
27     self.actual_hash = actual_hash
28
29   def __str__(self):
30     return 'Got hash "%s" but expected hash "%s" for "%s"' % (
31         self.actual_hash, self.expected_hash, self.download_url)
32
33 def EnsureFileCanBeWritten(filename):
34   directory = os.path.dirname(filename)
35   if not os.path.exists(directory):
36     os.makedirs(directory)
37
38
39 def WriteData(filename, data):
40   EnsureFileCanBeWritten(filename)
41   f = open(filename, 'wb')
42   f.write(data)
43   f.close()
44
45
46 def WriteDataFromStream(filename, stream, chunk_size, verbose=True):
47   EnsureFileCanBeWritten(filename)
48   dst = open(filename, 'wb')
49   try:
50     while True:
51       data = stream.read(chunk_size)
52       if len(data) == 0:
53         break
54       dst.write(data)
55       if verbose:
56         # Indicate that we're still writing.
57         sys.stdout.write('.')
58         sys.stdout.flush()
59   finally:
60     if verbose:
61       sys.stdout.write('\n')
62     dst.close()
63
64
65 def DoesStampMatch(stampfile, expected, index):
66   try:
67     f = open(stampfile, 'r')
68     stamp = f.read()
69     f.close()
70     if stamp.split('\n')[index] == expected:
71       return 'already up-to-date.'
72     elif stamp.startswith('manual'):
73       return 'manual override.'
74     return False
75   except IOError:
76     return False
77
78
79 def WriteStamp(stampfile, data):
80   EnsureFileCanBeWritten(stampfile)
81   f = open(stampfile, 'w')
82   f.write(data)
83   f.close()
84
85
86 def StampIsCurrent(path, stamp_name, stamp_contents, min_time=None, index=0):
87   stampfile = os.path.join(path, stamp_name)
88
89   stampmatch = DoesStampMatch(stampfile, stamp_contents, index)
90
91   # If toolchain was downloaded and/or created manually then keep it untouched
92   if stampmatch == 'manual override.':
93     return stampmatch
94
95   # Check if the stampfile is older than the minimum last mod time
96   if min_time:
97     try:
98       stamp_time = os.stat(stampfile).st_mtime
99       if stamp_time <= min_time:
100         return False
101     except OSError:
102       return False
103
104   return stampmatch
105
106
107 def WriteSourceStamp(path, url):
108   stampfile = os.path.join(path, SOURCE_STAMP)
109   WriteStamp(stampfile, url)
110
111
112 def WriteHashStamp(path, hash_val):
113   hash_stampfile = os.path.join(path, HASH_STAMP)
114   WriteStamp(hash_stampfile, hash_val)
115
116
117 def _HashFileHandle(fh):
118   """sha1 of a file like object.
119
120   Arguments:
121     fh: file handle like object to hash.
122   Returns:
123     sha1 as a string.
124   """
125   hasher = hashlib.sha1()
126   try:
127     while True:
128       data = fh.read(4096)
129       if not data:
130         break
131       hasher.update(data)
132   finally:
133     fh.close()
134   return hasher.hexdigest()
135
136
137 def HashFile(filename):
138   """sha1 a file on disk.
139
140   Arguments:
141     filename: filename to hash.
142   Returns:
143     sha1 as a string.
144   """
145   fh = open(filename, 'rb')
146   return _HashFileHandle(fh)
147
148
149 def HashUrlByDownloading(url):
150   """sha1 the data at an url.
151
152   Arguments:
153     url: url to download from.
154   Returns:
155     sha1 of the data at the url.
156   """
157   try:
158     fh = urllib2.urlopen(url)
159   except:
160     sys.stderr.write('Failed fetching URL: %s\n' % url)
161     raise
162   return _HashFileHandle(fh)
163
164
165 # Attempts to get the SHA1 hash of a file given a URL by looking for
166 # an adjacent file with a ".sha1hash" suffix.  This saves having to
167 # download a large tarball just to get its hash.  Otherwise, we fall
168 # back to downloading the main file.
169 def HashUrl(url):
170   hash_url = '%s.sha1hash' % url
171   try:
172     fh = urllib2.urlopen(hash_url)
173     data = fh.read(100)
174     fh.close()
175   except urllib2.HTTPError, exn:
176     if exn.code == 404:
177       return HashUrlByDownloading(url)
178     raise
179   else:
180     if not re.match('[0-9a-f]{40}\n?$', data):
181       raise AssertionError('Bad SHA1 hash file: %r' % data)
182     return data.strip()
183
184
185 def SyncURL(url, filename=None, stamp_dir=None, min_time=None,
186             hash_val=None, keep=False, verbose=False, stamp_index=0):
187   """Synchronize a destination file with a URL
188
189   if the URL does not match the URL stamp, then we must re-download it.
190
191   Arugments:
192     url: the url which will to compare against and download
193     filename: the file to create on download
194     path: the download path
195     stamp_dir: the filename containing the URL stamp to check against
196     hash_val: if set, the expected hash which must be matched
197     verbose: prints out status as it runs
198     stamp_index: index within the stamp file to check.
199   Returns:
200     True if the file is replaced
201     False if the file is not replaced
202   Exception:
203     HashError: if the hash does not match
204   """
205
206   assert url and filename
207
208   # If we are not keeping the tarball, or we already have it, we can
209   # skip downloading it for this reason. If we are keeping it,
210   # it must exist.
211   if keep:
212     tarball_ok = os.path.isfile(filename)
213   else:
214     tarball_ok = True
215
216   # If we don't need the tarball and the stamp_file matches the url, then
217   # we must be up to date.  If the URL differs but the recorded hash matches
218   # the one we'll insist the tarball has, then that's good enough too.
219   # TODO(mcgrathr): Download the .sha1sum file first to compare with
220   # the cached hash, in case --file-hash options weren't used.
221   if tarball_ok and stamp_dir is not None:
222     if StampIsCurrent(stamp_dir, SOURCE_STAMP, url, min_time):
223       if verbose:
224         print '%s is already up to date.' % filename
225       return False
226     if (hash_val is not None and
227         StampIsCurrent(stamp_dir, HASH_STAMP, hash_val, min_time, stamp_index)):
228       if verbose:
229         print '%s is identical to the up to date file.' % filename
230       return False
231
232   if (os.path.isfile(filename)
233       and hash_val is not None
234       and hash_val == HashFile(filename)):
235     return True
236
237   if verbose:
238     print 'Updating %s\n\tfrom %s.' % (filename, url)
239   EnsureFileCanBeWritten(filename)
240   http_download.HttpDownload(url, filename)
241
242   if hash_val:
243     tar_hash = HashFile(filename)
244     if hash_val != tar_hash:
245       raise HashError(actual_hash=tar_hash, expected_hash=hash_val,
246                       download_url=url)
247
248   return True