"""
This is a Custom Storage System for Django with CouchDB backend.
Created by Christian Klein.
(c) Copyright 2009 HUDORA GmbH. All Rights Reserved.
"""
import os
from cStringIO import StringIO
from urlparse import urljoin
from urllib import quote_plus

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

try:
    import couchdb
except ImportError:
    raise ImproperlyConfigured("Could not load couchdb dependency.\
    \nSee http://code.google.com/p/couchdb-python/")

DEFAULT_SERVER= getattr(settings, 'COUCHDB_DEFAULT_SERVER', 'http://couchdb.local:5984')
STORAGE_OPTIONS= getattr(settings, 'COUCHDB_STORAGE_OPTIONS', {})


class CouchDBStorage(Storage):
    """
    CouchDBStorage - a Django Storage class for CouchDB.

    The CouchDBStorage can be configured in settings.py, e.g.::
    
        COUCHDB_STORAGE_OPTIONS = {
            'server': "http://example.org", 
            'database': 'database_name'
        }

    Alternatively, the configuration can be passed as a dictionary.
    """
    def __init__(self, **kwargs):
        kwargs.update(STORAGE_OPTIONS)
        self.base_url = kwargs.get('server', DEFAULT_SERVER)
        server = couchdb.client.Server(self.base_url)
        self.db = server[kwargs.get('database')]

    def _put_file(self, name, content):
        self.db[name] = {'size': len(content)}
        self.db.put_attachment(self.db[name], content, filename='content')
        return name

    def get_document(self, name):
        return self.db.get(name)

    def _open(self, name, mode='rb'):
        couchdb_file = CouchDBFile(name, self, mode=mode)
        return couchdb_file

    def _save(self, name, content):
        content.open()
        if hasattr(content, 'chunks'):
            content_str = ''.join(chunk for chunk in content.chunks())
        else:
            content_str = content.read()
        name = name.replace('/', '-')
        return self._put_file(name, content_str)

    def exists(self, name):
        return name in self.db

    def size(self, name):
        doc = self.get_document(name)
        if doc:
            return doc['size']
        return 0

    def url(self, name):
        return urljoin(self.base_url, 
                       os.path.join(quote_plus(self.db.name), 
                       quote_plus(name), 
                       'content'))

    def delete(self, name):
        try:
            del self.db[name]
        except couchdb.client.ResourceNotFound:
            raise IOError("File not found: %s" % name)

    #def listdir(self, name):
    # _all_docs?
    #    pass


class CouchDBFile(File):
    """
    CouchDBFile - a Django File-like class for CouchDB documents.
    """

    def __init__(self, name, storage, mode):
        self._name = name
        self._storage = storage
        self._mode = mode
        self._is_dirty = False

        try:
            self._doc = self._storage.get_document(name)

            tmp, ext = os.path.split(name)
            if ext:
                filename = "content." + ext
            else:
                filename = "content"
            attachment = self._storage.db.get_attachment(self._doc, filename=filename)
            self.file = StringIO(attachment)
        except couchdb.client.ResourceNotFound:
            if 'r' in self._mode:
                raise ValueError("The file cannot be reopened.")
            else:
                self.file = StringIO()
                self._is_dirty = True

    @property
    def size(self):
        return self._doc['size']

    def write(self, content):
        if 'w' not in self._mode:
            raise AttributeError("File was opened for read-only access.")
        self.file = StringIO(content)
        self._is_dirty = True

    def close(self):
        if self._is_dirty:
            self._storage._put_file(self._name, self.file.getvalue())
        self.file.close()


