"""
Custom storage for django with Mosso Cloud Files backend.
Created by Rich Leland <rich@richleland.com>.
"""
import re

from django.conf import settings
from django.core.files import File
from django.core.files.storage import Storage
from django.core.exceptions import ImproperlyConfigured
from django.utils.text import get_valid_filename


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
CLOUDFILES_TTL = getattr(settings, 'CLOUDFILES_TTL', 600)


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.
    """

    def __init__(self, username=None, api_key=None, container=None):
        """
        Initialize the settings for the connection and container.
        """
        self.username = username or settings.CLOUDFILES_USERNAME
        self.api_key = api_key or settings.CLOUDFILES_API_KEY
        self.container_name = container or settings.CLOUDFILES_CONTAINER

    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)

    def _get_connection(self):
        if not hasattr(self, '_connection'):
            self._connection = cloudfiles.get_connection(self.username,
                                                         self.api_key)
        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, value):
        """
        Set the container, making it publicly available (on Limelight CDN) if
        it is not already.
        """
        if not value.is_public():
            value.make_public()
        self._container = value

    container = property(_get_container, _set_container)

    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'):
        """
        Not sure if this is the proper way to execute this. Would love input.
        """
        return File(self._get_cloud_obj(name).read())

    def _save(self, name, content):
        """
        Here we're opening the content object and saving it to the Cloud Files
        service. We have to set the content_type so it's delivered properly
        when requested via public URI.
        """
        content.open()
        if hasattr(content, 'chunks'):
            content_str = ''.join(chunk for chunk in content.chunks())
        else:
            content_str = content.read()
        cloud_obj = self.container.create_object(name)
        cloud_obj.content_type = content.file.content_type
        cloud_obj.send(content_str)
        content.close()
        return name

    def delete(self, name):
        """
        Deletes the specified file from the storage system.
        """
        self.container.delete_object(name)

    def exists(self, name):
        """
        Returns True if a file referened 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 of lists;
        the first item being directories, the second item being files.
        """
        return ([], self.container.list_objects(path=path))

    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 self._get_cloud_obj(name).public_uri()

@@ -1172,16 +1172,49 @@
 %0A    %22%22%22
+%0A    default_quick_listdir = True
 %0A%0A    de
@@ -4311,32 +4311,443 @@
 h):%0A        %22%22%22%0A
+        Lists the contents of the specified path, returning a 2-tuple; the%0A        first being an empty list of directories (not available for quick-%0A        listing), the second being a list of filenames.%0A        %0A        If the list of directories is required, use the full_listdir method.%0A        %22%22%22%0A        return (%5B%5D, self.container.list_objects(path=path))%0A%0A    def full_listdir(self, path):%0A        %22%22%22%0A
         Lists th
@@ -4881,35 +4881,32 @@
  files.%0A        
-%22%22%22
 %0A        return 
@@ -4902,58 +4902,700 @@
     
-return (%5B%5D, self.container.list_objects(path=path)
+On large containers, this may be a slow operation for root containers%0A        because every single object must be returned (cloudfiles does not%0A        provide an explicit way of listing directories).%0A        %22%22%22%0A        dirs = set()%0A        files = %5B%5D%0A        if path and not path.endswith('/'):%0A            path = '%25s/' %25 path%0A        path_len = len(path)%0A        for name in self.container.list_objects(prefix=path):%0A            name = name%5Bpath_len:%5D%0A            slash = name%5B1:-1%5D.find('/') + 1%0A            if slash:%0A                dirs.add(name%5B:slash%5D)%0A            elif name:%0A                files.append(name)%0A        dirs = list(dirs)%0A        dirs.sort()%0A        return (dirs, files
 )%0A%0A 

Add a full_listdir method for CloudFilesStorage which returns directory names as well (the default listdir method uses the cloudfiles' list_objects(path=...) method, which does not return directories)