########################################################################
# $Header: /var/local/cvsroot/4Suite/Ft/Server/Client/Commands/Export.py,v 1.17 2005/03/19 10:41:16 jkloth Exp $
"""
Implementation of '4ss export' command
(functions defined here are used by the Ft.Lib.CommandLine framework)

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

__doc__ = """This command will export an application from a 4Suite \
repository, creating a setup.xml file and a mirror of the \
application's resources as files on the local filesystem. The \
application is assumed to be all resources in a particular container \
(including descendant containers). The user doing the export must have \
read access to all of the files in the container.  If necessary, you \
will be prompted first for credentials and access info to connect to \
the repository."""

import os, sys
from distutils import archive_util, dir_util

from Ft.Lib import Uuid, Uri
from Ft.Server.Client import FtServerClientException
from Ft.Server.Client.Commands import CommandUtil
from Ft.Server.Common import AclConstants, ResourceTypes
from Ft.Server.Common.Install import InstallUtil


def Run(options, args):

    baseLocalDir = options.get('directory', '.') + os.sep
    if not os.path.isdir(baseLocalDir):
        raise SystemExit("Local path %s is not a directory" % baseLocalDir)

    baseRepoDir = args.get('path', '/')

    firstDirectory = None
    repo = CommandUtil.GetRepository(options, '4ss.export')
    if repo is not None:
        try:
            # why is this called curRepo
            curRepo = repo.fetchResource(baseRepoDir)

            if curRepo.getAbsolutePath() == '/':
                commonPath = '/'
            else:
                parent = curRepo.getParent()
                commonPath = parent.getAbsolutePath()
                if not commonPath[-1] == '/':
                    commonPath += '' # what's the point of this?

            packageDirName = curRepo.getName()
            if not curRepo.isResourceType(ResourceTypes.ResourceType.CONTAINER):
                raise SystemExit("%s must be a container" % baseRepoDir)

            resourceList, firstDirectory = ExportContainer(curRepo, baseLocalDir, commonPath)
        finally:
            try:
                repo.txRollback()
            except:
                pass

    if packageDirName == "/":
        setupPath = os.path.join(baseLocalDir, 'setup.xml')
    else:
        setupPath = os.path.join(baseLocalDir, packageDirName, 'setup.xml')
    refUri = os.path.join(baseLocalDir, packageDirName, '')
    sys.stderr.write("Writing Setup File %s\n" % setupPath)
    sys.stderr.flush()
    p = InstallUtil.Product(resourceList,
                            name=options.get('title'),
                            version=float(options.get('version', '0')),
                            description=options.get('description'))
    f = open(setupPath, 'w')
    p.serialize(f, refUri=refUri)
    f.close()

    #See if they want zip or tar
    if options.get('archive'):
        #Archive it!!
        base_name = options.get('title', None)
        if not base_name:
            base_name = resourceList[0].path[1:]
        if options.get('version'):
            base_name += '-' + options['version']

        sys.stderr.write("Creating %s archive..." % options['archive'])
        sys.stderr.flush()
        fName = archive_util.make_archive (base_name,
                                   options['archive'],
                                   root_dir=baseLocalDir,
                                   base_dir=resourceList[0].path[1:],
                                   verbose=0,
                                   dry_run=0)

        sys.stderr.write("Created: %s" % fName)
        sys.stderr.flush()
        dir_util.remove_tree(firstDirectory)

    return


def ExportContainer(cont, localDir, commonPath):
    rl = []

    path = cont.getAbsolutePath()
    if path != '/':
        path = path[len(commonPath):]

    rl.append(InstallUtil.Container(path, GetAcl(cont), GetOwner(cont),
                                    GetImt(cont), GetDocDef(cont, commonPath)))

    #Make it locally
    if path == '/':
        newLocalDir = localDir
    else:
        newLocalDir = os.path.join(localDir, cont.getName()) + os.sep
        os.mkdir(newLocalDir)

    #Now, do all of its children
    for child in cont:
        print "Exporting: %s" % child.getAbsolutePath()
        if child.isResourceType(ResourceTypes.ResourceType.CONTAINER):
            rl.extend(ExportContainer(child, newLocalDir, commonPath)[0])
        else:
            rl.extend(ExportRawFile(child, newLocalDir, commonPath))

    return rl, newLocalDir


def ExportRawFile(rf, localDir, commonPath):

    #Write the content
    localFileName = os.path.join(localDir, rf.getName())
    open(localFileName, 'wb').write(rf.getContent())

    acl = GetAcl(rf)
    owner = GetOwner(rf)
    imt = GetImt(rf)
    path = rf.getAbsolutePath()
    path = path[len(commonPath):]

    if rf.resourceType == ResourceTypes.ResourceType.XML_DOCUMENT:
        r = ExportXmlDocument(rf, path, acl, owner, imt, commonPath)
    elif rf.resourceType == ResourceTypes.ResourceType.RDF_DOCUMENT:
        r = ExportRdfDocument(rf, path, acl, owner, imt, commonPath)
    elif rf.resourceType == ResourceTypes.ResourceType.XSLT_DOCUMENT:
        r = ExportXsltDocument(rf, path, acl, owner, imt, commonPath)
    elif rf.resourceType == ResourceTypes.ResourceType.SCHEMATRON_DOCUMENT:
        r = ExportSchematronDocument(rf, path, acl, owner, imt, commonPath)
    elif rf.resourceType == ResourceTypes.ResourceType.XPATH_DOCUMENT_DEFINITION:
        r = ExportXPathDocumentDefinition(rf, path, acl, owner, imt, commonPath, localDir)
    elif rf.resourceType == ResourceTypes.ResourceType.XSLT_DOCUMENT_DEFINITION:
        r = ExportXsltDocumentDefinition(rf, path, acl, owner, imt, commonPath, localDir)
    elif rf.resourceType == ResourceTypes.ResourceType.USER:
        r = ExportUser(rf, path, acl, owner, imt, commonPath)
    elif rf.resourceType == ResourceTypes.ResourceType.GROUP:
        r = ExportGroup(rf, path, acl, owner, imt, commonPath)
    elif rf.resourceType == ResourceTypes.ResourceType.SERVER:
        r = ExportServer(rf, path, acl, owner, imt, commonPath)
    elif rf.resourceType == ResourceTypes.ResourceType.COMMAND:
        r = ExportCommand(rf, path, acl, owner, imt, commonPath)
    elif rf.resourceType == ResourceTypes.ResourceType.ALIAS:
        r = ExportAlias(rf, path, acl, owner, imt, commonPath)
    elif rf.resourceType == ResourceTypes.ResourceType.URI_REFERENCE_FILE:
        r = ExportUriReferenceFile(rf, path, acl, owner, imt, commonPath)
    elif rf.resourceType == ResourceTypes.ResourceType.RAW_FILE:
        r = InstallUtil.RawFile(path, acl, owner, imt)
    else:
        raise Exception("Unexpected resource type: %s" % str(rf.resourceType))

    if type(r) != type([]):
        r = [r]
    for x in r:
        x.setPath(Uri.OsPathToUri(localFileName))
    return r


def ExportXmlDocument(res, path, acl, owner, imt, commonPath):
    dd = GetDocDef(res, commonPath)
    return InstallUtil.XmlDocument(path, acl, owner, imt, dd)


def ExportRdfDocument(res, path, acl, owner, imt, commonPath):
    dd = GetDocDef(res, commonPath)
    return InstallUtil.RdfDocument(path, acl, owner, imt, dd)


def ExportXsltDocument(res, path, acl, owner, imt, commonPath):
    ddPath = GetDocDef(res, commonPath)
    return InstallUtil.XsltDocument(path, acl, owner, imt, ddPath)


def ExportXsltDocumentDefinition(res, path, acl, owner, imt, commonPath, localDir):
    dd = GetDocDef(res, commonPath)
    return InstallUtil.XsltDocumentDefinition(path, acl, owner, imt, dd)

##    People had better just use distutils
##    ext = ExportExtModules(res, localDir)
##    ext.append(InstallUtil.XsltDocumentDefinition(path, acl, owner, imt, dd))
##    return ext


def ExportXPathDocumentDefinition(res, path, acl, owner, imt, commonPath, localDir):
    dd = GetDocDef(res, commonPath)
    return InstallUtil.XPathDocumentDefinition(path, acl, owner, imt, dd)

##    People had better just use distutils
##    ext = ExportExtModules(res, localDir)
##    ext.append(InstallUtil.XPathDocumentDefinition(path, acl, owner, imt, dd))
##    return ext


def ExportServer(res, path, acl, owner, imt, commonPath):
    dd = GetDocDef(res, commonPath)
    return InstallUtil.Server(path, acl, owner, imt, dd)


def ExportCommand(res, path, acl, owner, imt, commonPath):
    dd = GetDocDef(res, commonPath)
    return InstallUtil.Command(path, acl, owner, imt, dd)


def ExportAlias(res, path, acl, owner, imt, commonPath):
    dd = GetDocDef(res, commonPath)
    return InstallUtil.Alias(path, acl, owner, imt, dd, res.getReference().getAbsolutePath())


def ExportUser(res, path, acl, owner, imt, commonPath):
    dd = GetDocDef(res, commonPath)
    return InstallUtil.User(os.path.basename(path),
                            acl,
                            owner,
                            imt,
                            dd,
                            "/".join(path.split("/")[:-1]),
                            None,
                            res.getUserData(),
                            res.getPassword(),
                            )

def ExportGroup(res, path, acl, owner, imt, commonPath):
    dd = GetDocDef(res, commonPath)
    members = map(lambda x:x.getUsername(), res.getMembers())
    return InstallUtil.Group(os.path.basename(path), acl, owner,
                            imt, dd, "/".join(path.split("/")[:-1]),
                            members
                            )


def ExportExtModules(docdef, localDir):
    res = []
    for m in docdef.getCreationParams().extModules:
        if str(m[:26]) == "Ft.Share.ExtensionModules.":
            print "Exporting: ", m
            res.append(InstallUtil.ExtensionModule(m[26:] + '.py'))
            localFile = os.path.join(localDir, res[-1]._file)
            srcFile = os.path.join(InstallUtil.EXT_DIR, res[-1]._file)
            open(localFile, 'w').write(open(srcFile).read())

    return res

def GetAcl(res):
    aclDict = res.getAcl()
    acl = []
    for access, info in aclDict.items():
        for ident, allowed in info.items():
            acl.append((access, ident, allowed))
    return acl

def GetOwner(res):
    owner = res.getOwner()
    ownerName = owner and owner.getUsername() or AclConstants.WORLD_GROUP_NAME
    if ownerName == res.getRoot().getCurrentUser().getUsername():
        ownerName = None
    return ownerName

def GetImt(res):
    imt = res.getImt()
    if res.resourceType == ResourceTypes.ResourceType.RAW_FILE and imt == 'text/plain':
        imt = None
    if res.resourceType in ResourceTypes.XML_DOCUMENTS and imt == 'text/xml':
        imt = None
    return imt

def GetDocDef(res, commonPath):
    dd = res.getDocumentDefinition()
    if dd is not None:
        dd = dd.getAbsolutePath()
        if dd[:len(commonPath)] == commonPath:
            dd = dd[len(commonPath):]
    return dd


def Register():
    from Ft.Lib.CommandLine import Options, Command, Arguments
    cmd = Command.Command('export',
                          'Export an application from a repository to the filesystem',
                          '--version="0.0a1" --title="GuestBook" --description="a guest book demo" /ftss/demos/GuestBook',
                          __doc__,
                          function = Run,
                          arguments = [Arguments.OptionalArgument('path',
                                                                  "the base path (in the repo) of the application to export (default: /)",
                                                                  str),
                                       ],
                          options = Options.Options([Options.Option('d',
                                                                    'directory=DIR',
                                                                    "directory to export to (must already exist)"),
                                                     Options.Option(None,
                                                                    'version=VERS',
                                                                    "set version of exported application"),
                                                     Options.Option(None,
                                                                    'description=DESC',
                                                                    "set description of exported application"),
                                                     Options.Option(None,
                                                                    'title=TITLE',
                                                                    "set title of exported application"),
                                                     Options.TypedOption(None,
                                                                         'archive=TYPE',
                                                                         "archive the exported data (options are platform-dependent)",
                                                                         map(lambda x: (x[0], x[1][-1]), archive_util.ARCHIVE_FORMATS.items()),
                                                                         ),
                                                     ]),

                          fileName = __file__,
                          )
    return cmd
