'''
Module containing the classes for mime-type dependant storage over WebDAV.
'''

# Copyright (c) 2004 Infrae. All rights reserved.
# See also LICENSE.txt


import sys
import os
import os.path
import mimetypes
import urllib
import urlparse
from pprint import pprint

import dav

try:
    True
    False
except NameError:
    True = 1
    False = not True
    pass

STDERR = sys.stderr

###

class FileExistsError ( Exception ):
    pass
#

class DirectoryMissingError ( Exception ):
    pass
#

class FileMissingError ( Exception ):
    pass
#
###

class Repository:
    """Class describing a WebDAV repository for mime-type dependant storage.
    """

    def __init__ ( self, davcollection ):
        """Initialize this Repository instance.

        Params:
          davcollection: Instance of dav.DAVCollection pointing to the
                         root of the repository.
                         All methods here assume that this instance has full
                         write access to the repository!
        """
        self._repo = davcollection
        return

    def get_path_for_type ( self, prefix, mimetype, encoding=None ):
        """Return the path into the repository for the given mime-type.

        Params:
           prefix:   Path component(s) to prepend to the mime-type part.
                     Normally the path to the repository root.
           mimetype: The mime-type to make the path for.
           encoding: The (optional) encoding to use.
                     If the encoding evaluates to False, no encoding will
                     be incorporated into the repository path.
                     If the encoding is set, it should evaluate to a
                     string wich is used as the last component in the
                     repository path.
        """
        if prefix and prefix[-1] != '/':
            prefix += '/'
        mime_path = urllib.pathname2url(mimetype + '/')
        if encoding is not None:
            mime_path += str(encoding) + '/'
        # build path into mime-type tree
        r_path = urlparse.urljoin(prefix, mime_path)
        return r_path

    def get_full_path ( self, mimetype, remote_path, encoding=None ):
        """Return the full path for the given mime-type, remote_path and encodnig.

        Params:
           mimetype:    The mime-type to make the path for.
           remote_path: The relative path to append to the repository path.
           encoding:    The (optional) encoding to use.
        """
        ret = self.get_path_for_type(self._repo.path, mimetype, encoding)
        r_url = urllib.pathname2url(remote_path)
        return urllib.unquote(urlparse.urljoin(ret, r_url))

    def get_repository_path_for ( self, url, remote_path, use_encoding=True ):
        '''Return the proposed path into the repository for the given url.

        This method tries to find out which mime-type the given url has
        and returns the appropiate path into the repository for this mime-type
        and remote_path component.

        Params:
          url:          The URL to get the mime-type for.
          remote_path:  The relative path to append to the repo path.
          use_encoding: If True, the encoding will be integrated into
                        repository path.
        '''
        # sanity checks
        url_path = urlparse.urlparse(url)[2]
        if not url_path:
            raise ValueError, 'URL %s has no valid path!' % url
        # try to find the mime-type of the object
        mime_type, encoding = mimetypes.guess_type(url)
        if mime_type is None:
            # built in couldn't find an appropriate type
            # try something else
            # XXX change to something sane XXX
            mime_type = 'application/octet-stream'
        if not use_encoding:
            # inclusion of encoding not wanted
            encoding = None
        r_path = self.get_full_path(mime_type, remote_path, encoding)
        return r_path

    def get_collection_for ( self, mime_type, remote_path, encoding=None,
                             create_path=False):
        """Return the collection for the given mime-type/remote path/encoding combo.

        Params:
           mime_type:   The mime-type to use.
           remote_path: The (relative) path appended to the path
                        created by the repository. If remote_path
                        ends in a '/', an error is raised
           encoding:    The encoding or None.
           create_path: if True, create the intermediate path components.
        """
        if not remote_path:
            raise ValueError, 'remote_path must not be empty!'
        rp = self.get_full_path(mime_type, remote_path, encoding)
        if create_path:
            collection = self._create_path_components(rp)
        else:
            # no creation of intermediate path components wanted
            # check if the path to the file exits
            # split and clean path elements
            path_list = [ p for p in rp.split('/') if p ]
            # remove the last path component
            path_list = path_list[1:-1]
            (missing, collection, rest) = self._check_dirs(self._repo, path_list)
            if missing:
                # there is a missing path component
                raise DirectoryMissingError, (missing, collection.path)
        return collection

    def get_file ( self, mime_type, remote_path, encoding=None ):
        """Return the DAVFile object for the given mime-type, remote_path
        and encoding.

        Params:
           mimetype:    The mime-type to make the path for.
           remote_path: The relative path to append to the repository path.
           encoding:    The (optional) encoding to use.
        """
        if not remote_path:
            raise ValueError, 'remote_path must not be empty!'
        if remote_path[-1] == '/':
            raise ValueError, 'remote_path must not be a dicretory!'
        collection = self.get_collection_for(mime_type, remote_path, encoding)
        rp = self.get_full_path(mime_type, remote_path, encoding)
        # build a list of all childs of the collection that have
        # the same path as the wanted file
        # which should result in a list with length 1 or 0
        xx = [ o for o in collection.get_child_objects() if o.path == rp ]
        if not xx:
            # empty list, file not found
            raise FileMissingError, 'File %s does not exist!' % rp
        remote_file = xx[0]
        # just in case do an update on the file object
        try:
            remote_file.update()
        except dav.DAVNotFoundError:
            # Ouch... file was deleted in the mean time
            raise FileMissingError, 'File %s does not exist!' % rp
        return remote_file

    def create_file ( self, mime_type, remote_path, data,
                      encoding=None, create_path=True ):
        """Create the a file in the repository.

        Creates a file in the right place in the repository according
        to the mime_type and encoding.
        The remote_path is appended to the path.

        Params:
           mime_type:   The mime-type to use.
           remote_path: The (relative) path appended to the path
                        created by the repository. If remote_path
                        ends in a '/', an error is raised
           data:        The content of the file (can be set to '').
           encoding:    The encoding or None.
           create_path: if True, create the intermediate path components.
        """
        if not mime_type:
            mime_type = 'application/octet-stream'
        if not remote_path:
            raise ValueError, 'remote_path must not be empty!'
        if remote_path[-1] == '/':
            raise ValueError, 'remote_path must not be a directory!'
        collection = self.get_collection_for(mime_type, remote_path, encoding,
                                             create_path=create_path)
        rp = self.get_full_path(mime_type, remote_path, encoding)
        # get the basename of path
        base_name = os.path.basename(urllib.url2pathname(rp))
        # create remote file
        try:
            remote_file = collection.create_file(base_name, data, content_type=mime_type)
            pd = {('contenttype', 'DAV:'): mime_type}
            remote_file.set_properties(pd)
        except dav.DAVCreationFailedError, err:
            # A file with this name was created in the mean time
            raise FileExistsError, 'File %s already exists!' % rp
        return remote_file

    def delete_file ( self, mime_type, remote_path, encoding=None ):
        if not mime_type:
            mime_type = 'application/octet-stream'
        if not remote_path:
            raise ValueError, 'remote_path must not be empty!'
        if remote_path[-1] == '/':
            raise ValueError, 'remote_path must not be a directory!'
        collection = self.get_collection_for(mime_type, remote_path, encoding)
        rp = self.get_full_path(mime_type, remote_path, encoding)
        # get the basename of path
        base_name = os.path.basename(urllib.url2pathname(rp))
        try:
            collection.delete(base_name)
        except dav.DAVDeleteFailedError, err:
            raise
        return

    def _make_path_list ( self, path ):
        # split and clean path elements
        path_list = [ p for p in path.split('/') if p ]
        # remove the last path component
##         path_list = path_list[:-1]
        return path_list

    def _create_path_components ( self, path ):
        """Helper to create all intermediate directories for the given path.
        """
        path_list = self._make_path_list(path)
        root = self._repo
        root_list = self._make_path_list(root.path)
        # remove root path from path to check
        path_list = path_list[len(root_list):-1]
        print "AAA", str(path_list)
        # traverse the path components and create them
        # if they're missing
        while True:
            missing, collection, rest = self._check_dirs(root, path_list)
            if not missing:
                break
            else:
                coll = collection.create_collection(missing)
                path_list = rest[:]
                root = coll
        return collection

    def _check_dirs ( self, root, path_list ):
        """Helper that checks if all components of a path a there.
        """
        # set the stage for testing for the components
        prefix = root.path
        curr = root
        u2p = urllib.url2pathname
        uq = urllib.unquote
        # check every remaining component if its exitant
        while path_list:
            component = path_list.pop(0)
            # normalize the path
            p = uq(prefix + component + '/')
            childs = curr.get_child_objects()
            found = False
            for o in childs:
                if not o.is_collection():
                    # short-cut, we're only interested in collections
                    continue
                op = u2p(o.path)
                if op == p:
                    # desired collection found
                    # set found, curr and prefix
                    # and leave inner loop
                    prefix = p
                    curr = o
                    found = True
                    break
            if not found:
                # the current component is missing
                # return it along with the last collection we got
                # and the remaining components to scan
                return (component, curr, path_list)
        # everything went well
        # return the current collection
        # (which is the one pointing to the last path element)
        return (None, curr, [])
#
###

if '__main__' == __name__:
    curr = 0
    conn = dav.DAVConnection('localhost')
    conn.set_auth('root', 'rootix')
    repo = Repository(dav.DAVCollection('http://localhost/rrr/repo/', conn))
    localfile = './mimerepo.py'
    data = open(localfile, 'rb').read()
    mime_type, encoding = mimetypes.guess_type(localfile)
##     mime_type, encoding = ('text/plain', None)
    print 'guessed:', mime_type, encoding
    while True:
        remotepath = '%02d/mimerepo.py' % curr
        print 'remote path:', remotepath
        try:
            remotefile = repo.create_file(mime_type, remotepath, data, encoding)
            print 'created', remotefile.path
            print repr(remotefile.get_all_properties())
            break
        except FileExistsError:
            print 'file does exist!'
            curr += 1
            pass
    print remotefile.url
