| | 206 | |
|---|
| | 207 | class CachedS3(S3): |
|---|
| | 208 | def __init__(self, config, cache): |
|---|
| | 209 | S3.__init__(self, config) |
|---|
| | 210 | self.cache = cache |
|---|
| | 211 | |
|---|
| | 212 | def getObject(self, *args, **kw): |
|---|
| | 213 | item = self.cache.get(*args, **kw) |
|---|
| | 214 | if not item: |
|---|
| | 215 | item = S3.getObject(self, *args, **kw) |
|---|
| | 216 | self.cache.set(*args, **kw) |
|---|
| | 217 | return item |
|---|
| | 218 | |
|---|
| | 219 | def putObject(self, *args, **kw): |
|---|
| | 220 | rv = S3.putObject(self, *args, **kw) |
|---|
| | 221 | self.cache.clear(*args, **kw) |
|---|
| | 222 | return rv |
|---|
| | 223 | |
|---|
| | 224 | def deleteObject(self, *args, **kw): |
|---|
| | 225 | rv = S3.deleteObject(self, *args, **kw) |
|---|
| | 226 | self.cache.clear(*args, **kw) |
|---|
| | 227 | return rv |
|---|
| | 228 | |
|---|
| 208 | | # util |
|---|
| 209 | | def getPage(url, access_key=None, secret_access_key=None, contextFactory=None, |
|---|
| 210 | | headers=None, method="GET", data=None, *args, **kwargs): |
|---|
| 211 | | """ small mod to client.getPage """ |
|---|
| 212 | | if not headers: |
|---|
| 213 | | headers = {} |
|---|
| 214 | | scheme, host, port, path = _parse(url) |
|---|
| 215 | | |
|---|
| 216 | | headers.setdefault("Host", host) |
|---|
| 217 | | |
|---|
| 218 | | if type(data) is type(""): |
|---|
| 219 | | data = stream.MemoryStream(data) |
|---|
| 220 | | |
|---|
| 221 | | # this is where we add the amazon auth |
|---|
| 222 | | add_auth_header(method, path, headers, access_key, secret_access_key) |
|---|
| 223 | | |
|---|
| 224 | | # build the request |
|---|
| 225 | | stupid_headers_part_1 = dict([(k, (v,)) for k, v in headers.items()]) |
|---|
| 226 | | stupid_headers = http_headers.Headers(rawHeaders=stupid_headers_part_1) |
|---|
| 227 | | req = http.ClientRequest(method, path, stupid_headers, stream=data) |
|---|
| 228 | | |
|---|
| 229 | | d = protocol.ClientCreator(reactor, http.HTTPClientProtocol).connectTCP(host, 80) |
|---|
| 230 | | d.addCallback(lambda p: p.submitRequest(req, closeAfter=False)) |
|---|
| 231 | | |
|---|
| 232 | | return d |
|---|
| | 234 | |
|---|
| | 235 | |
|---|
| | 236 | |
|---|
| | 237 | ### Cache |
|---|
| | 238 | class S3SimpleFileCache(object): |
|---|
| | 239 | def __init__(self, config): |
|---|
| | 240 | self.base = config['base'] |
|---|
| | 241 | |
|---|
| | 242 | def get(self, bucket, name, headers=None): |
|---|
| | 243 | data_path = os.path.join(self.base, 'data', bucket, name) |
|---|
| | 244 | meta_path = os.path.join(self.base, '.metadata', bucket, name) |
|---|
| | 245 | if not os.path.exists(data_path): |
|---|
| | 246 | return None |
|---|
| | 247 | metadata = pickle.load(open(meta_path)) |
|---|
| | 248 | data = stream.FileStream(open(data_path)) |
|---|
| | 249 | return S3Object(data, metadata) |
|---|
| | 250 | |
|---|
| | 251 | |
|---|
| | 252 | def put(self, bucket, name, headers=None, acl=None, meta=None): |
|---|
| | 253 | data_path = os.path.join(self.base, 'data', bucket, name) |
|---|
| | 254 | meta_path = os.path.join(self.base, '.metadata', bucket, name) |
|---|
| | 255 | data_dir = os.path.dirname(data_path) |
|---|
| | 256 | meta_dir = os.path.dirname(meta_path) |
|---|
| | 257 | for dir in [data_dir, meta_dir]: |
|---|
| | 258 | try: |
|---|
| | 259 | os.makedirs(dir) |
|---|
| | 260 | except: |
|---|
| | 261 | pass |
|---|
| | 262 | pickle.dump(meta, open(meta_path)) |
|---|
| | 263 | f = open(data_path) |
|---|
| 266 | | def add_auth_header(method, path, headers, access_key, secret_access_key): |
|---|
| 267 | | headers.setdefault("Date", time.strftime("%a, %d %b %Y %X GMT", time.gmtime())) |
|---|
| 268 | | |
|---|
| 269 | | interesting_headers = {} |
|---|
| 270 | | for key in headers: |
|---|
| 271 | | lk = key.lower() |
|---|
| 272 | | if lk in ['content-md5', 'content-type', 'date'] or lk.startswith(AMAZON_HEADER_PREFIX): |
|---|
| 273 | | interesting_headers[lk] = headers[key] |
|---|
| 274 | | |
|---|
| 275 | | # these keys get empty strings if they don't exist |
|---|
| 276 | | interesting_headers.setdefault('content-type', '') |
|---|
| 277 | | interesting_headers.setdefault('content-md5', '') |
|---|
| 278 | | |
|---|
| 279 | | # just in case someone used this. it's not necessary in this lib. |
|---|
| 280 | | if interesting_headers.has_key('x-amz-date'): |
|---|
| 281 | | interesting_headers['date'] = '' |
|---|
| 282 | | |
|---|
| 283 | | sorted_header_keys = interesting_headers.keys() |
|---|
| 284 | | sorted_header_keys.sort() |
|---|
| 285 | | |
|---|
| 286 | | canonical_string = "%s\n" % method |
|---|
| 287 | | for key in sorted_header_keys: |
|---|
| 288 | | if key.startswith(AMAZON_HEADER_PREFIX): |
|---|
| 289 | | canonical_string += "%s:%s\n" % (key, interesting_headers[key]) |
|---|
| 290 | | else: |
|---|
| 291 | | canonical_string += "%s\n" % interesting_headers[key] |
|---|
| 292 | | |
|---|
| 293 | | |
|---|
| 294 | | # don't include anything after the first ? in the resource... |
|---|
| 295 | | canonical_string += path.split('?')[0] |
|---|
| 296 | | |
|---|
| 297 | | # ...unless there is an acl or torrent parameter |
|---|
| 298 | | if re.search("[&?]acl($|=|&)", path): |
|---|
| 299 | | canonical_string += "?acl" |
|---|
| 300 | | elif re.search("[&?]torrent($|=|&)", path): |
|---|
| 301 | | canonical_string += "?torrent" |
|---|
| 302 | | |
|---|
| 303 | | encoded_canonical = base64.encodestring(hmac.new(secret_access_key, canonical_string, sha).digest()).strip() |
|---|
| 304 | | headers['Authorization'] = "AWS %s:%s" % (access_key, encoded_canonical) |
|---|
| 305 | | return headers |
|---|