########################################################################
# $Header: /var/local/cvsroot/4Suite/Ft/Server/Server/Commands/Backup.py,v 1.3 2004/10/07 16:18:44 uogbuji Exp $
"""
4Suite repository backup command (4ss_manager backup)

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

__doc__ = """\
Create a low level system backup of the entire repository
"""

import os, sys
from Ft.Server.Server.Commands.CommandUtil import GetRepository
from Ft.Server.Common import Schema, ResourceTypes
from Ft.Lib import Time, Uri
from Ft.Server import __version__,FTSERVER_NAMESPACE
import Ft.Rdf.Util
from distutils import archive_util, dir_util

 
def Run(options, args):

    username, password, properties, repo = \
              GetRepository(options, '4ss_manager.backup')


    basePaths = args.get('base-path')
    if not basePaths:
        basePaths = ['/']

    excludes = options.get('exclude',[])
    if type(excludes) != type([]):
        excludes = [excludes]

    baseDirectory = options.get('directory','.')

    lmd = options.get('modified-since')
    if lmd:
        lmd = str(Time.FromISO8601(lmd))

    quiet = options.get('quiet',0)

    if not os.path.exists(baseDirectory):
        raise Exception("Directory %s must exit" % baseDirectory)

    baseDirectory = os.path.join(baseDirectory,"ftss-backup")
    if not os.path.exists(baseDirectory):
        os.mkdir(baseDirectory)

    contentDirBase = os.path.join(baseDirectory,'content')
    if not os.path.exists(contentDirBase):
        os.mkdir(contentDirBase)
    else:
        sys.stderr.write("Directory: %s already exists, could be conflicts\n" % contentDirBase)

    metadataDirBase = os.path.join(baseDirectory,'metadata')
    if not os.path.exists(metadataDirBase):
        os.mkdir(metadataDirBase)
    else:
        sys.stderr.write("Directory: %s already exists, could be conflicts\n" % metadataDirBase)

    cacheDirBase = os.path.join(baseDirectory,'cache')
    if not os.path.exists(cacheDirBase):
        os.mkdir(cacheDirBase)
    else:
        sys.stderr.write("Directory: %s already exists, could be conflicts\n" % cacheDirBase)

    backupDate = Time.FromPythonTime()
    logFile = open(os.path.join(baseDirectory,"backup.log"),"w")
    print "Backup log created at: %s" % os.path.join(baseDirectory,"backup.log")
    logFile.write("<ftss:Backup xmlns:ftss='%s' date='%s' version='%s'>\n" % (FTSERVER_NAMESPACE,
                                                                              str(backupDate),
                                                                              __version__))
    stored = {}
    madeit = 0
    try:
        for stmt in repo.getModel().complete(None,Schema.TYPE,None):
            if stored.has_key(stmt.subject):
                #Already stored
                continue
            for basePath in basePaths:
                if stmt.subject[:len(basePath)] != basePath:
                    if not quiet:
                        print "Skipping %s, not in basePath %s" % (stmt.subject,basePath)
                    continue

                excluded = 0
                for exclude in excludes:
                    if stmt.subject[:len(exclude)] == exclude:
                        if not quiet:
                            print "Skipping %s, excluded by %s" % (stmt.subject,exclude)
                            excluded = 1
                            break
                if excluded:
                    continue
                #Get the resource at this point, we will check LMD on the actual resource
                if not repo.hasResource(stmt.subject):
                    print "Corrupt RDF entry for: " + stmt.subject
                    continue
                resource = repo.fetchResource(stmt.subject)
                if lmd and resource.getLastModifiedDate() < lmd:
                    if not quiet:
                        print "Skipping %s, LMD of %s is before %s" % (stmt.subject,resource.getLastModifiedDate(),lmd)
                    continue

                #This looks like one to backup
                stored[stmt.subject] = 1
                print stmt.subject
                refID = Uri.BASIC_RESOLVER.generate()[9:]
                logFile.write("  <ftss:Entry path='%s' ref-id='%s' " % (stmt.subject,refID))
                for (_type,base) in [(ResourceTypes.RESOURCE_CONTENT,contentDirBase),
                                     (ResourceTypes.RESOURCE_METADATA,metadataDirBase),
                                     (ResourceTypes.RESOURCE_CACHE,cacheDirBase),
                                     ]:
                    driver = repo._driver._driver
                    if driver.hasFile(stmt.subject,_type):
                        data = driver.fetchFile(stmt.subject,_type)
                        f = open(os.path.join(base,refID),'wb')
                        f.write(data)
                        f.close()
                        logFile.write(" data%d='yes'" % _type)
                    else:
                        logFile.write(" data%d='no'" % _type)
                logFile.write("/>\n")

        #Now dump the system model
        print "Serializing System RDF Model"
        stmts = []
        serialized = stored.keys()
        for stmt in repo._driver._driver.getSystemModel().complete(None,None,None):
            if stmt.subject in serialized or stmt.scope in serialized:
                stmts.append(stmt)

        f = open(os.path.join(baseDirectory,"system.rdf"),"wb")
        f.write(Ft.Rdf.Util.SerializeStatementList(stmts))
        f.close()
        logFile.write("  <ftss:SystemModel path='system.rdf'/>\n")

        if options.get('user-model'):
            print "Serializing User RDF Model"
            f = open(os.path.join(baseDirectory,"user.rdf"),"wb")
            f.write(Ft.Rdf.Util.SerializeModel(repo._driver._driver.getUserModel()))
            f.close()
            logFile.write("  <ftss:UserModel path='user.rdf'/>\n")
            
        madeit = 1
    finally:
        logFile.write("</ftss:Backup>\n")
        logFile.close()
        repo.txRollback()
        
    if madeit and options.get('archive'):
        #Archive it!!
        base_name = 'ftss-backup-%s' % backupDate.asISO8601Date()

        print "Creating %s archive..." % options['archive']
        fName = archive_util.make_archive (base_name,
                                           options['archive'],
                                           root_dir=options.get('directory','.'),
                                           base_dir=baseDirectory,
                                           verbose=0,
                                           dry_run=0)

        print "Created: %s" % fName
        dir_util.remove_tree(baseDirectory)

    


def Register():
    from Ft.Lib.CommandLine import Command, Options, Arguments
    options = [Options.Option('e', 'exclude=',
                             'Exclude a path (and all children) from backup (multiple allowed)'),
               Options.Option('d', 'directory=',
                             'Directory to backup too.'),
               Options.Option('m', 'modified-since=',
                             'Only backup files modified since specified ISO datetime'),
               Options.Option('q', 'quiet',
                             'No extra output'),
               Options.Option(None, 'user-model',
                             'Also dump the user model'),
               Options.TypedOption(None,
                                   'archive=',
                                   "Create an archive (not all options will work on all platforms!!)",
                                   map(lambda x: (x[0],x[1][-1]),archive_util.ARCHIVE_FORMATS.items()),
                                   ),
               ]

    cmd = Command.Command('backup',
                          "Create a low level backup of the entire system.",
                          '',
                          __doc__,
                          function=Run,
                          fileName=__file__,
                          options=Options.Options(options),
                          arguments = [Arguments.ZeroOrMoreArgument('base-path',
                                                                    'Initial resources to start the backup at.',
                                                                    str),
                                       ],

                          )
    return cmd
