Browse code

Initial import

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

Michal Ludvig authored on 2007/01/10 21:00:50
Showing 5 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,2 @@
0
+*.pyc
1
+tst.*
0 2
new file mode 100644
... ...
@@ -0,0 +1,31 @@
0
+class BidirMap:
1
+	def __init__(self, **map):
2
+		self.k2v = {}
3
+		self.v2k = {}
4
+		for key in map:
5
+			self.__setitem__(key, map[key])
6
+
7
+	def __setitem__(self, key, value):
8
+		if self.v2k.has_key(value):
9
+			if self.v2k[value] != key:
10
+				raise KeyError("Value '"+str(value)+"' already in use with key '"+str(self.v2k[value])+"'")
11
+		try:
12
+			del(self.v2k[self.k2v[key]])
13
+		except KeyError:
14
+			pass
15
+		self.k2v[key] = value
16
+		self.v2k[value] = key
17
+
18
+	def __getitem__(self, key):
19
+		return self.k2v[key]
20
+
21
+	def __str__(self):
22
+		return self.v2k.__str__()
23
+
24
+	def getkey(self, value):
25
+		return self.v2k[value]
26
+	
27
+	def getvalue(self, key):
28
+		return self.k2v[key]
29
+
30
+
0 31
new file mode 100644
... ...
@@ -0,0 +1,42 @@
0
+class SortedDictIterator:
1
+	def __init__(self, dict):
2
+		self.dict = dict
3
+		self.keys = dict.keys()
4
+		self.index = 0
5
+		self.length = len(self.keys)
6
+
7
+	def next(self):
8
+		if self.length <= self.index:
9
+			raise StopIteration
10
+
11
+		retval = self.keys[self.index]
12
+		self.index += 1
13
+		return retval
14
+
15
+
16
+class SortedDict(dict):
17
+	def __setitem__(self, name, value):
18
+		try:
19
+			value = value.strip()
20
+		except:
21
+			pass
22
+		dict.__setitem__(self, name.lower(), value)
23
+
24
+	def __iter__(self):
25
+		return SortedDictIterator(self)
26
+	
27
+
28
+	def keys(self):
29
+		keys = dict.keys(self)
30
+		keys.sort()
31
+		return keys
32
+	
33
+	def popitem(self):
34
+		keys = self.keys()
35
+		if len(keys) < 1:
36
+			raise KeyError("popitem(): dictionary is empty")
37
+		retval = (keys[0], dict.__getitem__(self, keys[0]))
38
+		dict.__delitem__(self, keys[0])
39
+		return retval
40
+
41
+
0 42
new file mode 100755
... ...
@@ -0,0 +1,206 @@
0
+#!/usr/bin/env python
1
+
2
+import httplib2
3
+import sys
4
+import logging
5
+import time
6
+import base64
7
+import hmac
8
+import hashlib
9
+import httplib
10
+
11
+from optparse import OptionParser
12
+from logging import debug, info, warn, error
13
+import elementtree.ElementTree as ET
14
+
15
+## Our modules
16
+from utils import *
17
+from SortedDict import SortedDict
18
+from BidirMap import BidirMap
19
+
20
+class AwsConfig:
21
+	access_key = "<Put Your Access Key Here>"
22
+	secret_key = "<Put Your Secret Key Here>"
23
+	host = "s3.amazonaws.com"
24
+	verbose = False
25
+
26
+class S3Error (Exception):
27
+	def __init__(self, response):
28
+		#Exception.__init__(self)
29
+		self.status = response["status"]
30
+		self.reason = response["reason"]
31
+		tree = ET.fromstring(response["data"])
32
+		for child in tree.getchildren():
33
+			if child.text != "":
34
+				debug(child.tag + ": " + repr(child.text))
35
+				self.__setattr__(child.tag, child.text)
36
+
37
+	def __str__(self):
38
+		return "%d (%s): %s" % (self.status, self.reason, self.Code)
39
+
40
+class S3:
41
+	http_methods = BidirMap(
42
+		GET = 0x01,
43
+		PUT = 0x02,
44
+		HEAD = 0x04,
45
+		DELETE = 0x08,
46
+		MASK = 0x0F,
47
+		)
48
+	
49
+	targets = BidirMap(
50
+		SERVICE = 0x0100,
51
+		BUCKET = 0x0200,
52
+		OBJECT = 0x0400,
53
+		MASK = 0x0700,
54
+		)
55
+
56
+	operations = BidirMap(
57
+		UNDFINED = 0x0000,
58
+		LIST_ALL_BUCKETS = targets["SERVICE"] | http_methods["GET"],
59
+		BUCKET_CREATE = targets["BUCKET"] | http_methods["PUT"],
60
+		BUCKET_LIST = targets["BUCKET"] | http_methods["GET"],
61
+		BUCKET_DELETE = targets["BUCKET"] | http_methods["DELETE"],
62
+		OBJECT_PUT = targets["OBJECT"] | http_methods["PUT"],
63
+		OBJECT_GET = targets["OBJECT"] | http_methods["GET"],
64
+		OBJECT_HEAD = targets["OBJECT"] | http_methods["HEAD"],
65
+		OBJECT_DELETE = targets["OBJECT"] | http_methods["DELETE"],
66
+	)
67
+
68
+	def __init__(self, config):
69
+		self.config = config
70
+
71
+	def list_all_buckets(self):
72
+		request = self.create_request("LIST_ALL_BUCKETS")
73
+		response = self.send_request(request)
74
+		return response
75
+	
76
+	def bucket_list(self, bucket):
77
+		request = self.create_request("BUCKET_LIST", bucket = bucket)
78
+		response = self.send_request(request)
79
+		return response
80
+
81
+	def create_request(self, operation, bucket = None, object = None, headers = None):
82
+		resource = "/"
83
+		if bucket:
84
+			resource += str(bucket)
85
+			if object:
86
+				resource += "/"+str(object)
87
+
88
+		if not headers:
89
+			headers = SortedDict()
90
+
91
+		if headers.has_key("date"):
92
+			if not headers.has_key("x-amz-date"):
93
+				headers["x-amz-date"] = headers["date"]
94
+			del(headers["date"])
95
+		
96
+		if not headers.has_key("x-amz-date"):
97
+			headers["x-amz-date"] = time.strftime("%a, %d %b %Y %H:%M:%S %z", time.gmtime(time.time()))
98
+
99
+		method_string = S3.http_methods.getkey(S3.operations[operation] & S3.http_methods["MASK"])
100
+		signature = self.sign_headers(method_string, resource, headers)
101
+		headers["Authorization"] = "AWS "+self.config.access_key+":"+signature
102
+		return (method_string, resource, headers)
103
+	
104
+	def send_request(self, request):
105
+		method_string, resource, headers = request
106
+		info("Processing request, please wait...")
107
+		conn = httplib.HTTPConnection(self.config.host)
108
+		conn.request(method_string, resource, {}, headers)
109
+		response = {}
110
+		http_response = conn.getresponse()
111
+		response["status"] = http_response.status
112
+		response["reason"] = http_response.reason
113
+		response["data"] =  http_response.read()
114
+		conn.close()
115
+		if response["status"] != 200:
116
+			raise S3Error(response)
117
+		return response
118
+
119
+	def sign_headers(self, method, resource, headers):
120
+		h  = method+"\n"
121
+		h += headers.pop("content-md5", "")+"\n"
122
+		h += headers.pop("content-type", "")+"\n"
123
+		h += headers.pop("date", "")+"\n"
124
+		for header in headers.keys():
125
+			if header.startswith("x-amz-"):
126
+				h += header+":"+str(headers[header])+"\n"
127
+		h += resource
128
+		return base64.encodestring(hmac.new(self.config.secret_key, h, hashlib.sha1).digest()).strip()
129
+
130
+def cmd_buckets_list_all(args):
131
+	s3 = S3(AwsConfig())
132
+	response = s3.list_all_buckets()
133
+	tree = ET.fromstring(response["data"])
134
+	xmlns = getNameSpace(tree)
135
+	nodes = tree.findall('.//%sBucket' % xmlns)
136
+	buckets = parseNodes(nodes, xmlns)
137
+	maxlen = 0
138
+	for bucket in buckets:
139
+		if len(bucket["Name"]) > maxlen:
140
+			maxlen = len(bucket["Name"])
141
+	for bucket in buckets:
142
+		print "%s  %s" % (
143
+			formatDateTime(bucket["CreationDate"]),
144
+			bucket["Name"].ljust(maxlen),
145
+			)
146
+
147
+def cmd_bucket_list(args):
148
+	bucket = args[0]
149
+	s3 = S3(AwsConfig())
150
+	try:
151
+		response = s3.bucket_list(bucket)
152
+	except S3Error, e:
153
+		if e.Code == "NoSuchBucket":
154
+			error("Bucket '%s' does not exist" % bucket)
155
+			return
156
+		else:
157
+			raise
158
+	tree = ET.fromstring(response["data"])
159
+	xmlns = getNameSpace(tree)
160
+	nodes = tree.findall('.//%sContents' % xmlns)
161
+	objects = parseNodes(nodes, xmlns)
162
+	maxlen = 0
163
+	for object in objects:
164
+		if len(object["Key"]) > maxlen:
165
+			maxlen = len(object["Key"])
166
+	for object in objects:
167
+		size, size_coeff = formatSize(object["Size"], True)
168
+		print "%s  %s%s  %s" % (
169
+			formatDateTime(object["LastModified"]),
170
+			str(size).rjust(4), size_coeff.ljust(1),
171
+			object["Key"].ljust(maxlen),
172
+			)
173
+
174
+
175
+commands = {
176
+	"la" : ("List all buckets", cmd_buckets_list_all),
177
+	"lb" : ("List objects in bucket", cmd_bucket_list),
178
+#	"cb" : ("Create bucket", cmd_bucket_create),
179
+#	"rb" : ("Remove bucket", cmd_bucket_remove)
180
+	}
181
+
182
+if __name__ == '__main__':
183
+	logging.basicConfig(level=logging.DEBUG, format='%(levelname)s: %(message)s')
184
+
185
+	optparser = OptionParser()
186
+	optparser.set_defaults(config="~/.s3fs.cfg")
187
+	optparser.add_option("-c", "--config", dest="config", metavar="FILE", help="Config file name")
188
+	optparser.add_option("-d", "--debug", action="store_false", help="Enable debug output")
189
+	optparser.add_option("-v", "--verbose", action="store_false", help="Enable verbose output")
190
+	(options, args) = optparser.parse_args()
191
+
192
+	if len(args) < 1:
193
+		error("Missing command. Please run with --help for more information.")
194
+		exit(1)
195
+
196
+	command = args[0]
197
+	args.remove(command)
198
+	try:
199
+		print commands[command][0]
200
+		commands[command][1](args)
201
+	except KeyError, e:
202
+		error("Invalid command: %s" % e)
203
+	except S3Error, e:
204
+		error("S3 error: " + str(e))
205
+
0 206
new file mode 100644
... ...
@@ -0,0 +1,51 @@
0
+import time
1
+import re
2
+
3
+def parseNodes(nodes, xmlns = ""):
4
+	retval = []
5
+	for node in nodes:
6
+		retval_item = {}
7
+		if xmlns != "":
8
+			## Take regexp compilation out of the loop
9
+			r = re.compile(xmlns)
10
+			fixup = lambda string : r.sub("", string)
11
+		else:
12
+			## Do-nothing function
13
+			fixup = lambda string : string
14
+
15
+		for child in node.getchildren():
16
+			name = fixup(child.tag)
17
+			retval_item[name] = node.findtext(".//%s" % child.tag)
18
+
19
+		retval.append(retval_item)
20
+	return retval
21
+
22
+def getNameSpace(element):
23
+	if not element.tag.startswith("{"):
24
+		return ""
25
+	return re.compile("^(\{[^}]+\})").match(element.tag).groups()[0]
26
+
27
+def dateS3toPython(date):
28
+	date = re.compile("\.\d\d\dZ").sub(".000Z", date)
29
+	return time.strptime(date, "%Y-%m-%dT%H:%M:%S.000Z")
30
+
31
+def dateS3toUnix(date):
32
+	## FIXME: This should be timezone-aware.
33
+	## Currently the argument to strptime() is GMT but mktime() 
34
+	## treats it as "localtime". Anyway...
35
+	return time.mktime(dateS3toPython(date))
36
+
37
+def formatSize(size, human_readable = False):
38
+	size = int(size)
39
+	if human_readable:
40
+		coeffs = ['k', 'M', 'G', 'T']
41
+		coeff = ""
42
+		while size > 2048:
43
+			size /= 2048
44
+			coeff = coeffs.pop(0)
45
+		return (size, coeff)
46
+	else:
47
+		return (size, "")
48
+
49
+def formatDateTime(s3timestamp):
50
+	return time.strftime("%Y-%m-%d %H:%M", dateS3toPython(s3timestamp))