########################################################################
# $Header: /var/local/cvsroot/4Suite/Ft/Server/Server/Drivers/PathImp.py,v 1.16 2005/04/06 06:17:13 mbrown Exp $
"""
Repository path related classes and functions

Copyright 2005 Fourthought, Inc. (USA).
Detailed license and copyright information: http://4suite.org/COPYRIGHT
Project home, documentation, distributions: http://4suite.org/
"""

import urllib, urlparse, types, warnings
from Ft.Server import FTSS_URI_SCHEME
from Ft.Server.Common import Schema, ResourceTypes
from Ft.Server.Server import FtServerServerException, Error

def RepoPathToUri(path):
    from Ft.Server.Common import Util
    return Util.RepoPathToUri(path)


def CreateInitialPath(documentRoot, driver):
    """Called to get the first path in a transaction"""
    tempP = Path('/;no-traverse', driver, None, None)
    p =  tempP.normalize(documentRoot+';no-traverse')
    #Now fix the display stuff
    p.displayPath = '/'
    p.displaySteps = ['/']
    p.displayPaths = ['/']
    p._documentRoot = p
    return p


def QuickCreate(path):
    p = CreateInitialPath('/',None)
    return p.normalize(path)


class Path:
    """
    An abstraction of a path in a particular repository; provides
    functionality for relative path resolution, alias traversal, and
    non-'/' document root.

    If basePath constructor arg is not None, pathString must be
    relative.
    """
    def __init__(self, pathString, driver, documentRoot, basePath):
        self._driver = driver  #Needed to resolve aliases
        self._documentRoot = documentRoot #Needed to resolve absolute paths

        if basePath is None:
            #Very special case
            self.displayPath = '/'
            self.absolutePath = '/'
            self.absolutePathAsUri = RepoPathToUri('/')
            self.displaySteps = ['/']
            #self.absoluteSteps = ['/']
            self.displayPaths = ['/']
            self.absolutePaths = ['/']
            self.name = '/'
            self.resourceType = ResourceTypes.RESOURCE_CONTENT
        else:
            #Initialize ourselves from the basePath

            # FIXME: urlparse is much less efficient than a regex tailored
            # to this task. We only need to split on the last ';' to get
            # a path and a suffix.
            #
            #Not anymore (since we support multiple ;s
            pathString, suffixString = urlparse.urlparse(pathString)[2:4]

            suffixes = suffixString.split(';')
            self.resourceType = ResourceTypes.RESOURCE_CONTENT
            traverseAlias = 1 #Default is to traverse

            #Do these in order to allow for overriding
            for s in suffixes:
                if not s: continue
                elif s == 'metadata':
                    self.resourceType = ResourceTypes.RESOURCE_METADATA
                elif s == 'cache':
                    self.resourceType = ResourceTypes.RESOURCE_CACHE
                elif s == 'content':
                    self.resourceType = ResourceTypes.RESOURCE_CONTENT
                elif s == 'no-traverse':
                    traverseAlias = 0
                elif s == 'traverse':
                    traverseAlias = 1
                else:
                    warnings.warn("Unknown Path argument: " + str(s),
                                  RuntimeWarning, 2)

            # pathString could be empty, so use [:1], not [0]
            if pathString[:1] == '/':
                #We got a whole
                # FIXME: l10n
                raise "Should not have a '/'"

            # When we get in here, we have a base path and the new path is
            # based off of the base path.  Always do this based on steps
            # because it is the easiest

            dSteps = basePath.displaySteps[:]
            dPaths = basePath.displayPaths[:]
            aPaths = basePath.absolutePaths[:]

            fields = filter(None, pathString.split('/'))

            for curStep in fields:
                lastStep = curStep is fields[-1]
                if curStep == '.':
                    continue
                elif curStep == '..':
                    #The limiting factor here is the dPath
                    if len(dPaths) > 1:
                        dSteps = dSteps[:-1]
                        dPaths = dPaths[:-1]
                        aPaths = aPaths[:-1]
                    continue

                dSteps.append(curStep)
                if dPaths[-1] == '/':
                    dPaths.append('/' + curStep)
                else:
                    dPaths.append(dPaths[-1] + '/' + curStep)

                #See if this step is an alias.  If it is, then we need to
                #Adjust the absolute step and path accordingly

                if aPaths[-1] == '/':
                    testPath = '/' + curStep
                else:
                    testPath = aPaths[-1] + '/' + curStep

                if self._driver and (not lastStep or traverseAlias):
                    while len(self._driver.getSystemModel().complete(testPath,
                                                                     Schema.TYPE,
                                                                     Schema.g_rdfResourceTypes[ResourceTypes.ResourceType.ALIAS])):
                        #It is!!!
                        #Actually add the alias reference to the path
                        testPath = str(self._driver.getSystemModel().complete(testPath,
                                                                              Schema.ALIAS_REFERENCE,
                                                                              None)[0].object)
                aPaths.append(testPath)

            self.absolutePath = aPaths[-1]
            self.absolutePathAsUri = RepoPathToUri(self.absolutePath)

            self.displayPath = dPaths[-1]
            if len(suffixes):
                #We never add in "no-traverse", "traverse" or "content"
                for s in suffixes:
                    if not (s in ['',"no-traverse","traverse","content"]):
                        self.displayPath += ';' + s

            self.displaySteps = dSteps
            self.displayPaths = dPaths
            self.absolutePaths = aPaths
            self.name = dSteps[-1]

        return


    def normalize(self, pathString, traverseLastAlias=None):
        """
        Returns a new Path instance representing the result of
        resolving the given pathString (a relative path, usually),
        with the current path as the base.
        """
        if not isinstance(pathString, (str, unicode)) or not pathString:
            raise FtServerServerException(Error.UNKNOWN_PATH,path=pathString)

        if (traverseLastAlias != None):
            warnings.warn("You are using a deprecated traverse alias feature.  Please specify with path arguments ;traverse or ;no-traverse",
                          DeprecationWarning, 2)
            if traverseLastAlias:
                pathString += ";traverse"
            else:
                pathString += ";no-traverse"

        #Quick normalization of // at the beginning
        while pathString[:2] == '//':
            pathString = pathString[1:]

        # this is a massive bug. sigh.
        scheme, netloc, pathString, params, query, fragment = urlparse.urlparse(pathString)

        #Quick normalization of // at the beginning (We must do this twice because of a potential scheme)
        while pathString[:2] == '//':
            pathString = pathString[1:]

        suffixes = params.split(';')
        rt = self.resourceType #Default to ours
        traverseAlias = 1 #Default is to traverse

        #Do these in order to allow for overiding
        for s in suffixes:
            if not s: continue
            elif s == 'metadata':
                rt = ResourceTypes.RESOURCE_METADATA
            elif s == 'cache':
                rt = ResourceTypes.RESOURCE_CACHE
            elif s == 'content':
                rt = ResourceTypes.RESOURCE_CONTENT
            elif s == 'no-traverse':
                traverseAlias = 0
            elif s == 'traverse':
                traverseAlias = 1
            else:
                warnings.warn("Unknown Path argument: " + str(s),
                              RuntimeWarning, 2)

        sufPathString = pathString
        if params:
            sufPathString += ';' + params

        # pathString could be empty, so use [:1], not [0]
        if pathString[:1] == '/':
            #Should alias traversal logic fall in here?
            if self._documentRoot:
                dr = self._documentRoot
            else:
                #If we have no document Root then we are the document Root
                dr = self
            if len(pathString) == 1:
                if rt == dr.resourceType:
                    return dr
                if rt == ResourceTypes.RESOURCE_METADATA:
                    return dr.normalize('.;metadata')
                elif rt == ResourceTypes.RESOURCE_CACHE:
                    return dr.normalize('.;cache')
                return dr.normalize('.;content')
            #Save a recurse here
            #return dr.normalize(sufPathString[1:])
            return Path(sufPathString[1:],
                        self._driver,
                        dr,
                        dr)

        return Path(sufPathString, self._driver, self._documentRoot,
                    self)

    def getParentPath(self):
        """
        Returns a new Path instance representing the current path's
        parent.
        """
        return self.normalize('..')


    def __str__(self):
        t = "content"
        if self.resourceType == ResourceTypes.RESOURCE_METADATA:
            t = "metadata"
        elif self.resourceType == ResourceTypes.RESOURCE_CACHE:
            t = 'cache'
        return "<Path '%s' abs='%s' type='%s'>" % (self.displayPath,
                                                   self.absolutePath,
                                                   t)
