import os
import log
import sys
import time
import shutil
import random
import libxml2
import logging
import cStringIO
import stacktrace
from xml.dom import minidom
from xml.parsers.expat import ExpatError

import pmm_task
import pmm_config
import plesk_config
import pmmcli_config
import pmmcli_session
import pmm_dump_formatter
import pmm_dump_transformer
import pmmcli_daemon_service
import pmm_migration_handler
import pmm_dump_access_service
import pmm_repository_access_service
from pmm_conflict_detector import PleskActualDump
from pmmcli_exceptions import PMMUtilityException, PMMException, writeExecutionResult
from pmmcli_daemon_actions import DaemonTaskDeleteDump, DaemonTaskDeleteTempDump
from pmm_api_xml_protocols import DumpListQuery, Data, RestoreTaskDescription, \
RestoreTaskResult, DumpValidityError, TaskId, BackupTaskDescription, \
ResolveConflictsTaskDescription, Response, ConflictResolutionRules, \
SrcDstFilesSpecification, CheckDumpResult, IPMapping, MigrationObjectList, \
OwnersMapping, DBMapping, MigrationTaskDescription, AgentToUse, ConfigParameters, parameter

import osutil
import dirutil
import subproc
import validator
from encoder import EncoderFile
import binascii

try:
    from hashlib import md5
except ImportError:
    from md5 import md5 as md5

mswindows = (sys.platform == "win32")

trace = True

_logger = logging.getLogger("pmm.pmmcli")


libxml2errorHandlerErr = ''
def libxml2errorHandler(ctx, str_):
    global libxml2errorHandlerErr
    libxml2errorHandlerErr = libxml2errorHandlerErr + "%s %s" % (ctx, str_)


# Sub returns tuple (errcode, message, destination-dump-specification)
# errcode==0 on success, otherwise it contains errcode from failed export operation
# If dst_dumps_specification is None, then 'file' dump will be created in temp directory
def importFtpDumpToFileDump(owner_type, owner_guid, src_dumps_specification, dst_dumps_specification = None):
    source_dump_specification_formatter = pmm_dump_formatter.DumpSpecificationFormatter(src_dumps_specification)
    source_dump_storage_credentials_formatter = source_dump_specification_formatter.get_dumps_storage_credentials_formatter()
    if source_dump_storage_credentials_formatter.get_storage_type() != 'foreign-ftp':
        raise Exception("Source Dump Storage credentilas should be 'foreign-ftp' type")
    
    destination_dump_specification_formatter = None
    if dst_dumps_specification is not None:
        destination_dump_specification_formatter = pmm_dump_formatter.DumpSpecificationFormatter(dst_dumps_specification)
        if destination_dump_specification_formatter.get_dumps_storage_credentials_formatter().buildXml().get_storage_type() != 'file':
            raise Exception("Destination Dump Storage credentilas should be 'file' type")
    else:
        destination_dump_specification_formatter = pmm_dump_formatter.getTempFileDumpSpecificationFormatter()
    
    pmm_ras = pmm_repository_access_service.FtpRepositoryAccessService(source_dump_storage_credentials_formatter)
    export_errcode, export_message = pmm_ras.export_file_as_file(destination_dump_specification_formatter)
    tmp_dst_file = destination_dump_specification_formatter.get_destination_file()
    _logger.debug("Export 'foreign-ftp' dump to 'file' dump operation ended with errcode=%d (%s). Filename is %s" % (export_errcode, export_message, tmp_dst_file))
    if export_errcode != 0:
        return export_errcode, export_message, None
    if not os.path.isfile(tmp_dst_file):
        unknown_pmm_ras_error = 199
        unknown_pmm_ras_error_message = u"Unknown pmm-ras error: FTP dump was not exported to local file"
        return unknown_pmm_ras_error, unknown_pmm_ras_error_message, None
    # dump is exported into 'destination_dump_specification_formatter'
    return 0, export_message, destination_dump_specification_formatter


# Sub returns tuple (errcode, message, destination-dump-specification)
# errcode==0|111 on success, otherwise it contains errcode from failed convertation
# retcode==111 if dump already exists in destination Dump Specification
def importFileDumpToLocalRepository(owner_type, owner_guid, src_dumps_specification, dst_dumps_specification):
    source_dump_specification_formatter = pmm_dump_formatter.DumpSpecificationFormatter(src_dumps_specification)
    destination_dump_specification_formatter = pmm_dump_formatter.DumpSpecificationFormatter(dst_dumps_specification)
    
    if source_dump_specification_formatter.get_dumps_storage_credentials_formatter().buildXml().get_storage_type() != 'file':
        raise Exception("Source Dump Storage credentilas should be 'file' type")
    if destination_dump_specification_formatter.get_dumps_storage_credentials_formatter().buildXml().get_storage_type() != 'local':
        raise Exception("Destination Dump Storage credentilas should be 'local' type")
    
    ret_filename = u''
    ret_errcode = 0
    ret_message = u''
    
    pmm_ras = pmm_repository_access_service.FtpRepositoryAccessService(source_dump_specification_formatter.get_dumps_storage_credentials_formatter())
    ret_errcode, ret_message, import_filename = pmm_ras.import_file_as_dump(source_dump_specification_formatter,destination_dump_specification_formatter,owner_guid,owner_type)
    _logger.debug("Import file operation ended with errcode=%d. Filename is %s" % (ret_errcode, import_filename))
    # error code 111 returned if such dump already exists in destination repository
    # error code 112 returned if object specified by guid and type has lower level than backup type
    # error code 116 returned if dump signature is wrong
    
    if ret_errcode != 0:
        if ret_errcode == 111:
            # replace message from utility with a special one
            ret_message = u"Dump already exists in repository"
        elif (ret_errcode != 116 or destination_dump_specification_formatter.get_dumps_storage_credentials_formatter().get_ignore_backup_sign() != 'true'):
            if ret_errcode == 112:
                # replace message from utility with a special one
                ret_message = unicode("The backup you are trying to import does not match the user type '%s'" % owner_type)
            else:
                # 'not-a-dump' format falls here
                pass
            return ret_errcode, ret_message, None
    destination_dump_specification_formatter.set_name_of_xml_file(import_filename.lstrip('/'))
    # Dump of '9' format is imported into destination_dump_specification_formatter
    
    return ret_errcode, ret_message, destination_dump_specification_formatter


class PmmcliException(Exception):
    def __init__(self):
        self.message = None


class PmmcliActionParamException(PmmcliException):
    def __init__(self, PmmcliAction, message):
        self.caller = PmmcliAction
        self.msg = message

    def get_message(self):
        return self.msg


class ActionRunner(object):
    def __init__(self, ActionProcessor,parameter_stdin = None, parameters = None):
        self.processor = ActionProcessor(parameter_stdin, parameters)

    def doActivity(self):
        libxml2.registerErrorHandler(libxml2errorHandler, '')
        input_valid, msg = self.processor.validate()
        if not input_valid:
            _logger.error("Validate failed: ActionProcessor " + str(self.processor))
            raise PmmcliActionParamException(self.processor, msg)
        result = None
        _logger.debug(str(self) + ": doActivity")
        result = self.processor.doActivity()
        return self.processor.response(result)


class PMMCliAction(object):
    def __init__(self, parameter_stdin, parameters):
        _logger.debug("--> " + str(self))
        if parameters:
            _logger.info("parameters: " + str(parameters))
        if parameter_stdin:
            _logger.info("stdin: " + parameter_stdin)
        self._stdin = parameter_stdin
        self._params = parameters
        self._process = None
        self.__input_validator = validator.Validator(pmm_config.pmm_api_xml_protocols_schema())

    def get_input_validator(self):
        return self.__input_validator

    def validate(self):
        _logger.debug(str(self) + ": validate")
        return 1

    def doActivity(self):
        raise PmmcliException()

    def response(self, result):
        _logger.debug(str(self) + ": response")
        return result

    def setProcess(self, process):
        if process == None:
            _logger.debug("Subprocess finished")
        else:
            _logger.debug(u"Subprocess " + unicode(process) + u" encountered.")
            self._process = process

    def unsetProcess(self, process):
        self.setProcess(None)

    def passwordToKey(self, password):
        md5Hash = md5(password).digest()
        for i in range(3):
            md5Hash = md5(md5Hash).digest()
        return binascii.b2a_base64(md5Hash)

class GetDumpsListAction(PMMCliAction):
    def __init__(self, parameter_stdin, parameters):
        PMMCliAction.__init__(self, parameter_stdin, parameters)
        self.__parameter_stdin = parameter_stdin
        self.__dump_list_query = None

    def validate(self):
        try:
            error_code, msg = self.get_input_validator().do(self.__parameter_stdin)
        except libxml2.parserError, ex:
            return None, 'XML parse error: ' + ex.msg + '\n' + libxml2errorHandlerErr
        if error_code:
            return None, 'Error ' + str(error_code) + ': ' + msg
        try:
            self.__dump_list_query = DumpListQuery.factory()
            self.__dump_list_query.build(minidom.parseString(self.__parameter_stdin).childNodes[0])
        except ExpatError:
            return None, 'ExpatError in DumpListQuery'
        return 1, ''

    def doActivity(self):
        if not self.__dump_list_query:
            self.__dump_list_query = DumpListQuery.factory()
            self.__dump_list_query.build(minidom.parseString(self.__parameter_stdin).childNodes[0])
        dumps_storage_credentials = self.__dump_list_query.get_dumps_storage_credentials()
        object_specification =  self.__dump_list_query.get_object_specification()
        storage_type = dumps_storage_credentials.get_storage_type()
        access_service = None
        if storage_type != 'local':
            access_service = pmm_repository_access_service.FtpRepositoryAccessService
        else:
            access_service = pmm_repository_access_service.LocalRepositoryAccessService
        result_dump_list, errcode, message = access_service(dumps_storage_credentials).getDumpList(object_specification)
        data = Data.factory( dump_list = result_dump_list )
        return data, errcode, message


class GetDumpOverviewAction(PMMCliAction):
    def __init__(self, parameter_stdin, parameters):
        PMMCliAction.__init__(self, parameter_stdin, parameters)
        self.__parameter_stdin = parameter_stdin
        self.__session_id = None
        self.__dump_specification_formatter = None

    def validate(self):
        # there is no validation - if stdin is not DumpSpecification suppose it is session-id
        return 1, ''

    def doActivity(self):
        session = None
        try:
            self.__dump_specification_formatter = pmm_dump_formatter.DumpSpecificationFormatter(self.__parameter_stdin)
        except pmm_dump_formatter.DumpSpecificationFormatException:
            # parameter_stdin is session-id
            self.__session_id = self.__parameter_stdin
            self.__dump_specification_formatter = None
        if self.__dump_specification_formatter:
            file_path = self.__dump_specification_formatter.get_destination_file()
            try:
                # copy dump to newly created session dir
                session = pmmcli_session.PmmcliSession.createSession('','',file_path,self.__dump_specification_formatter.buildString())
            except pmmcli_session.RSessionParamException, x:
                return None, 1, "Could not create restore session. Invalid parameter: " + x.get_message()
        else:
            #get dump from session dir
            session = pmmcli_session.PmmcliSession.openSession(self.__session_id)
        # start pmmcli_daemon
        pmmcli_daemon_service.PMMCliDaemon().start()
        # initialize dump overview maker and get dump-overview
        dump_overview_object = session.getDumpOverview()
        errcode = 0
        data = Data.factory( dump_overview = dump_overview_object)
        return data, errcode, None


class CheckDumpAction(PMMCliAction):
    def __init__(self, parameter_stdin, parameters):
        PMMCliAction.__init__(self, parameter_stdin, parameters)
        self.__parameter_stdin = parameter_stdin
        self.__dump_specification_formatter = None

    def validate(self):
        try:
            error_code, msg = self.get_input_validator().do(self.__parameter_stdin)
        except libxml2.parserError, ex:
            return None, 'XML parse error: ' + ex.msg + '\n' + libxml2errorHandlerErr
        if error_code:
            return None, 'Error ' + str(error_code) + ': ' + msg
        try:
            self.__dump_specification_formatter = pmm_dump_formatter.DumpSpecificationFormatter(self.__parameter_stdin)
        except pmm_dump_formatter.DumpSpecificationFormatException:
            return None, 'DumpSpecificationFormatException in DumpSpecification'
        return 1, ''

    def doActivity(self):
        if not self.__dump_specification_formatter:
            self.__dump_specification_formatter = pmm_dump_formatter.DumpSpecificationFormatter(self.__parameter_stdin)
        dumps_storage_credentials_formatter = self.__dump_specification_formatter.get_dumps_storage_credentials_formatter()
        dumps_storage_credentials = dumps_storage_credentials_formatter.buildXml()
        storage_type = dumps_storage_credentials_formatter.get_storage_type()
        data = None
        errcode = 0
        message = None
        access_service = None
        if storage_type in ['foreign-ftp','file']:
            access_service = pmm_repository_access_service.FtpRepositoryAccessService
        else:
            access_service = pmm_repository_access_service.LocalRepositoryAccessService
        dump_result, errcode, message = access_service(dumps_storage_credentials_formatter).getDumpStatus(self.__dump_specification_formatter)
        if dump_result:
            dump_status_array = dump_result.get_dump_status()
            check_dump_result_object = CheckDumpResult.factory( dump_status = dump_status_array )
            data = Data.factory( check_dump_result = check_dump_result_object )
        return data, errcode, message


class ExportDumpAsFileAction(PMMCliAction):
    def __init__(self, parameter_stdin, parameters):
        PMMCliAction.__init__(self, parameter_stdin, parameters)
        self.__parameter_stdin = parameter_stdin
        self.__parameters = parameters
        self.__src_dst_files_specification = None
        self.__temp_dump = None

    def validate(self):
        try:
            error_code, msg = self.get_input_validator().do(self.__parameter_stdin)
        except libxml2.parserError, ex:
            return None, 'XML parse error: ' + ex.msg + '\n' + libxml2errorHandlerErr
        if error_code:
            return None, 'Error ' + str(error_code) + ': ' + msg
        try:
            self.__src_dst_files_specification = SrcDstFilesSpecification.factory()
            self.__src_dst_files_specification.build(minidom.parseString(self.__parameter_stdin).childNodes[0])
            if not self.__src_dst_files_specification.get_src() or not self.__src_dst_files_specification.get_dst():
                return None, "SrcDstFilesSpecification has wrong format"
            if self.__src_dst_files_specification.get_src().get_dumps_storage_credentials().get_storage_type() != 'local':
                return None, "ExportDumpAsFileAction called for '" + self.__src_dst_files_specification.get_src().get_dumps_storage_credentials().get_storage_type() + "' storage type"
        except ExpatError:
            return None, 'ExpatError in SrcDstFilesSpecification'
        return 1, ''

    def addDaemonTaskDeleteTempDump(self, dump_specification_formatter):
        task_dump_specification_formatter = pmm_dump_formatter.DumpSpecificationFormatter(dump_specification_formatter)
        daemon_task = DaemonTaskDeleteTempDump(task_dump_specification_formatter)
        try:
            daemon_task.save()
        except Exception, e:
            _logger.error("Exception %s occured during save daemon task to delete temp dump: %s\n Call stack is:\n%s" %(str(e.__class__),str(e),stacktrace.stacktrace()))

    def doActivity(self):
        if not self.__src_dst_files_specification:
            self.__src_dst_files_specification = SrcDstFilesSpecification.factory()
            self.__src_dst_files_specification.build(minidom.parseString(self.__parameter_stdin).childNodes[0])
        if len(self.__parameters) > 0:
            if self.__parameters[0] == "-temp":
                self.__temp_dump = True
        src_dumps_specification = self.__src_dst_files_specification.get_src()
        dst_dumps_specification = self.__src_dst_files_specification.get_dst()
        source_dump_specification_formatter = pmm_dump_formatter.DumpSpecificationFormatter(src_dumps_specification)
        destination_dump_specification_formatter = pmm_dump_formatter.DumpSpecificationFormatter(dst_dumps_specification)
        destination_dumps_storage_credentials_formatter = destination_dump_specification_formatter.get_dumps_storage_credentials_formatter()
        
        delete_temp_dump = False
        
        storage_type = destination_dumps_storage_credentials_formatter.get_storage_type()
        if storage_type == 'foreign-ftp':
            ftp_password = destination_dumps_storage_credentials_formatter.get_password()
            os.environ['FTP_PASSWORD'] = str(ftp_password)
        elif storage_type == 'file':
            if self.__temp_dump:
                delete_temp_dump = True

        backup_password = self.__src_dst_files_specification.get_dst().get_dumps_storage_credentials().get_backup_password()
        if backup_password != '':
            os.environ['PLESK_BACKUP_CRYPT_KEY'] = self.passwordToKey(backup_password)
        if mswindows:
            source_dump_storage_credentials = source_dump_specification_formatter.get_dumps_storage_credentials_formatter().buildXml()
            pmm_ras = pmm_repository_access_service.FtpRepositoryAccessService(source_dump_storage_credentials)
            errcode, message, filename = pmm_ras.export_dump_as_file(source_dump_specification_formatter,destination_dump_specification_formatter)
            return None, errcode, message
        
        else:
            cmd = subproc.CmdLine(pmm_config.backup(), self)
            for backup_arg in pmm_config.plesk_backup_args():
                cmd.arg(backup_arg)
            cmd.arg('export-dump-as-file')
            dump_file_name_arg = '--dump-file-name=' + source_dump_specification_formatter.get_destination_file()
            cmd.arg(dump_file_name_arg)
            # output file format is one of these:
            # ftp://login@hostname/root_dir/file_name
            # /root_dir/file_name
            output_file_arg = '--output-file=' + destination_dump_specification_formatter.get_destination_file()
            cmd.arg(output_file_arg)

            cmd.arg('--no-gzip')
            
            use_passive_ftp_mode = destination_dumps_storage_credentials_formatter.get_use_passive_ftp_mode()
            if use_passive_ftp_mode == 'true':
                cmd.arg('--ftp-passive-mode')
            
            errcode = 0
            message = u''
            try:
                proc = cmd.spawn()
                #message = u"== STDOUT ====================\n" + proc.stdout + u"\n==============================\n" + u"== STDERR ====================\n" + proc.stderr + u"\n==============================\n"
            except subproc.NonzeroExitException, x:
                errcode = x.exitcode
                message =  u"export-dump-as-file error (Error code = " + unicode(errcode) + "):"  \
                         + u"\n== STDOUT ====================\n" + x.subprocess.stdout + u"\n==============================\n" \
                         + u"== STDERR ====================\n" + x.subprocess.stderr + u"\n==============================\n"
            if delete_temp_dump:
                self.addDaemonTaskDeleteTempDump(destination_dump_specification_formatter)
                pmmcli_daemon_service.PMMCliDaemon().start()
            return None, errcode, message


class ExportFileAsFileAction(PMMCliAction):
    def __init__(self, parameter_stdin, parameters):
        PMMCliAction.__init__(self, parameter_stdin, parameters)
        self.__parameter_stdin = parameter_stdin
        self.__src_dst_files_specification = None

    def validate(self):
        try:
            error_code, msg = self.get_input_validator().do(self.__parameter_stdin)
        except libxml2.parserError, ex:
            return None, 'XML parse error: ' + ex.msg + '\n' + libxml2errorHandlerErr
        if error_code:
            return None, 'Error ' + str(error_code) + ': ' + msg
        try:
            self.__src_dst_files_specification = SrcDstFilesSpecification.factory()
            self.__src_dst_files_specification.build(minidom.parseString(self.__parameter_stdin).childNodes[0])
            if not self.__src_dst_files_specification.get_src() or not self.__src_dst_files_specification.get_dst():
                return None, "SrcDstFilesSpecification has wrong format"
            if self.__src_dst_files_specification.get_src().get_dumps_storage_credentials().get_storage_type() == 'local':
                return None, "ExportFileAsFileAction called for 'local' source storage type"
            if self.__src_dst_files_specification.get_dst().get_dumps_storage_credentials().get_storage_type() == 'local':
                return None, "ExportFileAsFileAction called for 'local' destination storage type"
        except ExpatError:
            return None, 'ExpatError in SrcDstFilesSpecification'
        return 1, ''

    def doActivity(self):
        if not self.__src_dst_files_specification:
            self.__src_dst_files_specification = SrcDstFilesSpecification.factory()
            self.__src_dst_files_specification.build(minidom.parseString(self.__parameter_stdin).childNodes[0])
        src_dumps_specification = self.__src_dst_files_specification.get_src()
        dst_dumps_specification = self.__src_dst_files_specification.get_dst()
        source_dump_specification_formatter = pmm_dump_formatter.DumpSpecificationFormatter(src_dumps_specification)
        destination_dump_specification_formatter = pmm_dump_formatter.DumpSpecificationFormatter(dst_dumps_specification)
        source_dump_storage_credentials = source_dump_specification_formatter.get_dumps_storage_credentials_formatter().buildXml()
        pmm_ras = pmm_repository_access_service.FtpRepositoryAccessService(source_dump_storage_credentials)
        errcode, message = pmm_ras.export_file_as_file(destination_dump_specification_formatter)
        return None, errcode, message


class ImportFileAsDumpAction(PMMCliAction):
    def __init__(self, parameter_stdin, parameters):
        PMMCliAction.__init__(self, parameter_stdin, parameters)
        self.__parameter_stdin = parameter_stdin
        self.__src_dst_files_specification = None

    def validate(self):
        try:
            error_code, msg = self.get_input_validator().do(self.__parameter_stdin)
        except libxml2.parserError, ex:
            return None, 'XML parse error: ' + ex.msg + '\n' + libxml2errorHandlerErr
        if error_code:
            return None, 'Error ' + str(error_code) + ': ' + msg
        try:
            self.__src_dst_files_specification = SrcDstFilesSpecification.factory()
            self.__src_dst_files_specification.build(minidom.parseString(self.__parameter_stdin).childNodes[0])
            if not self.__src_dst_files_specification.get_src() or not self.__src_dst_files_specification.get_dst():
                return None, "SrcDstFilesSpecification has wrong format"
            if self.__src_dst_files_specification.get_dst().get_dumps_storage_credentials().get_storage_type() != 'local':
                return None, "ExportDumpAsFileAction called for '" + self.__src_dst_files_specification.get_dst().get_dumps_storage_credentials().get_storage_type() + "' storage type"
            owner_guid = self.__src_dst_files_specification.get_guid()
            owner_type = self.__src_dst_files_specification.get_type()
            if owner_type == None:
                return None, "SrcDstFilesSpecification has wrong format: 'type' attribute is required for '--import-file-as-dump' command'"
            if owner_guid == None:
                return None, "SrcDstFilesSpecification has wrong format: 'guid' attribute is required for '--import-file-as-dump' command'"
            if owner_guid == '':
                return None, "SrcDstFilesSpecification has wrong format: not-empty 'guid' attribute is required for '--import-file-as-dump' command'"
        except ExpatError:
            return None, 'ExpatError in SrcDstFilesSpecification'
        return 1, ''

    def doActivity(self):
        if not self.__src_dst_files_specification:
            self.__src_dst_files_specification = SrcDstFilesSpecification.factory()
            self.__src_dst_files_specification.build(minidom.parseString(self.__parameter_stdin).childNodes[0])
        src_dumps_specification = self.__src_dst_files_specification.get_src()
        dst_dumps_specification = self.__src_dst_files_specification.get_dst()
        owner_guid = self.__src_dst_files_specification.get_guid()
        owner_type = self.__src_dst_files_specification.get_type()

        os.environ['PLESK_BACKUP_CRYPT_KEY'] = self.passwordToKey(self.__src_dst_files_specification.get_src().get_dumps_storage_credentials().get_backup_password())
        
        source_dump_specification_formatter = pmm_dump_formatter.DumpSpecificationFormatter(src_dumps_specification)
        destination_dump_specification_formatter = pmm_dump_formatter.DumpSpecificationFormatter(dst_dumps_specification)
        
        ignored_import_errors = [0, 111]
        if destination_dump_specification_formatter.get_dumps_storage_credentials_formatter().get_ignore_backup_sign() == 'true':
            ignored_import_errors.append(116)

        tmp_file_dump_specification_formatter = None
        
        if source_dump_specification_formatter.get_dumps_storage_credentials_formatter().get_storage_type() == 'foreign-ftp':
            importftp_errcode, importftp_message, tmp_file_dump_specification_formatter = importFtpDumpToFileDump(owner_type, owner_guid, source_dump_specification_formatter)
            if importftp_errcode != 0:
                return None, importftp_errcode, importftp_message
            source_dump_specification_formatter = tmp_file_dump_specification_formatter
        
        import_errcode, import_message, destination_dump_specification_formatter = importFileDumpToLocalRepository(owner_type,owner_guid,source_dump_specification_formatter,destination_dump_specification_formatter)
        
        if tmp_file_dump_specification_formatter is not None:
            # remove temporary dump of 'file' format
            _logger.debug("Remove temporary dump: %s" % tmp_file_dump_specification_formatter.get_destination_file())
            pmm_ras = pmm_repository_access_service.FtpRepositoryAccessService(tmp_file_dump_specification_formatter.get_dumps_storage_credentials_formatter())
            errcode, message = pmm_ras.deleteDump(tmp_file_dump_specification_formatter)
            _logger.debug("Remove dump operation ended with errcode=%d (%s)" % (errcode, message))

        if import_errcode not in ignored_import_errors:
            return None, import_errcode, import_message
        
        ret_message = unicode(import_message)
        
        destination_dumps_storage_credentials_formatter = destination_dump_specification_formatter.get_dumps_storage_credentials_formatter()
        dump_result, check_errcode, check_message = pmm_repository_access_service.LocalRepositoryAccessService(destination_dumps_storage_credentials_formatter).getDumpStatus(destination_dump_specification_formatter)
        _logger.debug("Check dump operation ended.")
        if check_message:
            ret_message = ret_message + u"pmm-ras check dump error:\n" + unicode(check_message)
        
        data = Data.factory(dump=dump_result)
        
        if check_errcode == 0 and import_errcode in ignored_import_errors:
            ret_errcode = import_errcode
        else:
            ret_errcode = check_errcode
        
        return data, ret_errcode, ret_message


class RestoreAction(PMMCliAction):
    def __init__(self, parameter_stdin, parameters):
        PMMCliAction.__init__(self, parameter_stdin, parameters)
        self.__parameter_stdin = parameter_stdin
        self.__restore_task_description = None
        self.__parameters = parameters
        self.__fixnames = None

    def validate(self):
        try:
            error_code, msg = self.get_input_validator().do(self.__parameter_stdin)
        except libxml2.parserError, ex:
            return None, 'XML parse error: ' + ex.msg + '\n' + libxml2errorHandlerErr
        if error_code:
            return None, 'Error ' + str(error_code) + ': ' + msg
        try:
            self.__restore_task_description = RestoreTaskDescription.factory()
            self.__restore_task_description.build(minidom.parseString(self.__parameter_stdin).childNodes[0])
        except ExpatError:
            return None, 'ExpatError in RestoreTaskDescription'
        # require both of owner_guid and owner_type specified for file restore
        owner_guid = self.__restore_task_description.get_owner_guid()
        # Empty owner_guid is allowed and could be used instead of admin's guid in some cases ('domain_bu' utility).
        # Generally Conflict Resolver will treat an empty owner_guid as admin's guid.
        owner_type = self.__restore_task_description.get_owner_type()
        if not owner_type in ['server','reseller','client','domain']:
            return None, "Could not restore dump: owner-type must be one of 'server','reseller','client','domain'"
        return 1, ''

    def __getConflictResolveTask(self, session, dump_specification_formatter, create):
        conflict_resolve_task = None
        restore_task = self.__getRestoreTask(session, dump_specification_formatter)
        conflict_resolve_task_id = restore_task.get('conflict_resolve_task_id')
        if conflict_resolve_task_id is not None:
            conflict_resolve_task = pmm_task.getPMMTaskManager().realGetTask(conflict_resolve_task_id)
            conflict_resolve_task.set('session_path', session.get_session_path())
            pmm_task.getPMMTaskManager().updateTask(conflict_resolve_task)
        elif create:
            conflict_resolve_task = pmm_task.ConflictResolveTask({'session_path':session.get_session_path()})
            restore_task.set('conflict_resolve_task_id',conflict_resolve_task.get_task_id())
            pmm_task.getPMMTaskManager().updateTask(restore_task)
        return conflict_resolve_task

    def __getRestoreTask(self, session, dump_specification_formatter):
        task_id = session.get_task_id()
        task = None
        if task_id is not None:
            task = pmm_task.getPMMTaskManager().realGetTask(task_id)
        else:
            task = pmm_task.RestoreTask(osutil.get_cmd(os.getpid()), os.getpid(), {'conflict_resolve_task_id':None,
                                                                                   'deploy_task_id':None,
                                                                                   'owner_guid':self.__restore_task_description.get_owner_guid(),
                                                                                   'owner_type':self.__restore_task_description.get_owner_type(),
                                                                                   'dumps_storage_credentials':dump_specification_formatter.get_dumps_storage_credentials_formatter().buildString()})
            session.set_task_id(task.get_task_id())
        task.set('session_path',session.get_session_path())
        pmm_task.getPMMTaskManager().updateTask(task)
        return task

    def addDaemonTaskDeleteDump(self, dump_specification_formatter):
        task_dump_specification_formatter = pmm_dump_formatter.DumpSpecificationFormatter(dump_specification_formatter)
        daemon_task = DaemonTaskDeleteDump(task_dump_specification_formatter)
        try:
            daemon_task.save()
        except Exception, e:
            _logger.error("Exception %s occured during save daemon task to delete dump: %s\n Call stack is:\n%s" %(str(e.__class__),str(e),stacktrace.stacktrace()))

    def transformDump(self, session):
        restore_specification = session.get_restore_specification()
        restore_specification_out = restore_specification + ".tmp"
        dump_transformer_options_object = self.__restore_task_description.get_dump_transformer_options()
        dump_transformer_options_filename = None
        if dump_transformer_options_object is not None:
            dump_transformer_options_filename = os.path.join(session.get_session_path(), 'dump_transformer_options')
            dump_transformer_options = open(dump_transformer_options_filename, 'wt')
            try:
                resp_str = cStringIO.StringIO()
                resp_encoded = EncoderFile(resp_str, "utf-8")
                resp_encoded.write('<?xml version="1.0" encoding="UTF-8"?>\n')
                dump_transformer_options_object.export(resp_encoded, 0, name_ = 'dump-transformer-options')
                dump_transformer_options.write(resp_str.getvalue())
            finally:
                dump_transformer_options.close()
            
        dump_transformed = pmm_dump_transformer.DumpTransformer(dump_transformer_options_filename).run(restore_specification, restore_specification_out, session.get_session_path())
        if dump_transformed:
            shutil.copy2(restore_specification, restore_specification + ".srcformat")
            shutil.copy2(restore_specification_out, restore_specification)
        os.unlink(restore_specification_out)

    def doActivity(self):
        if not self.__restore_task_description:
            self.__restore_task_description = RestoreTaskDescription.factory()
            self.__restore_task_description.build(minidom.parseString(self.__parameter_stdin).childNodes[0])

        if len(self.__parameters) > 0:
            if self.__parameters[0] == "-fixnames":
                self.__fixnames = True
        ignore_sign = None
        ignore_conflicts = None
        ignore_errors = None
        if self.__restore_task_description.get_ignore_errors():
            ignore_errors = self.__restore_task_description.get_ignore_errors().get_ignore_error()
            for ignore_error in ignore_errors:
                if ignore_error.get_type() == 'conflicts':
                    ignore_conflicts = 1
                if ignore_error.get_type() == 'sign':
                    ignore_sign = 1
        
        selected_objects = self.__restore_task_description.get_objects().get_selected()
        
        conflict_resolution_rules = self.__restore_task_description.get_conflict_resolution_rules()
        
        add_daemon_delete_dump_task = False
        
        owner_guid = self.__restore_task_description.get_owner_guid()
        owner_type = self.__restore_task_description.get_owner_type()
        
        dump_specification_formatter = None
        imported_dump_specification_formatter = None
        d_s = self.__restore_task_description.get_source().get_dump_specification()
        if d_s:
            # source is dump specification so we have to create new session
            dump_specification_formatter = pmm_dump_formatter.DumpSpecificationFormatter(d_s)
            # do not change dump_specification_formatter after it have been initialize, it should contain DS from restore task description
            
            storage_type = dump_specification_formatter.get_dumps_storage_credentials_formatter().get_storage_type()
            if storage_type == 'local': 
                try:
                    pmm_ras = pmm_repository_access_service.LocalRepositoryAccessService(dump_specification_formatter.get_dumps_storage_credentials_formatter())
                    errcode, message = pmm_ras.convert_local_dump(dump_specification_formatter)
                    if errcode != 0:
                        return None, errcode, message
                    session = pmmcli_session.PmmcliSession.createSession(owner_type,owner_guid,dump_specification_formatter.get_destination_file(),dump_specification_formatter.buildString())
                except pmmcli_session.RSessionParamException, x:
                    return None, 1, "Could not create restore session. Invalid parameter: " + x.get_message()
            else:
                tmp_file_dump_specification_formatter = None
                if storage_type == 'foreign-ftp':
                    os.environ['PLESK_BACKUP_CRYPT_KEY'] = self.passwordToKey(d_s.get_dumps_storage_credentials().get_backup_password())
                    importftp_errcode, importftp_message, tmp_file_dump_specification_formatter = importFtpDumpToFileDump(owner_type, owner_guid, dump_specification_formatter)
                    if importftp_errcode != 0:
                        return None, importftp_errcode, importftp_message
                    dump_specification_formatter_for_import = tmp_file_dump_specification_formatter
                else:
                    dump_specification_formatter_for_import = dump_specification_formatter
                
                # now pmmcli should make import-file-as-dump operation, check dump and then restore it if dump is OK
                # should initialize destination_dump_specification_formatter with some name to avoid exception 'empty name with local storage-type'
                destination_dump_specification_formatter = pmm_dump_formatter.getLocalDumpSpecificationFormatter('dummy.xml')
                if ignore_sign == 1:
                    destination_dump_specification_formatter.get_dumps_storage_credentials_formatter().set_ignore_backup_sign('true')
                
                import_errcode, import_message, imported_dump_specification_formatter = importFileDumpToLocalRepository(
                    owner_type, owner_guid, dump_specification_formatter_for_import, destination_dump_specification_formatter)
                _logger.debug("Import file dump to local repository operation ended with errcode=%d (%s)" % (import_errcode, import_message))
                
                if tmp_file_dump_specification_formatter is not None:
                    # remove temporary dump of 'file' format
                    _logger.debug("Remove temporary dump: %s" % tmp_file_dump_specification_formatter.get_destination_file())
                    pmm_ras = pmm_repository_access_service.FtpRepositoryAccessService(tmp_file_dump_specification_formatter.get_dumps_storage_credentials_formatter())
                    errcode, message = pmm_ras.deleteDump(tmp_file_dump_specification_formatter)
                    _logger.debug("Remove dump operation ended with errcode=%d (%s)" % (errcode, message))
                
                # error code 111 returned if such dump already exists in destination repository
                # error code 112 returned if object specified by guid and type have no rights to import dump
                # other error code could indice an error
                # anyway we should continue only if errcode is 0 or 111
                ignored_import_errors = [0,111]
                if ignore_sign == 1:
                    ignored_import_errors.append(116)

                if (import_errcode not in ignored_import_errors):
                    return None, import_errcode, import_message
                
                # imported_dump_specification_formatter is not None here
                imported_dump_filename = imported_dump_specification_formatter.get_destination_file()
                _logger.debug("Imported file name is: %s" % imported_dump_filename)
                if import_errcode == 0:
                    add_daemon_delete_dump_task = True
                
                # call check dump for imported dump
                imported_dump_storage_credentials_formatter = imported_dump_specification_formatter.get_dumps_storage_credentials_formatter()
                dump_result, check_errcode, check_message = pmm_repository_access_service.LocalRepositoryAccessService(imported_dump_storage_credentials_formatter).getDumpStatus(imported_dump_specification_formatter)
                _logger.debug("Check dump operation ended (%s)" % check_message)
                # check_errcode is always None
                # we should analyse dump_result to get dump status
                status_array = dump_result.get_dump_status()
                dump_failed_on_status = False
                failed_errors = ['WRONG-FORMAT', 'CONTENT-ERROR']
                if not ignore_sign:
                    failed_errors.append('SIGN-ERROR')
                for status in status_array:
                    _logger.debug("Import dump status: %s" % status.get_dump_status())
                    if status.get_dump_status() in failed_errors:
                        dump_failed_on_status = True
                
                result_message = u''
                if dump_failed_on_status:
                    if message:
                        result_message = u"pmm-ras check dump error:\n" + unicode(message)
                    if add_daemon_delete_dump_task:
                        self.addDaemonTaskDeleteDump(imported_dump_specification_formatter)
                    return None, 14, u"Check imported dump " + unicode(imported_dump_filename) + u" failed.\n" + result_message
                
                # dump is imported and checked
                try:
                    session = pmmcli_session.PmmcliSession.createSession(owner_type,owner_guid,imported_dump_specification_formatter.get_destination_file(),imported_dump_specification_formatter.buildString())
                except pmmcli_session.RSessionParamException, x:
                    return None, 1000, "Could not create restore session for imported dump. Invalid parameter: " + x.get_message()
        else:
            # create new session based on selected objects
            session_id = self.__restore_task_description.get_source().get_session_id()
            source_session = pmmcli_session.PmmcliSession(session_id)
            if source_session.conflicts_resolved():
                session = pmmcli_session.PmmcliSession.cloneSession(session_id)
            else:
                session = pmmcli_session.PmmcliSession.deriveSession(session_id)
            dump_specification_formatter = pmm_dump_formatter.DumpSpecificationFormatter(session.get_dump_specification_value())
        
        # session is created
        errcode = 0
        errmsg = None

        self.__getConflictResolveTask(session, dump_specification_formatter, False)
        restore_task = self.__getRestoreTask(session, dump_specification_formatter)
        try:
            result = RestoreTaskResult(task_id = restore_task.get_task_id())
            plesk_actual_dump = None
            # omit sign checker if conflicts already are resolved
            # call Conflict Detector to fix guids if in first restore session
            if not session.conflicts_resolved():
                _logger.debug("Prepare to call sign checker")
                # invoke sign checker, validate result and generate error if necessary
                backup_sign_errcode = session.checkSign()
                if not ignore_sign:
                    if backup_sign_errcode != 0:
                        backup_sign_error_codes = {1: "no integrity", 2: "bad signature",3: "not signed"}
                        validity_error = DumpValidityError.factory()
                        validity_error.set_type(str(backup_sign_errcode))
                        backup_sign_error_message = backup_sign_error_codes.get(backup_sign_errcode,'unexpected error from backup-sign') 
                        validity_error.setValueOf_(backup_sign_error_message)
                        result.set_dump_validity_error([validity_error])
                        fatal_error_codes = [1,2]
                        if backup_sign_errcode in fatal_error_codes:
                            errcode = 105
                            data = Data.factory( restore_task_result = result )
                            if add_daemon_delete_dump_task:
                                self.addDaemonTaskDeleteDump(imported_dump_specification_formatter)
                            return data, errcode, "Bad dump: Check sign error: " + backup_sign_error_message
                # now 'restore.xml' is unsigned and it is the time adjust 'base'
                session.apply_base()

                _logger.debug("Prepare to call backup_encrypt")
                if self.__decryptBackup(session):
                    return None, 301, "Unable to decrypt backup. Specified key is not suitable."

                # invoke guids fixer
                _logger.debug("Prepare to call guids fixer")
                session.fixGuids()
                plesk_actual_dump = PleskActualDump(session.get_session_path()).get()

                if self.__fixnames:
                    # invoke names fixer
                    _logger.debug("Prepare to call names fixer")
                    session.fixNames(plesk_actual_dump)

            self.transformDump(session)

            session.create_restore_specification(selected_objects,owner_type,owner_guid)

            # at this time 'restore.xml' file is created and ready to deploy
            if not ignore_conflicts:
                _logger.debug("Prepare to detect conflicts")
                # work with conflict detector
                if plesk_actual_dump is None:
                    plesk_actual_dump = PleskActualDump(session.get_session_path()).get()
                if session.detectConflicts(plesk_actual_dump):
                    if conflict_resolution_rules:
                        self.__getConflictResolveTask(session, dump_specification_formatter, True)
                        conflicts_found = session.resolveConflicts(plesk_actual_dump,conflict_resolution_rules)
                    if not conflict_resolution_rules or conflicts_found != 0:
                        errcode = 0
                        overview = session.getDumpOverview()
                        conflicts = session.getConflictDescription()
                        result.set_dump_overview(overview)
                        result.set_conflicts_description(conflicts)
                        data = Data.factory( restore_task_result = result )
                        if add_daemon_delete_dump_task:
                            self.addDaemonTaskDeleteDump(imported_dump_specification_formatter)
                        return data, errcode, None

            # start deployer
            _logger.debug("Prepare to start deployer")
            cmd = subproc.AsyncCmdLine(pmm_config.deployer())
            cmd.arg('--deploy-dump')
            restore_task_description_misc = self.__restore_task_description.get_misc()
            if restore_task_description_misc:
                if restore_task_description_misc.get_suspend() == "true":
                    cmd.arg('--suspend')
                verbose_level = restore_task_description_misc.get_verbose_level()
                try:
                    verbose_level_value = int(verbose_level)
                    if verbose_level_value >= 1:
                        cmd.arg('--verbose')
                except (TypeError, ValueError):
                    pass

            if pmmcli_config.get().force_debug_log() == 1:
                cmd.arg('--verbose')
                cmd.arg('--debug')

            cmd.arg('--session-path')
            session_path_arg = session.get_session_path()
            cmd.arg(session_path_arg)

            # Get restore tasks list
            #
            # Need additional protection against two pmmcli instance run
            # because walking throw Task List takes some time.
            # Use osutil.Interlock instance to lock access to code section
            _logger.debug("Acquire deployer lock")
            mutex = osutil.Interlock(pmm_config.pmmcli_deployer_lock_file())
            if not mutex.lock():
                _logger.debug('Deployer lock already locked')
                return None, 201, "Unable to start Deployer because there is another Deployer run"

            _logger.debug("Deployer lock acquired")
            task_manager = pmm_task.getPMMTaskManager()
            restore_task_list = task_manager.realGetTaskList('Deploy')
            for task in restore_task_list:
                task_status = task.get_status()
                if task_status.get_working():
                    _logger.debug("Working restore task detected (%s)" % str(task.get_task_id()))
                    if add_daemon_delete_dump_task:
                        self.addDaemonTaskDeleteDump(imported_dump_specification_formatter)
                    mutex.unlock()
                    return None, 201, "Unable to start Deployer because there is another Deployer run"

            try:
                mutex.unlock()
                _logger.debug("Deployer lock unlocked")
                _logger.debug("Start deployer")
                cmd.asyncSpawn()
                pid = cmd.pid
            except subproc.NonzeroExitException, x:
                raise PMMUtilityException('deployer', x)

            storage_full_name = dump_specification_formatter.get_dumps_storage_credentials_formatter().getDumpStorage()
            name = session.get_info_xml_base().replace(storage_full_name,'')
            full_name = name
            restore_task.set('fullname',full_name)
            restore_task.set('name',name)
            pmm_task.getPMMTaskManager().updateTask(restore_task)

            task = pmm_task.DeployTask( cmd.get_cmd(), pid, {'owner_guid':self.__restore_task_description.get_owner_guid(),
                                                              'owner_type':self.__restore_task_description.get_owner_type(),
                                                              'session_path':session.get_session_path(),
                                                              'dumps_storage_credentials':dump_specification_formatter.get_dumps_storage_credentials_formatter().buildString(),
                                                              'fullname':full_name,
                                                              'name':name,
                                                              'mailto':self.__restore_task_description.get_mail_to()})
            restore_task.set('deploy_task_id',task.get_task_id())
            pmm_task.getPMMTaskManager().updateTask(restore_task)
        except PMMUtilityException, e:
            errcode = 1000
            errmsg = e.subprocess.stderr
            if not errmsg:
                errmsg = str(e)
        except Exception, e:
            errcode = 1001
            errmsg = str(e)

        task_id_object = TaskId()
        task_id_object.setValueOf_(restore_task.get_task_id())
        data = Data.factory( task_id = task_id_object )
        if add_daemon_delete_dump_task:
            self.addDaemonTaskDeleteDump(imported_dump_specification_formatter)
        return data, errcode, errmsg

    def __decryptBackup(self, session):
        ignore_backup_password = 0
        if self.__restore_task_description.get_ignore_errors():
            ignore_errors = self.__restore_task_description.get_ignore_errors().get_ignore_error()
            for ignore_error in ignore_errors:
                if ignore_error.get_type() == 'backup-password':
                    ignore_backup_password = 1
                    break

        backup_password = None
        d_s = self.__restore_task_description.get_source().get_dump_specification()
        if d_s:
            credentials = d_s.get_dumps_storage_credentials()
            backup_password = credentials.get_backup_password()
        else:
            backup_password = self.__restore_task_description.get_backup_password()

        if (backup_password is not None and backup_password != '') or ignore_backup_password != 0:
            return session.decryptBackup(self.passwordToKey(backup_password), ignore_backup_password)
        return 0


class MakeDumpAction(PMMCliAction):
    def __init__(self, parameter_stdin, parameters):
        PMMCliAction.__init__(self, parameter_stdin, parameters)
        self.__parameter_stdin = parameter_stdin
        self.__backup_task_description = None
        self.__dumps_storage_credentials_formatter = None

    def generateSessionPath(self):
        pmm_session_path = pmm_config.session_dir()
        if not os.path.isdir(pmm_session_path):
            dirutil.mkdirs(pmm_session_path, 0755 )

        try:
            session_path = os.path.join(pmm_config.session_dir(),time.strftime('%Y-%m-%d-%H%M%S.',time.localtime()) + str(random.randint(0,1000)))
            dirutil.mkdirs(session_path, 0750, -1, plesk_config.psaadm_gid())
        except OSError, e:
            import errno
            if e.errno == errno.EEXIST:
                session_path = os.path.join(pmm_config.session_dir(),time.strftime('%Y-%m-%d-%H%M%S.',time.localtime()) + str(random.randint(0,1000)))
                dirutil.mkdirs(session_path, 0750, -1, plesk_config.psaadm_gid())
            elif e.errno == errno.EMLINK:
                raise PMMException(e.errno, "Failed to create session directory in '%s': %s" % (pmm_config.session_dir(), e), "Clean directory '%s'" % pmm_config.session_dir())
            else:
                raise

        return session_path

    def validate(self):
        try:
            error_code, msg = self.get_input_validator().do(self.__parameter_stdin)
        except libxml2.parserError, ex:
            return None, 'XML parse error: ' + ex.msg + '\n' + libxml2errorHandlerErr
        if error_code:
            return None, 'Error ' + str(error_code) + ': ' + msg
        try:
            self.__backup_task_description = BackupTaskDescription.factory()
            self.__backup_task_description.build(minidom.parseString(self.__parameter_stdin).childNodes[0])
        except ExpatError:
            return None, 'ExpatError in BackupTaskDescription'
        try:
            self.__backup_task_description.get_misc().get_backup_profile_name().encode('ascii')
        except UnicodeEncodeError:
            return None, 'backup-profile-name must be ascii string'
        self.__dumps_storage_credentials_formatter = pmm_dump_formatter.DumpsStorageCredentialsFormatter(self.__backup_task_description.get_dumps_storage_credentials())
        commands_to_backup = self.convertBackupSpecification2Args(self.__backup_task_description.get_backup_specification())[0]
        if len(commands_to_backup) == 0:
            return None, 'Null-length command-to-backup in ExpatError in BackupSpecification'
        return 1, ''

    def convertBackupSpecification2Args(self, backup_specification):
        object_to_backup_list = backup_specification.get_object_to_backup()
        object_to_exclude_list = backup_specification.get_object_to_exclude()
        
        # list of allowed types & backup_commands sorted by priority
        allowed_types = ['server','reseller','client','domain']
        # list of the types, enumerable by ids or names 
        enum_types = ['reseller','client','domain']
        
        commands_to_backup = []
        objects_to_backup_args = []
        objects_to_backup_warnings = []
        
        backup_command_index = None
        
        backup_all = False
        
        # Lists are structured as followings: [enum_types('reseller','client','domain'):fixed length] X [(name,id):fixed length] X [objects list:growing length]
        # 'backup_objects[0][1][0]' is an id of the first reseller (given by its id) and
        # 'backup_objects[1][0][2]' is a name of the third client (given by its name) if BackupSpecification has clent names and ids mixed (specification v.0.20 allow this)
        backup_objects = [[[] for col in range(2)] for row in range(len(enum_types))]
        exclude_objects = [[[] for col in range(2)] for row in range(len(enum_types))]
        
        backup_objects_all = [0 for row in range (len(enum_types))]
        exclude_objects_all = [0 for row in range (len(enum_types))]
        
        for object_item in object_to_backup_list:
            object_type = object_item.get_type()
            if object_type in allowed_types:
                if (backup_command_index ==None) or (allowed_types.index(object_type) < backup_command_index):
                    backup_command_index = allowed_types.index(object_type)
                if allowed_types.index(object_type) != 0:
                    if object_item.get_all() == 'true':
                        backup_objects_all[enum_types.index(object_type)] = 1
                    else:
                        object_id = object_item.get_id()
                        if object_id == '':
                            object_id = None
                        object_name = object_item.get_name()
                        if object_name == '':
                            object_name = None
                        if object_id:
                            backup_objects[enum_types.index(object_type)][1].append(object_id)
                        elif object_name:
                            backup_objects[enum_types.index(object_type)][0].append(object_name)
                        else:
                            objects_to_backup_warnings.append("Backup Task warning: Object to backup specified by type='%s' has neither 'id' nor 'name' specified.\n" % (object_type))
        
        for object_item in object_to_exclude_list:
            object_type = object_item.get_type()
            if object_type in allowed_types:
                if allowed_types.index(object_type) == 0:
                    objects_to_backup_warnings.append("Backup Task warning: 'Exclude %s' is not supported option\n" % (allowed_types[0]))
                else:
                    if object_item.get_all() == 'true':
                        exclude_objects_all[enum_types.index(object_type)] = 1
                    else:
                        object_id = object_item.get_id()
                        if object_id == '':
                            object_id = None
                        object_name = object_item.get_name()
                        if object_name == '':
                            object_name = None
                        if object_name is not None:
                            exclude_objects[enum_types.index(object_type)][0].append(object_name)
                        elif object_id is not None:
                            exclude_objects[enum_types.index(object_type)][1].append(object_id)
                        else:
                            objects_to_backup_warnings.append("Backup Task warning: Object to exclude specified by type='%s' has neither 'id' nor 'name' specified.\n" % (object_type))
        if backup_command_index is None:
            objects_to_backup_warnings.append("Backup Task warning: Could not find backup command. '%s' command will be used instead" % (allowed_types[0]))
            commands_to_backup.append(allowed_types[0])
        elif backup_command_index == 0:
            commands_to_backup.append(allowed_types[0])
        else:
            if backup_objects_all[backup_command_index-1]:
                commands_to_backup.append(allowed_types[backup_command_index]+'s-id')
                backup_all = True 
                # no id args should be specifies in this case, this forces backing up all the clients/domains/etc
            else:
                if len(backup_objects[backup_command_index-1][1]) > 0:
                    commands_to_backup.append(allowed_types[backup_command_index]+'s-id')
                    for item_id in backup_objects[backup_command_index-1][1]:
                        objects_to_backup_args.append(item_id)
                    for item_name in backup_objects[backup_command_index-1][0]:
                        objects_to_backup_warnings.append("Backup Task warning: %s '%s' does not included into backup. Cannot specify %s's ids and names together.\n" % (allowed_types[backup_command_index], item_name, allowed_types[backup_command_index]))
                
                else:
                    commands_to_backup.append(allowed_types[backup_command_index]+'s-name')
                    for item_name in backup_objects[backup_command_index-1][0]:
                        objects_to_backup_args.append(item_name)
                
            rest_alls = backup_objects_all[backup_command_index:]
            for i in range(len(rest_alls)):
                if rest_alls[i]:
                    objects_to_backup_warnings.append("Backup Task warning: 'All %ss' does not included into backup being shadowed with backing up '%ss'.\n" % (enum_types[backup_command_index+i], allowed_types[backup_command_index]))
            
            for i in range(len(backup_objects) - backup_command_index):
                for item_name in backup_objects[i + backup_command_index][0]:
                    objects_to_backup_warnings.append("Backup Task warning: %s '%s' does not included into backup being shadowed with backing up '%ss'.\n" % (allowed_types[i+ backup_command_index], item_name, allowed_types[backup_command_index]))
                for item_id in backup_objects[i + backup_command_index][1]:
                    objects_to_backup_warnings.append("Backup Task warning: %s with id=%s does not included into backup being shadowed with backing up '%ss'.\n" % (allowed_types[i+ backup_command_index], item_id, allowed_types[backup_command_index]))
            
            # processing excludes, should forms one command '--exclude-XXXX' by one object type
            for i in range(len(exclude_objects_all)):
                if exclude_objects_all[i]:
                    objects_to_backup_warnings.append("Backup Task warning: 'Exclude all %ss' does not supported." % (enum_types[i]))
            
            for i in range(len(exclude_objects)):
                for item_id in exclude_objects[i][1]:
                    objects_to_backup_warnings.append("Backup Task warning: Exclude %s with id='%s' does not included into backup. Cannot exclude %s by its id.\n" % (enum_types[i], item_id, enum_types[i]))
            
        for i in range(len(exclude_objects)):
            if len(exclude_objects[i][0]) > 0:
                cmd = '--exclude-' + enum_types[i] + '='
                delimiter = ''
                for item_name in exclude_objects[i][0]:
                    cmd = cmd + delimiter + item_name
                    delimiter = ','
                commands_to_backup.append(cmd)
        
        warning_message = ''.join(objects_to_backup_warnings)
        return commands_to_backup, backup_all, objects_to_backup_args, warning_message

    def doActivity(self):
        if not self.__backup_task_description:
            self.__backup_task_description = BackupTaskDescription.factory()
            self.__backup_task_description.build(minidom.parseString(self.__parameter_stdin).childNodes[0])
        if not self.__dumps_storage_credentials_formatter:
            self.__dumps_storage_credentials_formatter = pmm_dump_formatter.DumpsStorageCredentialsFormatter(self.__backup_task_description.get_dumps_storage_credentials())
        
        session_path = self.generateSessionPath()
        
        # topobject_to_backup always exists, but may have invalid format
        topobject_to_backup = self.__backup_task_description.get_backup_specification().get_object_to_backup()[0]
        topobject_type = topobject_to_backup.get_type()
        topobject_id = topobject_to_backup.get_id()
        
        # check available disk space
        disk_space_required = pmmcli_config.get().get_free_disk_space()
        local_dump_directory = plesk_config.get("DUMP_D")
        if local_dump_directory:
            if osutil.free_bytes(local_dump_directory) < (long(disk_space_required) * 1048576):
                task = pmm_task.BackupTask( '', -1, {'owner_guid':self.__backup_task_description.get_misc().get_owner_guid(),
                                                     'owner_type':self.__backup_task_description.get_misc().get_owner_type(),
                                                     'session_path':session_path,
                                                     'dumps_storage_credentials':self.__dumps_storage_credentials_formatter.buildString(),
                                                     'backup_profile_name':self.__backup_task_description.get_misc().get_backup_profile_name(),
                                                     'mailto':None,
                                                     'topobject_id':topobject_id,
                                                     'topobject_type':topobject_type})
                task_id_object = TaskId()
                task_id_object.setValueOf_(task.get_task_id())
                data = Data.factory( task_id = task_id_object )
                error_message = "Low Disk space for backup"
                return data, 11, error_message
        
        ftp_output_file = None
        output_file = None
        use_passive_ftp_mode = None
        
        storage_type = self.__dumps_storage_credentials_formatter.get_storage_type()
        if storage_type in ['foreign-ftp','file']:
            output_file = self.__dumps_storage_credentials_formatter.getFileDumpStorage()
            if storage_type == 'foreign-ftp':
                ftp_password = self.__dumps_storage_credentials_formatter.get_password()
                os.environ['FTP_PASSWORD'] = str(ftp_password)
                if self.__dumps_storage_credentials_formatter.get_use_passive_ftp_mode() == 'true':
                    use_passive_ftp_mode = True
        
        commands_to_backup, backup_all, objects_to_backup_args, objects_to_backup_warning_message = self.convertBackupSpecification2Args(self.__backup_task_description.get_backup_specification())
        
        session_path_arg = '--session-path=' + session_path
        
        if len(commands_to_backup) >= 0 and commands_to_backup[0] != "server" and not backup_all and len(objects_to_backup_args) == 0:
            return None, 12, "Could not backup specified object: wrong object-to-backup format:\n" + objects_to_backup_warning_message
        
        backup_filter = self.__backup_task_description.get_backup_specification().get_backup_options().get_filter()
        
        check_backup_disk_space = pmmcli_config.get().get_check_backup_disk_space()
        if check_backup_disk_space and storage_type != 'foreign-ftp':
            cmdGetSize = subproc.CmdLine(pmm_config.backup(), stderr = open(os.path.join(session_path,'.getsize.stderr'),'wt'))
            for backup_arg in pmm_config.plesk_backup_args():
                cmdGetSize.arg(backup_arg)
            
            for backup_cmd in commands_to_backup:
                cmdGetSize.arg(backup_cmd)
            cmdGetSize.arg('--get-size')
            if self.__backup_task_description.get_backup_specification().get_backup_options().get_type() == "configuration-only":
                cmdGetSize.arg('-c')
            if backup_filter == "only-mail":
                cmdGetSize.arg('--only-mail')
            elif backup_filter == "only-phosting":
                cmdGetSize.arg('--only-hosting')
            if pmmcli_config.get().force_debug_log() == 1:
                cmdGetSize.arg('-vvvvv')
            cmdGetSize.arg(session_path_arg)
            # enumerate object names or ids 
            for object_to_backup_arg in objects_to_backup_args:
                cmdGetSize.arg(object_to_backup_arg)
            
            backup_size = 0
            try:
                proc = cmdGetSize.spawn()
                proc_output = proc.stdout.encode('utf-8')
                try:
                    backup_size = long(proc_output)
                    _logger.info('Backup utility reported backup size is ' + proc_output + ' bytes')
                except ValueError:
                    _logger.warning('Unable to get backup size: Backup utility reported :\n' + proc_output)
                    pass
            except subproc.NonzeroExitException, x:
                raise PMMUtilityException('PMMCli', x)
            
            if local_dump_directory:
                free_size = osutil.free_bytes(local_dump_directory)
                _logger.info('PMMcli detected free disk space is ' + str(free_size) + ' bytes')
                if (backup_size - free_size) > 0:
                    error_message = "Not enough free disk space to backup selected objects. At least " + str(backup_size/1048576) + " Mbytes free disk space is required."
                    return None, 13, error_message

        if self.__backup_task_description.get_dumps_storage_credentials().get_backup_password() != '':
            os.environ['PLESK_BACKUP_CRYPT_KEY'] = self.passwordToKey(self.__backup_task_description.get_dumps_storage_credentials().get_backup_password())

        cmd = subproc.CmdLine(pmm_config.backup(), stderr = open(os.path.join(session_path,'.stderr'),'wt'))
        for backup_arg in pmm_config.plesk_backup_args():
            cmd.arg(backup_arg)
        
        for backup_cmd in commands_to_backup:
            cmd.arg(backup_cmd)
        
        owner_guid_arg = '--owner-uid=' + self.__backup_task_description.get_misc().get_owner_guid()
        cmd.arg(owner_guid_arg)
        owner_type = self.__backup_task_description.get_misc().get_owner_type()
        if owner_type:
            owner_type_arg = '--owner-type=' + owner_type
            cmd.arg(owner_type_arg)
        
        dump_rotation = self.__backup_task_description.get_backup_specification().get_backup_options().get_rotation()
        if dump_rotation:
            dump_rotation_arg = '--dump-rotation=' + dump_rotation
            cmd.arg(dump_rotation_arg)
        
        backup_profile_id = self.__backup_task_description.get_misc().get_backup_profile_id()
        if backup_profile_id != '':
            backup_profile_id_arg = '--backup-profile-id=' + backup_profile_id
            cmd.arg(backup_profile_id_arg)
        
        # TODO: wrap backup profile name into "" when pleskbackup will understand this
        backup_profile_name = self.__backup_task_description.get_misc().get_backup_profile_name()
        if backup_profile_name != '':
            backup_profile_name_arg = '--backup-profile-name=' + backup_profile_name
            cmd.arg(backup_profile_name_arg)
        
        split_size = self.__backup_task_description.get_backup_specification().get_backup_options().get_split_size()
        if split_size:
            split_arg = '--split=' + split_size
            cmd.arg(split_arg)
        
        dump_description = self.__backup_task_description.get_backup_specification().get_backup_options().get_description()
        if dump_description:
            if mswindows:
                dump_description_file_name = os.path.join(session_path,'dump_description')
                dump_description_file = open(dump_description_file_name, "w")
                dump_description_file.write(dump_description.encode('utf-8'))
                dump_description_file.close()
                dump_description_file_arg = '--description-file=' + dump_description_file_name
                cmd.arg(dump_description_file_arg)
            else:
                dump_description_arg = '--description=' + dump_description
                cmd.arg(dump_description_arg)
        
        if self.__backup_task_description.get_backup_specification().get_backup_options().get_type() == "configuration-only":
            cmd.arg('-c')
        
        if self.__backup_task_description.get_backup_specification().get_backup_options().get_compression_level() == "do-not-compress":
            cmd.arg('--no-gzip')
        
        if backup_filter == "only-mail":
            cmd.arg('--only-mail')
        elif backup_filter == "only-phosting":
            cmd.arg('--only-hosting')
            
        if pmmcli_config.get().force_debug_log() == 1:
            cmd.arg('-vvvvv')

        if self.__backup_task_description.get_backup_specification().get_backup_options().get_suspend() == "true":
            cmd.arg('--suspend')
        
        verbose_level = self.__backup_task_description.get_misc().get_verbose_level()
        try:
            verbose_level_value = int(verbose_level)
            if verbose_level_value >= 1:
                if verbose_level_value > 4:
                    verbose_level_value = 4
                verbose_arg = '-' + 'v'*verbose_level_value
                cmd.arg(verbose_arg)
        except (TypeError, ValueError):
            pass
        
        cmd.arg(session_path_arg)
        
        if output_file:
            output_file_arg = '--output-file=' + output_file
            cmd.arg(output_file_arg)
        
        if use_passive_ftp_mode:
            cmd.arg('--ftp-passive-mode')
        
        # enumerate object names or ids 
        for object_to_backup_arg in objects_to_backup_args:
            cmd.arg(object_to_backup_arg)

        data = None
        try:
            subprocess = cmd.asyncSpawn()
            pid = subprocess.get_pid()

            if mswindows:
                import win32con, win32event, win32file

                h = win32file.FindFirstChangeNotification(session_path, 1, win32con.FILE_NOTIFY_CHANGE_FILE_NAME | win32con.FILE_NOTIFY_CHANGE_SIZE | win32con.FILE_NOTIFY_CHANGE_LAST_WRITE)
                if h != win32file.INVALID_HANDLE_VALUE:
                    win32event.WaitForSingleObject(h, 1000)
                    win32file.FindCloseChangeNotification(h)

            subprocess.poll()

            task = pmm_task.BackupTask( cmd.get_cmd(), pid, {'owner_guid':self.__backup_task_description.get_misc().get_owner_guid(),
                                                             'owner_type':self.__backup_task_description.get_misc().get_owner_type(),
                                                             'session_path':session_path,
                                                             'dumps_storage_credentials':self.__dumps_storage_credentials_formatter.buildString(),
                                                             'backup_profile_name':self.__backup_task_description.get_misc().get_backup_profile_name(),
                                                             'mailto':self.__backup_task_description.get_backup_specification().get_backup_options().get_mail_to(),
                                                             'topobject_id':topobject_id,
                                                             'topobject_type':topobject_type})
            task_id_object = TaskId()
            task_id_object.setValueOf_(task.get_task_id())
            data = Data.factory( task_id = task_id_object )
        except subproc.NonzeroExitException, x:
            e = PMMUtilityException(cmd.get_cmd(), x)
            if mswindows and x.exitcode == -2146232576: # 0x80131700 - CLR_E_SHIM_RUNTIMELOAD
                raise PMMException(x.exitcode, str(e), "Try to reinstall Microsoft .NET Framework 2.0 - it's required by '%s'" % pmm_config.backup())
            raise e
        return data, 0, objects_to_backup_warning_message


class DeleteDumpAction(PMMCliAction):
    def __init__(self, parameter_stdin, parameters):
        PMMCliAction.__init__(self, parameter_stdin, parameters)
        self.__parameter_stdin = parameter_stdin
        self.__dump_specification_formatter = None

    def validate(self):
        try:
            error_code, msg = self.get_input_validator().do(self.__parameter_stdin)
        except libxml2.parserError, ex:
            return None, 'XML parse error: ' + ex.msg + '\n' + libxml2errorHandlerErr
        if error_code:
            return None, 'Error ' + str(error_code) + ': ' + msg
        try:
            self.__dump_specification_formatter = pmm_dump_formatter.DumpSpecificationFormatter(self.__parameter_stdin)
        except pmm_dump_formatter.DumpSpecificationFormatException:
            return None, 'DumpSpecificationFormatException in DumpSpecification'
        return 1, ''

    def doActivity(self):
        if not self.__dump_specification_formatter:
            self.__dump_specification_formatter = pmm_dump_formatter.DumpSpecificationFormatter(self.__parameter_stdin)
        dumps_storage_credentials_formatter = self.__dump_specification_formatter.get_dumps_storage_credentials_formatter()
        dumps_storage_credentials = dumps_storage_credentials_formatter.buildXml()
        storage_type = dumps_storage_credentials_formatter.get_storage_type()
        errcode = 0
        message = None
        access_service = None
        if storage_type != 'local':
            access_service = pmm_repository_access_service.FtpRepositoryAccessService
        else:
            access_service = pmm_repository_access_service.LocalRepositoryAccessService
        errcode, message = access_service(dumps_storage_credentials).deleteDump(self.__dump_specification_formatter.buildXml())
        return None, errcode, message


class ResolveConflictsAction(PMMCliAction):
    def __init__(self, parameter_stdin, parameters):
        PMMCliAction.__init__(self, parameter_stdin, parameters)
        self.__parameter_stdin = parameter_stdin
        self.__resolve_conflicts_task_description = None

    def get_resolve_conflicts_task_description(self):
        return self.__resolve_conflicts_task_description

    def validate(self):
        try:
            error_code, msg = self.get_input_validator().do(self.__parameter_stdin)
        except libxml2.parserError, ex:
            return None, 'XML parse error: ' + ex.msg + '\n' + libxml2errorHandlerErr
        if error_code:
            return None, 'Error ' + str(error_code) + ': ' + msg
        try:
            self.__resolve_conflicts_task_description = ResolveConflictsTaskDescription.factory()
            self.__resolve_conflicts_task_description.build(minidom.parseString(self.__parameter_stdin).childNodes[0])
        except ExpatError:
            return None, 'ExpatError in ResolveConflictsTaskDescription'
        return 1, ''

    def __getConflictResolveTask(self, session):
        conflict_resolve_task = None
        restore_task = self.__getRestoreTask(session)
        conflict_resolve_task_id = restore_task.get('conflict_resolve_task_id')
        if conflict_resolve_task_id is not None:
            conflict_resolve_task = pmm_task.getPMMTaskManager().realGetTask(conflict_resolve_task_id)
            conflict_resolve_task.set('session_path', session.get_session_path())
            pmm_task.getPMMTaskManager().updateTask(conflict_resolve_task)
        else:
            conflict_resolve_task = pmm_task.ConflictResolveTask({'session_path':session.get_session_path()})
            restore_task.set('conflict_resolve_task_id',conflict_resolve_task.get_task_id())
            pmm_task.getPMMTaskManager().updateTask(restore_task)
        return conflict_resolve_task

    def __getRestoreTask(self, session ):
        task_id = session.get_task_id()
        return pmm_task.getPMMTaskManager().realGetTask(task_id)

    def doActivity(self):
        if not self.__resolve_conflicts_task_description:
            self.__resolve_conflicts_task_description = ResolveConflictsTaskDescription.factory()
            self.__resolve_conflicts_task_description.build(minidom.parseString(self.__parameter_stdin).childNodes[0])
        session_id = self.get_resolve_conflicts_task_description().get_session_id()
        rsession = pmmcli_session.PmmcliSession.cloneSession(session_id)
        rsession.resolveConflictsOnce(rsession.get_plesk_actual_dump(),self.get_resolve_conflicts_task_description().get_conflict_resolution_rules())
        self.__getConflictResolveTask(rsession)
        dump_overview_object = rsession.getDumpOverview()
        data = Data.factory(dump_overview = dump_overview_object)
        return data, 0, None


class GetConflictsDescriptionAction(PMMCliAction):
    def __init__(self, parameter_stdin, parameters):
        PMMCliAction.__init__(self, parameter_stdin, parameters)
        self.__parameters = parameters
        self.__session_id = None
        
    def validate(self):
        if len(self.__parameters) != 1:
            return None, "Parameter 'session_id' not specified"
        self.__session_id = self.__parameters[0]
        return 1, ''

    def doActivity(self):
        if not self.__session_id:
            self.__session_id = self.__parameters[0]
        errcode = 0
        session = pmmcli_session.PmmcliSession(self.__session_id)
        overview = session.getDumpOverview()
        conflicts = session.getConflictDescription()
        result = RestoreTaskResult(dump_overview = overview, conflicts_description = conflicts)
        data = Data.factory( restore_task_result = result )
        return data, errcode, None


class GetTasksListAction(PMMCliAction):
    def __init__(self, parameter_stdin, parameters):
        PMMCliAction.__init__(self, parameter_stdin, parameters)
        self.__parameters = parameters
        self.__type = ''
        self.__owner_guid = None
        self.__topobject_id = None
        self.__topobject_type = None

    def validate(self):
        if len(self.__parameters) < 1:
            return None, "Parameter 'task_type' is not specified"
        if not self.__parameters[0] in ['Backup','Restore','Migration','Deploy', 'ConflictResolve']:
            return None, "Task type must be 'Backup', 'Restore', 'Migration', 'Deploy' or 'ConflictResolve'"
        self.__type = self.__parameters[0]
        if len(self.__parameters) >= 2:
            self.__owner_guid = self.__parameters[1]
        if self.__type == 'Backup':
            if len(self.__parameters) >= 3:
                self.__topobject_id = self.__parameters[2]
            if len(self.__parameters) >= 4:
                if not self.__parameters[3] in ['server','reseller','client','domain']:
                    return None, "Top object type must be one of: 'server','reseller','client','domain'"
                self.__topobject_type = self.__parameters[3]
        return 1, ''

    def doActivity(self):
        data = Data.factory(task_list = pmm_task.getPMMTaskManager().getTaskList(self.__type,self.__owner_guid,self.__topobject_id,self.__topobject_type))
        return data, 0, None


class GetSessionsListAction(PMMCliAction):
    def __init__(self, parameter_stdin, parameters):
        PMMCliAction.__init__(self, parameter_stdin, parameters)
        self.__parameters = parameters
        self.__type = None

    def validate(self):
        if len(self.__parameters) < 1:
            return None, "Parameter 'session_type' is not specified"
        if not self.__parameters[0] in ['Migration']:
            return None, "Session type must be 'Migration'"
        self.__type = self.__parameters[0]
        return 1, ''

    def doActivity(self):
        if self.__type is None:
            self.__type = self.__parameters[0]
        if self.__type == 'Migration':
            if not pmm_migration_handler.MigrationHandler.installed():
                return None, 51, 'Migration Manager is not installed'
            return pmm_migration_handler.MigrationHandler.migrationGetSessionList( )
        else:
            return None, 1, ''


class GetTaskAction(PMMCliAction):
    def __init__(self, parameter_stdin, parameters):
        PMMCliAction.__init__(self, parameter_stdin, parameters)
        self.__parameters = parameters
        self.__task_id = None

    def validate(self):
        if len(self.__parameters) != 1:
            return None, "Parameter 'task_id' not specified"
        self.__task_id = self.__parameters[0]
        # there is no format requirements for task-id
        return 1, ''

    def doActivity(self):
        if not self.__task_id:
            self.__task_id = self.__parameters[0]
        pmm_task_list = pmm_task.getPMMTaskManager().getTask(self.__task_id)
        if pmm_task_list:
            data = Data.factory(task_list = pmm_task_list)
            return data, 0, None
        else:
            return None, 2, 'No task found with task_id = '+str(self.__task_id)


class GetTaskStatusAction(PMMCliAction):
    def __init__(self, parameter_stdin, task_param):
        PMMCliAction.__init__(self, parameter_stdin, task_param)
        self.__parameters = task_param
        self.__task_id = None

    def validate(self):
        if len(self.__parameters) != 1:
            return None, "Parameter 'task_id' not specified"
        self.__task_id = self.__parameters[0]
        # there is no format requirements for task-id
        return 1, ''

    def doActivity(self):
        if not self.__task_id:
            self.__task_id = self.__parameters[0]
        status = pmm_task.getPMMTaskManager().getTaskStatus(self.__task_id)
        if status:
            data = Data.factory( task_status = status )
            return data, 0, None
        else:
            return None, 2, 'No task found with task_id = '+str(self.__task_id)


class GetTaskLogAction(PMMCliAction):
    def __init__(self, parameter_stdin, task_param):
        PMMCliAction.__init__(self, parameter_stdin, task_param)
        self.__parameters = task_param
        self.__task_id = None

    def validate(self):
        if len(self.__parameters) != 1:
            return None, "Parameter 'task_id' not specified"
        self.__task_id = self.__parameters[0]
        # there is no format requirements for task-id
        return 1, ''

    def doActivity(self):
        if not self.__task_id:
            self.__task_id = self.__parameters[0]
        tasklog =  pmm_task.getPMMTaskManager().getTaskLog(self.__task_id)
        if tasklog:
            data = Data.factory(task_log = tasklog)
            return data, 0, None
        else:
            return None, 2, 'No task found with task_id = '+str(self.__task_id)


class RemoveTaskDataAction(PMMCliAction):
    def __init__(self, parameter_stdin, task_param):
        PMMCliAction.__init__(self, parameter_stdin, task_param)
        self.__parameters = task_param
        self.__task_id = None

    def validate(self):
        if len(self.__parameters) != 1:
            return None, "Parameter 'task_id' not specified"
        self.__task_id = self.__parameters[0]
        # there is no format requirements for task-id
        return 1, ''

    def doActivity(self):
        if not self.__task_id:
            self.__task_id = self.__parameters[0]
        result = pmm_task.getPMMTaskManager().removeTask(self.__task_id)
        if result:
            errcode = 0
            return None, errcode, None
        else:
            return None, 2, 'No task found with task_id = '+str(self.__task_id)


class StopTaskAction(PMMCliAction):
    def __init__(self, parameter_stdin, task_param):
        PMMCliAction.__init__(self, parameter_stdin, task_param)
        self.__parameters = task_param
        self.__task_id = None

    def validate(self):
        if len(self.__parameters) != 1:
            return None, "Parameter 'task_id' not specified"
        self.__task_id = self.__parameters[0]
        # there is no format requirements for task-id
        return 1, ''

    def doActivity(self):
        if not self.__task_id:
            self.__task_id = self.__parameters[0]
        result = pmm_task.getPMMTaskManager().stopTask(self.__task_id)
        errcode = 0
        if result:
            errcode = 0
            return None, errcode, None
        else:
            return None, 2, 'No task found with task_id = '+str(self.__task_id)


class MigrationGetObjectsListAction(PMMCliAction):
    def __init__(self, parameter_stdin, parameters):
        PMMCliAction.__init__(self, parameter_stdin, parameters)
        self.__parameter_stdin = parameter_stdin
        self.__parameters = parameters
        self.__migration_session_id = None
        self.__agent_to_use = None

    def validate(self):
        if len(self.__parameters) != 1:
            return None, "Parameter 'migration-session-id' is not specified"
        self.__migration_session_id = self.__parameters[0]
        try:
            error_code, msg = self.get_input_validator().do(self.__parameter_stdin)
        except libxml2.parserError, ex:
            return None, 'XML parse error: ' + ex.msg + '\n' + libxml2errorHandlerErr
        if error_code:
            return None, 'Error ' + str(error_code) + ': ' + msg
        try:
            self.__agent_to_use = AgentToUse.factory()
            self.__agent_to_use.build(minidom.parseString(self.__parameter_stdin).childNodes[0])
        except ExpatError:
            return None, 'ExpatError in MigrationGetObjectsListAction'
        
        return 1, ''

    def doActivity(self):
        if not pmm_migration_handler.MigrationHandler.installed():
            return None, 51, 'Migration Manager is not installed'
        
        if not self.__migration_session_id:
            self.__migration_session_id = self.__parameters[0]
        if not self.__agent_to_use:
            try:
                self.__agent_to_use = AgentToUse.factory()
                self.__agent_to_use.build(minidom.parseString(self.__parameter_stdin).childNodes[0])
            except ExpatError:
                return None, 1, 'ExpatError in MigrationGetObjectsListAction'
        
        return pmm_migration_handler.MigrationHandler.migrationGetObjectsList( self.__migration_session_id ,self.__agent_to_use.getValueOf_() )


class GetScoutInfoAction(PMMCliAction):
    def __init__(self, parameter_stdin, parameters):
        PMMCliAction.__init__(self, parameter_stdin, parameters)
        self.__parameter_stdin = parameter_stdin
        self.__parameters = parameters
        self.__migration_task_description = None

    def validate(self):
        try:
            error_code, msg = self.get_input_validator().do(self.__parameter_stdin)
        except libxml2.parserError, ex:
            return None, 'XML parse error: ' + ex.msg + '\n' + libxml2errorHandlerErr
        if error_code:
            return None, 'Error ' + str(error_code) + ': ' + msg
        try:
            self.__migration_task_description = MigrationTaskDescription.factory()
            self.__migration_task_description.build(minidom.parseString(self.__parameter_stdin).childNodes[0])
        except ExpatError:
            return None, 'ExpatError in GetScoutInfoAction'
        
        return 1, ''

    def doActivity(self):
        if not pmm_migration_handler.MigrationHandler.installed():
            return None, 51, 'Migration Manager is not installed'
        
        if not self.__migration_task_description:
            try:
                self.__migration_task_description = MigrationTaskDescription.factory()
                self.__migration_task_description.build(minidom.parseString(self.__parameter_stdin).childNodes[0])
            except ExpatError:
                return None, 1, 'ExpatError in GetScoutInfoAction'
        
        return pmm_migration_handler.MigrationHandler.migrationGetScoutInfo( self.__parameter_stdin )


class MigrationPutSessionDataAction(PMMCliAction):
    def __init__(self, parameter_stdin, parameters):
        PMMCliAction.__init__(self, parameter_stdin, parameters)
        self.__parameter_stdin = parameter_stdin
        self.__parameters = parameters
        self.__migration_session_id = None
        self.__session_type = None
        self.__ip_mapping = None
        self.__restore_policy = None
        self.__owner_mapping = None
        self.__db_mapping = None

    def validate(self):
        if len(self.__parameters) != 2:
            return None, "Parameters 'migration-session-id' or 'session-data' are not specified"
        self.__migration_session_id = self.__parameters[0]
        self.__session_type = self.__parameters[1]
        if not self.__session_type in ['ip-mapping', 'owners', 'db-mapping', 'restore-policy']:
            return None, "Parameter 'session-type' has unallowed value!"
        try:
            error_code, msg = self.get_input_validator().do(self.__parameter_stdin)
        except libxml2.parserError, ex:
            return None, 'XML parse error: ' + ex.msg + '\n' + libxml2errorHandlerErr
        if error_code:
            return None, 'Error ' + str(error_code) + ': ' + msg
        try:
            if self.__session_type=='ip-mapping':
                self.__ip_mapping = IPMapping.factory()
                self.__ip_mapping.build(minidom.parseString(self.__parameter_stdin).childNodes[0])
            elif self.__session_type=='db-mapping':
                self.__db_mapping = DBMapping.factory()
                self.__db_mapping.build(minidom.parseString(self.__parameter_stdin).childNodes[0])
            elif self.__session_type=='restore-policy':
                self.__restore_policy = ConflictResolutionRules.factory()
                self.__restore_policy.build(minidom.parseString(self.__parameter_stdin).childNodes[0])
            elif self.__session_type=='owners':
                self.__owner_mapping = OwnersMapping.factory()
                self.__owner_mapping.build(minidom.parseString(self.__parameter_stdin).childNodes[0])
        except ExpatError:
            return None, 'ExpatError in MigrationPutSessionDataAction'
        return 1, ''

    def doActivity(self):
        if not pmm_migration_handler.MigrationHandler.installed():
            return None, 51, 'Migration Manager is not installed'
        if not self.__migration_session_id:
            self.__migration_session_id = self.__parameters[0]
        if not self.__session_type:
            self.__session_type = self.__parameters[1]
        #work with pmm_migration_handler.MigrationHandler
        if self.__session_type=='ip-mapping':
            return pmm_migration_handler.MigrationHandler.migrationSetIPMapping( self.__migration_session_id, self.__parameter_stdin )
        elif self.__session_type=='db-mapping':
            return pmm_migration_handler.MigrationHandler.migrationSetDBMapping( self.__migration_session_id, self.__parameter_stdin )
        elif self.__session_type=='restore-policy':
            return pmm_migration_handler.MigrationHandler.migrationSetPolicy( self.__migration_session_id, self.__parameter_stdin )
        elif self.__session_type=='owners':
            return pmm_migration_handler.MigrationHandler.migrationSetOwnerMapping( self.__migration_session_id, self.__parameter_stdin )
        else:
            return None, 1, "Parameter 'session-type' has unallowed value!"


class MigrationGetSessionDataAction(PMMCliAction):
    def __init__(self, parameter_stdin, parameters):
        PMMCliAction.__init__(self, parameter_stdin, parameters)
        self.__parameters = parameters
        self.__migration_session_id = None
        self.__session_data_type = None
        
    def validate(self):
        if len(self.__parameters) != 2:
            return None, "Parameters 'migration-session-id' or 'session-data-type' are not specified"
        self.__migration_session_id = self.__parameters[0]
        self.__session_data_type = self.__parameters[1]
        if not self.__session_data_type in ['ip-mapping', 'ip-for-mapping', 'owners', 'db-mapping', 'restore-policy', 'selected-objects', 'objects-list', 'agent-credentials', 'dst-temporary-directory' ]:
            return None, "Invalid 'session-data-type' parameter"
        return 1, ''

    def doActivity(self):
        if not pmm_migration_handler.MigrationHandler.installed():
            return None, 51, 'Migration Manager is not installed'
        if self.__migration_session_id is None:
            self.__migration_session_id = self.__parameters[0]
        if self.__session_data_type is None:
            self.__session_data_type = self.__parameters[1]
        #work with pmm_migration_handler.MigrationHandler
        if self.__session_data_type=='ip-mapping':
            return pmm_migration_handler.MigrationHandler.migrationGetIPMapping( self.__migration_session_id )
        elif self.__session_data_type=='ip-for-mapping':
            return pmm_migration_handler.MigrationHandler.migrationGetIPForMapping( self.__migration_session_id )
        elif self.__session_data_type=='db-mapping':
            return pmm_migration_handler.MigrationHandler.migrationGetDBMapping( self.__migration_session_id )
        elif self.__session_data_type=='restore-policy':
            return pmm_migration_handler.MigrationHandler.migrationGetPolicy( self.__migration_session_id )
        elif self.__session_data_type=='owners':
            return pmm_migration_handler.MigrationHandler.migrationGetOwnerMapping( self.__migration_session_id )
        elif self.__session_data_type=='selected-objects':
            return pmm_migration_handler.MigrationHandler.migrationGetSelectedObjects( self.__migration_session_id )
        elif  self.__session_data_type=='objects-list': 
            return pmm_migration_handler.MigrationHandler.migrationGetSessionObjectsList( self.__migration_session_id )
        elif  self.__session_data_type=='agent-credentials': 
            return pmm_migration_handler.MigrationHandler.migrationGetAgentCredentials( self.__migration_session_id )
        elif  self.__session_data_type=='dst-temporary-directory': 
            return pmm_migration_handler.MigrationHandler.migrationGetDstTemporaryDirectory( self.__migration_session_id )
        else:
            return None, 1, "Invalid 'session-data-type' parameter"


class MigrationRemoveSessionDataAction(PMMCliAction):
    def __init__(self, parameter_stdin, parameters):
        PMMCliAction.__init__(self, parameter_stdin, parameters)
        self.__parameters = parameters
        self.__migration_session_id = None
        
    def validate(self):
        if len(self.__parameters) != 1:
            return None, "Parameter 'migration-session-id' is not specified"
        self.__migration_session_id = self.__parameters[0]
        return 1, ''

    def doActivity(self):
        if not pmm_migration_handler.MigrationHandler.installed():
            return None, 51, 'Migration Manager is not installed'
        if self.__migration_session_id is None:
            self.__migration_session_id = self.__parameters[0]
        
        #work with pmm_migration_handler.MigrationHandler
        data, errcode, message = pmm_migration_handler.MigrationHandler.migrationGetSession( self.__migration_session_id )
        if data is not None:
            session = data.get_session_list().get_session()[0]
            steps_passed = session.get_steps_passed()
            if steps_passed is not None:
                for step in steps_passed.get_step():
                    if 'migration-task-started' == step.get_id():
                        return None, 5, 'Migration session is associated with migration task and could not be removed alone'
            data, errcode, message = pmm_migration_handler.MigrationHandler.migrationRemoveSessionData( self.__migration_session_id )
        return data, errcode, message


class MigrationGetIPForMappingAction(PMMCliAction):
    def __init__(self, parameter_stdin, parameters):
        PMMCliAction.__init__(self, parameter_stdin, parameters)
        self.__parameter_stdin = parameter_stdin
        self.__parameters = parameters
        self.__migration_session_id = None
        self.__selected_objects = None

    def validate(self):
        try:
            error_code, msg = self.get_input_validator().do(self.__parameter_stdin)
        except libxml2.parserError, ex:
            return None, 'XML parse error: ' + ex.msg + '\n' + libxml2errorHandlerErr
        if error_code:
            return None, 'Error ' + str(error_code) + ': ' + msg
        if len(self.__parameters) != 1:
            return None, "Parameter 'migration-session-id' is not specified"
        self.__migration_session_id = self.__parameters[0]
        try:
            self.__selected_objects = MigrationObjectList.factory()
            self.__selected_objects.build(minidom.parseString(self.__parameter_stdin).childNodes[0])
        except ExpatError:
            return None, 'ExpatError in MigrationGetIPForMappingAction'
        return 1, ''

    def doActivity(self):
        if not pmm_migration_handler.MigrationHandler.installed():
            return None, 51, 'Migration Manager is not installed'
        #work with pmm_migration_handler.MigrationHandler
        if self.__migration_session_id is None:
            self.__migration_session_id = self.__parameters[0]
        if self.__selected_objects is None:
            self.__selected_objects = MigrationObjectList.factory()
            self.__selected_objects.build(minidom.parseString(self.__parameter_stdin).childNodes[0])
        return pmm_migration_handler.MigrationHandler.migrationGetIPForMappingObjects( self.__migration_session_id, self.__parameter_stdin )


class MigrationStartAction(PMMCliAction):
    def __init__(self, parameter_stdin, parameters):
        PMMCliAction.__init__(self, parameter_stdin, parameters)
        self.__parameters = parameters
        self.__migration_session_id = None

    def validate(self):
        if len(self.__parameters) != 1:
            return None, "Parameter 'migration-session-id' is not specified"
        self.__migration_session_id = self.__parameters[0]
        # there are no format requirements for migration-session-id
        return 1, ''

    def doActivity(self):
        if not self.__migration_session_id :
            self.__migration_session_id = self.__parameters[0]
        
        if not pmm_migration_handler.MigrationHandler.installed():
            return None, 51, 'Migration Manager is not installed'
        
        #work with pmm_migration_handler.MigrationHandler
        data, errcode, message = pmm_migration_handler.MigrationHandler.migrationStart(self.__migration_session_id)
        
        return data, errcode, message

class GetConfigParametersAction(PMMCliAction):
    def __init__(self, parameter_stdin, parameters):
        PMMCliAction.__init__(self, parameter_stdin, parameters)

    def validate(self):
        return 1, ''

    def doActivity(self):
        config_parameters_object = ConfigParameters.factory()
        for param_name, param_value in pmmcli_config.get().iteritems():
            param = parameter.factory( name = param_name, value = param_value )
            config_parameters_object.add_parameter(param)
        data = Data.factory( config_parameters = config_parameters_object )
        return data, 0, None

class SetConfigParametersAction(PMMCliAction):
    def __init__(self, parameter_stdin, parameters):
        PMMCliAction.__init__(self, parameter_stdin, parameters)
        self.__parameter_stdin = parameter_stdin
        self.__config_parameters = None
    
    def validate(self):
        try:
            error_code, msg = self.get_input_validator().do(self.__parameter_stdin)
        except libxml2.parserError, ex:
            return None, 'XML parse error: ' + ex.msg + '\n' + libxml2errorHandlerErr
        if error_code:
            return None, 'Error ' + str(error_code) + ': ' + msg
        try:
            self.__config_parameters = ConfigParameters.factory()
            self.__config_parameters.build(minidom.parseString(self.__parameter_stdin).childNodes[0])
        except ExpatError:
            return None, 'ExpatError in ConfigParameters'
        return 1, ''
    
    def doActivity(self):
        if not self.__config_parameters:
            self.__config_parameters = ConfigParameters.factory()
            self.__config_parameters.build(minidom.parseString(self.__parameter_stdin).childNodes[0])

        parameters_list = self.__config_parameters.get_parameter()

        for parameter in parameters_list:
            param_name = parameter.get_name()
            param_value = parameter.get_value()
            pmmcli_config.get().writeParameter(param_name, param_value)

#        return 1, ''
        return self.__config_parameters, 0, None
   
def usage():
    print "pmmcli. Plesk Migration Manager."
    print "(c) Parallels"
    print "" 
    print "Usage:"
    print "" 
    print "    pmmcli <action> [<param1-value>[<param2-value>[<param3-value>[<param4-value>]]]]"
    print "" 
    print "        Available action:"
    print "            --get-dumps-list"
    print "                 STDIN is 'dump-list-query' from pmm_api_xml_protocols.xsd"
    print "            --get-dump-overview"
    print "                 STDIN is 'dump-specification' from pmm_api_xml_protocols.xsd or session-id"
    print "            --check-dump"
    print "                 STDIN is 'dump-specification' from pmm_api_xml_protocols.xsd"
    print "            --export-dump-as-file"
    print "                 STDIN is 'src-dst-files-specification' from pmm_api_xml_protocols.xsd"
    print "            --export-file-as-file"
    print "                 STDIN is 'src-dst-files-specification' from pmm_api_xml_protocols.xsd"
    print "            --import-file-as-dump"
    print "                 STDIN is 'src-dst-files-specification' from pmm_api_xml_protocols.xsd"
    print "            --restore"
    print "                 STDIN is 'restore-task-description' from pmm_api_xml_protocols.xsd"
    print "            --resolve-conflicts"
    print "                 STDIN is 'resolve-conflilcts-task-description' from pmm_api_xml_protocols.xsd"
    print "            --get-conflicts-description"
    print "                 param1-value is session-id"
    print "            --get-tasks-list"
    print "                 param1-value is task type ( 'Restore' or 'Backup' or 'Migration')"
    print "                 param2-value is guid of the task owner (or 'any') (ignored for 'Migration' type)"
    print "                 param3-value (ignored for 'Restore' and 'Migration' task types) is id of the top object to backup ('backup-task-description/backup-specification/object-to-backup[0]/@id') or 'any' string"
    print "                 param4-value (ignored for 'Restore' and 'Migration' task types) is type of the top object to backup ('backup-task-description/backup-specification/object-to-backup[0]/@type')"
    print "            --get-sessions-list"
    print "                 param1-value is session type ( should be 'Migration')"
    print "            --get-task"
    print "                 param1-value is task-id"
    print "            --make-dump"
    print "                 STDIN is 'backup-task-description' from pmm_api_xml_protocols.xsd"
    print "            --delete-dump"
    print "                 STDIN is 'dump-specification' from pmm_api_xml_protocols.xsd"
    print "            --get-task-status"
    print "                 param1-value is task-id"
    print "            --get-task-log"
    print "                 param1-value is task-id"
    print "            --remove-task-data"
    print "                 param1-value is task-id"
    print "            --stop-task"
    print "                 param1-value is task-id"
    print "            --get-scout-info"
    print "                 STDIN is 'migraton-task-description' from pmm_api_xml_protocols.xsd"
    print "            --migration-get-objects-list"
    print "                 param1-value is migration-session-id"
    print "                 STDIN is 'agent-to-use' from pmm_api_xml_protocols.xsd"
    print "            --migration-put-session-data"
    print "                 param1-value is migration-session-id"
    print "                 param2-value is type of session data: 'ip-mapping', 'db-mapping', 'owners' or 'restore-policy'"
    print "                 STDIN is data which should be saved, data should be in format corresponding to data type"
    print "            --migration-get-session-data"
    print "                 param1-value is migration-session-id"
    print "                 param2-value is type of session data: 'ip-mapping', db-mapping', 'ip-for-mapping', 'owners', 'restore-policy', 'agent-credentials', 'selected-objects', 'objects-list' or 'dst-temporary-directory'"
    print "            --migration-remove-session-data"
    print "                 param1-value is migration-session-id"
    print "            --migration-get-ip-for-mapping"
    print "                 param1-value is migration-session-id"
    print "                 STDIN is selected objects"
    print "            --migration-start"
    print "                 param1-value is migration-session-id"
    print "            --help"
    print "                 Display this help"
    print ""
    print "    The pmm_api_xml_protocols.xsd is placed at " + pmm_config.pmm_api_xml_protocols_schema()
    print "    The output is 'response' from pmm_api_xml_protocols.xsd"
    print "    The task-id, session-id, migration-session-id is result of previous operation."
    print ""


def bind_stdin(command):
    path = pmm_config.pmmcli_dir() + "/" + command[2:] + ".stdin"
    if os.path.exists(path) and not os.path.isdir(path):
        sys.stdin = open(path,"rt")

def reset_stdin():
    if sys.stdin != sys.__stdin__:
        sys.stdin = sys.__stdin__
    
def safe_print(packet):
    try:
        sys.stdout.write(packet)
    except IOError:
        pass

def validate_path(path):
    if not os.path.isabs(path):
        return (False, "Session path must be absolute: " +  path + "\n")
    
    if os.path.exists(path) and not os.path.isdir(path):
        return (False, "Path " + path + " exists, but not a directory.\n")
    if not os.path.exists(path):
        try:
            dirutil.mkdirs(path, 0750)
        except UnicodeError, e:
            return (False, "Invalid character in session directory\n")
        except OSError, e:
            return (False, "Unable to create directory " + path + ": " + e.strerror + "\n")
    return (True, None)

def get_dumps_list(parameters):
    dump_list_query = sys.stdin.read()
    return ActionRunner(GetDumpsListAction, dump_list_query, None).doActivity()

def get_config_parameters(parameters):
    return ActionRunner(GetConfigParametersAction, None, None).doActivity()

def set_config_parameters(parameters):
    config_parameters = sys.stdin.read()
    return ActionRunner(SetConfigParametersAction, config_parameters, None).doActivity()

def get_dump_overview(parameters):
    dump_specification = sys.stdin.read()
    return ActionRunner(GetDumpOverviewAction, dump_specification, None).doActivity()

def check_dump(parameters):
    dump_specification = sys.stdin.read()
    return ActionRunner(CheckDumpAction, dump_specification, None).doActivity()

def export_dump_as_file(parameters):
    src_dst_files_specification = sys.stdin.read()
    temp = parameters
    return ActionRunner(ExportDumpAsFileAction, src_dst_files_specification, temp).doActivity()

def export_file_as_file(parameters):
    src_dst_files_specification = sys.stdin.read()
    return ActionRunner(ExportFileAsFileAction, src_dst_files_specification, None).doActivity()

def import_file_as_dump(parameters):
    src_dst_files_specification = sys.stdin.read()
    return ActionRunner(ImportFileAsDumpAction, src_dst_files_specification, None).doActivity()

def restore(parameters):
    # <parameter1> is Plesk User Id which is a owner of a restore process
    # if <parameter2> is set to --force, PMM should not make conflicts detection but should just restore received Restore Specification
    restore_task_specification = sys.stdin.read()
    return ActionRunner(RestoreAction, restore_task_specification, parameters).doActivity()

def resolve_conflicts(parameters):
    resolve_conflicts_task_description = sys.stdin.read()
    return ActionRunner(ResolveConflictsAction, resolve_conflicts_task_description, None).doActivity()

def get_conflicts_description(parameters):
    # <parameter1> is session_id
    session_id = parameters
    return ActionRunner(GetConflictsDescriptionAction, None, session_id).doActivity()

def get_tasks_list(parameters):
    # <parameter1> is a task type (Restore|Backup|Migration)
    # <parameter2> (optional) is a guid of the task owner or "any" string (ignored for 'Migration' task type)
    # <parameter3> (optional) is a id of the top object or "any" string (for 'Backup' task type, ignored for 'Restore' and 'Migration' task types)
    # <parameter4> (optional) is a type of the top object (for 'Backup' task type, ignored for 'Restore' and 'Migration' task types)
    task_params = parameters
    return ActionRunner(GetTasksListAction, None, task_params).doActivity()

def get_sessions_list(parameters):
    # <parameter1> is a task type (should be 'Migration')
    session_params = parameters
    return ActionRunner(GetSessionsListAction, None, session_params).doActivity()

def get_task(parameters):
    task_param = parameters
    return ActionRunner(GetTaskAction, None, task_param).doActivity()

def make_dump(parameters):
    backup_task_description = sys.stdin.read()
    return ActionRunner(MakeDumpAction, backup_task_description, None).doActivity()

def delete_dump(parameters):
    dump_description = sys.stdin.read()
    return ActionRunner(DeleteDumpAction, dump_description, None).doActivity()

def get_task_status(parameters):
    task_param = parameters
    return ActionRunner(GetTaskStatusAction, None, task_param).doActivity()

def get_task_log(parameters):
    task_param = parameters
    return ActionRunner(GetTaskLogAction, None, task_param).doActivity()

def remove_task_data(parameters):
    task_param = parameters
    return ActionRunner(RemoveTaskDataAction, None, task_param).doActivity()

def stop_task(parameters):
    task_param = parameters
    return ActionRunner(StopTaskAction, None, task_param).doActivity()

def get_scout_info(parameters):
    migration_task_description = sys.stdin.read()
    return ActionRunner(GetScoutInfoAction, migration_task_description, None).doActivity()

def migration_get_objects_list(parameters):
    migration_task_description = sys.stdin.read()
    migration_session_id = parameters
    return ActionRunner(MigrationGetObjectsListAction, migration_task_description, migration_session_id).doActivity()

def migration_put_session_data(parameters):
    # <parameter1> is migration_session_id
    # <parameter2> is type of session data
    session_parameters = parameters
    session_data = sys.stdin.read()
    return ActionRunner(MigrationPutSessionDataAction, session_data, session_parameters).doActivity()

def migration_get_session_data(parameters):
    # <parameter1> is migration_session_id
    # <parameter2> is type of session data
    session_parameters = parameters
    return ActionRunner(MigrationGetSessionDataAction, None, session_parameters).doActivity()

def migration_remove_session_data(parameters):
    # <parameter1> is migration_session_id
    migration_session_id = parameters
    return ActionRunner(MigrationRemoveSessionDataAction, None, migration_session_id).doActivity()

def migration_get_ip_for_mapping(parameters):
    # <parameter1> is migration_session_id
    migration_session_id = parameters
    selected_objects = sys.stdin.read()
    return ActionRunner(MigrationGetIPForMappingAction, selected_objects, migration_session_id).doActivity()

def migration_start(parameters):
    # <parameter1> is migration_session_id
    migration_session_id = parameters
    return ActionRunner(MigrationStartAction, None, migration_session_id).doActivity()

def convertToXmlString(response, name):
    resp_str = cStringIO.StringIO()
    resp_encoded = EncoderFile(resp_str, "utf-8")
    resp_encoded.write('<?xml version="1.0" encoding="UTF-8"?>\n')
    response.export(resp_encoded, 0, name_ = name)
    packet = resp_str.getvalue()
    return packet

def main():
    actions = {"get-dumps-list": get_dumps_list,
               "get-dump-overview": get_dump_overview,
               "check-dump": check_dump,
               "export-dump-as-file": export_dump_as_file,
               "export-file-as-file": export_file_as_file,
               "import-file-as-dump": import_file_as_dump,
               "restore": restore,
               "resolve-conflicts": resolve_conflicts,
               "get-conflicts-description": get_conflicts_description,
               "get-tasks-list": get_tasks_list,
               "get-sessions-list": get_sessions_list,
               "get-task": get_task,
               "make-dump": make_dump,
               "get-task-status": get_task_status,
               "get-task-log": get_task_log,
               "remove-task-data": remove_task_data,
               "stop-task": stop_task,
               "delete-dump": delete_dump,
               "get-scout-info": get_scout_info,
               "migration-get-objects-list": migration_get_objects_list,
               "migration-put-session-data": migration_put_session_data,
               "migration-get-session-data": migration_get_session_data,
               "migration-remove-session-data": migration_remove_session_data,
               "migration-get-ip-for-mapping": migration_get_ip_for_mapping,
               "migration-start": migration_start,
               "get-config-parameters": get_config_parameters,
               "set-config-parameters": set_config_parameters}
    
    debug_mode = os.environ.has_key("DEBUG_PMMCLI")
    
    if len(sys.argv) < 2:
        usage()
        sys.exit(1)
    
    if sys.argv[1] == '--help':
        usage()
        sys.exit(0)
    
    if not sys.argv[1][2:] in actions:
        print "Unknown command \'" + sys.argv[1] + "'.\n"
        usage()
        sys.exit(1)
    
    pmmcli_log_dir = pmm_config.pmm_logs_directory()
    parameters = sys.argv[2:]
    validate_path(pmmcli_log_dir)
    log.initPidLog("pmmcli", pmmcli_log_dir, 1)
    
    tempstdout = cStringIO.StringIO()
    tempstderr = cStringIO.StringIO()
    
    try:
        try:
            error_message = ""
            sys.stdout = tempstdout
            sys.stderr = tempstderr
            bind_stdin(sys.argv[1])
            try:
                data_action_response, errcode_response, error_message = actions.get(sys.argv[1][2:])(parameters)
                if not errcode_response:
                    errcode_response = 0
            except PmmcliActionParamException, ex:
                errcode_response = 1
                data_action_response = None
                error_message = "Invalid input parameters in '" + sys.argv[1] + "' command:\n" + ex.get_message()
            if not error_message:
                error_message = ""
            if tempstdout.getvalue():
                error_message = error_message + "STDOUT:\n-----------------------\n" + tempstdout.getvalue() + "\n" 
            if tempstderr.getvalue():
                error_message = error_message + "STDERR:\n-----------------------\n" + tempstderr.getvalue() + "\n"
        finally:
            sys.stdout = sys.__stdout__
            sys.stderr = sys.__stderr__
            reset_stdin()
        response = Response( errcode = errcode_response, data = data_action_response, errmsg = error_message)
        packet = convertToXmlString(response,'response')
        _logger.info("Outgoing packet:\n" + packet)
        safe_print(packet)
    except pmmcli_session.RSessionException, e:
        response = Response( errcode = 3, errmsg = "Session with id='" + str(e.get_session_id()) + "' not found")
        packet = convertToXmlString(response,'response')
        _logger.info("Outgoing packet:\n" + packet)
        safe_print(packet)
    except (pmm_dump_formatter.DumpsStorageCredentialsFormatException, pmm_dump_formatter.DumpSpecificationFormatException), e:
        response = Response( errcode = 1, errmsg = "Wrong format for dump specification: " + e.get_message()+ "\n" +  stacktrace.stacktrace())
        packet = convertToXmlString(response,'response')
        _logger.info("Outgoing packet:\n" + packet)
        safe_print(packet)
        sys.exit(1)
    except PMMUtilityException, e: 
        _logger.critical("PMMUtility exception: \n" +  str(unicode(e)) + "\n" +  stacktrace.stacktrace())
        response = Response( errcode = 1000, errmsg = "pmm utility '" + e.utility() + "' raised an exception. Error code is: " + str(e.exitcode) + "\n" + "See pmmcli.log to find out detailed information on this")
        packet = convertToXmlString(response,'response')
        _logger.info("Outgoing packet:\n" + packet)
        safe_print(packet)
        sys.exit(1)
    except PMMException, e:
        _logger.critical("PMMException: \n" + str(e) + "\n" + stacktrace.stacktrace())

        oe = cStringIO.StringIO()
        writeExecutionResult(EncoderFile(oe, "utf-8"), e)
        response = Response(errcode = 1004, errmsg = oe.getvalue())

        packet = convertToXmlString(response, 'response')
        _logger.info("Outgoing packet:\n" + packet)
        safe_print(packet)
        sys.exit(1)
    except osutil.PsException, e:
        _logger.critical("PsException in pmmcli: \n" + str(e.__class__) + " " + str(e) + "\n" +  stacktrace.stacktrace())
        response = Response( errcode = 1003, errmsg = "Exec format error in ps utility: command '" + str(e) + "' was failed")
        packet = convertToXmlString(response,'response')
        _logger.info("Outgoing packet:\n" + packet)
        safe_print(packet)
        sys.exit(1)
    except Exception, e:
        _logger.critical("Runtime error in pmmcli: \n" + str(e.__class__) + " " + str(e) + "\n" +  stacktrace.stacktrace())
        response = Response(errcode = 1001, errmsg = str(e))
        packet = convertToXmlString(response,'response')
        _logger.info("Outgoing packet:\n" + packet)
        safe_print(packet)
        sys.exit(1)
    except:
        _logger.critical("Unhandled exception in pmmcli: \n" +  stacktrace.stacktrace())
        response = Response( errcode = 1002, errmsg = "Unhandled exception in pmmcli: \n" +  stacktrace.stacktrace())
        packet = convertToXmlString(response,'response')
        _logger.info("Outgoing packet:\n" + packet)
        safe_print(packet)
        sys.exit(1)
    
if __name__ == '__main__':
    main()

