Browse code

* s3cmd, S3/Config.py: Implemented recursive [get]. Added --skip-existing option for [get] and [sync].

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
Showing 3 changed files
... ...
@@ -1,3 +1,8 @@
1
+2008-12-22  Michal Ludvig  <michal@logix.cz>
2
+
3
+	* s3cmd, S3/Config.py: Implemented recursive [get].
4
+	  Added --skip-existing option for [get] and [sync]. 
5
+
1 6
 2008-12-17  Michal Ludvig  <michal@logix.cz>
2 7
 
3 8
 	* TODO: Updated
... ...
@@ -25,6 +25,7 @@ class Config(object):
25 25
 	human_readable_sizes = False
26 26
 	force = False
27 27
 	get_continue = False
28
+	skip_existing = False
28 29
 	recursive = False
29 30
 	acl_public = False
30 31
 	proxy_host = ""
... ...
@@ -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.")