summaryrefslogtreecommitdiff
path: root/storages/backends/mosso.py
diff options
context:
space:
mode:
authorYuval Adam <yuval@segmanta.com>2014-03-20 17:49:16 +0200
committerYuval Adam <yuval@segmanta.com>2014-03-20 17:49:16 +0200
commit75f93a3f7854a7383febd047391e2bfd4caceee0 (patch)
treed5e634ef292c6b84acacc3982dbcaa0d300a346e /storages/backends/mosso.py
Initial git fork of django-storagesHEADmaster
Diffstat (limited to 'storages/backends/mosso.py')
-rw-r--r--storages/backends/mosso.py347
1 files changed, 347 insertions, 0 deletions
diff --git a/storages/backends/mosso.py b/storages/backends/mosso.py
new file mode 100644
index 0000000..5a4812d
--- /dev/null
+++ b/storages/backends/mosso.py
@@ -0,0 +1,347 @@
+"""
+Custom storage for django with Mosso Cloud Files backend.
+Created by Rich Leland <rich@richleland.com>.
+"""
+import os
+import warnings
+warnings.simplefilter('always', PendingDeprecationWarning)
+warnings.warn("The mosso module will be deprecated in version 1.2 of "
+ "django-storages. The CloudFiles code has been moved into"
+ "django-cumulus at http://github.com/richleland/django-cumulus.",
+ PendingDeprecationWarning)
+
+from django.conf import settings
+from django.core.exceptions import ImproperlyConfigured
+from django.core.files import File
+from django.core.files.storage import Storage
+from django.utils.text import get_valid_filename
+
+try:
+ from cStringIO import StringIO
+except:
+ from StringIO import StringIO
+
+try:
+ import cloudfiles
+ from cloudfiles.errors import NoSuchObject
+except ImportError:
+ raise ImproperlyConfigured("Could not load cloudfiles dependency. See "
+ "http://www.mosso.com/cloudfiles.jsp.")
+
+# TODO: implement TTL into cloudfiles methods
+TTL = getattr(settings, 'CLOUDFILES_TTL', 600)
+CONNECTION_KWARGS = getattr(settings, 'CLOUDFILES_CONNECTION_KWARGS', {})
+SSL = getattr(settings, 'CLOUDFILES_SSL', False)
+
+
+def cloudfiles_upload_to(self, filename):
+ """
+ Simple, custom upload_to because Cloud Files doesn't support
+ nested containers (directories).
+
+ Actually found this out from @minter:
+ @richleland The Cloud Files APIs do support pseudo-subdirectories, by
+ creating zero-byte files with type application/directory.
+
+ May implement in a future version.
+ """
+ return get_valid_filename(filename)
+
+
+class CloudFilesStorage(Storage):
+ """
+ Custom storage for Mosso Cloud Files.
+ """
+ default_quick_listdir = True
+
+ def __init__(self,
+ username=settings.CLOUDFILES_USERNAME,
+ api_key=settings.CLOUDFILES_API_KEY,
+ container=settings.CLOUDFILES_CONTAINER,
+ connection_kwargs=CONNECTION_KWARGS):
+ """
+ Initialize the settings for the connection and container.
+ """
+ self.username = username
+ self.api_key = api_key
+ self.container_name = container
+ self.connection_kwargs = connection_kwargs
+
+ def __getstate__(self):
+ """
+ Return a picklable representation of the storage.
+ """
+ return dict(username=self.username,
+ api_key=self.api_key,
+ container_name=self.container_name,
+ connection_kwargs=self.connection_kwargs)
+
+ def _get_connection(self):
+ if not hasattr(self, '_connection'):
+ self._connection = cloudfiles.get_connection(self.username,
+ self.api_key, **self.connection_kwargs)
+ return self._connection
+
+ def _set_connection(self, value):
+ self._connection = value
+
+ connection = property(_get_connection, _set_connection)
+
+ def _get_container(self):
+ if not hasattr(self, '_container'):
+ self.container = self.connection.get_container(
+ self.container_name)
+ return self._container
+
+ def _set_container(self, container):
+ """
+ Set the container, making it publicly available (on Limelight CDN) if
+ it is not already.
+ """
+ if not container.is_public():
+ container.make_public()
+ if hasattr(self, '_container_public_uri'):
+ delattr(self, '_container_public_uri')
+ self._container = container
+
+ container = property(_get_container, _set_container)
+
+ def _get_container_url(self):
+ if not hasattr(self, '_container_public_uri'):
+ if SSL:
+ self._container_public_uri = self.container.public_ssl_uri()
+ else:
+ self._container_public_uri = self.container.public_uri()
+ return self._container_public_uri
+
+ container_url = property(_get_container_url)
+
+ def _get_cloud_obj(self, name):
+ """
+ Helper function to get retrieve the requested Cloud Files Object.
+ """
+ return self.container.get_object(name)
+
+ def _open(self, name, mode='rb'):
+ """
+ Return the CloudFilesStorageFile.
+ """
+ return CloudFilesStorageFile(storage=self, name=name)
+
+ def _save(self, name, content):
+ """
+ Use the Cloud Files service to write ``content`` to a remote file
+ (called ``name``).
+ """
+ (path, last) = os.path.split(name)
+ if path:
+ try:
+ self.container.get_object(path)
+ except NoSuchObject:
+ self._save(path, CloudStorageDirectory(path))
+
+ cloud_obj = self.container.create_object(name)
+ cloud_obj.size = content.size
+
+ content.open()
+ # If the content type is available, pass it in directly rather than
+ # getting the cloud object to try to guess.
+ if hasattr(content.file, 'content_type'):
+ cloud_obj.content_type = content.file.content_type
+ cloud_obj.send(content)
+ content.close()
+ return name
+
+ def delete(self, name):
+ """
+ Deletes the specified file from the storage system.
+ """
+ # If the file exists, delete it.
+ if self.exists(name):
+ self.container.delete_object(name)
+
+ def exists(self, name):
+ """
+ Returns True if a file referenced by the given name already exists in
+ the storage system, or False if the name is available for a new file.
+ """
+ try:
+ self._get_cloud_obj(name)
+ return True
+ except NoSuchObject:
+ return False
+
+ def listdir(self, path):
+ """
+ Lists the contents of the specified path, returning a 2-tuple; the
+ first being an empty list of directories (not available for quick-
+ listing), the second being a list of filenames.
+
+ If the list of directories is required, use the full_listdir method.
+ """
+ files = []
+ if path and not path.endswith('/'):
+ path = '%s/' % path
+ path_len = len(path)
+ for name in self.container.list_objects(path=path):
+ files.append(name[path_len:])
+ return ([], files)
+
+ def full_listdir(self, path):
+ """
+ Lists the contents of the specified path, returning a 2-tuple of lists;
+ the first item being directories, the second item being files.
+
+ On large containers, this may be a slow operation for root containers
+ because every single object must be returned (cloudfiles does not
+ provide an explicit way of listing directories).
+ """
+ dirs = set()
+ files = []
+ if path and not path.endswith('/'):
+ path = '%s/' % path
+ path_len = len(path)
+ for name in self.container.list_objects(prefix=path):
+ name = name[path_len:]
+ slash = name[1:-1].find('/') + 1
+ if slash:
+ dirs.add(name[:slash])
+ elif name:
+ files.append(name)
+ dirs = list(dirs)
+ dirs.sort()
+ return (dirs, files)
+
+ def size(self, name):
+ """
+ Returns the total size, in bytes, of the file specified by name.
+ """
+ return self._get_cloud_obj(name).size
+
+ def url(self, name):
+ """
+ Returns an absolute URL where the file's contents can be accessed
+ directly by a web browser.
+ """
+ return '%s/%s' % (self.container_url, name)
+
+
+class CloudStorageDirectory(File):
+ """
+ A File-like object that creates a directory at cloudfiles
+ """
+
+ def __init__(self, name):
+ super(CloudStorageDirectory, self).__init__(StringIO(), name=name)
+ self.file.content_type = 'application/directory'
+ self.size = 0
+
+ def __str__(self):
+ return 'directory'
+
+ def __nonzero__(self):
+ return True
+
+ def open(self, mode=None):
+ self.seek(0)
+
+ def close(self):
+ pass
+
+
+class CloudFilesStorageFile(File):
+ closed = False
+
+ def __init__(self, storage, name, *args, **kwargs):
+ self._storage = storage
+ super(CloudFilesStorageFile, self).__init__(file=None, name=name,
+ *args, **kwargs)
+ self._pos = 0
+
+
+ def _get_size(self):
+ if not hasattr(self, '_size'):
+ self._size = self._storage.size(self.name)
+ return self._size
+
+ def _set_size(self, size):
+ self._size = size
+
+ size = property(_get_size, _set_size)
+
+ def _get_file(self):
+ if not hasattr(self, '_file'):
+ self._file = self._storage._get_cloud_obj(self.name)
+ return self._file
+
+ def _set_file(self, value):
+ if value is None:
+ if hasattr(self, '_file'):
+ del self._file
+ else:
+ self._file = value
+
+ file = property(_get_file, _set_file)
+
+ def read(self, num_bytes=None):
+ if self._pos == self._get_size():
+ return None
+ if self._pos + num_bytes > self._get_size():
+ num_bytes = self._get_size() - self._pos
+ data = self.file.read(size=num_bytes or -1, offset=self._pos)
+ self._pos += len(data)
+ return data
+
+ def open(self, *args, **kwargs):
+ """
+ Open the cloud file object.
+ """
+ self.file
+ self._pos = 0
+
+ def close(self, *args, **kwargs):
+ self._pos = 0
+
+ @property
+ def closed(self):
+ return not hasattr(self, '_file')
+
+ def seek(self, pos):
+ self._pos = pos
+
+
+class ThreadSafeCloudFilesStorage(CloudFilesStorage):
+ """
+ Extends CloudFilesStorage to make it thread safer.
+
+ As long as you don't pass container or cloud objects
+ between threads, you'll be thread safe.
+
+ Uses one cloudfiles connection per thread.
+ """
+
+ def __init__(self, *args, **kwargs):
+ super(ThreadSafeCloudFilesStorage, self).__init__(*args, **kwargs)
+
+ import threading
+ self.local_cache = threading.local()
+
+ def _get_connection(self):
+ if not hasattr(self.local_cache, 'connection'):
+ connection = cloudfiles.get_connection(self.username,
+ self.api_key, **self.connection_kwargs)
+ self.local_cache.connection = connection
+
+ return self.local_cache.connection
+
+ connection = property(_get_connection, CloudFilesStorage._set_connection)
+
+ def _get_container(self):
+ if not hasattr(self.local_cache, 'container'):
+ container = self.connection.get_container(self.container_name)
+ self.local_cache.container = container
+
+ return self.local_cache.container
+
+ container = property(_get_container, CloudFilesStorage._set_container)
+