| OLD | NEW |
| (Empty) | |
| 1 # Copyright 2010 Google Inc. |
| 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 os |
| 23 import StringIO |
| 24 from boto.exception import BotoClientError |
| 25 from boto.s3.key import Key as S3Key |
| 26 |
| 27 class Key(S3Key): |
| 28 generation = None |
| 29 meta_generation = None |
| 30 |
| 31 def endElement(self, name, value, connection): |
| 32 if name == 'Key': |
| 33 self.name = value |
| 34 elif name == 'ETag': |
| 35 self.etag = value |
| 36 elif name == 'IsLatest': |
| 37 if value == 'true': |
| 38 self.is_latest = True |
| 39 else: |
| 40 self.is_latest = False |
| 41 elif name == 'LastModified': |
| 42 self.last_modified = value |
| 43 elif name == 'Size': |
| 44 self.size = int(value) |
| 45 elif name == 'StorageClass': |
| 46 self.storage_class = value |
| 47 elif name == 'Owner': |
| 48 pass |
| 49 elif name == 'VersionId': |
| 50 self.version_id = value |
| 51 elif name == 'Generation': |
| 52 self.generation = value |
| 53 elif name == 'MetaGeneration': |
| 54 self.meta_generation = value |
| 55 else: |
| 56 setattr(self, name, value) |
| 57 |
| 58 def get_file(self, fp, headers=None, cb=None, num_cb=10, |
| 59 torrent=False, version_id=None, override_num_retries=None, |
| 60 response_headers=None): |
| 61 query_args = None |
| 62 if self.generation: |
| 63 query_args = ['generation=%s' % self.generation] |
| 64 self._get_file_internal(fp, headers=headers, cb=cb, num_cb=num_cb, |
| 65 override_num_retries=override_num_retries, |
| 66 response_headers=response_headers, |
| 67 query_args=query_args) |
| 68 |
| 69 def delete(self): |
| 70 return self.bucket.delete_key(self.name, version_id=self.version_id, |
| 71 generation=self.generation) |
| 72 |
| 73 def add_email_grant(self, permission, email_address): |
| 74 """ |
| 75 Convenience method that provides a quick way to add an email grant to a |
| 76 key. This method retrieves the current ACL, creates a new grant based on |
| 77 the parameters passed in, adds that grant to the ACL and then PUT's the |
| 78 new ACL back to GS. |
| 79 |
| 80 :type permission: string |
| 81 :param permission: The permission being granted. Should be one of: |
| 82 READ|FULL_CONTROL |
| 83 See http://code.google.com/apis/storage/docs/developer-guide.html#au
thorization |
| 84 for more details on permissions. |
| 85 |
| 86 :type email_address: string |
| 87 :param email_address: The email address associated with the Google |
| 88 account to which you are granting the permission. |
| 89 """ |
| 90 acl = self.get_acl() |
| 91 acl.add_email_grant(permission, email_address) |
| 92 self.set_acl(acl) |
| 93 |
| 94 def add_user_grant(self, permission, user_id): |
| 95 """ |
| 96 Convenience method that provides a quick way to add a canonical user |
| 97 grant to a key. This method retrieves the current ACL, creates a new |
| 98 grant based on the parameters passed in, adds that grant to the ACL and |
| 99 then PUT's the new ACL back to GS. |
| 100 |
| 101 :type permission: string |
| 102 :param permission: The permission being granted. Should be one of: |
| 103 READ|FULL_CONTROL |
| 104 See http://code.google.com/apis/storage/docs/developer-guide.html#au
thorization |
| 105 for more details on permissions. |
| 106 |
| 107 :type user_id: string |
| 108 :param user_id: The canonical user id associated with the GS account to |
| 109 which you are granting the permission. |
| 110 """ |
| 111 acl = self.get_acl() |
| 112 acl.add_user_grant(permission, user_id) |
| 113 self.set_acl(acl) |
| 114 |
| 115 def add_group_email_grant(self, permission, email_address, headers=None): |
| 116 """ |
| 117 Convenience method that provides a quick way to add an email group |
| 118 grant to a key. This method retrieves the current ACL, creates a new |
| 119 grant based on the parameters passed in, adds that grant to the ACL and |
| 120 then PUT's the new ACL back to GS. |
| 121 |
| 122 :type permission: string |
| 123 :param permission: The permission being granted. Should be one of: |
| 124 READ|FULL_CONTROL |
| 125 See http://code.google.com/apis/storage/docs/developer-guide.html#au
thorization |
| 126 for more details on permissions. |
| 127 |
| 128 :type email_address: string |
| 129 :param email_address: The email address associated with the Google |
| 130 Group to which you are granting the permission. |
| 131 """ |
| 132 acl = self.get_acl(headers=headers) |
| 133 acl.add_group_email_grant(permission, email_address) |
| 134 self.set_acl(acl, headers=headers) |
| 135 |
| 136 def add_group_grant(self, permission, group_id): |
| 137 """ |
| 138 Convenience method that provides a quick way to add a canonical group |
| 139 grant to a key. This method retrieves the current ACL, creates a new |
| 140 grant based on the parameters passed in, adds that grant to the ACL and |
| 141 then PUT's the new ACL back to GS. |
| 142 |
| 143 :type permission: string |
| 144 :param permission: The permission being granted. Should be one of: |
| 145 READ|FULL_CONTROL |
| 146 See http://code.google.com/apis/storage/docs/developer-guide.html#au
thorization |
| 147 for more details on permissions. |
| 148 |
| 149 :type group_id: string |
| 150 :param group_id: The canonical group id associated with the Google |
| 151 Groups account you are granting the permission to. |
| 152 """ |
| 153 acl = self.get_acl() |
| 154 acl.add_group_grant(permission, group_id) |
| 155 self.set_acl(acl) |
| 156 |
| 157 def set_contents_from_file(self, fp, headers=None, replace=True, |
| 158 cb=None, num_cb=10, policy=None, md5=None, |
| 159 res_upload_handler=None, size=None, rewind=False)
: |
| 160 """ |
| 161 Store an object in GS using the name of the Key object as the |
| 162 key in GS and the contents of the file pointed to by 'fp' as the |
| 163 contents. |
| 164 |
| 165 :type fp: file |
| 166 :param fp: the file whose contents are to be uploaded |
| 167 |
| 168 :type headers: dict |
| 169 :param headers: additional HTTP headers to be sent with the PUT request. |
| 170 |
| 171 :type replace: bool |
| 172 :param replace: If this parameter is False, the method will first check |
| 173 to see if an object exists in the bucket with the same key. If it |
| 174 does, it won't overwrite it. The default value is True which will |
| 175 overwrite the object. |
| 176 |
| 177 :type cb: function |
| 178 :param cb: a callback function that will be called to report |
| 179 progress on the upload. The callback should accept two integer |
| 180 parameters, the first representing the number of bytes that have |
| 181 been successfully transmitted to GS and the second representing the |
| 182 total number of bytes that need to be transmitted. |
| 183 |
| 184 :type num_cb: int |
| 185 :param num_cb: (optional) If a callback is specified with the cb |
| 186 parameter, this parameter determines the granularity of the callback |
| 187 by defining the maximum number of times the callback will be called |
| 188 during the file transfer. |
| 189 |
| 190 :type policy: :class:`boto.gs.acl.CannedACLStrings` |
| 191 :param policy: A canned ACL policy that will be applied to the new key |
| 192 in GS. |
| 193 |
| 194 :type md5: A tuple containing the hexdigest version of the MD5 checksum |
| 195 of the file as the first element and the Base64-encoded version of |
| 196 the plain checksum as the second element. This is the same format |
| 197 returned by the compute_md5 method. |
| 198 :param md5: If you need to compute the MD5 for any reason prior to |
| 199 upload, it's silly to have to do it twice so this param, if present, |
| 200 will be used as the MD5 values of the file. Otherwise, the checksum |
| 201 will be computed. |
| 202 |
| 203 :type res_upload_handler: ResumableUploadHandler |
| 204 :param res_upload_handler: If provided, this handler will perform the |
| 205 upload. |
| 206 |
| 207 :type size: int |
| 208 :param size: (optional) The Maximum number of bytes to read from |
| 209 the file pointer (fp). This is useful when uploading |
| 210 a file in multiple parts where you are splitting the |
| 211 file up into different ranges to be uploaded. If not |
| 212 specified, the default behaviour is to read all bytes |
| 213 from the file pointer. Less bytes may be available. |
| 214 Notes: |
| 215 |
| 216 1. The "size" parameter currently cannot be used when |
| 217 a resumable upload handler is given but is still |
| 218 useful for uploading part of a file as implemented |
| 219 by the parent class. |
| 220 2. At present Google Cloud Storage does not support |
| 221 multipart uploads. |
| 222 |
| 223 :type rewind: bool |
| 224 :param rewind: (optional) If True, the file pointer (fp) will be |
| 225 rewound to the start before any bytes are read from |
| 226 it. The default behaviour is False which reads from |
| 227 the current position of the file pointer (fp). |
| 228 |
| 229 :rtype: int |
| 230 :return: The number of bytes written to the key. |
| 231 |
| 232 TODO: At some point we should refactor the Bucket and Key classes, |
| 233 to move functionality common to all providers into a parent class, |
| 234 and provider-specific functionality into subclasses (rather than |
| 235 just overriding/sharing code the way it currently works). |
| 236 """ |
| 237 provider = self.bucket.connection.provider |
| 238 if res_upload_handler and size: |
| 239 # could use size instead of file_length if provided but... |
| 240 raise BotoClientError('"size" param not supported for resumable uplo
ads.') |
| 241 headers = headers or {} |
| 242 if policy: |
| 243 headers[provider.acl_header] = policy |
| 244 |
| 245 if rewind: |
| 246 # caller requests reading from beginning of fp. |
| 247 fp.seek(0, os.SEEK_SET) |
| 248 else: |
| 249 spos = fp.tell() |
| 250 fp.seek(0, os.SEEK_END) |
| 251 if fp.tell() == spos: |
| 252 fp.seek(0, os.SEEK_SET) |
| 253 if fp.tell() != spos: |
| 254 # Raise an exception as this is likely a programming error |
| 255 # whereby there is data before the fp but nothing after it. |
| 256 fp.seek(spos) |
| 257 raise AttributeError( |
| 258 'fp is at EOF. Use rewind option or seek() to data start.') |
| 259 # seek back to the correct position. |
| 260 fp.seek(spos) |
| 261 |
| 262 if hasattr(fp, 'name'): |
| 263 self.path = fp.name |
| 264 if self.bucket != None: |
| 265 if size: |
| 266 self.size = size |
| 267 else: |
| 268 # If md5 is provided, still need to size so |
| 269 # calculate based on bytes to end of content |
| 270 spos = fp.tell() |
| 271 fp.seek(0, os.SEEK_END) |
| 272 self.size = fp.tell() - spos |
| 273 fp.seek(spos) |
| 274 size = self.size |
| 275 |
| 276 if self.name == None: |
| 277 if md5 == None: |
| 278 md5 = self.compute_md5(fp, size) |
| 279 self.md5 = md5[0] |
| 280 self.base64md5 = md5[1] |
| 281 |
| 282 self.name = self.md5 |
| 283 if not replace: |
| 284 if self.bucket.lookup(self.name): |
| 285 return |
| 286 if res_upload_handler: |
| 287 res_upload_handler.send_file(self, fp, headers, cb, num_cb) |
| 288 else: |
| 289 # Not a resumable transfer so use basic send_file mechanism. |
| 290 self.send_file(fp, headers, cb, num_cb, size=size) |
| 291 |
| 292 def set_contents_from_filename(self, filename, headers=None, replace=True, |
| 293 cb=None, num_cb=10, policy=None, md5=None, |
| 294 reduced_redundancy=None, |
| 295 res_upload_handler=None): |
| 296 """ |
| 297 Store an object in GS using the name of the Key object as the |
| 298 key in GS and the contents of the file named by 'filename'. |
| 299 See set_contents_from_file method for details about the |
| 300 parameters. |
| 301 |
| 302 :type filename: string |
| 303 :param filename: The name of the file that you want to put onto GS |
| 304 |
| 305 :type headers: dict |
| 306 :param headers: Additional headers to pass along with the request to GS. |
| 307 |
| 308 :type replace: bool |
| 309 :param replace: If True, replaces the contents of the file if it |
| 310 already exists. |
| 311 |
| 312 :type cb: function |
| 313 :param cb: (optional) a callback function that will be called to report |
| 314 progress on the download. The callback should accept two integer |
| 315 parameters, the first representing the number of bytes that have |
| 316 been successfully transmitted from GS 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 |
| 321 parameter this parameter determines the granularity of the callback |
| 322 by defining the maximum number of times the callback will be called |
| 323 during the file transfer. |
| 324 |
| 325 :type policy: :class:`boto.gs.acl.CannedACLStrings` |
| 326 :param policy: A canned ACL policy that will be applied to the new key |
| 327 in GS. |
| 328 |
| 329 :type md5: A tuple containing the hexdigest version of the MD5 checksum |
| 330 of the file as the first element and the Base64-encoded version of |
| 331 the plain checksum as the second element. This is the same format |
| 332 returned by the compute_md5 method. |
| 333 :param md5: If you need to compute the MD5 for any reason prior to |
| 334 upload, it's silly to have to do it twice so this param, if present, |
| 335 will be used as the MD5 values of the file. Otherwise, the checksum |
| 336 will be computed. |
| 337 |
| 338 :type res_upload_handler: ResumableUploadHandler |
| 339 :param res_upload_handler: If provided, this handler will perform the |
| 340 upload. |
| 341 """ |
| 342 # Clear out any previously computed md5 hashes, since we are setting the
content. |
| 343 self.md5 = None |
| 344 self.base64md5 = None |
| 345 |
| 346 fp = open(filename, 'rb') |
| 347 self.set_contents_from_file(fp, headers, replace, cb, num_cb, |
| 348 policy, md5, res_upload_handler) |
| 349 fp.close() |
| 350 |
| 351 def set_contents_from_string(self, s, headers=None, replace=True, |
| 352 cb=None, num_cb=10, policy=None, md5=None): |
| 353 """ |
| 354 Store an object in S3 using the name of the Key object as the |
| 355 key in S3 and the string 's' as the contents. |
| 356 See set_contents_from_file method for details about the |
| 357 parameters. |
| 358 |
| 359 :type headers: dict |
| 360 :param headers: Additional headers to pass along with the |
| 361 request to AWS. |
| 362 |
| 363 :type replace: bool |
| 364 :param replace: If True, replaces the contents of the file if |
| 365 it already exists. |
| 366 |
| 367 :type cb: function |
| 368 :param cb: a callback function that will be called to report |
| 369 progress on the upload. The callback should accept |
| 370 two integer parameters, the first representing the |
| 371 number of bytes that have been successfully |
| 372 transmitted to S3 and the second representing the |
| 373 size of the to be transmitted object. |
| 374 |
| 375 :type cb: int |
| 376 :param num_cb: (optional) If a callback is specified with |
| 377 the cb parameter this parameter determines the |
| 378 granularity of the callback by defining |
| 379 the maximum number of times the callback will |
| 380 be called during the file transfer. |
| 381 |
| 382 :type policy: :class:`boto.s3.acl.CannedACLStrings` |
| 383 :param policy: A canned ACL policy that will be applied to the |
| 384 new key in S3. |
| 385 |
| 386 :type md5: A tuple containing the hexdigest version of the MD5 |
| 387 checksum of the file as the first element and the |
| 388 Base64-encoded version of the plain checksum as the |
| 389 second element. This is the same format returned by |
| 390 the compute_md5 method. |
| 391 :param md5: If you need to compute the MD5 for any reason prior |
| 392 to upload, it's silly to have to do it twice so this |
| 393 param, if present, will be used as the MD5 values |
| 394 of the file. Otherwise, the checksum will be computed. |
| 395 """ |
| 396 |
| 397 # Clear out any previously computed md5 hashes, since we are setting the
content. |
| 398 self.md5 = None |
| 399 self.base64md5 = None |
| 400 |
| 401 if isinstance(s, unicode): |
| 402 s = s.encode("utf-8") |
| 403 fp = StringIO.StringIO(s) |
| 404 r = self.set_contents_from_file(fp, headers, replace, cb, num_cb, |
| 405 policy, md5) |
| 406 fp.close() |
| 407 return r |
| OLD | NEW |