~blamar/+junk/openstack-api-arrrg

« back to all changes in this revision

Viewing changes to vendor/boto/boto/s3/key.py

  • Committer: Jesse Andrews
  • Date: 2010-05-28 06:05:26 UTC
  • Revision ID: git-v1:bf6e6e718cdc7488e2da87b21e258ccc065fe499
initial commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/
 
2
#
 
3
# Permission is hereby granted, free of charge, to any person obtaining a
 
4
# copy of this software and associated documentation files (the
 
5
# "Software"), to deal in the Software without restriction, including
 
6
# without limitation the rights to use, copy, modify, merge, publish, dis-
 
7
# tribute, sublicense, and/or sell copies of the Software, and to permit
 
8
# persons to whom the Software is furnished to do so, subject to the fol-
 
9
# lowing conditions:
 
10
#
 
11
# The above copyright notice and this permission notice shall be included
 
12
# in all copies or substantial portions of the Software.
 
13
#
 
14
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 
15
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
 
16
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
 
17
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 
 
18
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 
19
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 
20
# IN THE SOFTWARE.
 
21
 
 
22
import mimetypes
 
23
import os
 
24
import rfc822
 
25
import StringIO
 
26
import base64
 
27
import boto.utils
 
28
from boto.exception import S3ResponseError, S3DataError, BotoClientError
 
29
from boto.s3.user import User
 
30
from boto import UserAgent
 
31
 
 
32
try:
 
33
    from hashlib import md5
 
34
except ImportError:
 
35
    from md5 import md5
 
36
 
 
37
 
 
38
class Key(object):
 
39
 
 
40
    DefaultContentType = 'application/octet-stream'
 
41
 
 
42
    BufferSize = 8192
 
43
 
 
44
    def __init__(self, bucket=None, name=None):
 
45
        self.bucket = bucket
 
46
        self.name = name
 
47
        self.metadata = {}
 
48
        self.content_type = self.DefaultContentType
 
49
        self.content_encoding = None
 
50
        self.filename = None
 
51
        self.etag = None
 
52
        self.last_modified = None
 
53
        self.owner = None
 
54
        self.storage_class = None
 
55
        self.md5 = None
 
56
        self.base64md5 = None
 
57
        self.path = None
 
58
        self.resp = None
 
59
        self.mode = None
 
60
        self.size = None
 
61
        self.version_id = None
 
62
        self.source_version_id = None
 
63
        self.delete_marker = False
 
64
 
 
65
    def __repr__(self):
 
66
        if self.bucket:
 
67
            return '<Key: %s,%s>' % (self.bucket.name, self.name)
 
68
        else:
 
69
            return '<Key: None,%s>' % self.name
 
70
 
 
71
    def __getattr__(self, name):
 
72
        if name == 'key':
 
73
            return self.name
 
74
        else:
 
75
            raise AttributeError
 
76
 
 
77
    def __setattr__(self, name, value):
 
78
        if name == 'key':
 
79
            self.__dict__['name'] = value
 
80
        else:
 
81
            self.__dict__[name] = value
 
82
 
 
83
    def __iter__(self):
 
84
        return self
 
85
 
 
86
    def handle_version_headers(self, resp):
 
87
        self.version_id = resp.getheader('x-amz-version-id', None)
 
88
        self.source_version_id = resp.getheader('x-amz-copy-source-version-id', None)
 
89
        if resp.getheader('x-amz-delete-marker', 'false') == 'true':
 
90
            self.delete_marker = True
 
91
        else:
 
92
            self.delete_marker = False
 
93
 
 
94
    def open_read(self, headers=None, query_args=None):
 
95
        """
 
96
        Open this key for reading
 
97
        
 
98
        :type headers: dict
 
99
        :param headers: Headers to pass in the web request
 
100
        
 
101
        :type query_args: string
 
102
        :param query_args: Arguments to pass in the query string (ie, 'torrent')
 
103
        """
 
104
        if self.resp == None:
 
105
            self.mode = 'r'
 
106
            
 
107
            self.resp = self.bucket.connection.make_request('GET',
 
108
                                                            self.bucket.name,
 
109
                                                            self.name, headers,
 
110
                                                            query_args=query_args)
 
111
            if self.resp.status < 199 or self.resp.status > 299:
 
112
                body = self.resp.read()
 
113
                raise S3ResponseError(self.resp.status, self.resp.reason, body)
 
114
            response_headers = self.resp.msg
 
115
            self.metadata = boto.utils.get_aws_metadata(response_headers)
 
116
            for name,value in response_headers.items():
 
117
                if name.lower() == 'content-length':
 
118
                    self.size = int(value)
 
119
                elif name.lower() == 'etag':
 
120
                    self.etag = value
 
121
                elif name.lower() == 'content-type':
 
122
                    self.content_type = value
 
123
                elif name.lower() == 'content-encoding':
 
124
                    self.content_encoding = value
 
125
                elif name.lower() == 'last-modified':
 
126
                    self.last_modified = value
 
127
            self.handle_version_headers(self.resp)
 
128
 
 
129
    def open_write(self, headers=None):
 
130
        """
 
131
        Open this key for writing. 
 
132
        Not yet implemented
 
133
        
 
134
        :type headers: dict
 
135
        :param headers: Headers to pass in the write request
 
136
        """
 
137
        raise BotoClientError('Not Implemented')
 
138
 
 
139
    def open(self, mode='r', headers=None, query_args=None):
 
140
        if mode == 'r':
 
141
            self.mode = 'r'
 
142
            self.open_read(headers=headers, query_args=query_args)
 
143
        elif mode == 'w':
 
144
            self.mode = 'w'
 
145
            self.open_write(headers=headers)
 
146
        else:
 
147
            raise BotoClientError('Invalid mode: %s' % mode)
 
148
 
 
149
    closed = False
 
150
    def close(self):
 
151
        if self.resp:
 
152
            self.resp.read()
 
153
        self.resp = None
 
154
        self.mode = None
 
155
        self.closed = True
 
156
    
 
157
    def next(self):
 
158
        """
 
159
        By providing a next method, the key object supports use as an iterator.
 
160
        For example, you can now say:
 
161
 
 
162
        for bytes in key:
 
163
            write bytes to a file or whatever
 
164
 
 
165
        All of the HTTP connection stuff is handled for you.
 
166
        """
 
167
        self.open_read()
 
168
        data = self.resp.read(self.BufferSize)
 
169
        if not data:
 
170
            self.close()
 
171
            raise StopIteration
 
172
        return data
 
173
 
 
174
    def read(self, size=0):
 
175
        if size == 0:
 
176
            size = self.BufferSize
 
177
        self.open_read()
 
178
        data = self.resp.read(size)
 
179
        if not data:
 
180
            self.close()
 
181
        return data
 
182
 
 
183
    def copy(self, dst_bucket, dst_key, metadata=None):
 
184
        """
 
185
        Copy this Key to another bucket.
 
186
 
 
187
        :type dst_bucket: string
 
188
        :param dst_bucket: The name of the destination bucket
 
189
 
 
190
        :type dst_key: string
 
191
        :param dst_key: The name of the destinatino key
 
192
        
 
193
        :type metadata: dict
 
194
        :param metadata: Metadata to be associated with new key.
 
195
                         If metadata is supplied, it will replace the
 
196
                         metadata of the source key being copied.
 
197
                         If no metadata is supplied, the source key's
 
198
                         metadata will be copied to the new key.
 
199
 
 
200
        :rtype: :class:`boto.s3.key.Key` or subclass
 
201
        :returns: An instance of the newly created key object
 
202
        """
 
203
        dst_bucket = self.bucket.connection.lookup(dst_bucket)
 
204
        return dst_bucket.copy_key(dst_key, self.bucket.name, self.name, metadata)
 
205
 
 
206
    def startElement(self, name, attrs, connection):
 
207
        if name == 'Owner':
 
208
            self.owner = User(self)
 
209
            return self.owner
 
210
        else:
 
211
            return None
 
212
 
 
213
    def endElement(self, name, value, connection):
 
214
        if name == 'Key':
 
215
            self.name = value.encode('utf-8')
 
216
        elif name == 'ETag':
 
217
            self.etag = value
 
218
        elif name == 'LastModified':
 
219
            self.last_modified = value
 
220
        elif name == 'Size':
 
221
            self.size = int(value)
 
222
        elif name == 'StorageClass':
 
223
            self.storage_class = value
 
224
        elif name == 'Owner':
 
225
            pass
 
226
        elif name == 'VersionId':
 
227
            self.version_id = value
 
228
        else:
 
229
            setattr(self, name, value)
 
230
 
 
231
    def exists(self):
 
232
        """
 
233
        Returns True if the key exists
 
234
        
 
235
        :rtype: bool
 
236
        :return: Whether the key exists on S3
 
237
        """
 
238
        return bool(self.bucket.lookup(self.name))
 
239
 
 
240
    def delete(self):
 
241
        """
 
242
        Delete this key from S3
 
243
        """
 
244
        return self.bucket.delete_key(self.name)
 
245
 
 
246
    def get_metadata(self, name):
 
247
        return self.metadata.get(name)
 
248
 
 
249
    def set_metadata(self, name, value):
 
250
        self.metadata[name] = value
 
251
 
 
252
    def update_metadata(self, d):
 
253
        self.metadata.update(d)
 
254
    
 
255
    # convenience methods for setting/getting ACL
 
256
    def set_acl(self, acl_str, headers=None):
 
257
        if self.bucket != None:
 
258
            self.bucket.set_acl(acl_str, self.name, headers=headers)
 
259
 
 
260
    def get_acl(self, headers=None):
 
261
        if self.bucket != None:
 
262
            return self.bucket.get_acl(self.name, headers=headers)
 
263
 
 
264
    def get_xml_acl(self, headers=None):
 
265
        if self.bucket != None:
 
266
            return self.bucket.get_xml_acl(self.name, headers=headers)
 
267
 
 
268
    def set_xml_acl(self, acl_str, headers=None):
 
269
        if self.bucket != None:
 
270
            return self.bucket.set_xml_acl(acl_str, self.name, headers=headers)
 
271
 
 
272
    def set_canned_acl(self, acl_str, headers=None):
 
273
        return self.bucket.set_canned_acl(acl_str, self.name, headers)
 
274
        
 
275
    def make_public(self, headers=None):
 
276
        return self.bucket.set_canned_acl('public-read', self.name, headers)
 
277
 
 
278
    def generate_url(self, expires_in, method='GET', headers=None,
 
279
                     query_auth=True, force_http=False):
 
280
        """
 
281
        Generate a URL to access this key.
 
282
        
 
283
        :type expires_in: int
 
284
        :param expires_in: How long the url is valid for, in seconds
 
285
        
 
286
        :type method: string
 
287
        :param method: The method to use for retrieving the file (default is GET)
 
288
        
 
289
        :type headers: dict
 
290
        :param headers: Any headers to pass along in the request
 
291
        
 
292
        :type query_auth: bool
 
293
        :param query_auth: 
 
294
        
 
295
        :rtype: string
 
296
        :return: The URL to access the key
 
297
        """
 
298
        return self.bucket.connection.generate_url(expires_in, method,
 
299
                                                   self.bucket.name, self.name,
 
300
                                                   headers, query_auth, force_http)
 
301
 
 
302
    def send_file(self, fp, headers=None, cb=None, num_cb=10):
 
303
        """
 
304
        Upload a file to a key into a bucket on S3.
 
305
        
 
306
        :type fp: file
 
307
        :param fp: The file pointer to upload
 
308
        
 
309
        :type headers: dict
 
310
        :param headers: The headers to pass along with the PUT request
 
311
        
 
312
        :type cb: function
 
313
        :param cb: a callback function that will be called to report
 
314
                    progress on the upload.  The callback should accept two integer
 
315
                    parameters, the first representing the number of bytes that have
 
316
                    been successfully transmitted to S3 and the second representing
 
317
                    the total number of bytes that need to be transmitted.
 
318
                    
 
319
        :type cb: int
 
320
        :param num_cb: (optional) If a callback is specified with the cb parameter
 
321
             this parameter determines the granularity of the callback by defining
 
322
             the maximum number of times the callback will be called during the file transfer.  
 
323
             
 
324
        """
 
325
        def sender(http_conn, method, path, data, headers):
 
326
            http_conn.putrequest(method, path)
 
327
            for key in headers:
 
328
                http_conn.putheader(key, headers[key])
 
329
            http_conn.endheaders()
 
330
            fp.seek(0)
 
331
            save_debug = self.bucket.connection.debug
 
332
            self.bucket.connection.debug = 0
 
333
            if cb:
 
334
                if num_cb > 2:
 
335
                    cb_count = self.size / self.BufferSize / (num_cb-2)
 
336
                else:
 
337
                    cb_count = 0
 
338
                i = total_bytes = 0
 
339
                cb(total_bytes, self.size)
 
340
            l = fp.read(self.BufferSize)
 
341
            while len(l) > 0:
 
342
                http_conn.send(l)
 
343
                if cb:
 
344
                    total_bytes += len(l)
 
345
                    i += 1
 
346
                    if i == cb_count:
 
347
                        cb(total_bytes, self.size)
 
348
                        i = 0
 
349
                l = fp.read(self.BufferSize)
 
350
            if cb:
 
351
                cb(total_bytes, self.size)
 
352
            response = http_conn.getresponse()
 
353
            body = response.read()
 
354
            fp.seek(0)
 
355
            self.bucket.connection.debug = save_debug
 
356
            if response.status == 500 or response.status == 503 or \
 
357
                    response.getheader('location'):
 
358
                # we'll try again
 
359
                return response
 
360
            elif response.status >= 200 and response.status <= 299:
 
361
                self.etag = response.getheader('etag')
 
362
                if self.etag != '"%s"'  % self.md5:
 
363
                    raise S3DataError('ETag from S3 did not match computed MD5')
 
364
                return response
 
365
            else:
 
366
                raise S3ResponseError(response.status, response.reason, body)
 
367
 
 
368
        if not headers:
 
369
            headers = {}
 
370
        else:
 
371
            headers = headers.copy()
 
372
        headers['User-Agent'] = UserAgent
 
373
        headers['Content-MD5'] = self.base64md5
 
374
        if headers.has_key('Content-Type'):
 
375
            self.content_type = headers['Content-Type']
 
376
        elif self.path:
 
377
            self.content_type = mimetypes.guess_type(self.path)[0]
 
378
            if self.content_type == None:
 
379
                self.content_type = self.DefaultContentType
 
380
            headers['Content-Type'] = self.content_type
 
381
        else:
 
382
            headers['Content-Type'] = self.content_type
 
383
        headers['Content-Length'] = str(self.size)
 
384
        headers['Expect'] = '100-Continue'
 
385
        headers = boto.utils.merge_meta(headers, self.metadata)
 
386
        resp = self.bucket.connection.make_request('PUT', self.bucket.name,
 
387
                                                   self.name, headers,
 
388
                                                   sender=sender)
 
389
        self.handle_version_headers(resp)
 
390
 
 
391
    def compute_md5(self, fp):
 
392
        """
 
393
        :type fp: file
 
394
        :param fp: File pointer to the file to MD5 hash.  The file pointer will be
 
395
                   reset to the beginning of the file before the method returns.
 
396
        
 
397
        :rtype: tuple
 
398
        :return: A tuple containing the hex digest version of the MD5 hash
 
399
                 as the first element and the base64 encoded version of the
 
400
                 plain digest as the second element.
 
401
        """
 
402
        m = md5()
 
403
        fp.seek(0)
 
404
        s = fp.read(self.BufferSize)
 
405
        while s:
 
406
            m.update(s)
 
407
            s = fp.read(self.BufferSize)
 
408
        hex_md5 = m.hexdigest()
 
409
        base64md5 = base64.encodestring(m.digest())
 
410
        if base64md5[-1] == '\n':
 
411
            base64md5 = base64md5[0:-1]
 
412
        self.size = fp.tell()
 
413
        fp.seek(0)
 
414
        return (hex_md5, base64md5)
 
415
 
 
416
    def set_contents_from_file(self, fp, headers=None, replace=True, cb=None, num_cb=10,
 
417
                               policy=None, md5=None):
 
418
        """
 
419
        Store an object in S3 using the name of the Key object as the
 
420
        key in S3 and the contents of the file pointed to by 'fp' as the
 
421
        contents.
 
422
        
 
423
        :type fp: file
 
424
        :param fp: the file whose contents to upload
 
425
        
 
426
        :type headers: dict
 
427
        :param headers: additional HTTP headers that will be sent with the PUT request.
 
428
 
 
429
        :type replace: bool
 
430
        :param replace: If this parameter is False, the method
 
431
                        will first check to see if an object exists in the
 
432
                        bucket with the same key.  If it does, it won't
 
433
                        overwrite it.  The default value is True which will
 
434
                        overwrite the object.
 
435
                    
 
436
        :type cb: function
 
437
        :param cb: a callback function that will be called to report
 
438
                    progress on the upload.  The callback should accept two integer
 
439
                    parameters, the first representing the number of bytes that have
 
440
                    been successfully transmitted to S3 and the second representing
 
441
                    the total number of bytes that need to be transmitted.
 
442
                    
 
443
        :type cb: int
 
444
        :param num_cb: (optional) If a callback is specified with the cb parameter
 
445
             this parameter determines the granularity of the callback by defining
 
446
             the maximum number of times the callback will be called during the file transfer.
 
447
 
 
448
        :type policy: :class:`boto.s3.acl.CannedACLStrings`
 
449
        :param policy: A canned ACL policy that will be applied to the new key in S3.
 
450
             
 
451
        :type md5: A tuple containing the hexdigest version of the MD5 checksum of the
 
452
                   file as the first element and the Base64-encoded version of the plain
 
453
                   checksum as the second element.  This is the same format returned by
 
454
                   the compute_md5 method.
 
455
        :param md5: If you need to compute the MD5 for any reason prior to upload,
 
456
                    it's silly to have to do it twice so this param, if present, will be
 
457
                    used as the MD5 values of the file.  Otherwise, the checksum will be computed.
 
458
        """
 
459
        if policy:
 
460
            if headers:
 
461
                headers['x-amz-acl'] = policy
 
462
            else:
 
463
                headers = {'x-amz-acl' : policy}
 
464
        if hasattr(fp, 'name'):
 
465
            self.path = fp.name
 
466
        if self.bucket != None:
 
467
            if not md5:
 
468
                md5 = self.compute_md5(fp)
 
469
            self.md5 = md5[0]
 
470
            self.base64md5 = md5[1]
 
471
            if self.name == None:
 
472
                self.name = self.md5
 
473
            if not replace:
 
474
                k = self.bucket.lookup(self.name)
 
475
                if k:
 
476
                    return
 
477
            self.send_file(fp, headers, cb, num_cb)
 
478
 
 
479
    def set_contents_from_filename(self, filename, headers=None, replace=True, cb=None, num_cb=10,
 
480
                                   policy=None, md5=None):
 
481
        """
 
482
        Store an object in S3 using the name of the Key object as the
 
483
        key in S3 and the contents of the file named by 'filename'.
 
484
        See set_contents_from_file method for details about the
 
485
        parameters.
 
486
        
 
487
        :type filename: string
 
488
        :param filename: The name of the file that you want to put onto S3
 
489
        
 
490
        :type headers: dict
 
491
        :param headers: Additional headers to pass along with the request to AWS.
 
492
        
 
493
        :type replace: bool
 
494
        :param replace: If True, replaces the contents of the file if it already exists.
 
495
        
 
496
        :type cb: function
 
497
        :param cb: (optional) a callback function that will be called to report
 
498
             progress on the download.  The callback should accept two integer
 
499
             parameters, the first representing the number of bytes that have
 
500
             been successfully transmitted from S3 and the second representing
 
501
             the total number of bytes that need to be transmitted.        
 
502
                    
 
503
        :type cb: int
 
504
        :param num_cb: (optional) If a callback is specified with the cb parameter
 
505
             this parameter determines the granularity of the callback by defining
 
506
             the maximum number of times the callback will be called during the file transfer.  
 
507
             
 
508
        :type policy: :class:`boto.s3.acl.CannedACLStrings`
 
509
        :param policy: A canned ACL policy that will be applied to the new key in S3.
 
510
             
 
511
        :type md5: A tuple containing the hexdigest version of the MD5 checksum of the
 
512
                   file as the first element and the Base64-encoded version of the plain
 
513
                   checksum as the second element.  This is the same format returned by
 
514
                   the compute_md5 method.
 
515
        :param md5: If you need to compute the MD5 for any reason prior to upload,
 
516
                    it's silly to have to do it twice so this param, if present, will be
 
517
                    used as the MD5 values of the file.  Otherwise, the checksum will be computed.
 
518
        """
 
519
        fp = open(filename, 'rb')
 
520
        self.set_contents_from_file(fp, headers, replace, cb, num_cb, policy)
 
521
        fp.close()
 
522
 
 
523
    def set_contents_from_string(self, s, headers=None, replace=True, cb=None, num_cb=10,
 
524
                                 policy=None, md5=None):
 
525
        """
 
526
        Store an object in S3 using the name of the Key object as the
 
527
        key in S3 and the string 's' as the contents.
 
528
        See set_contents_from_file method for details about the
 
529
        parameters.
 
530
        
 
531
        :type headers: dict
 
532
        :param headers: Additional headers to pass along with the request to AWS.
 
533
        
 
534
        :type replace: bool
 
535
        :param replace: If True, replaces the contents of the file if it already exists.
 
536
        
 
537
        :type cb: function
 
538
        :param cb: (optional) a callback function that will be called to report
 
539
             progress on the download.  The callback should accept two integer
 
540
             parameters, the first representing the number of bytes that have
 
541
             been successfully transmitted from S3 and the second representing
 
542
             the total number of bytes that need to be transmitted.        
 
543
                    
 
544
        :type cb: int
 
545
        :param num_cb: (optional) If a callback is specified with the cb parameter
 
546
             this parameter determines the granularity of the callback by defining
 
547
             the maximum number of times the callback will be called during the file transfer.  
 
548
             
 
549
        :type policy: :class:`boto.s3.acl.CannedACLStrings`
 
550
        :param policy: A canned ACL policy that will be applied to the new key in S3.
 
551
             
 
552
        :type md5: A tuple containing the hexdigest version of the MD5 checksum of the
 
553
                   file as the first element and the Base64-encoded version of the plain
 
554
                   checksum as the second element.  This is the same format returned by
 
555
                   the compute_md5 method.
 
556
        :param md5: If you need to compute the MD5 for any reason prior to upload,
 
557
                    it's silly to have to do it twice so this param, if present, will be
 
558
                    used as the MD5 values of the file.  Otherwise, the checksum will be computed.
 
559
        """
 
560
        fp = StringIO.StringIO(s)
 
561
        r = self.set_contents_from_file(fp, headers, replace, cb, num_cb, policy)
 
562
        fp.close()
 
563
        return r
 
564
 
 
565
    def get_file(self, fp, headers=None, cb=None, num_cb=10,
 
566
                 torrent=False, version_id=None):
 
567
        """
 
568
        Retrieves a file from an S3 Key
 
569
        
 
570
        :type fp: file
 
571
        :param fp: File pointer to put the data into
 
572
        
 
573
        :type headers: string
 
574
        :param: headers to send when retrieving the files
 
575
        
 
576
        :type cb: function
 
577
        :param cb: (optional) a callback function that will be called to report
 
578
             progress on the download.  The callback should accept two integer
 
579
             parameters, the first representing the number of bytes that have
 
580
             been successfully transmitted from S3 and the second representing
 
581
             the total number of bytes that need to be transmitted.
 
582
        
 
583
                    
 
584
        :type cb: int
 
585
        :param num_cb: (optional) If a callback is specified with the cb parameter
 
586
             this parameter determines the granularity of the callback by defining
 
587
             the maximum number of times the callback will be called during the file transfer.  
 
588
             
 
589
        :type torrent: bool
 
590
        :param torrent: Flag for whether to get a torrent for the file
 
591
        """
 
592
        if cb:
 
593
            if num_cb > 2:
 
594
                cb_count = self.size / self.BufferSize / (num_cb-2)
 
595
            else:
 
596
                cb_count = 0
 
597
            i = total_bytes = 0
 
598
            cb(total_bytes, self.size)
 
599
        save_debug = self.bucket.connection.debug
 
600
        if self.bucket.connection.debug == 1:
 
601
            self.bucket.connection.debug = 0
 
602
 
 
603
        query_args = ''
 
604
        if torrent:
 
605
            query_args = 'torrent'
 
606
        elif version_id:
 
607
            query_args = 'versionId=%s' % version_id
 
608
        self.open('r', headers, query_args=query_args)
 
609
        for bytes in self:
 
610
            fp.write(bytes)
 
611
            if cb:
 
612
                total_bytes += len(bytes)
 
613
                i += 1
 
614
                if i == cb_count:
 
615
                    cb(total_bytes, self.size)
 
616
                    i = 0
 
617
        if cb:
 
618
            cb(total_bytes, self.size)
 
619
        self.close()
 
620
        self.bucket.connection.debug = save_debug
 
621
 
 
622
    def get_torrent_file(self, fp, headers=None, cb=None, num_cb=10):
 
623
        """
 
624
        Get a torrent file (see to get_file)
 
625
        
 
626
        :type fp: file
 
627
        :param fp: The file pointer of where to put the torrent
 
628
        
 
629
        :type headers: dict
 
630
        :param headers: Headers to be passed
 
631
        
 
632
        :type cb: function
 
633
        :param cb: Callback function to call on retrieved data
 
634
        
 
635
        :type cb: int
 
636
        :param num_cb: (optional) If a callback is specified with the cb parameter
 
637
             this parameter determines the granularity of the callback by defining
 
638
             the maximum number of times the callback will be called during the file transfer.  
 
639
             
 
640
        """
 
641
        return self.get_file(fp, headers, cb, num_cb, torrent=True)
 
642
    
 
643
    def get_contents_to_file(self, fp, headers=None,
 
644
                             cb=None, num_cb=10,
 
645
                             torrent=False,
 
646
                             version_id=None):
 
647
        """
 
648
        Retrieve an object from S3 using the name of the Key object as the
 
649
        key in S3.  Write the contents of the object to the file pointed
 
650
        to by 'fp'.
 
651
        
 
652
        :type fp: File -like object
 
653
        :param fp:
 
654
        
 
655
        :type headers: dict
 
656
        :param headers: additional HTTP headers that will be sent with the GET request.
 
657
        
 
658
        :type cb: function
 
659
        :param cb: (optional) a callback function that will be called to report
 
660
             progress on the download.  The callback should accept two integer
 
661
             parameters, the first representing the number of bytes that have
 
662
             been successfully transmitted from S3 and the second representing
 
663
             the total number of bytes that need to be transmitted.
 
664
             
 
665
                    
 
666
        :type cb: int
 
667
        :param num_cb: (optional) If a callback is specified with the cb parameter
 
668
             this parameter determines the granularity of the callback by defining
 
669
             the maximum number of times the callback will be called during the file transfer.  
 
670
             
 
671
        :type torrent: bool
 
672
        :param torrent: If True, returns the contents of a torrent file as a string.
 
673
 
 
674
        """
 
675
        if self.bucket != None:
 
676
            self.get_file(fp, headers, cb, num_cb, torrent=torrent,
 
677
                          version_id=version_id)
 
678
 
 
679
    def get_contents_to_filename(self, filename, headers=None,
 
680
                                 cb=None, num_cb=10,
 
681
                                 torrent=False,
 
682
                                 version_id=None):
 
683
        """
 
684
        Retrieve an object from S3 using the name of the Key object as the
 
685
        key in S3.  Store contents of the object to a file named by 'filename'.
 
686
        See get_contents_to_file method for details about the
 
687
        parameters.
 
688
        
 
689
        :type filename: string
 
690
        :param filename: The filename of where to put the file contents
 
691
        
 
692
        :type headers: dict
 
693
        :param headers: Any additional headers to send in the request
 
694
        
 
695
        :type cb: function
 
696
        :param cb: (optional) a callback function that will be called to report
 
697
             progress on the download.  The callback should accept two integer
 
698
             parameters, the first representing the number of bytes that have
 
699
             been successfully transmitted from S3 and the second representing
 
700
             the total number of bytes that need to be transmitted.
 
701
             
 
702
                    
 
703
        :type cb: int
 
704
        :param num_cb: (optional) If a callback is specified with the cb parameter
 
705
             this parameter determines the granularity of the callback by defining
 
706
             the maximum number of times the callback will be called during the file transfer.  
 
707
             
 
708
        :type torrent: bool
 
709
        :param torrent: If True, returns the contents of a torrent file as a string.
 
710
        
 
711
        """
 
712
        fp = open(filename, 'wb')
 
713
        self.get_contents_to_file(fp, headers, cb, num_cb, torrent=torrent,
 
714
                                  version_id=version_id)
 
715
        fp.close()
 
716
        # if last_modified date was sent from s3, try to set file's timestamp
 
717
        if self.last_modified != None:
 
718
            try:
 
719
                modified_tuple = rfc822.parsedate_tz(self.last_modified)
 
720
                modified_stamp = int(rfc822.mktime_tz(modified_tuple))
 
721
                os.utime(fp.name, (modified_stamp, modified_stamp))
 
722
            except Exception: pass
 
723
 
 
724
    def get_contents_as_string(self, headers=None,
 
725
                               cb=None, num_cb=10,
 
726
                               torrent=False,
 
727
                               version_id=None):
 
728
        """
 
729
        Retrieve an object from S3 using the name of the Key object as the
 
730
        key in S3.  Return the contents of the object as a string.
 
731
        See get_contents_to_file method for details about the
 
732
        parameters.
 
733
        
 
734
        :type headers: dict
 
735
        :param headers: Any additional headers to send in the request
 
736
        
 
737
        :type cb: function
 
738
        :param cb: (optional) a callback function that will be called to report
 
739
             progress on the download.  The callback should accept two integer
 
740
             parameters, the first representing the number of bytes that have
 
741
             been successfully transmitted from S3 and the second representing
 
742
             the total number of bytes that need to be transmitted.
 
743
 
 
744
        :type cb: int
 
745
        :param num_cb: (optional) If a callback is specified with the cb parameter
 
746
             this parameter determines the granularity of the callback by defining
 
747
             the maximum number of times the callback will be called during the file transfer.  
 
748
             
 
749
                    
 
750
        :type cb: int
 
751
        :param num_cb: (optional) If a callback is specified with the cb parameter
 
752
             this parameter determines the granularity of the callback by defining
 
753
             the maximum number of times the callback will be called during the file transfer.  
 
754
             
 
755
        :type torrent: bool
 
756
        :param torrent: If True, returns the contents of a torrent file as a string.
 
757
        
 
758
        :rtype: string
 
759
        :returns: The contents of the file as a string
 
760
        """
 
761
        fp = StringIO.StringIO()
 
762
        self.get_contents_to_file(fp, headers, cb, num_cb, torrent=torrent,
 
763
                                  version_id=version_id)
 
764
        return fp.getvalue()
 
765
 
 
766
    def add_email_grant(self, permission, email_address):
 
767
        """
 
768
        Convenience method that provides a quick way to add an email grant to a key.
 
769
        This method retrieves the current ACL, creates a new grant based on the parameters
 
770
        passed in, adds that grant to the ACL and then PUT's the new ACL back to S3.
 
771
        
 
772
        :type permission: string
 
773
        :param permission: The permission being granted.  Should be one of:
 
774
                            READ|WRITE|READ_ACP|WRITE_ACP|FULL_CONTROL
 
775
                            See http://docs.amazonwebservices.com/AmazonS3/2006-03-01/UsingAuthAccess.html
 
776
                            for more details on permissions.
 
777
        
 
778
        :type email_address: string
 
779
        :param email_address: The email address associated with the AWS account your are granting
 
780
                                the permission to.
 
781
        """
 
782
        policy = self.get_acl()
 
783
        policy.acl.add_email_grant(permission, email_address)
 
784
        self.set_acl(policy)
 
785
 
 
786
    def add_user_grant(self, permission, user_id):
 
787
        """
 
788
        Convenience method that provides a quick way to add a canonical user grant to a key.
 
789
        This method retrieves the current ACL, creates a new grant based on the parameters
 
790
        passed in, adds that grant to the ACL and then PUT's the new ACL back to S3.
 
791
        
 
792
        :type permission: string
 
793
        :param permission: The permission being granted.  Should be one of:
 
794
                            READ|WRITE|READ_ACP|WRITE_ACP|FULL_CONTROL
 
795
                            See http://docs.amazonwebservices.com/AmazonS3/2006-03-01/UsingAuthAccess.html
 
796
                            for more details on permissions.
 
797
        
 
798
        :type user_id: string
 
799
        :param user_id: The canonical user id associated with the AWS account your are granting
 
800
                        the permission to.
 
801
        """
 
802
        policy = self.get_acl()
 
803
        policy.acl.add_user_grant(permission, user_id)
 
804
        self.set_acl(policy)