<?php
// Copyright 1999-2017. Plesk International GmbH. All rights reserved.

namespace Plesk\MigrationLibV8\SiteImport;

use Plesk\MigrationLibV8\Common\SessionDirectory;
use Plesk\MigrationLibV8\SiteImport\Backend\Files\MigrationList;
use Plesk\MigrationLibV8\SiteImport\Backend\Tool;
use Plesk\MigrationLibV8\SiteImport\Entity\FileTransports;
use Plesk\MigrationLibV8\SiteImport\Entity\SiteInfo\Application;
use Plesk\MigrationLibV8\SiteImport\Entity\SiteInfo\File;
use Plesk\MigrationLibV8\SiteImport\Entity\SiteInfo\SiteInfo;
use Plesk\MigrationLibV8\SiteImport\Entity\SourceServerAccess;
use Plesk\MigrationLibV8\SiteImport\Entity\Status\Import;
use Plesk\MigrationLibV8\SiteImport\Entity\Status\Import\Database;
use Plesk\MigrationLibV8\SiteImport\Entity\Status\Import\Folder;
use Plesk\MigrationLibV8\SiteImport\Entity\Status\Issue;
use Plesk\MigrationLibV8\SiteImport\Entity\Status\RetrieveSiteInfo;
use Plesk\MigrationLibV8\SiteImport\Entity\ImportOperation;
use Plesk\MigrationLibV8\Utils\ArrayUtils;
use Plesk\MigrationLibV8\Utils\FileUtils;

/**
 * A wrapper around backend of Plesk Migrator to perform site import.
 * Site import allows you to import applications, folders and databases from source site using FTP/SSH.
 *
 * The key object of site import is a session directory - a directory which stores state of import
 * and temporary files necessary for import.
 * Before running any site import operation, you must obtain a session directory object.
 *
 * To perform an application/folder/database import, the following workflow is suggested:
 *
 * 1. Obtain a domain object for the target site, to which you want to import applications, folders and databases.
 * For example, ask customer to select domain from a list of available domains,
 * or use domain from the current context. The domain must have physical hosting enabled. Import to domain
 * without hosting or with forwarding type of hosting is not supported.
 *
 * $domain = new \pm_Domain(123);
 *
 * 2. Obtain a session object. A session is a directory which contains configuration files,
 * logs, reports and other temporary data for Plesk Migrator's backend. Each session has an unique string ID.
 * It is recommended to use domain ID as a part of session ID.
 *
 * $session = \Plesk\MigrationLibV8\Common\SessionDirectory::getForCurrentExtension('site-migration-' . $domain->getId());
 *
 * 3. Obtain credentials to access the source server (source domain name, FTP/SSH login, FTP/SSH password, and so on).
 * For example, ask customer to provide this information. Create an object:
 *
 * $sourceServerParameters = new \Plesk\MigrationLibV8\SiteImport\Entity\SourceServerAccess('example.com', 'john', '123qwe');
 *
 * 4. Create site import manager:
 *
 * $manager = new \Plesk\MigrationLibV8\SiteImport\Manager();
 *
 * 5. Request Plesk Migrator to get information about the source site:
 *
 * $manager->retrieveSiteInfo($session, $domain, $sourceServerParameters);
 *
 * The function is executed in synchronous way. Sometimes it could be useful to split configuration
 * and retrieve site info into 2 separate operations - "configure", which is fast,
 * and "run" which could take a lot of time:
 *
 * $manager->configureRetrieveSiteInfo($session, $domain, $sourceServerParameters);
 * $manager->runRetrieveSiteInfo($session, $domain, $sourceServerParameters);
 *
 * It is reasonable to execute "retrieveSiteInfo"/"runRetrieveSiteInfo" withing a long task.
 *
 * 6. Poll information about retrieve site info progress from another process/thread:
 *
 * $retrieveStatus = $manager->getRetrieveSiteInfoStatus($session);
 *
 * Retrieve site information has 2 separate operations: connect to the source site and scan content.
 * You could get their status separately. To get status:
 *
 * $connectStatus = $retrieveStatus->getOperationStatus(
 *     \Plesk\MigrationLibV8\SiteImport\Entity\Status\RetrieveSiteInfo::OPERATION_CONNECT
 * );
 * $retrieveStatus->getOperationStatus(
 *     \Plesk\MigrationLibV8\SiteImport\Entity\Status\RetrieveSiteInfo::OPERATION_SCAN_CONTENT
 * );
 *
 * Compare status with constants:
 * - \Plesk\MigrationLibV8\SiteImport\Entity\Status\RetrieveSiteInfo::STATUS_NOT_STARTED
 * - \Plesk\MigrationLibV8\SiteImport\Entity\Status\RetrieveSiteInfo::STATUS_SUCCESS
 * - \Plesk\MigrationLibV8\SiteImport\Entity\Status\RetrieveSiteInfo::STATUS_FAILURE
 *
 * Also there are auxiliary methods "isOperationFinished" and "isOperationSuccessful".
 *
 * In case when any of operations has failed, you could get error message, which could be displayed to customer:
 *
 * $errorMessage = $retrieveStatus->getErrorMessage();
 *
 * 7. Once Plesk Migrator finished retrieving information about the source site, you could get it with:
 *
 * $siteInfo = $manager->getSiteInfo($session);
 *
 * You could read list of detected applications:
 *
 * $applications = $siteInfo->getApplications();
 *
 * Also, you could iterate over tree of files on the source server:
 *
 * $rootFolder = $siteInfo->getRootFolder();
 * $rootFiles = $rootFolder->getFiles();
 *
 * 8. According to the information about the source site, select applications/folders/databases and schedule import.
 *
 * To schedule import of an application:
 *
 * $application = new \Plesk\MigrationLibV8\SiteImport\Entity\ImportOperation\Application(
 *     \Plesk\MigrationLibV8\SiteImport\Entity\ApplicationTypes::WORDPRESS, '/source/path', '/target/path'
 * );
 * $manager->scheduleApplicationImport($session, $application);
 *
 * To schedule import of a folder:
 *
 * $folder = new \Plesk\MigrationLibV8\SiteImport\Entity\ImportOperation\Folder('/source/path', '/target/path');
 * $manager->scheduleFolderImport($session, $folder);
 *
 * To schedule import of a database:
 *
 * $database = new \Plesk\MigrationLibV8\SiteImport\Entity\ImportOperation\Database(
 *     'databaseName', 'localhost', '3306', 'login', '123qwe'
 * );
 * $manager->scheduleDatabaseImport($session, $database);
 *
 * Scheduling import is fast operation (in most cases should take less than 1 second).
 *
 * 9. Once you've scheduled import, start task runner which processes schedules import tasks:
 *
 * $manager->startTasksRunner($session);
 *
 * Task runner works in synchronous way on Windows and in asynchronous way on Linux. That is quite weird,
 * the difference is caused by difficulties implementing asynchronous command execution on Windows.
 *
 * To improve stability, it is useful to start task runner periodically, for example once a minute.
 * If another task runner is already running, the new one will exit immediately. In that case
 * if server was rebooted, or task runner has stopped for any reason, import will continue to work.
 *
 * 10. Once task runner is started, poll import status of imported/schedules applications/folders/databases:
 *
 * $importStatus = $manager->getImportStatus($session);
 *
 * $applicationsStatus = $importStatus->getApplicationsStatus();
 * $foldersStatus = $importStatus->getFoldersStatus();
 * $databasesStatus = $importStatus->getDatabasesStatus();
 *
 * To get status and issues of applications, you could use the following code snippet:
 *
 * foreach ($applicationsStatus as $applicationStatus) {
 *     $type = $applicationStatus->getType();
 *     $sourcePath = $applicationStatus->getSourcePath();
 *     $targetPath = $applicationStatus->getTargetPath();
 *     $status = $applicationStatus->getStatus();
 *     $issues = $applicationStatus->getIssues();
 *
 *     foreach ($issue as $issue) {
 *         $severity = $issue->getSeverity();
 *         $problemText = $issue->getProblemText();
 *         $detailsText = $issue->getDetailsText();
 *     }
 * }
 *
 * 11. Additionally, you have the following abilities:
 *
 * - Cancel migration of scheduled/importing applications/folders/databases with
 * "cancelApplicationImport", "cancelFolderImport", "cancelDatabaseImport" and "cancelImport" methods.
 * - Check whether session is configured: "isSessionConfigured" method.
 * - Check whether site information was retrieved: "isSiteInfoRetrieved" method.
 */
class Manager
{
    /**
     * Commands which can be put in migration list for task runner.
     */
    const IMPORT_COMMAND = 'import';
    const CHECK_APPLICATIONS_COMMAND = 'check';
    const CANCEL_COMMAND = 'cancel';

    /**
     * Request Plesk Migrator to get information about source site: list files and detect applications.
     *
     * Method is executed in synchronous way: the function will return control only once Plesk Migrator
     * retrieved all information from the source site or failed.
     * Operation could take a lot of time (up to several minutes), so run it within a long task.
     *
     * @param SessionDirectory $session session directory to save migration state and temporary data
     * @param \pm_Domain $site Plesk SDK object for target domain
     * @param SourceServerAccess $sourceServerAccess source server access: hostname, FTP/SSH login and password
     */
    public function retrieveSiteInfo(
        SessionDirectory $session, \pm_Domain $site,
        SourceServerAccess $sourceServerAccess
    ) {
        $this->configureRetrieveSiteInfo($session, $site, $sourceServerAccess);
        $this->runRetrieveSiteInfo($session);
    }

    /**
     * Prepare to get information about source site (list files and detect applications).
     *
     * This function is executed quickly: it just creates/modifies configuration files.
     *
     * @param SessionDirectory $session session directory to save migration state and temporary data
     * @param \pm_Domain $site Plesk SDK object for target domain
     * @param SourceServerAccess $sourceServerAccess source server access: hostname, FTP/SSH login and password
     */
    public function configureRetrieveSiteInfo(
        SessionDirectory $session, \pm_Domain $site,
        SourceServerAccess $sourceServerAccess
    )
    {
        $session->initialize();
        $sessionObjects = SessionObjects::getInstance($session);

        $config = $sessionObjects->getConfig();
        $config->initDefaultOptions();
        $config->setSessionDirectory($session->getId());
        $config->setTargetOS(\pm_ProductInfo::isUnix() ? 'unix' : 'windows');
        $config->setSourceType('web');
        $config->setSourceHost($sourceServerAccess->getHost());
        $config->setSourceUsername($sourceServerAccess->getUsername());
        $config->setSourcePassword($sourceServerAccess->getPassword());
        if ($sourceServerAccess->getFtpEncoding()) {
            $config->setSourceFtpEncoding($sourceServerAccess->getFtpEncoding());
        }
        if ($sourceServerAccess->getFileTransport() !== null) {
            $config->setSourceFileTransport($sourceServerAccess->getFileTransport());
        }
        if ($sourceServerAccess->getTransferFilesMode() !== null) {
            $config->setSourceTransferFilesMode($sourceServerAccess->getTransferFilesMode());
        }
        if ($sourceServerAccess->getAllowAutoWebStreaming() !== null) {
            $config->setSourceAllowAutoWebStreaming($sourceServerAccess->getAllowAutoWebStreaming());
        }
        if ($sourceServerAccess->getSSHPort() !== null) {
            $config->setSourceSSHPort($sourceServerAccess->getSSHPort());
        }
        if ($sourceServerAccess->getIgnorePassiveModeServerIp() !== null) {
            $config->setIgnorePassiveModeServerIp($sourceServerAccess->getIgnorePassiveModeServerIp());
        }
        $config->setSourceBaseUrl($sourceServerAccess->getSiteUrl());
        $config->setSourceDocumentRoot($sourceServerAccess->getSiteDocumentRoot());
        if ($sourceServerAccess->getFilesRoot() !== null) {
            $config->setSourceFilesRoot($sourceServerAccess->getFilesRoot());
        }
        if ($sourceServerAccess->getListFilesMode() !== null) {
            $config->setListFilesMode($sourceServerAccess->getListFilesMode());
        }
        $config->setSourceTargetDomain($site->getName());
        $config->save();

        $this->resetStatus($session);
    }

    /**
     * Reset migration status to be ready to the new migration attempt.
     */
    public function resetStatus($session) {
        $sessionObjects = SessionObjects::getInstance($session);
        $status = $sessionObjects->getSiteMigrationStatus();
        $status->resetOperationsStatus();
        $progress = $sessionObjects->getProgress();
        $progress->cleanup();
    }

    /**
     * Save configuration
     */
    public function saveConfig($session) {
        $sessionObjects = SessionObjects::getInstance($session);
        $config = $sessionObjects->getConfig();
        $config->save();
    }

    /**
     * Allow insecure connections by setting option in session`s config.
     */
    public function allowInsecureConnections($session) {
        $sessionObjects = SessionObjects::getInstance($session);
        $config = $sessionObjects->getConfig();
        $config->setAllowInsecureConnections(true);
    }

    /**
     * Set FTP as file transport.
     */
    public function setFileTransportFtp($session) {
        $sessionObjects = SessionObjects::getInstance($session);
        $config = $sessionObjects->getConfig();
        $config->setSourceFileTransport(FileTransports::FTP);
    }

    /**
     * Request Plesk Migrator to get information about source site for pre-configured session:
     * list files and detect applications.
     *
     * Session directory must be pre-configured with "configureRetrieveSiteInfo" method.
     *
     * Method is executed in synchronous way: the function will return control only once Plesk Migrator
     * retrieved all information from the source site or failed.
     * Operation could take a lot of time (up to several minutes), so run it within a long task.
     *
     * @param SessionDirectory $session pre-configured session directory to save migration state and temporary data
     */
    public function runRetrieveSiteInfo(SessionDirectory $session)
    {
        $sessionObjects = SessionObjects::getInstance($session);
        $config = $sessionObjects->getConfig();

        try {
            $migratorTool = new Tool();
            $migratorTool->getSiteInfo($config, true, false);
        } catch (\Exception $e) {
            // In case of any error - store the error to the status file
            $status = $sessionObjects->getSiteMigrationStatus();
            $status->setBackendFailure($e->getMessage());
        }
    }

    /**
     * Check whether session directory is pre-configured and could be used for migration.
     *
     * If session is not configured - you could configure it with "configureRetrieveSiteInfo" method.
     * If session is configured - you could retrieve information about source site with "runRetrieveSiteInfo".
     *
     * @param SessionDirectory $session | null session directory object to check
     * @return bool
     */
    public function isSessionConfigured(SessionDirectory $session=null)
    {
        try {
            if (
                is_null($session) ||
                !FileUtils::fileExists($session->getPath())
            ) {
                return false;
            }

            $sessionObjects = SessionObjects::getInstance($session);

            return (
                $sessionObjects->getConfig()->getSourceType() &&
                $sessionObjects->getConfig()->getSourceHost()
            );
        } catch(\Exception $e) {
            \pm_Log::err(
                "Failed to check if session is configured, it will be considered as not configured: " .
                $e->getMessage()
            );
        }

        return false;
    }

    /**
     * Get status of retrieve source site info operation,
     * started by "runRetrieveSiteInfo" or "retrieveSiteInfo" methods.
     *
     * @param SessionDirectory $session session directory on which retrieve site info operation was started
     * @return RetrieveSiteInfo object with status and progress of operation
     */
    public function getRetrieveSiteInfoStatus(
        SessionDirectory $session
    ) {
        $sessionObjects = SessionObjects::getInstance($session);
        $status = $sessionObjects->getSiteMigrationStatus();
        $status->read();
        return new RetrieveSiteInfo(
            [
                $status::OPERATION_CONNECT => $status->getOperationStatus($status::OPERATION_CONNECT),
                $status::OPERATION_SCAN_CONTENT => $status->getOperationStatus($status::OPERATION_SCAN_CONTENT)
            ],
            implode("\n", $status->getErrorMessages()),
            $status->getIssueId()
        );
    }

    /**
     * Check whether source site info (list of applications and files) was retrieved.
     *
     * @param SessionDirectory $session session directory object
     * @return bool
     */
    public function isSiteInfoRetrieved(
        SessionDirectory $session
    ) {
        $sessionObjects = SessionObjects::getInstance($session);
        $siteInfoFile = $sessionObjects->getSiteInfo();
        return $siteInfoFile->isComplete();
    }

    /**
     * Get information about source site, retrieved in advance by "runRetrieveSiteInfo" or "retrieveSiteInfo" methods.
     *
     * @param SessionDirectory $session session directory for which retrieve site info was finished.
     * @return SiteInfo information about source site: list of applications and files
     */
    public function getSiteInfo(
        SessionDirectory $session
    ) {
        $sessionObjects = SessionObjects::getInstance($session);
        $siteInfoFile = $sessionObjects->getSiteInfo();

        $importStatus = $this->getImportStatus($session);

        $applications = [];
        foreach ($siteInfoFile->getApplications() as $application) {
            $applicationTargetPath = $application['target_path'];
            foreach ($importStatus->getApplicationsStatus() as $applicationStatus) {
                if (
                    $application['type'] == $applicationStatus->getType()
                    && $application['source_path'] == $applicationStatus->getSourcePath()
                ) {
                    $applicationTargetPath = $siteInfoFile->getTargetRelativePath($applicationStatus->getTargetPath());
                }
            }
            $applications[] = new Application(
                $application['type'], $application['source_path'], $applicationTargetPath
            );
        }

        $rootFolder = $this->_convertFolder('', $siteInfoFile->getFiles());

        return new SiteInfo(
            $applications, $rootFolder,
            $siteInfoFile->getRootPath(), $siteInfoFile->getSourceDocumentRoot(), $siteInfoFile->getSiteName()
        );
    }

    /**
     * Schedule import of an application.
     *
     * Schedule import works fast (in most cases less than a second).
     * Once import is scheduled, you should call "startTasksRunner" to actually start import.
     *
     * @param SessionDirectory $session
     * @param \Plesk\MigrationLibV8\SiteImport\Entity\ImportOperation\Application $application
     */
    public function scheduleApplicationImport(SessionDirectory $session, ImportOperation\Application $application)
    {
        $this->scheduleImport($session, [$application], [], []);
    }

    /**
     * Schedule import of a folder.
     *
     * Schedule import works fast (in most cases less than a second).
     * Once import is scheduled, you should call "startTasksRunner" to actually start import.
     *
     * @param SessionDirectory $session
     * @param \Plesk\MigrationLibV8\SiteImport\Entity\ImportOperation\Folder $folder
     */
    public function scheduleFolderImport(SessionDirectory $session, ImportOperation\Folder $folder)
    {
        $this->scheduleImport($session, [], [$folder], []);
    }

    /**
     * Schedule import of a database.
     *
     * Schedule import works fast (in most cases less than a second).
     * Once import is scheduled, you should call "startTasksRunner" to actually start import.
     *
     * @param SessionDirectory $session
     * @param \Plesk\MigrationLibV8\SiteImport\Entity\ImportOperation\Database $database
     */
    public function scheduleDatabaseImport(SessionDirectory $session, ImportOperation\Database $database)
    {
        $this->scheduleImport($session, [], [], [$database]);
    }

    /**
     * Schedule import of applications, folders and databases.
     *
     * Use this function if you need to schedule import of several applications, folders, databases.
     * If you need to schedule import of a single application, folder or database then
     * use "scheduleApplicationImport", "scheduleFolderImport" or "scheduleDatabaseImport" respectively,
     * which are more simple to call.
     *
     * Schedule import works fast (in most cases less than a second).
     * Once import is scheduled, you should call "startTasksRunner" to actually start import.
     *
     * @param SessionDirectory $session
     * @param \Plesk\MigrationLibV8\SiteImport\Entity\ImportOperation\Application[] $applications
     * @param \Plesk\MigrationLibV8\SiteImport\Entity\ImportOperation\Folder[] $folders
     * @param \Plesk\MigrationLibV8\SiteImport\Entity\ImportOperation\Database[] $databases
     */
    public function scheduleImport(
        SessionDirectory $session,
        $applications, $folders, $databases
    ) {
        $this->_scheduleTask($session, $applications, $folders, $databases, self::IMPORT_COMMAND);
    }

    /**
     * Schedule check of applications.
     *
     * Use this function if you need to schedule check of several applications.
     *
     * Schedule check works fast (in most cases less than a second).
     * Once check is scheduled, you should call "startTasksRunner" to actually start check.
     *
     * @param SessionDirectory $session
     * @param \Plesk\MigrationLibV8\SiteImport\Entity\ImportOperation\Application[] $applications
     */
    public function scheduleCheckApplications(
        SessionDirectory $session, $applications
    ) {
        $this->_scheduleTask($session, $applications, [], [], self::CHECK_APPLICATIONS_COMMAND);
    }

    /**
     * Start task runner, which runs import for previously scheduled imports for applications, folders and databases.
     *
     * On Unix this function returns once the task runner was started (asynchronously).
     * On Windows this function returns only once the task runner was finished (synchronously).
     * The difference is caused by difficulties implementing asynchronous command execution on Windows.
     *
     * @param SessionDirectory $session
     */
    public function startTasksRunner(
        SessionDirectory $session
    ) {
        $sessionObjects = SessionObjects::getInstance($session);

        $migratorTool = new Tool();
        $migratorTool->runSiteMigrationTasks($sessionObjects->getConfig());
    }

    /**
     * Get import status and import issues for applications, databases and folders.
     *
     * Workflow of import is the following:
     * - schedule import
     * ("scheduleApplicationImport", "scheduleFolderImport" , "scheduleDatabaseImport", "scheduleImport" methods)
     * - start task runner
     * ("startTasksRunner" method)
     * - periodically (e.g. once a second) poll import status with that function,
     * display status/issues in user interface.
     *
     * @param SessionDirectory $session
     * @return Import
     */
    public function getImportStatus(
        SessionDirectory $session
    ) {
        $sessionObjects = SessionObjects::getInstance($session);
        $status = $sessionObjects->getSiteMigrationStatus();

        $siteData = $status->getSiteData();
        $siteDataApplications = ArrayUtils::getElementDefaultArray($siteData, 'applications');
        $siteDataDatabases = ArrayUtils::getElementDefaultArray($siteData, 'databases');
        $siteDataFolders = ArrayUtils::getElementDefaultArray($siteData, 'files');

        $applicationsStatus = [];
        foreach ($siteDataApplications as $application) {
            $applicationsStatus[] = new Entity\Status\Import\Application(
                $application['type'], $application['source_path'], $application['target_path'],
                $this->_convertIssuesToObject(
                    ArrayUtils::getElementDefaultArray($application, 'issues')
                ),
                $application['status'],
                $this->_convertIssuesToObject(
                    ArrayUtils::getElementDefaultArray($application, 'check-issues')
                ),
                $application['check-status'] ?? null
            );
        }

        $foldersStatus = [];
        foreach ($siteDataFolders as $folder) {
            $foldersStatus[] = new Folder(
                $folder['source_path'], $folder['target_path'],
                $this->_convertIssuesToObject(
                    ArrayUtils::getElementDefaultArray($folder, 'issues')
                ),
                $folder['status']
            );
        }

        $databasesStatus = [];
        foreach ($siteDataDatabases as $database) {
            $databasesStatus[] = new Database(
                $database['name'], $database['host'], $database['port'],
                $database['username'], $database['password'],
                $this->_convertIssuesToObject(
                    ArrayUtils::getElementDefaultArray($database, 'issues')
                ),
                $database['status']
            );
        }

        return new Import(
            $applicationsStatus, $foldersStatus, $databasesStatus
        );
    }

    /**
     * Cancel scheduled or running import or specified application.
     *
     * Operation is synchronous - function returns control once import is actually cancelled.
     * Usually that is fast, but sometimes could take up to a minute, for example in case of hanged connection
     * to the source server.
     *
     * @param SessionDirectory $session
     * @param \Plesk\MigrationLibV8\SiteImport\Entity\ImportOperation\Application $application
     */
    public function cancelApplicationImport(SessionDirectory $session, ImportOperation\Application $application)
    {
        $this->cancelImport($session, [$application], [], []);
    }

    /**
     * Cancel scheduled or running import or specified folder.
     *
     * Operation is synchronous - function returns control once import is actually cancelled.
     * Usually that is fast, but sometimes could take up to a minute, for example in case of hanged connection
     * to the source server.
     *
     * @param SessionDirectory $session
     * @param \Plesk\MigrationLibV8\SiteImport\Entity\ImportOperation\Folder $folder
     */
    public function cancelFolderImport(SessionDirectory $session, ImportOperation\Folder $folder)
    {
        $this->cancelImport($session, [], [$folder], []);
    }

    /**
     * Cancel scheduled or running import or specified database.
     *
     * Operation is synchronous - function returns control once import is actually cancelled.
     * Usually that is fast, but sometimes could take up to a minute, for example in case of hanged connection
     * to the source server.
     *
     * @param SessionDirectory $session
     * @param \Plesk\MigrationLibV8\SiteImport\Entity\ImportOperation\Database $database
     */
    public function cancelDatabaseImport(SessionDirectory $session, ImportOperation\Database $database)
    {
        $this->cancelImport($session, [], [], [$database]);
    }

    /**
     * Cancel scheduled or running import or specified applications, folders and databases.
     *
     * Operation is synchronous - function returns control once import is actually cancelled.
     * Usually that is fast, but sometimes could take up to a minute, for example in case of hanged connection
     * to the source server.
     *
     * Use this function if you need to cancel import of several applications, folders, databases.
     * If you need to cancel import of a single application, folder or database then
     * use "cancelApplicationImport", "cancelFolderImport" or "cancelDatabaseImport" respectively,
     * which are more simple to call.
     *
     * @param SessionDirectory $session
     * @param \Plesk\MigrationLibV8\SiteImport\Entity\ImportOperation\Application[] $applications
     * @param \Plesk\MigrationLibV8\SiteImport\Entity\ImportOperation\Folder[] $folders
     * @param \Plesk\MigrationLibV8\SiteImport\Entity\ImportOperation\Database[] $databases
     */
    public function cancelImport(
        SessionDirectory $session,
        $applications, $folders, $databases
    ) {
        $sessionObjects = SessionObjects::getInstance($session);

        $progress = $sessionObjects->getProgress();
        $progress->cleanup();

        $migrationList = $this->_createMigrationListWithRandomName(
            $session, $applications, $folders, $databases, self::CANCEL_COMMAND
        );

        $migratorTool = new Tool();
        $migratorTool->cancelSiteMigrationTasks($sessionObjects->getConfig(), $migrationList->getFilePath());
    }

    private function _convertFolder($name, $folderData)
    {
        $folders = [];
        $files = [];
        foreach ($folderData as $item) {
            if (is_array($item)) {
                foreach ($item as $key => $subfolder) {
                    $folders[] = $this->_convertFolder($key, $subfolder);
                }
            } else {
                $files[] = new File($item);
            }
        }

        return new Entity\SiteInfo\Folder($name, $files, $folders);
    }

    private function _convertIssuesToObject($issuesArray)
    {
        $issues = [];
        foreach ($issuesArray as $issue) {
            $issues[] = new Issue(
                $issue['severity'] ?? '',
                $issue['execution_date'] ?? '',
                $issue['problem_text'] ?? '',
                $issue['details_text'] ?? ''
            );
        }
        return $issues;
    }

    /**
     * @param SessionDirectory $session
     * @param \Plesk\MigrationLibV8\SiteImport\Entity\ImportOperation\Application[] $applications
     * @param \Plesk\MigrationLibV8\SiteImport\Entity\ImportOperation\Folder[] $folders
     * @param \Plesk\MigrationLibV8\SiteImport\Entity\ImportOperation\Database[] $databases
     * @param string $command
     * @return MigrationList
     */
    private function _createMigrationListWithRandomName(
        SessionDirectory $session, $applications, $folders, $databases, $command
    ) {
        $sessionObjects = SessionObjects::getInstance($session);
        $migrationListSuffix = date('Y-m-d-H-i-s');
        $migrationList = $sessionObjects->getSiteMigrationList($migrationListSuffix);
        $migrationList->write(
            $applications, $folders, $databases, $command
        );
        return $migrationList;
    }

    /**
     * @param SessionDirectory $session
     * @param \Plesk\MigrationLibV8\SiteImport\Entity\ImportOperation\Application[] $applications
     * @param \Plesk\MigrationLibV8\SiteImport\Entity\ImportOperation\Folder[] $folders
     * @param \Plesk\MigrationLibV8\SiteImport\Entity\ImportOperation\Database[] $databases
     * @param string $command
     */
    private function _scheduleTask(
        SessionDirectory $session,
        $applications, $folders, $databases, $command
    ) {
        $sessionObjects = SessionObjects::getInstance($session);

        $progress = $sessionObjects->getProgress();
        $progress->cleanup();

        $migrationList = $this->_createMigrationListWithRandomName(
            $session, $applications, $folders, $databases, $command
        );

        $tool = new Tool();
        $tool->addSiteMigrationTasks($sessionObjects->getConfig(), $migrationList->getFilePath());
    }
}
