OLD | NEW |
(Empty) | |
| 1 #!/usr/bin/env 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 """Script to download files from Google Storage.""" |
| 6 import hashlib |
| 7 import os |
| 8 import Queue |
| 9 import re |
| 10 import subprocess |
| 11 import sys |
| 12 import tempfile |
| 13 import threading |
| 14 import time |
| 15 import zipfile |
| 16 |
| 17 from optparse import OptionParser |
| 18 |
| 19 GSUTIL_DEFAULT_PATH = os.path.join(os.path.dirname(os.path.normpath(__file__)), |
| 20 '..', '..', 'third_party', 'gsutil', 'gsutil') |
| 21 |
| 22 |
| 23 class Gsutil(): |
| 24 def __init__(self, path): |
| 25 if os.path.exists(path): |
| 26 self.path = path |
| 27 else: |
| 28 raise IOError('GSUtil not found in %s' % path) |
| 29 |
| 30 def call(self, *args): |
| 31 p = subprocess.Popen((sys.executable, self.path) + args) |
| 32 return p.wait() |
| 33 |
| 34 def check_call(self, *args): |
| 35 p = subprocess.Popen((sys.executable, self.path) + args, |
| 36 stdout=subprocess.PIPE, |
| 37 stderr=subprocess.PIPE) |
| 38 code = p.wait() |
| 39 out, err = p.communicate() |
| 40 self.stdout = out |
| 41 self.stderr = err |
| 42 |
| 43 if code == 0: |
| 44 return 0 |
| 45 |
| 46 status_code_match = re.search('status=([0-9]+)', err) |
| 47 if status_code_match: |
| 48 return int(status_code_match.groups(1)) |
| 49 elif ('You are attempting to access protected data with ' |
| 50 'no configured credentials.' in err): |
| 51 return 403 |
| 52 elif 'No such object' in err: |
| 53 return 404 |
| 54 else: |
| 55 return code |
| 56 |
| 57 |
| 58 class GsutilFactory(): |
| 59 def __init__(self, path, boto=None): |
| 60 self.path = path |
| 61 |
| 62 |
| 63 def CheckSHA1(sha1_sum, filename): |
| 64 sha1 = hashlib.sha1() |
| 65 sha1.update(open(filename).read()) |
| 66 return sha1_sum == sha1.hexdigest() |
| 67 |
| 68 def _upload_worker(thread_num, q, base_url, gsutil, options, md5_lock): |
| 69 while True: |
| 70 try: |
| 71 filename, sha1_sum = q.get_nowait() |
| 72 file_url = '%s/%s' % (base_url, sha1_sum) |
| 73 if gsutil.call('ls', file_url) == 0 and not options.force: |
| 74 # File exists, check MD5 hash. |
| 75 gsutil.call('ls', '-L', file_url) |
| 76 etag_match = re.search('ETag:\s+([a-z0-9]{32})', gsutil.stdout) |
| 77 if etag_match: |
| 78 remote_md5 = etag_match.groups()[0] |
| 79 md5_calculator = hashlib.md5() |
| 80 with md5_lock: |
| 81 md5_calculator.update(open(filename).read()) |
| 82 local_md5 = md5_calculator.hexdigest() |
| 83 if local_md5 == remote_md5: |
| 84 print ('File already exists at %s and MD5 matches, exiting' % |
| 85 file_url) |
| 86 continue |
| 87 print 'Uploading %s to %s' % (filename, file_url) |
| 88 code = gsutil.call_interactive('cp', '-q', filename, file_url) |
| 89 if code != 0: |
| 90 print >>sys.stderr, gsutil.stderr |
| 91 continue |
| 92 except Queue.Empty: |
| 93 return |
| 94 |
| 95 def main(args): |
| 96 parser = OptionParser() |
| 97 parser.add_option('-d', '--delete', action='store_true', default=False, |
| 98 help='Deletes the target file after upload.') |
| 99 parser.add_option('-b', '--bucket', default='chrome-artifacts', |
| 100 help='Google Storage bucket to fetch from.') |
| 101 parser.add_option('-f', '--force', action='store_true', default=False, |
| 102 help='Force upload even if remote file exists.') |
| 103 parser.add_option('-g', '--gsutil_path', default=GSUTIL_DEFAULT_PATH, |
| 104 help='Path to the gsutil script.') |
| 105 parser.add_option('-t', '--num_threads', default=1, type='int', |
| 106 help='Number of uploader threads to run.') |
| 107 parser.add_option('-s', '--skip_hashing', action='store_true', default=False, |
| 108 help='Skip hashing if .sha1 file exists.') |
| 109 (options, args) = parser.parse_args() |
| 110 |
| 111 if len(args) < 1: |
| 112 print >>sys.stderr, 'Missing target.' |
| 113 return 1 |
| 114 else: |
| 115 input_filename = args[0] |
| 116 base_url = 'gs://%s' % options.bucket |
| 117 if os.path.exists(options.gsutil_path): |
| 118 gsutil = Gsutil(options.gsutil_path) |
| 119 else: |
| 120 for path in os.environ["PATH"].split(os.pathsep): |
| 121 if os.path.exists(path) and 'gsutil' in os.listdir(path): |
| 122 gsutil = Gsutil(os.path.join(path, 'gsutil')) |
| 123 |
| 124 # Check if we have permissions. |
| 125 code = gsutil.call('ls', base_url) |
| 126 if code == 403: |
| 127 code = gsutil.call_interactive('config') |
| 128 if code != 0: |
| 129 print >>sys.stderr, 'Error while authenticating to %s, exiting' % base_url |
| 130 return 403 |
| 131 elif code == 404: |
| 132 print >>sys.stderr, '%s not found.' % base_url |
| 133 return 404 |
| 134 elif code != 0: |
| 135 print >>sys.stderr, gsutil.stderr |
| 136 return code |
| 137 |
| 138 # Enumerate the list of file(s) we want to transfer over. |
| 139 hash_queue = [] |
| 140 if input_filename == '-': |
| 141 # Take stdin as a newline-seperated list of files. |
| 142 for line in sys.stdin.readlines(): |
| 143 hash_queue.append(line.strip()) |
| 144 else: |
| 145 hash_queue.append(input_filename) |
| 146 |
| 147 # We want to hash everything in a single thread since its faster. |
| 148 # The bottleneck is in disk IO, not CPU. |
| 149 upload_queue = Queue.Queue() |
| 150 hash_timer = time.time() |
| 151 for filename in hash_queue: |
| 152 if os.path.exists('%s.sha1' % filename) and options.skip_hashing: |
| 153 print 'Found hash for %s, skipping.' % filename |
| 154 upload_queue.put((filename, open('%s.sha1' % filename).read())) |
| 155 continue |
| 156 print 'Calculating hash for %s...' % filename, |
| 157 sha1_calculator = hashlib.sha1() |
| 158 sha1_calculator.update(open(filename).read()) |
| 159 sha1_sum = sha1_calculator.hexdigest() |
| 160 with open(filename + '.sha1', 'w') as f: |
| 161 f.write(sha1_sum) |
| 162 print 'done' |
| 163 upload_queue.put((filename, sha1_sum)) |
| 164 hash_time = time.time() - hash_timer |
| 165 |
| 166 # Start up all the worker threads. |
| 167 all_threads = [] |
| 168 |
| 169 # We only want one MD5 calculation happening at a time. |
| 170 md5_lock = threading.Lock() |
| 171 upload_timer = time.time() |
| 172 |
| 173 for thread_num in range(options.num_threads): |
| 174 t = threading.Thread(target=_upload_worker, args=[thread_num, |
| 175 upload_queue, base_url, gsutil, options, md5_lock]) |
| 176 t.daemon = True |
| 177 t.start() |
| 178 all_threads.append(t) |
| 179 |
| 180 def _wait_thread(threads, done): |
| 181 for t in threads: |
| 182 t.join() |
| 183 print 'Now we\'re done' |
| 184 done.set() |
| 185 |
| 186 # Have a thread set a flag when all the tasks are done. |
| 187 done = threading.Event() |
| 188 done_thread = threading.Thread(target=_wait_thread, args=[all_threads, done]) |
| 189 done_thread.daemon = True |
| 190 done_thread.start() |
| 191 |
| 192 while not done.is_set(): |
| 193 time.sleep(1) # Do a sleep loop so we can ctrl + c out of this anytime. |
| 194 print 'Hashing %s took %1f seconds' % (len(hash_queue), hash_time) |
| 195 print 'Uploading took %1f seconds' % (time.time() - upload_timer) |
| 196 |
| 197 if __name__ == '__main__': |
| 198 sys.exit(main(sys.argv)) |
OLD | NEW |