git-svn-id: https://s3tools.svn.sourceforge.net/svnroot/s3tools/s3cmd/trunk@294 830e0280-6d2a-0410-9c65-932aecc39d9d
Michal Ludvig authored on 2008/12/22 10:09:44... | ... |
@@ -263,14 +263,14 @@ def cmd_object_get(args): |
263 | 263 |
|
264 | 264 |
## Each item will contain the following attributes |
265 | 265 |
# 'remote_uri' 'local_filename' 'remote_label' 'local_label' |
266 |
- download_list = [ ] |
|
266 |
+ download_list = [] |
|
267 | 267 |
|
268 | 268 |
remote_uris = [] |
269 | 269 |
|
270 | 270 |
if len(args) == 0: |
271 | 271 |
raise ParameterError("Nothing to download. Expecting S3 URI.") |
272 | 272 |
|
273 |
- if S3Uri(args[-1]).type != 's3': |
|
273 |
+ if S3Uri(args[-1]).type == 'file': |
|
274 | 274 |
destination_base = args.pop() |
275 | 275 |
else: |
276 | 276 |
destination_base = "." |
... | ... |
@@ -284,10 +284,26 @@ def cmd_object_get(args): |
284 | 284 |
raise ParameterError("Expecting S3 URI instead of '%s'" % arg) |
285 | 285 |
remote_uris.append(uri) |
286 | 286 |
|
287 |
+ remote_keys = [] |
|
287 | 288 |
if cfg.recursive: |
288 |
- raise NotImplementedError("Recursive get is not yet implemented") |
|
289 |
+ if not os.path.isdir(destination_base): |
|
290 |
+ raise ParameterError("Destination must be a directory for recursive get.") |
|
291 |
+ if destination_base[-1] != os.path.sep: |
|
292 |
+ destination_base += os.path.sep |
|
293 |
+ for uri in remote_uris: |
|
294 |
+ objectlist = _get_filelist_remote(uri) |
|
295 |
+ for key in objectlist.iterkeys(): |
|
296 |
+ object = S3Uri(objectlist[key]['object_uri_str']) |
|
297 |
+ ## Remove leading '/' from remote filenames |
|
298 |
+ if key.find("/") == 0: |
|
299 |
+ key = key[1:] |
|
300 |
+ destination = destination_base + key |
|
301 |
+ download_item = { |
|
302 |
+ 'remote_uri' : object, |
|
303 |
+ 'local_filename' : destination |
|
304 |
+ } |
|
305 |
+ remote_keys.append(download_item) |
|
289 | 306 |
else: |
290 |
- remote_keys = [] |
|
291 | 307 |
if not os.path.isdir(destination_base) or destination_base == '-': |
292 | 308 |
if len(remote_uris) > 1: |
293 | 309 |
raise ParameterError("Destination must be a directory when downloading multiple sources.") |
... | ... |
@@ -319,7 +335,16 @@ def cmd_object_get(args): |
319 | 319 |
## File |
320 | 320 |
try: |
321 | 321 |
file_exists = os.path.exists(destination) |
322 |
- dst_stream = open(destination, "ab") |
|
322 |
+ try: |
|
323 |
+ dst_stream = open(destination, "ab") |
|
324 |
+ except IOError, e: |
|
325 |
+ if e.errno == errno.ENOENT: |
|
326 |
+ basename = destination[:destination.rindex(os.path.sep)] |
|
327 |
+ info("Creating directory: %s" % basename) |
|
328 |
+ os.makedirs(basename) |
|
329 |
+ dst_stream = open(destination, "ab") |
|
330 |
+ else: |
|
331 |
+ raise |
|
323 | 332 |
if file_exists: |
324 | 333 |
if Config().get_continue: |
325 | 334 |
start_position = dst_stream.tell() |
... | ... |
@@ -327,9 +352,12 @@ def cmd_object_get(args): |
327 | 327 |
start_position = 0L |
328 | 328 |
dst_stream.seek(0L) |
329 | 329 |
dst_stream.truncate() |
330 |
+ elif Config().skip_existing: |
|
331 |
+ info("Skipping over existing file: %s" % (destination)) |
|
332 |
+ continue |
|
330 | 333 |
else: |
331 | 334 |
dst_stream.close() |
332 |
- raise ParameterError("File %s already exists. Use either --force or --continue or give it a new name." % destination) |
|
335 |
+ raise ParameterError("File %s already exists. Use either of --force / --continue / --skip-existing or give it a new name." % destination) |
|
333 | 336 |
except IOError, e: |
334 | 337 |
error("Skipping %s: %s" % (destination, e.strerror)) |
335 | 338 |
continue |
... | ... |
@@ -470,14 +498,27 @@ def _get_filelist_remote(remote_uri): |
470 | 470 |
rem_base = remote_uri.object() |
471 | 471 |
rem_base_len = len(rem_base) |
472 | 472 |
rem_list = {} |
473 |
+ break_now = False |
|
473 | 474 |
for object in response['list']: |
474 |
- key = object['Key'][rem_base_len:] |
|
475 |
+ if object['Key'] == rem_base and object['Key'][-1] != os.path.sep: |
|
476 |
+ ## We asked for one file and we got that file :-) |
|
477 |
+ key = os.path.basename(object['Key']) |
|
478 |
+ object_uri_str = remote_uri.uri() |
|
479 |
+ break_now = True |
|
480 |
+ rem_list = {} ## Remove whatever has already been put to rem_list |
|
481 |
+ else: |
|
482 |
+ key = object['Key'][rem_base_len:] ## Beware - this may be '' if object['Key']==rem_base !! |
|
483 |
+ object_uri_str = remote_uri.uri() + key |
|
475 | 484 |
rem_list[key] = { |
476 | 485 |
'size' : int(object['Size']), |
477 | 486 |
'timestamp' : dateS3toUnix(object['LastModified']), ## Sadly it's upload time, not our lastmod time :-( |
478 | 487 |
'md5' : object['ETag'][1:-1], |
479 | 488 |
'object_key' : object['Key'], |
489 |
+ 'object_uri_str' : object_uri_str, |
|
490 |
+ 'base_uri' : remote_uri, |
|
480 | 491 |
} |
492 |
+ if break_now: |
|
493 |
+ break |
|
481 | 494 |
return rem_list |
482 | 495 |
|
483 | 496 |
def _compare_filelists(src_list, dst_list, src_is_local_and_dst_is_remote): |
... | ... |
@@ -510,6 +551,15 @@ def _compare_filelists(src_list, dst_list, src_is_local_and_dst_is_remote): |
510 | 510 |
else: |
511 | 511 |
debug("PASS: %s" % (os.sep + file)) |
512 | 512 |
if dst_list.has_key(file): |
513 |
+ ## Was --skip-existing requested? |
|
514 |
+ if cfg.skip_existing: |
|
515 |
+ debug("IGNR: %s (used --skip-existing)" % (file)) |
|
516 |
+ exists_list[file] = src_list[file] |
|
517 |
+ del(src_list[file]) |
|
518 |
+ ## Remove from destination-list, all that is left there will be deleted |
|
519 |
+ del(dst_list[file]) |
|
520 |
+ continue |
|
521 |
+ |
|
513 | 522 |
## Check size first |
514 | 523 |
if dst_list[file]['size'] == src_list[file]['size']: |
515 | 524 |
#debug("%s same size: %s" % (file, dst_list[file]['size'])) |
... | ... |
@@ -1034,6 +1084,7 @@ def main(): |
1034 | 1034 |
optparser.add_option( "--no-encrypt", dest="encrypt", action="store_false", help="Don't encrypt files.") |
1035 | 1035 |
optparser.add_option("-f", "--force", dest="force", action="store_true", help="Force overwrite and other dangerous operations.") |
1036 | 1036 |
optparser.add_option( "--continue", dest="get_continue", action="store_true", help="Continue getting a partially downloaded file (only for [get] command).") |
1037 |
+ optparser.add_option( "--skip-existing", dest="skip_existing", action="store_true", help="Skip over files that exist at the destination (only for [get] and [sync] commands).") |
|
1037 | 1038 |
optparser.add_option("-r", "--recursive", dest="recursive", action="store_true", help="Recursive upload, download or removal.") |
1038 | 1039 |
optparser.add_option("-P", "--acl-public", dest="acl_public", action="store_true", help="Store objects with ACL allowing read for anyone.") |
1039 | 1040 |
optparser.add_option( "--acl-private", dest="acl_public", action="store_false", help="Store objects with default ACL allowing access for you only.") |