blob: 32675fe9d8228460bd528751d615b450047221f1 [file] [log] [blame]
maruel@chromium.orgc6f90062012-11-07 18:32:22 +00001#!/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
6"""Archives a set of files to a server."""
7
8import binascii
vadimsh@chromium.org53f8d5a2013-06-19 13:03:55 +00009import cStringIO
maruel@chromium.orgc6f90062012-11-07 18:32:22 +000010import hashlib
vadimsh@chromium.org53f8d5a2013-06-19 13:03:55 +000011import itertools
maruel@chromium.orgc6f90062012-11-07 18:32:22 +000012import logging
13import optparse
14import os
15import sys
16import time
maruel@chromium.orge82112e2013-04-24 14:41:55 +000017import urllib
csharp@chromium.org59c7bcf2012-11-21 21:13:18 +000018import zlib
maruel@chromium.orgc6f90062012-11-07 18:32:22 +000019
20import run_isolated
csharp@chromium.org07fa7592013-01-11 18:19:30 +000021import run_test_cases
maruel@chromium.orgc6f90062012-11-07 18:32:22 +000022
23
maruel@chromium.orgc6f90062012-11-07 18:32:22 +000024# The minimum size of files to upload directly to the blobstore.
maruel@chromium.orgaef29f82012-12-12 15:00:42 +000025MIN_SIZE_FOR_DIRECT_BLOBSTORE = 20 * 1024
maruel@chromium.orgc6f90062012-11-07 18:32:22 +000026
vadimsh@chromium.org53f8d5a2013-06-19 13:03:55 +000027# The number of files to check the isolate server per /contains query. The
28# number here is a trade-off; the more per request, the lower the effect of HTTP
29# round trip latency and TCP-level chattiness. On the other hand, larger values
30# cause longer lookups, increasing the initial latency to start uploading, which
31# is especially an issue for large files. This value is optimized for the "few
32# thousands files to look up with minimal number of large files missing" case.
33ITEMS_PER_CONTAINS_QUERY = 100
csharp@chromium.org07fa7592013-01-11 18:19:30 +000034
csharp@chromium.org59c7bcf2012-11-21 21:13:18 +000035# A list of already compressed extension types that should not receive any
36# compression before being uploaded.
37ALREADY_COMPRESSED_TYPES = [
38 '7z', 'avi', 'cur', 'gif', 'h264', 'jar', 'jpeg', 'jpg', 'pdf', 'png',
39 'wav', 'zip'
40]
41
maruel@chromium.orgc6f90062012-11-07 18:32:22 +000042
maruel@chromium.orgcb3c3d52013-03-14 18:55:30 +000043def randomness():
44 """Generates low-entropy randomness for MIME encoding.
45
46 Exists so it can be mocked out in unit tests.
47 """
48 return str(time.time())
49
50
maruel@chromium.orgc6f90062012-11-07 18:32:22 +000051def encode_multipart_formdata(fields, files,
52 mime_mapper=lambda _: 'application/octet-stream'):
53 """Encodes a Multipart form data object.
54
55 Args:
56 fields: a sequence (name, value) elements for
57 regular form fields.
58 files: a sequence of (name, filename, value) elements for data to be
59 uploaded as files.
60 mime_mapper: function to return the mime type from the filename.
61 Returns:
62 content_type: for httplib.HTTP instance
63 body: for httplib.HTTP instance
64 """
maruel@chromium.orgcb3c3d52013-03-14 18:55:30 +000065 boundary = hashlib.md5(randomness()).hexdigest()
maruel@chromium.orgc6f90062012-11-07 18:32:22 +000066 body_list = []
67 for (key, value) in fields:
68 if isinstance(key, unicode):
69 value = key.encode('utf-8')
70 if isinstance(value, unicode):
71 value = value.encode('utf-8')
72 body_list.append('--' + boundary)
73 body_list.append('Content-Disposition: form-data; name="%s"' % key)
74 body_list.append('')
75 body_list.append(value)
76 body_list.append('--' + boundary)
77 body_list.append('')
78 for (key, filename, value) in files:
79 if isinstance(key, unicode):
80 value = key.encode('utf-8')
81 if isinstance(filename, unicode):
82 value = filename.encode('utf-8')
83 if isinstance(value, unicode):
84 value = value.encode('utf-8')
85 body_list.append('--' + boundary)
86 body_list.append('Content-Disposition: form-data; name="%s"; '
87 'filename="%s"' % (key, filename))
88 body_list.append('Content-Type: %s' % mime_mapper(filename))
89 body_list.append('')
90 body_list.append(value)
91 body_list.append('--' + boundary)
92 body_list.append('')
93 if body_list:
94 body_list[-2] += '--'
95 body = '\r\n'.join(body_list)
96 content_type = 'multipart/form-data; boundary=%s' % boundary
97 return content_type, body
98
99
maruel@chromium.org037758d2012-12-10 17:59:46 +0000100def sha1_file(filepath):
101 """Calculates the SHA-1 of a file without reading it all in memory at once."""
102 digest = hashlib.sha1()
103 with open(filepath, 'rb') as f:
104 while True:
105 # Read in 1mb chunks.
106 chunk = f.read(1024*1024)
107 if not chunk:
108 break
109 digest.update(chunk)
110 return digest.hexdigest()
111
112
vadimsh@chromium.org80f73002013-07-12 14:52:44 +0000113def url_read(url, **kwargs):
114 result = run_isolated.url_read(url, **kwargs)
115 if result is None:
maruel@chromium.orgef333122013-03-12 20:36:40 +0000116 # If we get no response from the server, assume it is down and raise an
117 # exception.
118 raise run_isolated.MappingError('Unable to connect to server %s' % url)
119 return result
maruel@chromium.orgc6f90062012-11-07 18:32:22 +0000120
121
maruel@chromium.orgdc359e62013-03-14 13:08:55 +0000122def upload_hash_content_to_blobstore(
123 generate_upload_url, data, hash_key, content):
vadimsh@chromium.org80f73002013-07-12 14:52:44 +0000124 """Uploads the given hash contents directly to the blobstore via a generated
maruel@chromium.orgc6f90062012-11-07 18:32:22 +0000125 url.
126
127 Arguments:
128 generate_upload_url: The url to get the new upload url from.
maruel@chromium.orgdc359e62013-03-14 13:08:55 +0000129 data: extra POST data.
130 hash_key: sha1 of the uncompressed version of content.
131 content: The contents to upload. Must fit in memory for now.
maruel@chromium.orgc6f90062012-11-07 18:32:22 +0000132 """
133 logging.debug('Generating url to directly upload file to blobstore')
maruel@chromium.org92a3d2e2012-12-20 16:22:29 +0000134 assert isinstance(hash_key, str), hash_key
135 assert isinstance(content, str), (hash_key, content)
maruel@chromium.orgd58bf5b2013-04-26 17:57:42 +0000136 # TODO(maruel): Support large files. This would require streaming support.
maruel@chromium.orgc6f90062012-11-07 18:32:22 +0000137 content_type, body = encode_multipart_formdata(
maruel@chromium.orgd58bf5b2013-04-26 17:57:42 +0000138 data, [('content', hash_key, content)])
maruel@chromium.org2b2139a2013-04-30 20:14:58 +0000139 for attempt in xrange(run_isolated.URL_OPEN_MAX_ATTEMPTS):
maruel@chromium.orgd58bf5b2013-04-26 17:57:42 +0000140 # Retry HTTP 50x here.
vadimsh@chromium.org80f73002013-07-12 14:52:44 +0000141 upload_url = run_isolated.url_read(generate_upload_url, data=data)
142 if not upload_url:
maruel@chromium.orgd58bf5b2013-04-26 17:57:42 +0000143 raise run_isolated.MappingError(
144 'Unable to connect to server %s' % generate_upload_url)
maruel@chromium.orgd58bf5b2013-04-26 17:57:42 +0000145
146 # Do not retry this request on HTTP 50x. Regenerate an upload url each time
147 # since uploading "consumes" the upload url.
vadimsh@chromium.org80f73002013-07-12 14:52:44 +0000148 result = run_isolated.url_read(
maruel@chromium.orgd58bf5b2013-04-26 17:57:42 +0000149 upload_url, data=body, content_type=content_type, retry_50x=False)
vadimsh@chromium.org80f73002013-07-12 14:52:44 +0000150 if result is not None:
151 return result
maruel@chromium.org2b2139a2013-04-30 20:14:58 +0000152 if attempt != run_isolated.URL_OPEN_MAX_ATTEMPTS - 1:
153 run_isolated.HttpService.sleep_before_retry(attempt, None)
maruel@chromium.orgd58bf5b2013-04-26 17:57:42 +0000154 raise run_isolated.MappingError(
155 'Unable to connect to server %s' % generate_upload_url)
maruel@chromium.orgc6f90062012-11-07 18:32:22 +0000156
157
158class UploadRemote(run_isolated.Remote):
maruel@chromium.org034e3962013-03-13 13:34:25 +0000159 def __init__(self, namespace, base_url, token):
maruel@chromium.org21243ce2012-12-20 17:43:00 +0000160 self.namespace = str(namespace)
maruel@chromium.org034e3962013-03-13 13:34:25 +0000161 self._token = token
162 super(UploadRemote, self).__init__(base_url)
csharp@chromium.org59c7bcf2012-11-21 21:13:18 +0000163
164 def get_file_handler(self, base_url):
maruel@chromium.org21243ce2012-12-20 17:43:00 +0000165 base_url = str(base_url)
maruel@chromium.orgc6f90062012-11-07 18:32:22 +0000166 def upload_file(content, hash_key):
maruel@chromium.org034e3962013-03-13 13:34:25 +0000167 # TODO(maruel): Detect failures.
maruel@chromium.org21243ce2012-12-20 17:43:00 +0000168 hash_key = str(hash_key)
maruel@chromium.orgc6f90062012-11-07 18:32:22 +0000169 content_url = base_url.rstrip('/') + '/content/'
maruel@chromium.orgc6f90062012-11-07 18:32:22 +0000170 if len(content) > MIN_SIZE_FOR_DIRECT_BLOBSTORE:
maruel@chromium.orgdc359e62013-03-14 13:08:55 +0000171 url = '%sgenerate_blobstore_url/%s/%s' % (
172 content_url, self.namespace, hash_key)
maruel@chromium.orge82112e2013-04-24 14:41:55 +0000173 # self._token is stored already quoted but it is unnecessary here, and
174 # only here.
175 data = [('token', urllib.unquote(self._token))]
maruel@chromium.orgdc359e62013-03-14 13:08:55 +0000176 upload_hash_content_to_blobstore(url, data, hash_key, content)
maruel@chromium.orgc6f90062012-11-07 18:32:22 +0000177 else:
maruel@chromium.org034e3962013-03-13 13:34:25 +0000178 url = '%sstore/%s/%s?token=%s' % (
179 content_url, self.namespace, hash_key, self._token)
vadimsh@chromium.org80f73002013-07-12 14:52:44 +0000180 url_read(url, data=content, content_type='application/octet-stream')
maruel@chromium.orgc6f90062012-11-07 18:32:22 +0000181 return upload_file
182
183
vadimsh@chromium.org53f8d5a2013-06-19 13:03:55 +0000184def check_files_exist_on_server(query_url, queries):
maruel@chromium.orgc6f90062012-11-07 18:32:22 +0000185 """Queries the server to see which files from this batch already exist there.
186
187 Arguments:
188 queries: The hash files to potential upload to the server.
vadimsh@chromium.org53f8d5a2013-06-19 13:03:55 +0000189 Returns:
190 missing_files: list of files that are missing on the server.
maruel@chromium.orgc6f90062012-11-07 18:32:22 +0000191 """
vadimsh@chromium.org53f8d5a2013-06-19 13:03:55 +0000192 logging.info('Checking existence of %d files...', len(queries))
maruel@chromium.orgc6f90062012-11-07 18:32:22 +0000193 body = ''.join(
maruel@chromium.orge5c17132012-11-21 18:18:46 +0000194 (binascii.unhexlify(meta_data['h']) for (_, meta_data) in queries))
maruel@chromium.orgc6f90062012-11-07 18:32:22 +0000195 assert (len(body) % 20) == 0, repr(body)
196
vadimsh@chromium.org80f73002013-07-12 14:52:44 +0000197 response = url_read(
198 query_url, data=body, content_type='application/octet-stream')
maruel@chromium.orgc6f90062012-11-07 18:32:22 +0000199 if len(queries) != len(response):
200 raise run_isolated.MappingError(
201 'Got an incorrect number of responses from the server. Expected %d, '
202 'but got %d' % (len(queries), len(response)))
203
vadimsh@chromium.org53f8d5a2013-06-19 13:03:55 +0000204 missing_files = [
205 queries[i] for i, flag in enumerate(response) if flag == chr(0)
206 ]
207 logging.info('Queried %d files, %d cache hit',
208 len(queries), len(queries) - len(missing_files))
209 return missing_files
maruel@chromium.orgc6f90062012-11-07 18:32:22 +0000210
211
csharp@chromium.org59c7bcf2012-11-21 21:13:18 +0000212def compression_level(filename):
213 """Given a filename calculates the ideal compression level to use."""
214 file_ext = os.path.splitext(filename)[1].lower()
215 # TODO(csharp): Profile to find what compression level works best.
216 return 0 if file_ext in ALREADY_COMPRESSED_TYPES else 7
217
218
maruel@chromium.orgcb3c3d52013-03-14 18:55:30 +0000219def read_and_compress(filepath, level):
220 """Reads a file and returns its content gzip compressed."""
221 compressor = zlib.compressobj(level)
222 compressed_data = cStringIO.StringIO()
223 with open(filepath, 'rb') as f:
224 while True:
csharp@chromium.org59c7bcf2012-11-21 21:13:18 +0000225 chunk = f.read(run_isolated.ZIPPED_FILE_CHUNK)
226 if not chunk:
227 break
maruel@chromium.orgcb3c3d52013-03-14 18:55:30 +0000228 compressed_data.write(compressor.compress(chunk))
229 compressed_data.write(compressor.flush(zlib.Z_FINISH))
230 value = compressed_data.getvalue()
231 compressed_data.close()
232 return value
csharp@chromium.org59c7bcf2012-11-21 21:13:18 +0000233
maruel@chromium.orgcb3c3d52013-03-14 18:55:30 +0000234
235def zip_and_trigger_upload(infile, metadata, upload_function):
236 # TODO(csharp): Fix crbug.com/150823 and enable the touched logic again.
237 # if not metadata['T']:
238 compressed_data = read_and_compress(infile, compression_level(infile))
239 priority = (
240 run_isolated.Remote.HIGH if metadata.get('priority', '1') == '0'
241 else run_isolated.Remote.MED)
242 return upload_function(priority, compressed_data, metadata['h'], None)
csharp@chromium.org59c7bcf2012-11-21 21:13:18 +0000243
244
vadimsh@chromium.org53f8d5a2013-06-19 13:03:55 +0000245def batch_files_for_check(infiles):
246 """Splits list of files to check for existence on the server into batches.
maruel@chromium.org35fc0c82013-01-17 15:14:14 +0000247
vadimsh@chromium.org53f8d5a2013-06-19 13:03:55 +0000248 Each batch corresponds to a single 'exists?' query to the server.
249
250 Yields:
251 batches: list of batches, each batch is a list of files.
maruel@chromium.org35fc0c82013-01-17 15:14:14 +0000252 """
vadimsh@chromium.org53f8d5a2013-06-19 13:03:55 +0000253 # TODO(maruel): Make this adaptative, e.g. only query a few, like 10 in one
254 # request, for the largest files, since they are the ones most likely to be
255 # missing, then batch larger requests (up to 500) for the tail since they are
256 # likely to be present.
maruel@chromium.org35fc0c82013-01-17 15:14:14 +0000257 next_queries = []
csharp@chromium.org90c45812013-01-23 14:27:21 +0000258 items = ((k, v) for k, v in infiles.iteritems() if 's' in v)
259 for relfile, metadata in sorted(items, key=lambda x: -x[1]['s']):
maruel@chromium.org35fc0c82013-01-17 15:14:14 +0000260 next_queries.append((relfile, metadata))
261 if len(next_queries) == ITEMS_PER_CONTAINS_QUERY:
vadimsh@chromium.org53f8d5a2013-06-19 13:03:55 +0000262 yield next_queries
maruel@chromium.org35fc0c82013-01-17 15:14:14 +0000263 next_queries = []
264 if next_queries:
vadimsh@chromium.org53f8d5a2013-06-19 13:03:55 +0000265 yield next_queries
266
267
268def get_files_to_upload(contains_hash_url, infiles):
269 """Yields files that are missing on the server."""
270 with run_isolated.ThreadPool(1, 16, 0, prefix='get_files_to_upload') as pool:
271 for files in batch_files_for_check(infiles):
272 pool.add_task(0, check_files_exist_on_server, contains_hash_url, files)
273 for missing_file in itertools.chain.from_iterable(pool.iter_results()):
274 yield missing_file
maruel@chromium.org35fc0c82013-01-17 15:14:14 +0000275
276
csharp@chromium.org59c7bcf2012-11-21 21:13:18 +0000277def upload_sha1_tree(base_url, indir, infiles, namespace):
maruel@chromium.orgc6f90062012-11-07 18:32:22 +0000278 """Uploads the given tree to the given url.
279
280 Arguments:
csharp@chromium.org59c7bcf2012-11-21 21:13:18 +0000281 base_url: The base url, it is assume that |base_url|/has/ can be used to
282 query if an element was already uploaded, and |base_url|/store/
283 can be used to upload a new element.
284 indir: Root directory the infiles are based in.
maruel@chromium.orgcb3c3d52013-03-14 18:55:30 +0000285 infiles: dict of files to upload files from |indir| to |base_url|.
csharp@chromium.org59c7bcf2012-11-21 21:13:18 +0000286 namespace: The namespace to use on the server.
maruel@chromium.orgc6f90062012-11-07 18:32:22 +0000287 """
288 logging.info('upload tree(base_url=%s, indir=%s, files=%d)' %
289 (base_url, indir, len(infiles)))
maruel@chromium.orgcb3c3d52013-03-14 18:55:30 +0000290 assert base_url.startswith('http'), base_url
291 base_url = base_url.rstrip('/')
maruel@chromium.orgc6f90062012-11-07 18:32:22 +0000292
maruel@chromium.org034e3962013-03-13 13:34:25 +0000293 # TODO(maruel): Make this request much earlier asynchronously while the files
294 # are being enumerated.
vadimsh@chromium.org80f73002013-07-12 14:52:44 +0000295 token = urllib.quote(url_read(base_url + '/content/get_token'))
maruel@chromium.org034e3962013-03-13 13:34:25 +0000296
csharp@chromium.org07fa7592013-01-11 18:19:30 +0000297 # Create a pool of workers to zip and upload any files missing from
298 # the server.
maruel@chromium.org6b0c9ec2013-01-18 00:34:31 +0000299 num_threads = run_test_cases.num_processors()
vadimsh@chromium.org53f8d5a2013-06-19 13:03:55 +0000300 zipping_pool = run_isolated.ThreadPool(min(2, num_threads),
301 num_threads, 0, 'zip')
maruel@chromium.org034e3962013-03-13 13:34:25 +0000302 remote_uploader = UploadRemote(namespace, base_url, token)
csharp@chromium.org07fa7592013-01-11 18:19:30 +0000303
vadimsh@chromium.org53f8d5a2013-06-19 13:03:55 +0000304 # Starts the zip and upload process for files that are missing
305 # from the server.
306 contains_hash_url = '%s/content/contains/%s?token=%s' % (
307 base_url, namespace, token)
csharp@chromium.org20a888c2013-01-15 15:06:55 +0000308 uploaded = []
vadimsh@chromium.org53f8d5a2013-06-19 13:03:55 +0000309 for relfile, metadata in get_files_to_upload(contains_hash_url, infiles):
csharp@chromium.org07fa7592013-01-11 18:19:30 +0000310 infile = os.path.join(indir, relfile)
maruel@chromium.org831958f2013-01-22 15:01:46 +0000311 zipping_pool.add_task(0, zip_and_trigger_upload, infile, metadata,
csharp@chromium.org07fa7592013-01-11 18:19:30 +0000312 remote_uploader.add_item)
vadimsh@chromium.org53f8d5a2013-06-19 13:03:55 +0000313 uploaded.append((relfile, metadata))
maruel@chromium.orgc6f90062012-11-07 18:32:22 +0000314
csharp@chromium.org59c7bcf2012-11-21 21:13:18 +0000315 logging.info('Waiting for all files to finish zipping')
316 zipping_pool.join()
vadimsh@chromium.org53f8d5a2013-06-19 13:03:55 +0000317 zipping_pool.close()
csharp@chromium.org59c7bcf2012-11-21 21:13:18 +0000318 logging.info('All files zipped.')
319
320 logging.info('Waiting for all files to finish uploading')
maruel@chromium.org13eca0b2013-01-22 16:42:21 +0000321 # Will raise if any exception occurred.
maruel@chromium.orgc6f90062012-11-07 18:32:22 +0000322 remote_uploader.join()
vadimsh@chromium.org53f8d5a2013-06-19 13:03:55 +0000323 remote_uploader.close()
csharp@chromium.org59c7bcf2012-11-21 21:13:18 +0000324 logging.info('All files are uploaded')
maruel@chromium.orgc6f90062012-11-07 18:32:22 +0000325
maruel@chromium.orgc6f90062012-11-07 18:32:22 +0000326 total = len(infiles)
maruel@chromium.orge5c17132012-11-21 18:18:46 +0000327 total_size = sum(metadata.get('s', 0) for metadata in infiles.itervalues())
maruel@chromium.orgc6f90062012-11-07 18:32:22 +0000328 logging.info(
329 'Total: %6d, %9.1fkb',
330 total,
maruel@chromium.orge5c17132012-11-21 18:18:46 +0000331 sum(m.get('s', 0) for m in infiles.itervalues()) / 1024.)
csharp@chromium.org20a888c2013-01-15 15:06:55 +0000332 cache_hit = set(infiles.iterkeys()) - set(x[0] for x in uploaded)
maruel@chromium.orge5c17132012-11-21 18:18:46 +0000333 cache_hit_size = sum(infiles[i].get('s', 0) for i in cache_hit)
maruel@chromium.orgc6f90062012-11-07 18:32:22 +0000334 logging.info(
335 'cache hit: %6d, %9.1fkb, %6.2f%% files, %6.2f%% size',
336 len(cache_hit),
337 cache_hit_size / 1024.,
338 len(cache_hit) * 100. / total,
csharp@chromium.org59c7bcf2012-11-21 21:13:18 +0000339 cache_hit_size * 100. / total_size if total_size else 0)
csharp@chromium.org20a888c2013-01-15 15:06:55 +0000340 cache_miss = uploaded
maruel@chromium.orge5c17132012-11-21 18:18:46 +0000341 cache_miss_size = sum(infiles[i[0]].get('s', 0) for i in cache_miss)
maruel@chromium.orgc6f90062012-11-07 18:32:22 +0000342 logging.info(
343 'cache miss: %6d, %9.1fkb, %6.2f%% files, %6.2f%% size',
344 len(cache_miss),
345 cache_miss_size / 1024.,
346 len(cache_miss) * 100. / total,
csharp@chromium.org59c7bcf2012-11-21 21:13:18 +0000347 cache_miss_size * 100. / total_size if total_size else 0)
maruel@chromium.orgcb3c3d52013-03-14 18:55:30 +0000348 return 0
maruel@chromium.orgc6f90062012-11-07 18:32:22 +0000349
350
maruel@chromium.orgcb3c3d52013-03-14 18:55:30 +0000351def main(args):
maruel@chromium.org46e61cc2013-03-25 19:55:34 +0000352 run_isolated.disable_buffering()
maruel@chromium.orgc6f90062012-11-07 18:32:22 +0000353 parser = optparse.OptionParser(
354 usage='%prog [options] <file1..fileN> or - to read from stdin',
355 description=sys.modules[__name__].__doc__)
maruel@chromium.orgcb3c3d52013-03-14 18:55:30 +0000356 parser.add_option('-r', '--remote', help='Remote server to archive to')
maruel@chromium.orgc6f90062012-11-07 18:32:22 +0000357 parser.add_option(
358 '-v', '--verbose',
csharp@chromium.org59c7bcf2012-11-21 21:13:18 +0000359 action='count', default=0,
maruel@chromium.orgc6f90062012-11-07 18:32:22 +0000360 help='Use multiple times to increase verbosity')
csharp@chromium.org59c7bcf2012-11-21 21:13:18 +0000361 parser.add_option('--namespace', default='default-gzip',
362 help='The namespace to use on the server.')
maruel@chromium.orgc6f90062012-11-07 18:32:22 +0000363
maruel@chromium.orgcb3c3d52013-03-14 18:55:30 +0000364 options, files = parser.parse_args(args)
maruel@chromium.orgc6f90062012-11-07 18:32:22 +0000365
366 levels = [logging.ERROR, logging.INFO, logging.DEBUG]
367 logging.basicConfig(
368 level=levels[min(len(levels)-1, options.verbose)],
vadimsh@chromium.org53f8d5a2013-06-19 13:03:55 +0000369 format='[%(threadName)s] %(asctime)s,%(msecs)03d %(levelname)5s'
370 ' %(module)15s(%(lineno)3d): %(message)s',
371 datefmt='%H:%M:%S')
maruel@chromium.orgc6f90062012-11-07 18:32:22 +0000372 if files == ['-']:
373 files = sys.stdin.readlines()
374
375 if not files:
376 parser.error('Nothing to upload')
maruel@chromium.orgcb3c3d52013-03-14 18:55:30 +0000377 if not options.remote:
378 parser.error('Nowhere to send. Please specify --remote')
maruel@chromium.orgc6f90062012-11-07 18:32:22 +0000379
380 # Load the necessary metadata. This is going to be rewritten eventually to be
381 # more efficient.
382 infiles = dict(
383 (
384 f,
385 {
maruel@chromium.orge5c17132012-11-21 18:18:46 +0000386 's': os.stat(f).st_size,
maruel@chromium.org037758d2012-12-10 17:59:46 +0000387 'h': sha1_file(f),
maruel@chromium.orgc6f90062012-11-07 18:32:22 +0000388 }
389 )
390 for f in files)
391
392 with run_isolated.Profiler('Archive'):
maruel@chromium.orgcb3c3d52013-03-14 18:55:30 +0000393 return upload_sha1_tree(
394 base_url=options.remote,
maruel@chromium.orgc6f90062012-11-07 18:32:22 +0000395 indir=os.getcwd(),
csharp@chromium.org59c7bcf2012-11-21 21:13:18 +0000396 infiles=infiles,
397 namespace=options.namespace)
maruel@chromium.orgc6f90062012-11-07 18:32:22 +0000398
399
400if __name__ == '__main__':
maruel@chromium.orgcb3c3d52013-03-14 18:55:30 +0000401 sys.exit(main(sys.argv[1:]))