Browse code

2009-05-27 Michal Ludvig <michal@logix.cz>

* S3/SortedDict.py: Add case-sensitive mode.
* s3cmd, S3/S3.py, S3/Config.py: Use SortedDict() in
case-sensitive mode to avoid dropping filenames
differing only in capitalisation
* run-tests.py: Testsuite for the above.
* NEWS: Updated.



git-svn-id: https://s3tools.svn.sourceforge.net/svnroot/s3tools/s3cmd/trunk@386 830e0280-6d2a-0410-9c65-932aecc39d9d

Michal Ludvig authored on 2009/05/27 14:51:28
Showing 7 changed files
... ...
@@ -1,3 +1,12 @@
1
+2009-05-27  Michal Ludvig  <michal@logix.cz>
2
+
3
+	* S3/SortedDict.py: Add case-sensitive mode.
4
+	* s3cmd, S3/S3.py, S3/Config.py: Use SortedDict() in 
5
+	  case-sensitive mode to avoid dropping filenames
6
+	  differing only in capitalisation
7
+	* run-tests.py: Testsuite for the above.
8
+	* NEWS: Updated.
9
+
1 10
 2009-03-20  Michal Ludvig  <michal@logix.cz>
2 11
 
3 12
 	* S3/S3.py: Re-sign requests before retrial to avoid 
... ...
@@ -2,6 +2,8 @@ s3cmd 1.0.0
2 2
 ===========
3 3
 * New command 'sign' for signing for instance
4 4
   the POST upload policies.
5
+* Fixed handling of filenames that differ only in 
6
+  capitalisation (eg blah.txt vs Blah.TXT).
5 7
 
6 8
 s3cmd 0.9.9   -   2009-02-17
7 9
 ===========
... ...
@@ -27,7 +27,7 @@ class Config(object):
27 27
 	recv_chunk = 4096
28 28
 	list_md5 = False
29 29
 	human_readable_sizes = False
30
-	extra_headers = SortedDict()
30
+	extra_headers = SortedDict(ignore_case = True)
31 31
 	force = False
32 32
 	get_continue = False
33 33
 	skip_existing = False
... ...
@@ -27,7 +27,7 @@ from ACL import ACL
27 27
 class S3Request(object):
28 28
 	def __init__(self, s3, method_string, resource, headers, params = {}):
29 29
 		self.s3 = s3
30
-		self.headers = SortedDict(headers or {})
30
+		self.headers = SortedDict(headers or {}, ignore_case = True)
31 31
 		self.resource = resource
32 32
 		self.method_string = method_string
33 33
 		self.params = params
... ...
@@ -195,7 +195,7 @@ class S3(object):
195 195
 		return response
196 196
 
197 197
 	def bucket_create(self, bucket, bucket_location = None):
198
-		headers = SortedDict()
198
+		headers = SortedDict(ignore_case = True)
199 199
 		body = ""
200 200
 		if bucket_location and bucket_location.strip().upper() != "US":
201 201
 			body  = "<CreateBucketConfiguration><LocationConstraint>"
... ...
@@ -235,7 +235,7 @@ class S3(object):
235 235
 			size = os.stat(filename)[ST_SIZE]
236 236
 		except IOError, e:
237 237
 			raise InvalidFileError(u"%s: %s" % (unicodise(filename), e.strerror))
238
-		headers = SortedDict()
238
+		headers = SortedDict(ignore_case = True)
239 239
 		if extra_headers:
240 240
 			headers.update(extra_headers)
241 241
 		headers["content-length"] = size
... ...
@@ -273,7 +273,7 @@ class S3(object):
273 273
 			raise ValueError("Expected URI type 's3', got '%s'" % src_uri.type)
274 274
 		if dst_uri.type != "s3":
275 275
 			raise ValueError("Expected URI type 's3', got '%s'" % dst_uri.type)
276
-		headers = SortedDict()
276
+		headers = SortedDict(ignore_case = True)
277 277
 		headers['x-amz-copy-source'] = "/%s/%s" % (src_uri.bucket(), self.urlencode_string(src_uri.object()))
278 278
 		if self.config.acl_public:
279 279
 			headers["x-amz-acl"] = "public-read"
... ...
@@ -17,11 +17,19 @@ class SortedDictIterator(object):
17 17
 			raise StopIteration
18 18
 
19 19
 class SortedDict(dict):
20
-	keys_sort_lowercase = True
20
+	def __init__(self, mapping = {}, ignore_case = True, **kwargs):
21
+		"""
22
+		WARNING: SortedDict() with ignore_case==True will
23
+		         drop entries differing only in capitalisation!
24
+				 Eg: SortedDict({'auckland':1, 'Auckland':2}).keys() => ['Auckland']
25
+				 With ignore_case==False it's all right
26
+		"""
27
+		dict.__init__(self, mapping, **kwargs)
28
+		self.ignore_case = ignore_case
21 29
 
22 30
 	def keys(self):
23 31
 		keys = dict.keys(self)
24
-		if self.keys_sort_lowercase:
32
+		if self.ignore_case:
25 33
 			# Translation map
26 34
 			xlat_map = BidirMap()
27 35
 			for key in keys:
... ...
@@ -38,18 +46,16 @@ class SortedDict(dict):
38 38
 		return SortedDictIterator(self, self.keys())
39 39
 
40 40
 if __name__ == "__main__":
41
-	d = SortedDict()
42
-	d['AWS'] = 1
43
-	d['Action'] = 2
44
-	d['america'] = 3
45
-	d.keys_sort_lowercase = True
46
-	print "Wanted: Action, america, AWS,"
41
+	d = { 'AWS' : 1, 'Action' : 2, 'america' : 3, 'Auckland' : 4, 'America' : 5 }
42
+	sd = SortedDict(d)
43
+	print "Wanted: Action, america, Auckland, AWS,    [ignore case]"
47 44
 	print "Got:   ",
48
-	for key in d:
45
+	for key in sd:
49 46
 		print "%s," % key,
50
-	print "   __iter__()"
51
-	d.keys_return_lowercase = True
47
+	print "   [used: __iter__()]"
48
+	d = SortedDict(d, ignore_case = False)
49
+	print "Wanted: AWS, Action, Auckland, america,    [case sensitive]"
52 50
 	print "Got:   ",
53 51
 	for key in d.keys():
54 52
 		print "%s," % key,
55
-	print "   keys()"
53
+	print "   [used: keys()]"
... ...
@@ -338,6 +338,9 @@ test_s3cmd("Get multiple files", ['get', 's3://s3cmd-autotest-1/xyz/etc2/Logo.PN
338 338
 test_s3cmd("Copy between buckets", ['cp', 's3://s3cmd-autotest-1/xyz/etc2/Logo.PNG', 's3://s3cmd-Autotest-3'],
339 339
 	must_find = [ "File s3://s3cmd-autotest-1/xyz/etc2/Logo.PNG copied to s3://s3cmd-Autotest-3/xyz/etc2/Logo.PNG" ])
340 340
 
341
+## ====== Upload files differing in capitalisation
342
+test_s3cmd("blah.txt / Blah.txt", ['put', '-r', 'testsuite/blahBlah', 's3://s3cmd-autotest-1/'],
343
+	must_find = [ 's3://s3cmd-autotest-1/blahBlah/Blah.txt', 's3://s3cmd-autotest-1/blahBlah/blah.txt' ])
341 344
 
342 345
 ## ====== Simple delete
343 346
 test_s3cmd("Simple delete", ['del', 's3://s3cmd-autotest-1/xyz/etc2/Logo.PNG'],
... ...
@@ -187,7 +187,7 @@ def cmd_bucket_delete(args):
187 187
 
188 188
 def fetch_local_list(args, recursive = None):
189 189
 	local_uris = []
190
-	local_list = SortedDict()
190
+	local_list = SortedDict(ignore_case = False)
191 191
 	single_file = False
192 192
 
193 193
 	if type(args) not in (list, tuple):
... ...
@@ -220,7 +220,7 @@ def fetch_local_list(args, recursive = None):
220 220
 
221 221
 def fetch_remote_list(args, require_attribs = False, recursive = None):
222 222
 	remote_uris = []
223
-	remote_list = SortedDict()
223
+	remote_list = SortedDict(ignore_case = False)
224 224
 
225 225
 	if type(args) not in (list, tuple):
226 226
 		args = [args]
... ...
@@ -586,7 +586,7 @@ def _get_filelist_local(local_uri):
586 586
 		local_path = deunicodise(local_uri.dirname())
587 587
 		filelist = [( local_path, [], [deunicodise(local_uri.basename())] )]
588 588
 		single_file = True
589
-	loc_list = SortedDict()
589
+	loc_list = SortedDict(ignore_case = False)
590 590
 	for root, dirs, files in filelist:
591 591
 		rel_root = root.replace(local_path, local_base, 1)
592 592
 		for f in files:
... ...
@@ -637,7 +637,7 @@ def _get_filelist_remote(remote_uri, recursive = True):
637 637
 		rem_base = rem_base[:rem_base.rfind('/')+1]
638 638
 		remote_uri = S3Uri("s3://%s/%s" % (remote_uri.bucket(), rem_base))
639 639
 	rem_base_len = len(rem_base)
640
-	rem_list = SortedDict()
640
+	rem_list = SortedDict(ignore_case = False)
641 641
 	break_now = False
642 642
 	for object in response['list']:
643 643
 		if object['Key'] == rem_base_original and object['Key'][-1] != os.path.sep:
... ...
@@ -664,7 +664,7 @@ def _get_filelist_remote(remote_uri, recursive = True):
664 664
 def _filelist_filter_exclude_include(src_list):
665 665
 	info(u"Applying --exclude/--include")
666 666
 	cfg = Config()
667
-	exclude_list = SortedDict()
667
+	exclude_list = SortedDict(ignore_case = False)
668 668
 	for file in src_list.keys():
669 669
 		debug(u"CHECK: %s" % file)
670 670
 		excluded = False
... ...
@@ -693,7 +693,7 @@ def _filelist_filter_exclude_include(src_list):
693 693
 def _compare_filelists(src_list, dst_list, src_is_local_and_dst_is_remote):
694 694
 	info(u"Verifying attributes...")
695 695
 	cfg = Config()
696
-	exists_list = SortedDict()
696
+	exists_list = SortedDict(ignore_case = False)
697 697
 
698 698
 	for file in src_list.keys():
699 699
 		debug(u"CHECK: %s" % file)