#include "worker.h"
#include "update.h"

#include <QCoreApplication>
#include <QApplication>
#include <QDebug>
#include <QTimer>
#include <QFileInfo>
#include <QDir>
#include <QDirIterator>
#include <QThread>
#include <QRegularExpression>
#include <QDateTime>
#include <QString>
#include <QMessageBox>
#include <QPushButton>
#include <QJsonParseError>
#include <Qt>
#include <QScopedPointer>
#include <QRegularExpression>
#include <QJsonArray>

#include <memory>

#include "message_handler.h"
#include <DeviceController/interfaces.h>
#include "ismas/ismas_client.h"
#include "progress_event.h"
#include "mainwindow.h"
#include "utils.h"  // deprecated
#include "utils_internal.h"
#include "process/command.h"
#include "process/update_command.h"
#include "process/check_ismas_connectivity_command.h"
#include "process/check_update_activation_command.h"
#include "process/check_and_fetch_customer_repository_command.h"
#include "process/update_json_command.h"
#include "process/update_filesystem_command.h"
#include "process/exec_opkg_command.h"
#include "process/update_dc_command.h"
#include "process/show_software_status_command.h"

QString const Worker::UPDATE_STEP_OK     (   " [  ok]");
QString const Worker::UPDATE_STEP_DONE   (   " [done]");
QString const Worker::UPDATE_STEP_WRONG   (  "[WRONG]");
QString const Worker::UPDATE_STEP_FAIL   (   " [FAIL]");
QString const Worker::UPDATE_STEP_SUCCESS(" [SUCCESS]");

bool Worker::sendLastVersionOnce = false;

using UPDATE_STEP = Worker::UPDATE_STEP;
const QMap<UPDATE_STEP, const char*> Worker::smap (
    std::initializer_list<std::pair<UPDATE_STEP, const char*>>{
#define INSERT_ELEMENT(p) std::pair(p, #p)
        INSERT_ELEMENT(UPDATE_STEP::STARTED),
        INSERT_ELEMENT(UPDATE_STEP::CHECK_REPOSITORY),
        INSERT_ELEMENT(UPDATE_STEP::CHECK_REPOSITORY_SUCCESS),
        INSERT_ELEMENT(UPDATE_STEP::CHECK_REPOSITORY_FAILURE),
        INSERT_ELEMENT(UPDATE_STEP::CHECK_SANITY),
        INSERT_ELEMENT(UPDATE_STEP::CHECK_SANITY_SUCCESS),
        INSERT_ELEMENT(UPDATE_STEP::CHECK_SANITY_FAILURE),
        INSERT_ELEMENT(UPDATE_STEP::REPOSITORY_RECOVERED_SUCCESS),
        INSERT_ELEMENT(UPDATE_STEP::REPOSITORY_RECOVERED_FAILURE),
        INSERT_ELEMENT(UPDATE_STEP::CLONE_REPOSITORY),
        INSERT_ELEMENT(UPDATE_STEP::CLONE_REPOSITORY_SUCCESS),
        INSERT_ELEMENT(UPDATE_STEP::CLONE_REPOSITORY_FAILURE),
        INSERT_ELEMENT(UPDATE_STEP::CHECKOUT_REPOSITORY),
        INSERT_ELEMENT(UPDATE_STEP::CHECKOUT_REPOSITORY_SUCCESS),
        INSERT_ELEMENT(UPDATE_STEP::CHECKOUT_REPOSITORY_FAILURE),
        INSERT_ELEMENT(UPDATE_STEP::CHECK_ISMAS_TRIGGER),
        INSERT_ELEMENT(UPDATE_STEP::CHECK_ISMAS_TRIGGER_WRONG_VALUE),
        INSERT_ELEMENT(UPDATE_STEP::CHECK_ISMAS_TRIGGER_SUCCESS),
        INSERT_ELEMENT(UPDATE_STEP::CHECK_ISMAS_TRIGGER_FAILURE),
        INSERT_ELEMENT(UPDATE_STEP::INITIAL_CLONE_WITHOUT_ACTIVE_ISMAS_TRIGGER),
        INSERT_ELEMENT(UPDATE_STEP::INITIAL_CLONE_WITH_ACTIVE_ISMAS_TRIGGER),
        INSERT_ELEMENT(UPDATE_STEP::PULL_NEW_BRANCH),
        INSERT_ELEMENT(UPDATE_STEP::PULL_NEW_BRANCH_FAILURE),
        INSERT_ELEMENT(UPDATE_STEP::PULL_NEW_BRANCH_SUCCESS),
        INSERT_ELEMENT(UPDATE_STEP::CHECKOUT_BRANCH),
        INSERT_ELEMENT(UPDATE_STEP::CHECKOUT_BRANCH_SUCCESS),
        INSERT_ELEMENT(UPDATE_STEP::CHECKOUT_BRANCH_FAILURE),
        INSERT_ELEMENT(UPDATE_STEP::UPDATE_REPOSITORY),
        INSERT_ELEMENT(UPDATE_STEP::UPDATE_REPOSITORY_SUCCESS),
        INSERT_ELEMENT(UPDATE_STEP::UPDATE_REPOSITORY_FAILURE),
        INSERT_ELEMENT(UPDATE_STEP::CHECK_FOR_REPOSITORY_CHANGES),
        INSERT_ELEMENT(UPDATE_STEP::CHECK_FOR_REPOSITORY_CHANGES_SUCCESS),
        INSERT_ELEMENT(UPDATE_STEP::CHECK_FOR_REPOSITORY_CHANGES_FAILURE),
        INSERT_ELEMENT(UPDATE_STEP::FILES_TO_UPDATE),
        INSERT_ELEMENT(UPDATE_STEP::FILES_TO_DOWNLOAD),
        INSERT_ELEMENT(UPDATE_STEP::EXEC_OPKG_COMMANDS),
        INSERT_ELEMENT(UPDATE_STEP::EXEC_OPKG_COMMAND_1),
        INSERT_ELEMENT(UPDATE_STEP::EXEC_OPKG_COMMAND_2),
        INSERT_ELEMENT(UPDATE_STEP::EXEC_OPKG_COMMAND_3),
        INSERT_ELEMENT(UPDATE_STEP::EXEC_OPKG_COMMAND_4),
        INSERT_ELEMENT(UPDATE_STEP::EXEC_OPKG_COMMAND_5),
        INSERT_ELEMENT(UPDATE_STEP::EXEC_OPKG_COMMAND_6),
        INSERT_ELEMENT(UPDATE_STEP::EXEC_OPKG_COMMAND_7),
        INSERT_ELEMENT(UPDATE_STEP::EXEC_OPKG_COMMAND_8),
        INSERT_ELEMENT(UPDATE_STEP::EXEC_OPKG_COMMAND_9),
        INSERT_ELEMENT(UPDATE_STEP::EXEC_OPKG_COMMAND_LAST),
        INSERT_ELEMENT(UPDATE_STEP::EXEC_OPKG_COMMAND_SUCCESS),
        INSERT_ELEMENT(UPDATE_STEP::EXEC_OPKG_COMMAND_FAILURE),
        INSERT_ELEMENT(UPDATE_STEP::DOWNLOAD_CONFIG_FILE),
        INSERT_ELEMENT(UPDATE_STEP::DOWNLOAD_CONFIG_FILE_SUCCESS),
        INSERT_ELEMENT(UPDATE_STEP::DOWNLOAD_CONFIG_FILE_FAILURE),
        INSERT_ELEMENT(UPDATE_STEP::DOWNLOAD_CASH_FILE),
        INSERT_ELEMENT(UPDATE_STEP::DOWNLOAD_CASH_FILE_FAILURE),
        INSERT_ELEMENT(UPDATE_STEP::DOWNLOAD_CASH_FILE_SUCCESS),
        INSERT_ELEMENT(UPDATE_STEP::DOWNLOAD_DEVICE_FILE),
        INSERT_ELEMENT(UPDATE_STEP::DOWNLOAD_DEVICE_FILE_FAILURE),
        INSERT_ELEMENT(UPDATE_STEP::DOWNLOAD_DEVICE_FILE_SUCCESS),
        INSERT_ELEMENT(UPDATE_STEP::DOWNLOAD_JSON_FILE),
        INSERT_ELEMENT(UPDATE_STEP::DOWNLOAD_JSON_FILE_FAILURE),
        INSERT_ELEMENT(UPDATE_STEP::DOWNLOAD_JSON_FILE_SUCCESS),
        INSERT_ELEMENT(UPDATE_STEP::DOWNLOAD_DEVICE_CONTROLLER),
        INSERT_ELEMENT(UPDATE_STEP::DOWNLOAD_DEVICE_CONTROLLER_SUCCESS),
        INSERT_ELEMENT(UPDATE_STEP::DOWNLOAD_DEVICE_CONTROLLER_FAILURE),
        INSERT_ELEMENT(UPDATE_STEP::DOWNLOAD_FILES_TO_PSA_HARDWARE),
        INSERT_ELEMENT(UPDATE_STEP::DOWNLOAD_FILES_TO_PSA_HARDWARE_SUCCESS),
        INSERT_ELEMENT(UPDATE_STEP::DOWNLOAD_FILES_TO_PSA_HARDWARE_FAILURE),
        INSERT_ELEMENT(UPDATE_STEP::SYNC_CUSTOMER_REPOSITORY),
        INSERT_ELEMENT(UPDATE_STEP::SYNC_CUSTOMER_REPOSITORY_SUCCESS),
        INSERT_ELEMENT(UPDATE_STEP::SYNC_CUSTOMER_REPOSITORY_FAILURE),
        INSERT_ELEMENT(UPDATE_STEP::SAVE_LOGS),
        INSERT_ELEMENT(UPDATE_STEP::SAVE_LOGS_SUCCESS),
        INSERT_ELEMENT(UPDATE_STEP::SAVE_LOGS_FAILURE),
        INSERT_ELEMENT(UPDATE_STEP::SEND_LAST_VERSION),
        INSERT_ELEMENT(UPDATE_STEP::UPDATE_FINALIZE),
        INSERT_ELEMENT(UPDATE_STEP::UPDATE_SUCCEEDED),
        INSERT_ELEMENT(UPDATE_STEP::UPDATE_NOT_NECESSARY),
        INSERT_ELEMENT(UPDATE_STEP::UPDATE_FAILED),
        INSERT_ELEMENT(UPDATE_STEP::UPDATE_ACTIVATED),
        INSERT_ELEMENT(UPDATE_STEP::FINISHED),
        INSERT_ELEMENT(UPDATE_STEP::DEBUG),
        INSERT_ELEMENT(UPDATE_STEP::ERROR),
        INSERT_ELEMENT(UPDATE_STEP::NONE)
#undef INSERT_ELEMENT
});

Worker *Worker::instance = nullptr;

Worker::Worker(int customerNr,
               int machineNr,
               int zoneNr,
               QString repositoryUrl,
               QString branchName,
               QString pluginDir,
               QString pluginName,
               QString workingDirectory,
               bool noUpdatePsaHardware,
               bool alwaysDownloadConfig,
               bool alwaysDownloadDC,
               bool dryRun,
               QObject *parent,
               char const *serialInterface,
               char const *baudrate)
  : m_customerNr(customerNr)
  , m_customerNrStr(QString("customer_") + QString::number(m_customerNr))
  , m_machineNr(machineNr)
  , m_zoneNr(zoneNr)
  , m_pluginDir(pluginDir)
  , m_pluginName(pluginName)
  , m_workingDirectory(workingDirectory)
  , m_branchName(branchName)
  , m_customerRepositoryPath(QString("%1/%2.git").arg(repositoryUrl).arg(m_customerNrStr))
  , m_customerRepository(QDir::cleanPath(m_workingDirectory + QDir::separator() + m_customerNrStr))
  , m_noUpdatePsaHardware(noUpdatePsaHardware)
  , m_alwaysDownloadConfig(alwaysDownloadConfig)
  , m_alwaysDownloadDC(alwaysDownloadDC)
  , m_dryRun(dryRun)
  , m_parent(parent)
  , m_serialInterface(serialInterface)
  , m_baudrate(baudrate)
  , m_gc(m_customerRepositoryPath, m_customerNrStr, m_customerRepository, m_workingDirectory, m_branchName, this)
  , m_versionInfo(QStringList())
  , m_osVersion(getOsVersion())
  , m_atbqtVersion(getATBQTVersion())
  , m_atbUpdateToolVersion(getATBUpdateToolVersion())
  , m_cpuSerial(getCPUSerial())
  , m_pluginVersionATBDeciceController(getPluginVersion("/opt/app/ATBAPP/plugins/libATBDeviceControllerPlugin.so"))
  , m_pluginVersionIngenicoISelf(getPluginVersion("/opt/app/ATBAPP/plugins/libIngenicoISelf_CCPlugin.so"))
  , m_pluginVersionMobilisisCalc(getPluginVersion("/opt/app/ATBAPP/plugins/libMOBILISIS_CalculatePricePlugin.so"))
  , m_pluginVersionMobilisisCalcConfig(getPluginVersion("/opt/app/ATBAPP/plugins/libMOBILISIS_CalculatePricePlugin_ConfigUi.so"))
  , m_pluginVersionPrmCalc(getPluginVersion("/opt/app/ATBAPP/plugins/libPRM_CalculatePricePlugin.so"))
  , m_pluginVersionPrmCalcConfig(getPluginVersion("/opt/app/ATBAPP/plugins/libPRM_CalculatePricePlugin_ConfigUi.so"))
  , m_pluginVersionTcpZvt(getPluginVersion("/opt/app/ATBAPP/plugins/libTCP_ZVT_CCPlugin.so"))
  , m_ismasUpdateRequests(ISMAS_UPDATE_REQUESTS)
  , m_waitForNewUpdates(this)
  , m_filesToUpdate()
  , m_updateProcessRunning(true)
  , m_mainWindow(nullptr) /* contains plugin */
  , m_dcDownloadFirmware(new Command("/opt/app/tools/atbupdate/ATBDownloadDCFirmware --read-dc-version true"))
  , m_dcDownloadJsonFiles(new Command(
        QString("/opt/app/tools/atbupdate/ATBDownloadDCJsonFiles --set-ppid %1").arg(QCoreApplication::applicationPid())))
  //, m_withoutIsmasDirectPort(true) /* useful for testing */ {
  , m_withoutIsmasDirectPort(false) /* useful for testing */  {

    // *** check ISMAS connectivity ***
    // NOTE: if the customer repository does not exist, then it does not matter
    // if there is a connection to ISMAS (via APISM).
    // NOTE: the several processes will be started WorkList::exec().
    int next = 1;
    m_workList.push_back(
                std::make_unique<CheckIsmasConnectivityCommand>(
                    //QString("echo CheckIsmasConnectivityCommand")
                    QString("/opt/app/tools/atbupdate/ATBUpdateCheck --ismas-connected")
                    , this, ++next));

    // *** check if update activated in ISMAS ***
    // NOTE: if the customer repository does not exist, then it does not matter
    // if the update has been activated via ISMAS.
    m_workList.push_back(
                std::make_unique<CheckUpdateActivationCommand>(
                    //QString("echo CheckUpdateActivationCommand")
                    QString("/opt/app/tools/atbupdate/ATBUpdateCheck --update-requested")
                    , this, ++next));

    // *** check and fetch git-customer repository ***
    // (1): if the customer repository does not exist, clone the repository.
    // (2): if the repository exists, pull the repository. Optionally, checkout
    //      the corresponding branch, and check the integrity of the repository.
    m_workList.push_back(
                std::make_unique<CheckAndFetchCustomerRepositoryCommand>(
                    // QString("echo CheckAndFetchCustomerRepositoryCommand")
                    QString("/opt/app/tools/atbupdate/ATBUpdateGit")
                    , this, ++next));

    // *** exec opkg-commands (noaction) ***
    // NOTE: first run the opkg commands with no action -> dry-run
    m_workList.push_back(
                std::make_unique<ExecOpkgCommand>(
                    // QString("echo ExecOpkgCommand noaction")
                    QString("/opt/app/tools/atbupdate/ATBUpdateOpkg --noaction")
                    , this, ++next, true));

    // *** exec opkg-commands ***
    // NOTE: first run the opkg commands with action -> no dry-run
    m_workList.push_back(
                std::make_unique<ExecOpkgCommand>(
                    //QString("echo ExecOpkgCommand run")
                    QString("/opt/app/tools/atbupdate/ATBUpdateOpkg")
                    , this, ++next, false));

    // *** send json files down to device controller ***
    m_workList.push_back(
                std::make_unique<UpdateJsonCommand>(
                    QString("echo ATBDownloadDCJsonFiles")
                    //QString("/opt/app/tools/atbupdate/ATBDownloadDCJsonFiles --set-ppid %1").arg(QCoreApplication::applicationPid())
                    , this, ++next, false));

    // sync json files in repo etc-directory with /etc fs-directory
    m_workList.push_back(
                std::make_unique<UpdateFileSystemCommand>(
                    QString("echo ATBUpdateSync")
                    , this, ++next));

    // send device-controller firmware down to device-controller-hardware
    m_workList.push_back(
                std::make_unique<UpdateDCCommand>(
                    QString("echo ATBDownloadDCFirmware")
                    // QString("/opt/app/tools/atbupdate/ATBDownloadDCFirmware --read-dc-version true")
                    , this, ++next));

    // show/send software-status
    m_workList.push_back(
                std::make_unique<ShowSoftwareStatusCommand>(
                    QString("echo ATBUpdateShowPSAInstalled")
                    , this, -1));

    // reboot machine
    ///////////////////////////////////////////////////////////

    m_start = QDateTime::currentDateTime();
    m_dcDownloadFirmware->setWorker(this);
    m_dcDownloadJsonFiles->setWorker(this);

    // TODO: turn object into singleton
    instance = this;
    m_lastFailedUpdateStep = UPDATE_STEP::NONE;

    if (m_noUpdatePsaHardware == false) {
        m_update = new Update(this,
                              QDir::cleanPath(m_workingDirectory + QDir::separator() + m_customerNrStr),
                              m_customerNrStr,
                              m_branchName,
                              m_pluginDir,
                              m_pluginName,
                              m_workingDirectory);
    }

    this->setObjectName("worker-object");
    QDir::setCurrent(m_workingDirectory);

    m_apismVersion = getAPISMYoctoVersion();
}

Worker::~Worker() {
    if (m_update != nullptr) {
        delete m_update;
        m_update = nullptr;
    }
}

void Worker::displayProgressInMainWindow(int progress) {
    if (m_mainWindow) {
        QApplication::postEvent(m_mainWindow,
            new ProgressEvent(this, progress));
    }
}

void Worker::setProgress(int progress) {
    m_ismasClient.setProgressInPercent(progress);
    displayProgressInMainWindow(progress);
}


void Worker::startProgressLoop() {
    displayProgressInMainWindow(MainWindow::START_PROGRESS_LOOP);
}

void Worker::stopProgressLoop() {
    displayProgressInMainWindow(MainWindow::STOP_PROGRESS_LOOP);
}

static std::once_flag once;
void Worker::run() {
    // user should not start the update process several times
    std::call_once(once, &Worker::privateUpdate, this);
}

bool Worker::isRepositoryCorrupted() {
    QDir customerRepository(m_customerRepository);
    if (customerRepository.exists()) {
        QDir customerRepositoryGit(QDir::cleanPath(m_customerRepository + QDir::separator() + ".git/"));
        if (!m_gc.gitFsck()) {
            // should never happen
            Utils::printCriticalErrorMsg("CORRUPTED CUSTOMER REPOSITORY: GIT_FSCK FAILED");
            return true;
        }
        // .git-directory inside git-repository does not exist, which means the
        // git-repository is corrupted -> remove it and start from scratch
        if (!customerRepositoryGit.exists()) {
            // should never happen
            Utils::printCriticalErrorMsg("CORRUPTED CUSTOMER REPOSITORY .GIT DOES NOT EXIST");
            return true;
        }
        QDir customerRepositoryEtc(QDir::cleanPath(m_customerRepository + QDir::separator() + "etc/"));
        if (!customerRepositoryEtc.exists()) {
            // should never happen
            Utils::printCriticalErrorMsg(QString("CORRUPTED CUSTOMER REPOSITORY %1/etc DOES NOT EXIST").arg(m_customerRepository));
            return true;
        }
    }
    return false;
}

bool Worker::repairCorruptedRepository() {
    QDir customerRepository(m_customerRepository);
    if (!customerRepository.removeRecursively()) {

        Utils::printCriticalErrorMsg("ERROR REMOVING CORR. CUST-REPOSITORY");

        //m_updateStatus = UpdateStatus(UPDATE_STATUS::REMOVE_GIT_REPOSITORY_FAILED,
        //                    QString("REMOVAL OF GIT-REPOSITORY %1 FAILED").arg(m_customerRepository));
        //IsmasClient::sendRequestReceiveResponse(IsmasClient::APISM::DB_PORT,
        //    QString("#M=APISM#C=CMD_EVENT#J=") +
        //        m_ismasClient.sanityCheckFailed(IsmasClient::RESULT_CODE::INSTALL_ERROR,
        //                                        m_updateStatus.m_statusDescription));
        //emit showErrorMessage("apism sanity check", m_updateStatus.m_statusDescription);

        return false;
    }
    return true;
}

void Worker::privateUpdate() {
    if (!m_mainWindow) {
        Utils::printCriticalErrorMsg("m_mainWindow NOT SET");
        return;
    }

    return;

    QString func(__PRETTY_FUNCTION__);

    GUI() << (ISMAS() << (CONSOLE() << UPDATE_STEP::STARTED));

    QScopedPointer<UpdateProcessRunning> upr(new UpdateProcessRunning(this));
    QStringList lst;

    ////////////////////////////////////////////////////////////////////////////
    //
    //                         CHECK UPDATE TRIGGER
    //
    ////////////////////////////////////////////////////////////////////////////
    // NOTE: make sure that nothing is sent to ISMAS during updateTriggerSet
    ISMAS() << UPDATE_STEP::CHECK_ISMAS_TRIGGER;
    m_ismasTriggerActive = false;
    m_updateNotNecessary = false;

    if (QDir(m_customerRepository).exists()) { // ignore a possibly corrupted repository
        m_ismasTriggerActive = updateTriggerSet();
        if (m_ismasTriggerActive == false) {
            QDateTime const &current = QDateTime::currentDateTime();
            m_automaticUpdate = (current.time().hour() < 4);
            m_versionInfo = m_gc.gitShowReason(m_branchName);

            qCritical() << "***";
            qCritical() << "privateUpdate ............. m_versionInfo:" << m_versionInfo;
            qCritical() << "privateUpdate ......... m_automaticUpdate:" << m_automaticUpdate;

            if (m_automaticUpdate) { // update has been triggered within [00:00:00, 00:03:59]
                m_updateNotNecessary = true;
                m_ismasTriggerStatusMessage = QStringList(QString("NO UPDATE NECESSARY (%1)").arg(current.toString(Qt::ISODate)));

                qCritical() << "privateUpdate m_ismasTriggerStatusMessage:"
                            << QStringList(m_ismasTriggerStatusMessage);
                qCritical() << "***";

                // the customer-repository does exist, and the ISMAS-trigger is
                // *NOT* "WAIT", but from 00:00:00 - 00:03:59 this counts as an
                // automatic update

                QStringList lst = m_ismasTriggerStatusMessage;
                // trigger message to ISMAS and CONSOLE
                CONSOLE(lst) << (m_lastFailedUpdateStep = UPDATE_STEP::CHECK_ISMAS_TRIGGER_SUCCESS);
                // overwrite m_lastFailedUpdateStep
                m_lastFailedUpdateStep = UPDATE_STEP::NONE;
                return;
            }

            qCritical() << "***";

            // the customer-repository does exist, but the ISMAS-trigger is
            // *NOT* "WAIT", so STOP the update procedure
            return;
        }
        // the customer-repository does exist, and the ISMAS-trigger is "WAIT",
        // so continue the update procedure
    } else {
        // the customer-repository does not exist, so PROCEED with the
        // update procedure, even if ISMAS-trigger is not correctly set ("WAIT")
    }

    emit this->disableExit();

    QDir customerRepository(m_customerRepository);

    CONSOLE() << (ISMAS() << UPDATE_STEP::CHECK_SANITY);

    m_clone = false;
    m_repairClone = false;
    m_initialClone = false;
    m_pulledNewBranch = false;
                                    // the customer repository is cloned or
                                    // repaired/re-cloned without checking the
                                    // ISMAS-trigger (WAIT-)button.

    // Case 1: no existing repository:

                                    // if there was a sane repository
                                    // available, then the trigger-button is
                                    // checked:
                                    // 1: trigger == WAIT: then
                                    // have been activated in ISMAS.

    bool continueUpdate = true;     // check if git-clone command has timed-out,
                                    // resulting in a corrupted git-repository, which
                                    // does not contain an ./etc-directory

    if (isRepositoryCorrupted()) {  // a not-existing repository is not meant
                                    // to be corrupted
        CONSOLE() << (ISMAS() << UPDATE_STEP::CHECK_SANITY_FAILURE);
        if ((continueUpdate = repairCorruptedRepository()) == true) {
            m_repairClone = true;
            CONSOLE() << (ISMAS() << UPDATE_STEP::REPOSITORY_RECOVERED_SUCCESS);
        } else {
            ISMAS() << (GUI() << (CONSOLE() << UPDATE_STEP::REPOSITORY_RECOVERED_FAILURE));
            return;
        }
    }

    CONSOLE() << (ISMAS() << UPDATE_STEP::CHECK_SANITY_SUCCESS);

    if (continueUpdate) {
        if ((continueUpdate = customerRepository.exists()) == false) {
            m_initialClone = (m_repairClone == false);
            ISMAS() << (GUI() << (CONSOLE() << UPDATE_STEP::CLONE_REPOSITORY));
            for (int i = 0; i < 5; ++i) {   // try to checkout git repository
                setProgress(i);             // and switch to branch
                if (m_gc.gitCloneAndCheckoutBranch()) {
                    if (!isRepositoryCorrupted()) {
                        m_versionInfo = m_gc.gitShowReason(m_branchName);
                        GUI() << (ISMAS() << (CONSOLE() << UPDATE_STEP::CLONE_REPOSITORY_SUCCESS));
                        continueUpdate = true;
                        m_clone = true;
                        break;
                    }
                }
                QThread::sleep(1);  // maybe git needs more time
            }

            if (continueUpdate == false) {
                GUI() << (ISMAS() << (CONSOLE() << (m_lastFailedUpdateStep = UPDATE_STEP::CLONE_REPOSITORY_FAILURE)));
                return;
            }

            Q_ASSERT_X(m_clone, (func + QString(":%1").arg(__LINE__)).toStdString().c_str(), "clone failed");

        } else {

            Q_ASSERT_X(!m_clone, (func + QString(":%1").arg(__LINE__)).toStdString().c_str(), "m_clone not false");
            Q_ASSERT_X(!m_initialClone, (func + QString(":%1").arg(__LINE__)).toStdString().c_str(), "m_initialClone not false");
            Q_ASSERT_X(!m_repairClone, (func + QString(":%1").arg(__LINE__)).toStdString().c_str(), "m_repairClone not false");

            CONSOLE() << UPDATE_STEP::CHECK_REPOSITORY;
            if (isRepositoryCorrupted()) {
                ISMAS() << (GUI() << (CONSOLE() << (m_lastFailedUpdateStep = UPDATE_STEP::CHECK_REPOSITORY_FAILURE)));
                return;
            }
        }
    }
    m_versionInfo = m_gc.gitShowReason(m_branchName);

    CONSOLE() << UPDATE_STEP::CHECK_REPOSITORY_SUCCESS;
    setProgress(_CHECKOUT_REPOSITORY_SUCCESS);

    if (m_clone == false) {
        if (m_ismasTriggerActive == false) {
            return;
        } else {
            GUI() << (ISMAS() << (CONSOLE() << UPDATE_STEP::CHECK_ISMAS_TRIGGER_SUCCESS));
            setProgress(_CHECK_ISMAS_TRIGGER_SUCCESS);
        }
    } else {
        if (m_initialClone) {
            GUI() << (ISMAS() << (CONSOLE() << UPDATE_STEP::INITIAL_CLONE_WITHOUT_ACTIVE_ISMAS_TRIGGER));
        }
    }

    if (m_ismasTriggerActive == false) {// make it explicit again: only if the
                                        // ismas trigger is active ('WAIT'),
                                        // then proceed

        if (m_clone == false) {  // if it is an (initial) clone, then
            return;              // run the whole update process:
        }                        // sync tariff-files, download jsons,
    }                            // download device controller


    ////////////////////////////////////////////////////////////////////////////
    //
    //                           CHECK-OUT BRANCH
    //
    ////////////////////////////////////////////////////////////////////////////
    if ((continueUpdate = customerEnvironment()) == false) {
        // even if something goes wrong creating the environment, try to execute
        // opkg_commands
        if (QDir(m_customerRepository).exists()) {
            // always execute contents of opkg_commands-file
            m_filesToUpdate.clear();
            m_filesToUpdate << "etc/psa_update/opkg_commands";
            execOpkgCommands();
        }
        return;
    }
    m_versionInfo = m_gc.gitShowReason(m_branchName);
    lst = QStringList(QString(smap[UPDATE_STEP::CHECKOUT_BRANCH_SUCCESS]));
    ISMAS(lst) << (CONSOLE(lst) << UPDATE_STEP::CHECKOUT_BRANCH);
    setProgress(_CHECKOUT_BRANCH_SUCCESS);


    ////////////////////////////////////////////////////////////////////////////
    //
    //               COMPUTE CHANGED FILES OF CUSTOMER REPOSITORY
    //
    ////////////////////////////////////////////////////////////////////////////
    if ((continueUpdate = filesToUpdate()) == false) {
        // even if something goes wrong in filesToUpdate, try to execute
        // opkg_commands
        if (QDir(m_customerRepository).exists()) {
            // always execute contents of opkg_commands-file
            m_filesToUpdate.clear();
            m_filesToUpdate << "etc/psa_update/opkg_commands";
            execOpkgCommands();
        }
        return;
    }
    m_versionInfo = m_gc.gitShowReason(m_branchName);
    lst = QStringList(QString(smap[UPDATE_STEP::UPDATE_REPOSITORY_SUCCESS]));
    ISMAS() << (GUI() << (CONSOLE() << UPDATE_STEP::UPDATE_REPOSITORY));
    setProgress(_UPDATE_REPOSITORY_SUCCESS);

    ////////////////////////////////////////////////////////////////////////////
    //
    //             (R)SYNC THE REPOSITORY WITH THE LOCAL FILEYSTEM
    //
    ////////////////////////////////////////////////////////////////////////////
    if ((continueUpdate = syncCustomerRepositoryAndFS()) == false) {
        // even if something goes wrong with rsync, try to execute
        // opkg_commands
        if (QDir(m_customerRepository).exists()) {
            // always execute contents of opkg_commands-file
            m_filesToUpdate.clear();
            m_filesToUpdate << "etc/psa_update/opkg_commands";
            execOpkgCommands();
        }
        return;
    }
    lst = QStringList(QString(smap[UPDATE_STEP::SYNC_CUSTOMER_REPOSITORY_SUCCESS]));
    ISMAS(lst) << (GUI(lst) << (CONSOLE(lst) << UPDATE_STEP::SYNC_CUSTOMER_REPOSITORY_SUCCESS));
    setProgress(_SYNC_CUSTOMER_REPOSITORY_SUCCESS);


    ////////////////////////////////////////////////////////////////////////////
    //
    //                         EXECUTE OPKG COMMANDS
    //
    ////////////////////////////////////////////////////////////////////////////
    if ((continueUpdate = execOpkgCommands()) == false) {
        return;
    }
    lst = QStringList(QString(smap[UPDATE_STEP::EXEC_OPKG_COMMAND_SUCCESS]));
    GUI(lst) << (CONSOLE(lst) << UPDATE_STEP::EXEC_OPKG_COMMANDS);
    setProgress(_EXEC_OPKG_COMMAND_SUCCESS);


    ////////////////////////////////////////////////////////////////////////////
    //
    //                  UPDATE THE PSA USING THE CHANGED FILES
    //
    ////////////////////////////////////////////////////////////////////////////
    if ((continueUpdate = downloadFilesToPSAHardware()) == false) {
        return;
    }
    lst = QStringList(QString("DONE"));
    ISMAS(lst) << (GUI(lst) << (CONSOLE(lst) << UPDATE_STEP::DOWNLOAD_FILES_TO_PSA_HARDWARE_SUCCESS));

    ////////////////////////////////////////////////////////////////////////////
    //
    //                            FUTURE: SAVE LOG FILES
    //
    ////////////////////////////////////////////////////////////////////////////
    if ((continueUpdate = saveLogFile()) == false) {
        return;
    }
    // ISMAS() << (GUI() << (CONSOLE() << UPDATE_STEP::SAVE_LOGS_SUCCESS));
    setProgress(_SAVE_LOGS_SUCCESS);

    // final messages: see destructor of UpdateProcessRunning subclass
    m_lastFailedUpdateStep = UPDATE_STEP::NONE;
}

bool Worker::updateTriggerSet() {
    // repository is existent and not corrupted. check now if the ISMAS-trigger
    // (WAIT-button) is activated even in case of initial checkout
    static const QString func = "UPDATE-TRIGGER-SET";

    // if (m_withoutIsmasDirectPort) { // useful for testing
    //    return true;
    //}

    m_ismasTriggerStatusMessage.clear();

    GUI() << (CONSOLE() << UPDATE_STEP::CHECK_ISMAS_TRIGGER);

    bool const automaticUpdate = (QDateTime::currentDateTime().time().hour() < 4);

    QString triggerValue("NOT CHECKED YET");
    static constexpr int const repeats = 15;
    for (int repeat = 1; repeat <= repeats; ++repeat) {

        if (repeat > 1) {
            int const startMs = QTime::currentTime().msecsSinceStartOfDay();
            int const durationMs = QTime::currentTime().msecsSinceStartOfDay() - startMs;
            QString const &s = QString("elapsed: %1.%2s").arg(durationMs / 1000).arg(durationMs % 1000);
            QStringList lst = (m_ismasTriggerStatusMessage = (QStringList(func) << s));
            CONSOLE(lst) << UPDATE_STEP::DEBUG;
        } else {
            QStringList lst = (m_ismasTriggerStatusMessage = (QStringList(func) << QString("-> REPEAT=%1 (%2)").arg(repeat).arg(repeats-repeat)));
            CONSOLE(lst) << UPDATE_STEP::DEBUG;
        }

        if ((repeat % 8) == 0) {
            CONSOLE(QStringList(func) << "RESTART APISM") << UPDATE_STEP::DEBUG;
            Command c("systemctl restart apism");
            if (c.execute("/tmp")) {
                QThread::sleep(20); // give APISM some time to reconnect
                QStringList lst = (m_ismasTriggerStatusMessage = (QStringList(func) << "RESTART APISM DONE"));
                CONSOLE(lst) << UPDATE_STEP::DEBUG;
            }
        }

        if (std::optional<QString> result
                = IsmasClient::sendRequestReceiveResponse(
                    IsmasClient::APISM::DIRECT_PORT, "#M=APISM#C=REQ_ISMASPARAMETER#J={}")) {

            QString const &msg = QString("APISM RESPONSE(%1)=(").arg(repeat) + result.value() + ")";
            QStringList lst = (m_ismasTriggerStatusMessage = (QStringList(func) << msg));
            CONSOLE(lst) << UPDATE_STEP::DEBUG;

            QJsonParseError parseError;
            QJsonDocument document(QJsonDocument::fromJson(result.value().toUtf8(), &parseError));
            if (parseError.error != QJsonParseError::NoError) {
                m_ismasTriggerStatusMessage = QStringList(QString("INVALID JSON MSG: PARSING FAILED (json=<START>%1<END> error=[%2] str=[%3] offset=[%4])")
                                                .arg(msg)
                                                .arg(parseError.error)
                                                .arg(parseError.errorString())
                                                .arg(parseError.offset));
                QStringList lst = m_ismasTriggerStatusMessage;
                ISMAS(lst) << (GUI(lst) << (CONSOLE(lst) << (m_lastFailedUpdateStep = UPDATE_STEP::CHECK_ISMAS_TRIGGER_FAILURE)));
                return false;
            }
            if (!document.isObject()) {
                m_ismasTriggerStatusMessage = QStringList(QString("not a json-object %1").arg(result.value()));
                QStringList lst = m_ismasTriggerStatusMessage;
                ISMAS(lst) << (GUI(lst) << (CONSOLE(lst) << (m_lastFailedUpdateStep = UPDATE_STEP::CHECK_ISMAS_TRIGGER_FAILURE)));
                return false;
            }

            QJsonObject obj = document.object();

            // always look for an 'error' first
            if (obj.contains("error")) {
                m_ismasTriggerStatusMessage = QStringList(obj.value("error").toString());
                QStringList lst = m_ismasTriggerStatusMessage;
                CONSOLE(QStringList(lst)) << (m_lastFailedUpdateStep = UPDATE_STEP::CHECK_ISMAS_TRIGGER_FAILURE);
                QThread::sleep(6);
                continue;
            }
            // sanity check: cust_nr and machine_nr of PSA correct ?
            // note: this check has to be done here, as the cust_nr and the machine_nr
            // of the PSA are sent by ISMAS.
            if (obj.contains("Dev_ID")) {
                QJsonValue v = obj.value("Dev_ID");
                if (v.isObject()) {
                    QJsonObject obj = v.toObject();
                    if (obj.contains("Custom_ID") && obj.contains("Device_ID")) {
                        int const customerNr = obj.value("Custom_ID").toInt(-1);
                        int const machineNr = obj.value("Device_ID").toInt(-1);
                        if (customerNr != m_customerNr) {
                            m_ismasTriggerStatusMessage = QStringList(QString("CUSTOMER-NR (%1) != LOCAL CUSTOMER-NR (%2)")
                                                            .arg(customerNr).arg(m_customerNr));
                            QStringList lst = m_ismasTriggerStatusMessage;
                            ISMAS(lst) << (GUI(lst) << (CONSOLE(lst) << (m_lastFailedUpdateStep = UPDATE_STEP::CHECK_ISMAS_TRIGGER_FAILURE)));
                            return false;
                        }
                        if (machineNr != m_machineNr) {
                            m_ismasTriggerStatusMessage = QStringList(QString("MACHINE-NR (%1) != LOCAL MACHINE-NR (%2)")
                                                            .arg(machineNr).arg(m_machineNr));
                            QStringList lst = m_ismasTriggerStatusMessage;
                            ISMAS(lst) << (GUI(lst) << (CONSOLE(lst) << (m_lastFailedUpdateStep = UPDATE_STEP::CHECK_ISMAS_TRIGGER_FAILURE)));
                            return false;
                        }
                    } else {
                        QStringList lst(QString("Dev_ID DOES NOT CONTAIN Custom_ID AND/OR Device_ID (LINE=%1)").arg(__LINE__));
                        ISMAS(lst) << (GUI(lst) << (CONSOLE(lst) << (m_lastFailedUpdateStep = UPDATE_STEP::CHECK_ISMAS_TRIGGER_FAILURE)));
                        return false;
                    }
                } else {
                    m_ismasTriggerStatusMessage = QStringList(QString("Dev_ID KEY NOT A JSON-OBJECT (LINE=%1)").arg(__LINE__));
                    QStringList lst = m_ismasTriggerStatusMessage;
                    ISMAS(lst) << (GUI(lst) << (CONSOLE(lst) << (m_lastFailedUpdateStep = UPDATE_STEP::CHECK_ISMAS_TRIGGER_FAILURE)));
                    return false;
                }
            } else {
                m_ismasTriggerStatusMessage = QStringList(QString("Dev_ID KEY NOT AVAILABLE (LINE=%1)").arg(__LINE__));
                QStringList lst = m_ismasTriggerStatusMessage;
                ISMAS(lst) << (GUI(lst) << (CONSOLE(lst) << (m_lastFailedUpdateStep = UPDATE_STEP::CHECK_ISMAS_TRIGGER_FAILURE)));
                return false;
            }

            if (obj.contains("Fileupload")) {
                QJsonValue v = obj.value("Fileupload");
                if (v.isObject()) {
                    obj = v.toObject();
                    if (obj.contains("TRG")) {
                        if ((triggerValue = obj.value("TRG").toString()) == "WAIT") {
                            m_ismasTriggerStatusMessage = QStringList("ISMAS_UPDATE-TRIGGER SET TO WAIT");
                            m_ismasTriggerActive = true;
                            return m_ismasTriggerActive;
                        } else
                        if (QRegExp("\\s*").exactMatch(triggerValue)) { // check for whitespace
                            m_ismasTriggerStatusMessage = QStringList(QString("%1 EMPTY UPDATE TRIGGER (%2)").arg(repeat).arg(repeats-repeat));
                            QStringList lst = m_ismasTriggerStatusMessage;
                            if (m_clone) {
                                ISMAS(lst) << (GUI(lst) << (CONSOLE(lst) << (m_lastFailedUpdateStep = UPDATE_STEP::CHECK_ISMAS_TRIGGER_WRONG_VALUE)));
                                                    // if the customer repository has just been cloned
                                return false;       // it is OK the ISMAS trigger might not be 'WAIT'
                            }
                            // not a clone and empty update-trigger
                            if (automaticUpdate) {
                                // do not inform ISMAS in case of automatic update, because the
                                // update is not necessary as the trigger-button is not set to WAIT.
                                GUI(lst) << (CONSOLE(lst) << (m_lastFailedUpdateStep = UPDATE_STEP::CHECK_ISMAS_TRIGGER_WRONG_VALUE));
                                return false;
                            }

                            CONSOLE(lst) << (m_lastFailedUpdateStep = UPDATE_STEP::CHECK_ISMAS_TRIGGER_FAILURE);

                            QThread::sleep(6);
                            continue;
                        } else {
                            // if the download-button once has a wrong value, it will never recover
                            if (m_clone) {
                                m_ismasTriggerStatusMessage = QStringList(QString("TRIGGER-VALUE='%1' != 'WAIT'").arg(triggerValue));
                                QStringList lst = m_ismasTriggerStatusMessage;
                                if (automaticUpdate) {
                                    // do not inform ISMAS in case of automatic update
                                    GUI(lst) << (CONSOLE(lst) << (m_lastFailedUpdateStep = UPDATE_STEP::CHECK_ISMAS_TRIGGER_WRONG_VALUE));
                                } else {
                                    ISMAS(lst) << (GUI(lst) << (CONSOLE(lst) << (m_lastFailedUpdateStep = UPDATE_STEP::CHECK_ISMAS_TRIGGER_WRONG_VALUE)));
                                }
                            } else {
                                m_ismasTriggerStatusMessage = QStringList(QString("TRIGGER-VALUE='%1' != 'WAIT'").arg(triggerValue));
                                QStringList lst = m_ismasTriggerStatusMessage;
                                if (automaticUpdate) {
                                    // do not inform ISMAS in case of automatic update
                                    GUI(lst) << (CONSOLE(lst) << (m_lastFailedUpdateStep = UPDATE_STEP::CHECK_ISMAS_TRIGGER_WRONG_VALUE));
                                } else {
                                    ISMAS(lst) << (GUI(lst) << (CONSOLE(lst) << (m_lastFailedUpdateStep = UPDATE_STEP::CHECK_ISMAS_TRIGGER_FAILURE)));
                                }
                            }
                            return false;
                        }
                    } else {
                        m_ismasTriggerStatusMessage = QStringList(QString("TRG key not available (LINE=%1)").arg(__LINE__));
                        QStringList lst = m_ismasTriggerStatusMessage;
                        ISMAS(lst) << (GUI(lst) << (CONSOLE(lst) << (m_lastFailedUpdateStep = UPDATE_STEP::CHECK_ISMAS_TRIGGER_FAILURE)));
                        return false;
                    }
                } else {
                    m_ismasTriggerStatusMessage = QStringList(QString("Fileupload not a json-object (LINE=%1)").arg(__LINE__));
                    QStringList lst = m_ismasTriggerStatusMessage;
                    ISMAS(lst) << (GUI(lst) << (CONSOLE(lst) << (m_lastFailedUpdateStep = UPDATE_STEP::CHECK_ISMAS_TRIGGER_FAILURE)));
                    return false;
                }
            } else {
                m_ismasTriggerStatusMessage = QStringList(QString("Fileupload not available (LINE=%1)").arg(__LINE__));
                QStringList lst = m_ismasTriggerStatusMessage;
                ISMAS(lst) << (GUI(lst) << (CONSOLE(lst) << (m_lastFailedUpdateStep = UPDATE_STEP::CHECK_ISMAS_TRIGGER_FAILURE)));
                return false;
            }
        } else {
            m_ismasTriggerStatusMessage = QStringList(QString("no ISMAS response (LINE=%1)").arg(__LINE__));
            QStringList lst = m_ismasTriggerStatusMessage;
            CONSOLE(lst) << (m_lastFailedUpdateStep = UPDATE_STEP::CHECK_ISMAS_TRIGGER_FAILURE);
            QThread::sleep(6);
            continue;
        }
    }

    if (m_initialClone == false) {
        if (!triggerValue.contains("WAIT", Qt::CaseInsensitive)) {
            m_ismasTriggerStatusMessage = QStringList(QString("ISMAS_UPDATE-TRIGGER-NOT-SET-OR-WRONG: VALUE=(") + triggerValue + ")");
            QStringList lst = m_ismasTriggerStatusMessage;
            ISMAS(lst) << (GUI(lst) << (CONSOLE(lst) << (m_lastFailedUpdateStep = UPDATE_STEP::CHECK_ISMAS_TRIGGER_FAILURE)));
            return false;
        }
    }

    return false;
}

bool Worker::customerEnvironment() {
    // configure customer environment -> checkout branch in case someone has
    // changed the zone_nr

    ISMAS() << (GUI() << (CONSOLE() << UPDATE_STEP::CHECKOUT_BRANCH));

    if (QDir(m_customerRepository).exists()) {
        if (m_clone == false) {
            if (m_gc.branchExistsRemotely()) {
                QString msg;
                QStringList lst;
                if (!m_gc.branchExistsLocally()) {
                    lst.clear();
                    msg = QString("PULLING OF NEW BRANCH " + m_branchName + " DOES NOT EXIST LOCALLY");
                    lst << msg;
                    CONSOLE(lst) << UPDATE_STEP::PULL_NEW_BRANCH;
                    if (!m_gc.gitPullNewBranches()) {
                        lst.clear();
                        msg = QString("PULLING OF NEW BRANCH " + m_branchName + " FAILED");
                        lst << msg;
                        ISMAS(lst) << (GUI(lst) << (CONSOLE(lst) << (m_lastFailedUpdateStep = UPDATE_STEP::PULL_NEW_BRANCH_FAILURE)));
                        return false;
                    } else {
                        lst.clear();
                        msg = QString("PULLING OF NEW BRANCH " + m_branchName + " SUCCESS");
                        lst << msg;
                        ISMAS(lst) << (GUI(lst) << (CONSOLE(lst) << (m_lastFailedUpdateStep = UPDATE_STEP::PULL_NEW_BRANCH_SUCCESS)));
                        m_pulledNewBranch = true;
                    }
                } else {
                    m_pulledNewBranch = false;
                }
            }
        }

        if (m_gc.gitCheckoutBranch()) {
            return true;
        } else {
            QStringList lst(QString("CHECKOUT OF " + m_customerRepository + "FAILED"));
            ISMAS(lst) << (GUI(lst) << (CONSOLE(lst) << (m_lastFailedUpdateStep = UPDATE_STEP::CHECKOUT_BRANCH_FAILURE)));
        }
    } else {// cannot happen
        QStringList lst(QString(m_customerRepository + " DOES NOT EXIST"));
        ISMAS(lst) << (GUI(lst) << (CONSOLE(lst) << (m_lastFailedUpdateStep = UPDATE_STEP::CHECKOUT_BRANCH_FAILURE)));
    }

    return false;
}

bool Worker::filesToUpdate() {
    // determine which files has to be updated: either sent to the hardware or
    // rsynced with the filesystem in case of tariff-files

    ISMAS() << (GUI() << (CONSOLE() << UPDATE_STEP::UPDATE_REPOSITORY));

    // always execute contents of opkg_commands-file
    m_filesToUpdate << "etc/psa_update/opkg_commands";

    if ((m_clone || m_pulledNewBranch) && m_alwaysDownloadConfig) {
        // always download all json-config files, even if none of them have been
        // changed in the git repository. useful for first installation.
        QDir dir(QDir::cleanPath(m_customerRepository + QDir::separator() + "etc/psa_config"));
        if (dir.exists()) {
            QStringList jsons = dir.entryList(QStringList() << "DC2C*.json", QDir::Files);
            if (!jsons.isEmpty()) {
                for (QStringList::size_type i=0; i<jsons.size(); ++i) {
                    m_filesToUpdate << QDir::cleanPath(QString("etc/psa_config/") + jsons.at(i));
                }
            }
        }
    }

    if ((m_clone || m_pulledNewBranch) && m_alwaysDownloadConfig) {
        // always download the last dc-binary, even if not changed in the
        // git repository. useful for first installation.
        QDir dir(QDir::cleanPath(m_customerRepository + QDir::separator() + "etc/dc"));
        if (dir.exists()) {
            QStringList dc = dir.entryList(QStringList() << "dc2c.bin", QDir::Files,
                                           QDir::SortFlag::Time | QDir::SortFlag::Reversed);
            if (!dc.isEmpty()) {
                m_filesToUpdate << QDir::cleanPath(QString("etc/dc/") + dc.first());
            }
        }
    }

    if (std::optional<QString> changes = m_gc.gitPull()) {
        if (!changes.value().contains("Already up to date")) {
            if (std::optional<QStringList> changedFileNames = m_gc.gitDiff(changes.value())) {
                m_filesToUpdate << changedFileNames.value();
            }
        }
        m_filesToUpdate.removeDuplicates();

        qCritical() << "(" << __func__ << ":" << __LINE__ << ") FILES-TO-UPDATE" << m_filesToUpdate;

        GUI(m_filesToUpdate) << (CONSOLE(m_filesToUpdate) << UPDATE_STEP::FILES_TO_UPDATE);
        setProgress(_FILES_TO_UPDATE);
    } else {
        ISMAS() << (GUI() << (CONSOLE() << (m_lastFailedUpdateStep = UPDATE_STEP::UPDATE_REPOSITORY_FAILURE)));
        return false;
    }

    return true;
}

bool Worker::computeFilesToDownload() {
    m_filesToDownload.clear();
    for (int i = 0; i < m_filesToUpdate.size(); ++i) {
        QString const fName = m_filesToUpdate.at(i);
        if (fName.contains("DC2C_print", Qt::CaseInsensitive) ||
            fName.contains("DC2C_device", Qt::CaseInsensitive) ||
            fName.contains("DC2C_conf", Qt::CaseInsensitive) ||
            fName.contains("DC2C_cash", Qt::CaseInsensitive)) {
            m_filesToDownload << fName;  // download printer-config-files
        } else {
            if (fName.contains("dc2c.bin")) {
                m_filesToDownload << fName; // download device controller
            }
        }
    }

    return (m_filesToDownload.size() > 0);
}


bool Worker::cleanUpOpkgCache() {
    bool removedFiles = true;
    QDir dir("/var/cache/opkg");
    if (dir.exists()) {
        dir.setNameFilters(QStringList() << ".gz" << ".ipk");
        dir.setFilter(QDir::Files);
        foreach(QString dirFile, dir.entryList()) {
            removedFiles &= dir.remove(dirFile);
        }
    }
    return removedFiles;
}

bool Worker::execOpkgCommands() {
    if (!cleanUpOpkgCache()) {
        CONSOLE() << "INFO: some cached opkg files not removed";
    }
    for (int i = 0; i < m_filesToUpdate.size(); ++i) {
        QString const fName = m_filesToUpdate.at(i);
        if (fName.contains("opkg_commands", Qt::CaseInsensitive)) {
            GUI() << (CONSOLE() << UPDATE_STEP::EXEC_OPKG_COMMANDS);
            // execute opkg commands
            if (QDir::setCurrent(m_customerRepository)) {
                QFile f(fName);
                if (f.exists()) {
                    if (f.open(QIODevice::ReadOnly)) {
                        QTextStream in(&f);
                        m_opkgCommands.clear();
                        QStringList opkgErrorLst;
                        while (!in.atEnd()) {
                            QString line = in.readLine();
                            // TODO: "^\\s*[#]{0,}$" : empty line or comment line starting with #
                            static const QRegularExpression comment("^\\s*[#].*$");
                            static const QRegularExpression emptyLine("^\\s*$");
                            if (line.indexOf(emptyLine, 0) == -1 &&
                                line.indexOf(comment, 0) == -1) {
                                QString opkgCommand = line.trimmed();
                                qCritical() << "Found opkg-command" << opkgCommand;
                                if (!executeOpkgCommand(opkgCommand)) {
                                    opkgErrorLst << opkgCommand;
                                } else {
                                    QString cmd = "\n  " + opkgCommand;
                                    emit appendText(cmd);
                                    m_opkgCommands << cmd;

                                    QStringList const opkgLst(opkgCommand);
                                    QStringList const cmdLst(cmd);

                                    switch(m_opkgCommands.size()) {
                                    case 1:
                                        ISMAS(opkgLst) << (GUI(cmdLst) << (CONSOLE() << UPDATE_STEP::EXEC_OPKG_COMMAND_1));
                                        setProgress(_EXEC_OPKG_COMMAND_1);
                                    break;
                                    case 2:
                                        ISMAS(opkgLst) << (GUI(cmdLst) << (CONSOLE() << UPDATE_STEP::EXEC_OPKG_COMMAND_2));
                                        setProgress(_EXEC_OPKG_COMMAND_2);
                                    break;
                                    case 3:
                                        ISMAS(opkgLst) << (GUI(cmdLst) << (CONSOLE() << UPDATE_STEP::EXEC_OPKG_COMMAND_3));
                                        setProgress(_EXEC_OPKG_COMMAND_3);
                                    break;
                                    case 4:
                                        ISMAS(opkgLst) << (GUI(cmdLst) << (CONSOLE() << UPDATE_STEP::EXEC_OPKG_COMMAND_4));
                                        setProgress(_EXEC_OPKG_COMMAND_4);
                                    break;
                                    case 5:
                                        ISMAS(opkgLst) << (GUI(cmdLst) << (CONSOLE() << UPDATE_STEP::EXEC_OPKG_COMMAND_5));
                                        setProgress(_EXEC_OPKG_COMMAND_5);
                                    break;
                                    case 6:
                                        ISMAS(opkgLst) << (GUI(cmdLst) << (CONSOLE() << UPDATE_STEP::EXEC_OPKG_COMMAND_6));
                                        setProgress(_EXEC_OPKG_COMMAND_6);
                                    break;
                                    case 7:
                                        ISMAS(opkgLst) << (GUI(cmdLst) << (CONSOLE() << UPDATE_STEP::EXEC_OPKG_COMMAND_7));
                                        setProgress(_EXEC_OPKG_COMMAND_7);
                                    break;
                                    case 8:
                                        ISMAS(opkgLst) << (GUI(cmdLst) << (CONSOLE() << UPDATE_STEP::EXEC_OPKG_COMMAND_8));
                                        setProgress(_EXEC_OPKG_COMMAND_8);
                                    break;
                                    case 9:
                                        ISMAS(opkgLst) << (GUI(cmdLst) << (CONSOLE() << UPDATE_STEP::EXEC_OPKG_COMMAND_9));
                                        setProgress(_EXEC_OPKG_COMMAND_9);
                                    break;
                                    default:
                                        ISMAS(opkgLst) << (GUI(cmdLst) << (CONSOLE() << UPDATE_STEP::EXEC_OPKG_COMMAND_LAST));
                                        setProgress(_EXEC_OPKG_COMMAND_LAST);
                                    }
                                }
                            }
                        }
                        f.close();
                        if (opkgErrorLst.size() == 0) {
                            if (m_opkgCommands.size() > 0) {
                                m_displayIndex = 1;
                                ISMAS() << (GUI() << (CONSOLE() << UPDATE_STEP::EXEC_OPKG_COMMAND_SUCCESS));
                                setProgress(_EXEC_OPKG_COMMAND_SUCCESS);
                            }
                        } else {
                            m_displayIndex = 1;
                            ISMAS(opkgErrorLst) << (GUI(opkgErrorLst) << (CONSOLE() << (m_lastFailedUpdateStep = UPDATE_STEP::EXEC_OPKG_COMMAND_FAILURE)));
                            GUI() << UPDATE_STEP::EXEC_OPKG_COMMAND_FAILURE;
                            setProgress(_EXEC_OPKG_COMMAND_FAILURE);
                            return false;
                        }
                    }
                }
            }
        }
    }
    return true;
}

bool Worker::downloadFilesToPSAHardware() {
    m_displayIndex = 0;
    QStringList lst(QString("START"));

    ISMAS(lst) << (GUI(m_filesToDownload) << (CONSOLE(m_filesToDownload) << UPDATE_STEP::DOWNLOAD_FILES_TO_PSA_HARDWARE));

    if (m_noUpdatePsaHardware == false) {
        if (computeFilesToDownload() > 0) {
            lst.clear();
            for (int i = 0; i < m_filesToDownload.size(); ++i) {
                lst << QFileInfo(m_filesToDownload.at(i)).fileName();
            }
            ISMAS(lst) << (CONSOLE(lst) << UPDATE_STEP::FILES_TO_DOWNLOAD);

            if (m_update && m_update->doUpdate(m_displayIndex, m_filesToDownload)) {
                // prepared for use: at the moment, the dc-library does not work
                // as expected.

                // static const QRegularExpression re("^.*\\.json$");
                // return update.checkDownloadedJsonVersions(m_filesToDownload.filter(re));

                return true;
            }

            ISMAS(lst) << (CONSOLE(lst) << (m_lastFailedUpdateStep = UPDATE_STEP::DOWNLOAD_FILES_TO_PSA_HARDWARE_FAILURE));
            setProgress(_DOWNLOAD_FILES_TO_PSA_HARDWARE_FAILURE);

            return false;
        }
    }

    return true;
}

bool Worker::syncCustomerRepositoryAndFS() {
    QString msg("START");
    QStringList lst(msg);
    ISMAS(lst) << (GUI(lst) << (CONSOLE(lst) << UPDATE_STEP::SYNC_CUSTOMER_REPOSITORY));
    lst.clear();

    if (QDir(m_customerRepository).exists()) {
        if (QDir::setCurrent(m_customerRepository)) {
            Command md("bash");
            if (!md.execute(m_customerRepository,
                            QStringList() << "-c" << "mkdir -p /etc/psa_config /etc/dc /etc/psa_tariff")) {
                msg = QString("COULD NOT EXECUTE '%1', exitCode=%2").arg(md.command()).arg(md.exitCode());
                qCritical() << msg;
                QStringList lst2(msg);
                ISMAS(lst2) << (CONSOLE(lst2) << UPDATE_STEP::SYNC_CUSTOMER_REPOSITORY);
            }
            QString const params("-v                      "
                                 "--recursive             "
                                 "--progress              "
                                 "--checksum              "
                                 "--exclude=.*            "
                                 "--include=*.bin         "
                                 "--include=*.json        "
                                 "--include=*.ini");
            QStringList cmds;

            if (QDir(QDir::cleanPath(m_customerRepository + QDir::separator() + "etc/")).exists()) {
                cmds << QString("rsync ") + params.simplified() + " etc/ /etc";
                Utils::printInfoMsg(QString("CONFIGURED SYNCING TO /ETC"));
            }
            if (QDir(QDir::cleanPath(m_customerRepository + QDir::separator() + "opt/")).exists()) {
                cmds << QString("rsync ") + params.simplified() + " opt/ /opt";
                Utils::printInfoMsg(QString("CONFIGURED SYNCING TO /OPT"));
            }

            QString cmd;
            bool error = false;
            foreach (cmd, cmds) {
                if (!error) {
                    Command c("bash");
                    qInfo() << "EXECUTING CMD..." << cmd;
                    Utils::printInfoMsg(QString("EXECUTING CMD %1...").arg(cmd));
                    if (c.execute(m_customerRepository, QStringList() << "-c" << cmd)) {
                        QStringList result = c.getCommandResult().split('\n');
                        QString const &p1 = "send_files mapped ";
                        QString const &p2 = "of size";

                        for (int i = 0; i < result.size(); ++i) {
                            QString line = result.at(i);
                            qInfo() << line;

                            // "send_files mapped etc/psa_tariff/tariff01.json of size 19339"
                            int sendFilesAtPos = line.indexOf(p1);
                            int ofSizeAtPos = line.indexOf(p2);
                            if (sendFilesAtPos != -1 && ofSizeAtPos != -1) {
                                sendFilesAtPos += p1.length();
                                QString const &s = line.mid(sendFilesAtPos, ofSizeAtPos - sendFilesAtPos).trimmed();

                                //m_updateStatus = UpdateStatus(UPDATE_STATUS::RSYNC_FILE_SUCCESS,
                                //    QString("RSYNC FILE ") + s.split("/").last() +
                                //            " LAST-COMMIT: " + m_gc.gitLastCommit(s));
                                //IsmasClient::sendRequestReceiveResponse(IsmasClient::APISM::DB_PORT,
                                //        QString("#M=APISM#C=CMD_EVENT#J=") +
                                //            m_ismasClient.rsyncFile(m_updateStatus.m_statusDescription, ""));
                            }
                        }

                    } else {
                        Utils::printCriticalErrorMsg(QString("CMD ") + cmd + " FAILED: "
                                                   + c.getCommandResult() + QString(" EXIT_CODE=(%1)").arg(c.exitCode()));
                        error = true;
                    }
                }
                if (!error) {
                    lst.clear();
                    lst << QString("SUCCESS %1").arg(cmd);
                    ISMAS(lst) << (CONSOLE(lst) << UPDATE_STEP::SYNC_CUSTOMER_REPOSITORY);
                } else {
                    msg = QString("FAILURE %1").arg(cmd);
                    lst << msg;
                    QStringList lst2(msg);
                    ISMAS(lst2) << (CONSOLE(lst2) << UPDATE_STEP::SYNC_CUSTOMER_REPOSITORY);
                }
            }
            if (!error) {
                // now check tariff-files in etc and /etc/psa_tariff
                QDir dir1(QDir::cleanPath(m_customerRepository + QDir::separator() + "etc/psa_tariff"));
                QDir dir2("/etc/psa_tariff");
                if (Utils::sameFilesInDirs(dir1, dir2) == false) {
                    CONSOLE() <<  QDir::cleanPath(m_customerRepository + QDir::separator() + "etc/psa_tariff")
                              << "AND /etc/psa_tariff ARE DIFFERENT: CHANGED CUSTOMER-NUMBER?";
                }
                CONSOLE() << UPDATE_STEP::SYNC_CUSTOMER_REPOSITORY_SUCCESS;
                setProgress(_SYNC_CUSTOMER_REPOSITORY_SUCCESS);
                return true;
            }
        } else {
            lst << QString("CAN NOT CD TO CUST-REPO %1").arg(m_customerRepository);
        }
    } else {
        lst << QString("CUST-REPO %1 DOES NOT EXIST").arg(m_customerRepository);
    }
    ISMAS(lst) << (GUI(lst) << (CONSOLE(lst) << (m_lastFailedUpdateStep = UPDATE_STEP::SYNC_CUSTOMER_REPOSITORY_FAILURE)));
    setProgress(_SYNC_CUSTOMER_REPOSITORY_FAILURE);
    return false;
}

bool Worker::saveLogFile() {
    // ISMAS() << (GUI() << (CONSOLE() << UPDATE_STEP::SAVE_LOGS));
    // ISMAS() << (GUI() << (CONSOLE() << UPDATE_STEP::SAVE_LOGS_FAILURE));
    return true;
}

QString Worker::getOsVersion() const {
    QString const cmd = QString("echo -n $(cat /etc/os-release | head -n 1 | cut -d'\"' -f2 | tr -d '\"')");
    Command c("bash");
    if (c.execute(m_workingDirectory, QStringList() << "-c" << cmd)) {
        return c.getCommandResult();
    }
    return "N/A";
}

QString Worker::getATBUpdateToolYoctoVersion() {
    if (QFile::exists("/var/lib/opkg/status")) {
        QString const cmd = QString("echo -n $(cat /var/lib/opkg/status | grep -A1 atbupdatetool | tail -n 1 | cut -d':' -f2 | cut -d' ' -f2)");
        Command c("bash");
        if (c.execute("/tmp", QStringList() << "-c" << cmd)) {
            return c.getCommandResult(); // 1.3.9+git0+226553a8ab-r0
        }
    }
    return "N/A";
}

QString Worker::getAPISMYoctoVersion() {
    if (QFile::exists("/var/lib/opkg/status")) {
        QString const cmd = QString("echo -n $(cat /var/lib/opkg/status | grep -A1 -e apism[[:space:]]*$ | tail -n 1 | cut -d':' -f2 | cut -d' ' -f2)");
        Command c("bash");
        if (c.execute("/tmp", QStringList() << "-c" << cmd)) {
            return c.getCommandResult(); // 1.4.1.0-r4
        }
    }
    return "N/A";
}

QString Worker::getATBUpdateToolYoctoInstallationStatus()  {
    if (QFile::exists("/var/lib/opkg/status")) {
        QString const cmd = QString("echo -n $(cat /var/lib/opkg/status | grep -A3 atbupdatetool | tail -n 1 | cut -d':' -f2 | cut -d' ' -f2,3,4)");
        Command c("bash");
        if (c.execute("/tmp", QStringList() << "-c" << cmd)) {
            return c.getCommandResult(); // 1.3.9+git0+226553a8ab-r0
        }
    }
    return "N/A";
}

QString Worker::getAPISMYoctoInstallationStatus()  {
    if (QFile::exists("/var/lib/opkg/status")) {
        QString const cmd = QString("echo -n $(cat /var/lib/opkg/status | grep -A3 apism | tail -n 1 | cut -d':' -f2 | cut -d' ' -f2,3,4)");
        Command c("bash");
        if (c.execute("/tmp", QStringList() << "-c" << cmd)) {
            return c.getCommandResult(); // 1.3.9+git0+226553a8ab-r0
        }
    }
    return "N/A";
}

QString Worker::getDCVersionPreparedForDownload(QString const &filename) {
    if (QFile::exists(filename)) {  // <customer-repo/etc/dc/dc2c.bin>
        QString const cmd = QString("strings %1 | grep -e DC2[Cc]\\. | head -n1").arg(filename);
        Command c("bash");
        if (c.execute("/tmp", QStringList() << "-c" << cmd)) {
            return c.getCommandResult(); // DC2c.04.42  14.09.2023
        }
    }
    return "N/A";
}


QString Worker::getATBQTVersion() const {
    QString const cmd = QString("echo -n $(/opt/app/ATBAPP/ATBQT -v | head -n 2 | cut -d':' -f2)");
    Command c("bash");
    if (c.execute(m_workingDirectory, QStringList() << "-c" << cmd)) {
        return c.getCommandResult();
    }
    return "N/A";
}

QString Worker::getATBUpdateToolVersion() const {
    return APP_EXTENDED_VERSION;
}

QString Worker::getCPUSerial() const {
    QString const cmd = QString("echo -n $(cat /proc/cpuinfo | grep -i Serial | cut -d':' -f2)");
    Command c("bash");
    if (c.execute(m_workingDirectory, QStringList() << "-c" << cmd)) {
        return c.getCommandResult();
    }
    return "N/A";
}

QString Worker::getRaucVersion() const {
    QString const cmd = QString("echo -n $(rauc --version)");
    Command c("bash");
    if (c.execute(m_workingDirectory, QStringList() << "-c" << cmd)) {
        return c.getCommandResult();
    }
    return "N/A";
}

QString Worker::getOpkgVersion() const {
    QString const cmd = QString("echo -n $(opkg --version)");
    Command c("bash");
    if (c.execute(m_workingDirectory, QStringList() << "-c" << cmd)) {
        return c.getCommandResult();
    }
    return "N/A";
}

QString Worker::getPluginVersion(QString const &pluginFileName) const {
    QString const cmd = QString("echo -n $(strings %1 | grep \\\"Version\\\" | cut -d':' -f2 | tr -d '\"' | tr -d ',')").arg(pluginFileName);
    Command c("bash");
    if (c.execute(m_workingDirectory, QStringList() << "-c" << cmd)) {
        return c.getCommandResult();
    }
    return "N/A";
}

QStringList Worker::getDCVersion() const {
    QStringList lst = (QStringList() << "N/A" << "N/A");
#if 0
    Update const *up = update();
    if (up) {
        hwinf const *caPlugin = up->hw();
        if (caPlugin) {
            caPlugin->dc_autoRequest(true); // turn auto-request setting on

            QByteArray const cmp(8, char(0));
            QByteArray hw(""), sw("");
            for (int i=0; i<5; ++i) {
                hw = caPlugin->dc_getHWversion().toUtf8();
                sw = caPlugin->dc_getSWversion().toUtf8();
                if (!hw.startsWith(cmp)) {
                    lst.clear();
                    qInfo() << hw << sw;
                    lst << hw << sw;
                    break;
                }
                QThread::sleep(1);
            }
        }
    }
#endif
    return lst;
}

qint64 Worker::getFileSize(QString const &fileName) const {
    // fileName has to be an absolute path
    QFileInfo fInfo(fileName);
    return fInfo.exists() ? fInfo.size() : -1;
}

bool Worker::executeOpkgCommand(QString opkgCommand) {
    Command c(opkgCommand);
    if (c.execute(m_workingDirectory)) {
        QString const r = c.getCommandResult();
        Utils::printInfoMsg(QString("EXECUTE OPKG COMMAND %1 OK: %2")
                                    .arg(opkgCommand)
                                    .arg(c.getCommandResult()));
        return true;
    } else {
        Utils::printCriticalErrorMsg(QString("EXECUTE OPKG COMMAND %1 FAILED")
                                    .arg(opkgCommand));
    }
    return false;
}

PSAInstalled Worker::getPSAInstalled() {
    QStringList const dcVersion = getDCVersion();
    QString const deviceControllerVersionHW = dcVersion.first();
    QString const deviceControllerVersionSW = dcVersion.last();

    qInfo() << "CURRENT DC-HW-VERSION: " << deviceControllerVersionHW;
    qInfo() << "CURRENT DC-SW-VERSION: " << deviceControllerVersionSW;

    QString const deviceControllerGitBlob = "N/A";
    QString const deviceControllerGitLastCommit = "N/A";

    PSAInstalled psaInstalled;
    QString printSysDir("/etc/psa_config");
    QString tariffSysDir("/etc/psa_tariff");
    QString tariffRepoDir("etc/psa_tariff");
    QString opkgRepoDir("etc/psa_update");
    QString const &absPathNameRepositoryOpkg = QDir::cleanPath(opkgRepoDir + QDir::separator() + "opkg_commands");
    QString absPathName;
    QString absPathNameRepository;

    psaInstalled.versionInfo.lastCommit = "N/A";
    psaInstalled.versionInfo.reason = "N/A";
    psaInstalled.versionInfo.created = "N/A";

    if (m_versionInfo.size() == 3) {
        QString const &lastCommit = m_versionInfo.at(0);
        QString reason = m_versionInfo.at(1);
        QDateTime const dt = QDateTime::fromString(m_versionInfo.at(2), Qt::ISODate);
        QString version{""};
        QString date{""};
        if (dt.isValid()) {
            date += " ";
            date += dt.date().toString(Qt::ISODate);
        }
        static const QRegularExpression re("^\\s*(\\d+)\\.(\\d+)\\.(\\d+)(.*$)");
        QRegularExpressionMatch match = re.match(reason);
        if (match.hasMatch()) {
            int const lastCapturedIndex = match.lastCapturedIndex();
            if (lastCapturedIndex >= 1) {
                version += " v";
                version += match.captured(1);   // major
            }
            if (lastCapturedIndex >= 2) {
                version += ".";
                version += match.captured(2);   // minor
            }
            if (lastCapturedIndex >= 3) {
                version += ".";
                version += match.captured(3);   // patch
            }
            if (lastCapturedIndex >= 4) {       // rest after version
                reason = match.captured(4);
            }
        }

        psaInstalled.versionInfo.lastCommit = QString("%1%2").arg(lastCommit).arg(version);
        psaInstalled.versionInfo.reason = reason;
        psaInstalled.versionInfo.created = m_versionInfo.at(2);
    }

    //qCritical() << "";
    //qCritical() << "VERSION-INFO";
    //qCritical() << "LAST-COMMIT" << psaInstalled.versionInfo.lastCommit;
    //qCritical() << "REASON" << psaInstalled.versionInfo.reason;
    //qCritical() << "CREATED" << psaInstalled.versionInfo.created;
    //qCritical() << "";

    if (m_zoneNr != 0) {
        QString const &n = QString("%1").arg(m_zoneNr).rightJustified(2, '0');
        psaInstalled.tariff.name = QString("tariff%1.json").arg(n);
        absPathName = QDir::cleanPath(tariffSysDir + QDir::separator() + psaInstalled.tariff.name);
        psaInstalled.tariff.blob = m_gc.gitBlob(absPathName);
        absPathNameRepository = QDir::cleanPath(tariffRepoDir + QDir::separator() + psaInstalled.tariff.name);
        psaInstalled.tariff.lastCommit = m_gc.gitLastCommit(absPathNameRepository);
        psaInstalled.tariff.size = getFileSize(absPathName);
        psaInstalled.tariff.zone = m_zoneNr;
        psaInstalled.tariff.loadTime = Utils::getTariffLoadTime(absPathName);
        psaInstalled.tariff.project = Utils::getLocation(absPathName);
        psaInstalled.tariff.version = Utils::getTariffVersion(absPathName);
        psaInstalled.tariff.info = Utils::getTariffInfo(absPathName);
    }

    psaInstalled.hw.linuxVersion = getOsVersion();
    psaInstalled.hw.cpuSerial = m_cpuSerial;

    psaInstalled.opkg.blob = m_gc.gitBlob(absPathNameRepositoryOpkg);
    // psaInstalled.opkg.size = getFileSize(absPathNameRepositoryOpkg);
    // psaInstalled.opkg.loadTime = Utils::getTariffLoadTime(absPathNameRepositoryOpkg);
    psaInstalled.opkg.lastCommit = m_gc.gitLastCommit(absPathNameRepositoryOpkg);

    psaInstalled.dc.versionHW = deviceControllerVersionHW;
    psaInstalled.dc.versionSW = deviceControllerVersionSW;
    psaInstalled.dc.gitBlob = "N/A";
    psaInstalled.dc.gitLastCommit = "N/A";
    psaInstalled.dc.size = -1;

    psaInstalled.sw.apismVersion = getAPISMYoctoVersion();
    psaInstalled.sw.atbQTVersion = getATBQTVersion();
    psaInstalled.sw.atbUpdateToolVersion = m_atbUpdateToolVersion;

    psaInstalled.pluginVersion.deviceController = m_pluginVersionATBDeciceController;
    psaInstalled.pluginVersion.ingenicoISelfCC = m_pluginVersionIngenicoISelf;
    psaInstalled.pluginVersion.mobilisisCalculatePrice = m_pluginVersionMobilisisCalc;
    psaInstalled.pluginVersion.mobilisisCalculatePriceConfigUi = m_pluginVersionMobilisisCalcConfig;
    psaInstalled.pluginVersion.prmCalculatePrice = m_pluginVersionPrmCalc;
    psaInstalled.pluginVersion.prmCalculatePriceConfigUi = m_pluginVersionPrmCalcConfig;
    psaInstalled.pluginVersion.tcpZVT = m_pluginVersionTcpZvt;

    // key: conf-json-filename; value: installed version on DC
    QMap<QString, QString> map;
    if (m_update) {
        map = m_update->getInstalledJsonVersions();
    }

    psaInstalled.cash.name = "DC2C_cash.json";
    absPathName = QDir::cleanPath(printSysDir + QDir::separator() + psaInstalled.cash.name);

    psaInstalled.cash.blob = m_gc.gitBlob(absPathName);
    psaInstalled.cash.size = getFileSize(absPathName);
    if (map.contains("DC2C_cash.json")) {
        psaInstalled.cash.blob = map.value("DC2C_cash.json", "inst.vers.not.avail");
    }

    psaInstalled.conf.name = "DC2C_conf.json";
    absPathName = QDir::cleanPath(printSysDir + QDir::separator() + psaInstalled.conf.name);
    psaInstalled.conf.blob = m_gc.gitBlob(absPathName);
    psaInstalled.conf.size = getFileSize(absPathName);
    if (map.contains("DC2C_conf.json")) {
        psaInstalled.conf.blob = map.value("DC2C_conf.json", "inst.vers.not.avail");
    }

    psaInstalled.device.name = "DC2C_device.json";
    absPathName = QDir::cleanPath(printSysDir + QDir::separator() + psaInstalled.device.name);
    psaInstalled.device.blob = m_gc.gitBlob(absPathName);
    psaInstalled.device.size = getFileSize(absPathName);
    if (map.contains("DC2C_device.json")) {
        psaInstalled.device.blob = map.value("DC2C_device.json", "inst.vers.not.avail");
    }

    for (int i=0; i < 32; ++i) {
        QString const &n = QString("%1").arg(i+1).rightJustified(2, '0');
        psaInstalled.print[i].name = QString("DC2C_print%1.json").arg(n);
        absPathName = QDir::cleanPath(printSysDir + QDir::separator() + psaInstalled.print[i].name);
        psaInstalled.print[i].blob = m_gc.gitBlob(absPathName);
        psaInstalled.print[i].size = getFileSize(absPathName);
        if (map.contains(psaInstalled.print[i].name)) {
            psaInstalled.print[i].blob = map.value(psaInstalled.print[i].name, "inst.vers.not.avail");
        }
    }

    psaInstalled.ptuPackageVersion = "{}";
    if (QFile::exists("/usr/bin/ptuPackageVersions")) {
        Command c("/usr/bin/ptuPackageVersions -i -o json");
        if (c.execute(m_workingDirectory)) {
            QString r = c.getCommandResult();
            // ptuPackageVersions returns a json-array
            QJsonArray const &ja = QJsonDocument::fromJson(r.remove(QRegExp("\\n")).toUtf8()).array();
            if (!ja.empty()) {
                // transform the array into an object, containing the objects
                // of the array (christian needs it this way)
                QJsonObject o;
                foreach (QJsonValue const &value, ja) {
                    if (value.isObject()) {
                        QJsonObject obj = value.toObject();
                        QStringList keys = obj.keys();
                        if (!keys.isEmpty()) {
                            QString const &k = obj.keys().first();
                            QJsonValue const &v = obj.value(k);
                            o.insert(k, v);
                        }
                    }
                }

                psaInstalled.ptuPackageVersion =
                    QJsonDocument(o).toJson(QJsonDocument::Compact);

            } else {
                qCritical() << __func__ << ":" << __LINE__
                            << "ERROR array return by ptuPackageVersions empty";
            }
        } else {
            qCritical() << __func__ << ":" << __LINE__
                        << "ERROR executing ptuPackageVersions";
        }
    }

    return psaInstalled;
}

void Worker::readyReadStandardOutput() {
    QProcess *p = (QProcess *)sender();
    m_standardOutput = p->readAllStandardOutput();


    qCritical() << "ZZZZ" << m_standardOutput;
}

bool Worker::jsUpdate() {
    return m_dcDownloadJsonFiles->start("/opt/app/tools/atbupdate");
}

bool Worker::dcUpdate() {
    return m_dcDownloadFirmware->start("/opt/app/tools/atbupdate");
}

void Worker::summary() {

    QString summary, first, second, line, tmp;
    QVector<QPair<QString, QString>> vec = Utils::installedPackages();

    vec.append(Utils::installedTariffFiles(this, m_customerRepository));
    vec.append(Utils::installedJsonFiles(this, m_customerRepository));

    int max_first = 0, max_second = 0;
    for (int i = 0; i < vec.size(); ++i) {
        max_first = std::max(max_first, vec[i].first.length());
        max_second = std::max(max_second, vec[i].second.length());
    }

    max_first += 5;

    summary = "UPDATE SUMMARY\n\n";

    first = QString("%1").arg("start", max_first, QChar(' '));
    tmp = QString("%1").arg(start().toString(Qt::ISODate));
    second = QString("%1").arg(tmp, -max_second, QChar(' '));
    line = first + ": " + second;
    summary += line + "\n";

    first = QString("%1").arg("update tool version", max_first, QChar(' '));
    tmp = QString("%1 - %2 %3").arg(APP_VERSION).arg(APP_BUILD_DATE).arg(APP_BUILD_TIME);
    second = QString("%1").arg(tmp, -max_second, QChar(' '));
    line = first + ": " + second;
    summary += line + "\n";

    first = QString("%1").arg("machine number", max_first, QChar(' '));
    tmp = QString("%1").arg(machineNr());
    second = QString("%1").arg(tmp, -max_second, QChar(' '));
    line = first + ": " + second;
    summary += line + "\n";

    first = QString("%1").arg("customer number", max_first, QChar(' '));
    tmp = QString("%1").arg(customerNr());
    second = QString("%1").arg(tmp, -max_second, QChar(' '));
    line = first + ": " + second;
    summary += line + "\n";

    first = QString("%1").arg("zone number", max_first, QChar(' '));
    tmp = QString("%1").arg(zoneNr());
    second = QString("%1").arg(tmp, -max_second, QChar(' '));
    line = first + ": " + second;
    summary += line + "\n";

    if (m_mainWindow) {
        tmp = m_mainWindow->targetDcVersion();
        if (!tmp.isEmpty()) {
            first = QString("%1").arg("target device controller", max_first, QChar(' '));
            second = QString("%1").arg(tmp, -max_second, QChar(' '));
            line = first + ": " + second;
            summary += line + "\n";
        }
    }

    first = QString("%1").arg("apism", max_first, QChar(' '));
    tmp = QString("%1").arg(apismVersion());
    second = QString("%1").arg(tmp, -max_second, QChar(' '));
    line = first + ": " + second;
    summary += line + "\n";

    for (int i = 0; i < vec.size(); ++i) {
        first = QString("%1").arg(vec[i].first, max_first, QChar(' '));
        second = QString("%1").arg(vec[i].second, -max_second, QChar(' '));
        line = first + ": " + second;
        summary += line + "\n";
    }

    emit showSummary(summary);
    emit enableExit();
}