"""
StorageObject module
See COPYING for license information
"""
import json
import mimetypes
import os
import StringIO
import UserDict
from object_storage import errors
from object_storage.utils import get_path
class StorageObjectModel(UserDict.UserDict):
def __init__(self, controller, container, name, headers={}):
self.container = container
self.name = name
_headers = {}
# Lowercase headers
for key, value in headers.iteritems():
_key = key.lower()
_headers[_key] = value
self.headers = _headers
self._meta = None
_properties = {'container': self.container, 'name': self.name}
_properties['size'] = int(self.headers.get('content-length') or\
self.headers.get('bytes') or\
self.headers.get('size') or 0)
_properties['content_type'] = self.headers.get('content_type') or\
self.headers.get('content-type')
_properties['last_modified'] = self.headers.get('last_modified') or\
self.headers.get('last-modified')
_properties['hash'] = self.headers.get('etag') or\
self.headers.get('hash')
_properties['manifest'] = self.headers.get('manifest')
_properties['content_encoding'] = self.headers.get('content_encoding') or\
self.headers.get('content-encoding')
_properties['cache_control'] = self.headers.get('cache-control')
_properties['cdn_url'] = self.headers.get('x-cdn-url')
_properties['cdn_ssl_url'] = self.headers.get('x-cdn-ssl-url')
_properties['path'] = controller.path
_properties['url'] = controller.url
meta = {}
for key, value in self.headers.iteritems():
if key.startswith('meta_'):
meta[key[5:]] = value
elif key.startswith('x-object-meta-'):
meta[key[14:]] = value
self.meta = meta
_properties['meta'] = self.meta
self.properties = _properties
self.data = self.properties
[docs]class StorageObject:
"""
Representation of a Object Storage object.
"""
chunk_size=10*1024
def __init__(self, container, name, headers=None, client=None):
""" constructor for StorageObject
@param container: container name
@param name: object name
@param headers: init headers to use when initializing the object
@param client: `object_storage.client` instance.
"""
self.container = container
self.name = name
self.client = client
self.model = None
self.content_type = None
if headers:
self.model = StorageObjectModel(self, self.container, self.name, headers)
[docs] def exists(self):
""" Tries to load the object to check existance
@raises ResponseError
@return: boolean, true if exists else false
"""
def _formatter(res):
self.model = StorageObjectModel(self, self.container, self.name, res.headers)
return True
try:
return self.make_request('HEAD', headers={'X-Context': 'cdn'}, formatter=_formatter)
except errors.NotFound:
return False
[docs] def load(self, cdn=True):
""" load data for the object
@param cdn: True if you want CDN information; default=True
@return: object_storage.storage_object, self
"""
headers = {}
if cdn:
headers.setdefault('X-Context', 'cdn')
def _formatter(res):
self.model = StorageObjectModel(self, self.container, self.name, res.headers)
return self
return self.make_request('HEAD', headers=headers, formatter=_formatter)
[docs] def get_info(self):
""" loads data if not already available and returns the properties """
if not self.model:
self.load()
return self.model.properties
@property
[docs] def properties(self):
""" loads data if not already available and returns the properties """
return self.get_info()
props = properties
@property
@property
@property
[docs] def url(self):
""" Get the URL of the object """
path = [self.container, self.name]
return self.client.get_url(path)
@property
[docs] def path(self):
""" Get the path of the object """
path = [self.container, self.name]
return get_path(path)
[docs] def list(self, limit=None, marker=None):
""" Uses sudo-hierarchical structure to list the children objects.
@param limit: limit of results to return.
@param marker: start listing after this object name
@raises ResponseError
@return: list of StorageObject instances
"""
params = {'format': 'json',
'path': self.name}
if limit:
params['limit'] = limit
if marker:
params['marker'] = marker
def _formatter(res):
objects = []
if res.content:
items = json.loads(res.content)
for item in items:
obj = self.client.storage_object(self.container,
item['name'],
headers=item)
objects.append(obj)
return objects
return self.client.make_request('GET', [self.container], params=params, formatter=_formatter)
[docs] def is_dir(self):
""" returns True if content_type is 'text/directory' """
return self.model.content_type == 'text/directory'
[docs] def create(self):
""" Create object
@raises ResponseError
@return: StorageObject - self
"""
content_type = self.content_type or mimetypes.guess_type(self.name)[0]
if not content_type:
content_type = 'application/octet-stream'
headers = {'content-type': content_type, 'Content-Length': '0'}
def _formatter(res):
return self
return self.make_request('PUT', headers=headers, formatter=_formatter)
[docs] def delete(self, recursive=False):
""" Delete object
@raises ResponseError
@return: True
"""
return self.client.delete_object(self.container, self.name)
[docs] def read(self, size=0, offset=0):
""" Reads object content
@param size: number of bytes to read (0 reads all of the object data)
@param offset: number of bytes to offset the read
@raises ResponseError
@return: str, data
"""
headers = {}
if size > 0:
_range = 'bytes=%d-%d' % (offset, (offset + size) - 1)
headers['Range'] = _range
def _formatter(res):
return res.content
return self.make_request('GET', headers=headers, formatter=_formatter)
[docs] def save_to_filename(self, filename):
""" Reads object content into a file
@param filename: filename
@raises ResponseError
"""
f = open(filename, 'wb')
conn = self.chunk_download()
try:
for data in conn:
f.write(data)
finally:
f.close()
[docs] def chunk_download(self, chunk_size=None):
""" Returns an iterator to read the object data.
@param chunk_size: size of the chunks to read in.
If not defined uses self.chunk_size
@raises: ResponseError
@return: iterable
"""
chunk_size = chunk_size or self.chunk_size
return self.client.chunk_download([self.container, self.name], chunk_size=chunk_size)
iter_content = chunk_download
__iter__ = chunk_download
[docs] def chunk_upload(self, headers=None):
""" Returns a chunkable upload instance.
This is needed for transient data uploads
@param headers: extra headers to use to initialize the request
@raises: ResponseError
@return: object that responds to o.send('data') to send data
and o.finish() to finish the upload.
"""
chunkable = self.client.chunk_upload([self.container, self.name], headers=headers)
return chunkable
[docs] def send(self, data):
""" Uploads object data
@param data: either a file-like object or a string.
@raises: ResponseError
@return: StorageObject, self
"""
size = None
if isinstance(data, file):
try:
data.flush()
except IOError:
pass
size = int(os.fstat(data.fileno())[6])
else:
if hasattr(data, '__len__'):
size = len(data)
headers = {}
content_type = self.content_type
if not content_type:
_type = None
if hasattr(data, 'name'):
_type = mimetypes.guess_type(data.name)[0]
content_type = _type or mimetypes.guess_type(self.name)[0] or 'application/octet-stream'
headers['Content-Type'] = content_type
if size or size == 0:
headers['Content-Length'] = str(size)
else:
headers['Transfer-Encoding'] = 'chunked'
return self.make_request('PUT', data=data, headers=headers, formatter=lambda r: self)
write = send
[docs] def upload_directory(self, directory):
""" Uploads an entire directory
@param directory: path of the directory to upload
@raises: ResponseError
"""
directories = []
files = []
for root, dirnames, filenames in os.walk(directory):
for _dir in dirnames:
directories.append(os.path.relpath(os.path.join(root, _dir)))
for _file in filenames:
files.append(os.path.relpath(os.path.join(root, _file)))
for _dir in directories:
obj = self.__class__(self.container, _dir, client=self.client)
obj.content_type = 'application/directory'
obj.create()
for _file in files:
obj = self.__class__(self.container, _file, client=self.client)
obj.load_from_filename(_file)
[docs] def load_from_filename(self, filename):
""" Uploads a file from the local filename
@param filename: path of the directory to upload
@raises: ResponseError, IOError
"""
if os.path.isdir(filename):
self.upload_directory(filename)
else:
with open(filename, 'rb') as _file:
return self.send(_file)
[docs] def copy_from(self, old_obj, *args, **kwargs):
""" Copies content from an existing object
@param old_obj: StorageObject instance to copy data from
@raises: ResponseError
@return: StorageObject, self
"""
headers = {}
headers['X-Copy-From'] = old_obj.path
headers['Content-Length'] = "0"
return self.make_request('PUT', headers=headers, *args, formatter=lambda r: self, **kwargs)
[docs] def copy_to(self, new_obj, *args, **kwargs):
""" Copies content from an existing object
@param new_obj: StorageObject instance to copy data to
@raises: ResponseError
@return: StorageObject, new_obj
"""
headers = {}
headers['Destination'] = new_obj.path
headers['Content-Length'] = "0"
return self.make_request('COPY', headers=headers, *args, formatter=lambda r: new_obj, **kwargs)
[docs] def rename(self, new_obj, *args, **kwargs):
""" Copies content to a new object existing object and deletes the current object
@param new_obj: StorageObject instance to copy data to
@raises: ResponseError
"""
def _delete(res):
return self.delete()
def _copy_to(res):
return new_obj.copy_from(self, *args, formatter=_delete, **kwargs)
return new_obj.make_request('PUT', headers={'Content-Length': '0'}, formatter=_copy_to)
[docs] def search(self, q, options=None, **kwargs):
""" Search within path """
options = options or {}
options.update({'path': "%s/%s" % (self.container, self.name)})
return self.client.search(q, options=options, **kwargs)
[docs] def prime_cdn(self):
""" Prime the object for CDN usage """
headers = {'X-Context': 'cdn', 'X-Cdn-Load': True}
return self.make_request('POST', headers=headers, *args, **kwargs)
[docs] def purge_cdn(self):
""" Purge the object for CDN usage """
headers = {'X-Context': 'cdn', 'X-Cdn-Purge': True}
return self.make_request('POST', headers=headers, *args, **kwargs)
[docs] def make_request(self, method, path=None, *args, **kwargs):
""" returns a request object """
path = [self.container, self.name]
return self.client.make_request(method, path, *args, **kwargs)
[docs] def fileno(self):
return 1
def __len__(self):
if not self.model:
self.load()
return int(self.model['size'])
def __getitem__(self, name):
new_name = self.client.delimiter.join([self.name, name])
return self.client.storage_object(self.container, new_name)
def __str__(self):
size = 'Unknown'
if self.model:
size = self.model.get('size', 0)
return 'StorageObject({0}, {1}, {2}B)'.format(self.container.encode("utf-8"), self.name.encode("utf-8"), size)
__repr__ = __str__
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
pass