#include "GhostUImg.h"
#include "utils/RsyncTask.h"
#include "utils/SquashfsTask.h"
#include "utils/CheckTask.h"
#include "utils/Utils.h"
#include "utils/Device.h"
#include "utils/FsTab.h"
#include "utils/RemoveTask.h"
#include "DBInterface.h"
#include <QDir>
#include <QDebug>
#include <QSettings>
#include <unistd.h>
#include <QRegularExpression>

const QString SystemUpgradeMountPoint = "/SystemUpgradeMountPoint";
static const int AFTER_MAKE_SQUASHFS_PROGRESS = 95;

GhostUImg::GhostUImg()
{
    m_subTimer = new QTimer(this);
    connect(m_subTimer, &QTimer::timeout, this, &GhostUImg::subTimeSlot);
}

GhostUImg::~GhostUImg()
{
    disconnect(m_subTimer, &QTimer::timeout, this, &GhostUImg::subTimeSlot);
}

ErrorCode GhostUImg::CheckGhostBackupDiskSpace(const QString &selectDir)
{
    m_destDir = selectDir;
    QJsonObject destPartInfoObj;
    QString err = "";
    if ( !Utils::getDestPartInfoByDir(m_destDir, destPartInfoObj, err)) {
        QJsonObject errJson;
        errJson.insert("errCode", UnKnow);
        errJson.insert("backupSizeBytes", 0);
        errJson.insert("liveFlag", 0);
        errJson.insert("errMsg", err);
        errJson.insert("uuid", "");
        errJson.insert("operateType", static_cast<int>(OperateType::CheckGhostBackupSpace));
        Q_EMIT spaceCheckFinished(Utils::JsonToQString(errJson));
        return UnKnow;
    }

    QString destUUID = destPartInfoObj.value("uuid").toString();

    if (nullptr == m_checkTask) {
        m_checkTask = new CheckTask(OperateType::CheckGhostBackupSpace, RecoveryType::Rsync);
        m_checkTask->setParent(this);
        connect(m_checkTask, &CheckTask::spaceCheckFinished, [=](const QJsonObject &jsonObject) {
            QJsonObject spaceJson = jsonObject;
            QString uuid = spaceJson.value("uuid").toString();
            DevicePtr pDestDevice(new Device(uuid));
            quint64 totalSizeBytes = pDestDevice->getDeviceInfo()->sizeBytes;
            quint64 usedSizeBytes = pDestDevice->getDeviceInfo()->usedBytes;
            spaceJson.insert("totalSizeBytes", QString::number(totalSizeBytes));
            spaceJson.insert("usedSizeBytes", QString::number(usedSizeBytes));
            spaceJson.insert("operateType", static_cast<int>(OperateType::CheckGhostBackupSpace));
            spaceJson.insert("destDir", m_destDir);
            QString spaceInfo = Utils::JsonToQString(spaceJson);
            qWarning()<<"CheckGhostBackupSpace spaceCheckFinished, spaceInfo = "<<spaceInfo.toLocal8Bit().data();
            Q_EMIT spaceCheckFinished(spaceInfo);
        });

        connect(m_checkTask, &CheckTask::error, [=](const QJsonObject &jsonObject) {
            QJsonObject errJson = jsonObject;
            errJson.insert("errCode", UnKnow);
            errJson.insert("operateType", static_cast<int>(OperateType::CheckGhostBackupSpace));
            QString spaceInfo = Utils::JsonToQString(errJson);
            qWarning()<<"CheckGhostBackupSpace error, spaceInfo = "<<spaceInfo.toLocal8Bit().data();
            Q_EMIT spaceCheckFinished(spaceInfo);
        });
    }

    if (nullptr != m_checkTask) {
        m_checkTask->setDestPath(selectDir);
        m_checkTask->setDestUUID(destUUID);
        m_checkTask->start();
    }
    return OK;
}

ErrorCode GhostUImg::createUImg(const SystemCloneRequest &request)
{
    m_progress = 0;
    m_lastProgress = 0;
    m_afterMakeSquashfs = -1;
    m_remainSecond = 0;
    m_lastRemainSecond = 0;
    QString backupDir = request.destPath;
    quint64 totalSize = request.totalSize;
    m_doSystemUpgradeConfig = Utils::isSystemUpgrade();
    m_curOperateType = static_cast<OperateType> (request.operateType);
    m_curOperateID = request.operateID;

    // 另外一个根未挂载则挂载到SystemUpgradeMountPoint
    // TODO:如果系统盘下还有其他未挂载的分区，在制作ghsot镜像的时候也要将其挂载起来
    // if (!doSystemUpgradeConfig()) {
    //     return UnKnow;
    // }

    QJsonObject destPartInfoObj;
    QString err = "";
    qDebug()<<"createUImg getDestPartInfoByDir ...";
    if ( !Utils::getDestPartInfoByDir(backupDir, destPartInfoObj, err)) {
        Response rsp(-1, 0, 0, -1, m_curOperateType, err, "", request.operateID,"");
        QString rspStr = Utils::JsonToQString(rsp.marshal());
        qCritical()<<"createUImg getDestPartInfoByDir failed, rspStr = "<<rspStr.toLocal8Bit().data();
        Q_EMIT error(rspStr);
        return UnKnow;
    }

    // 计算总共需要的时间，按照2M/s的速度做时间预估
    m_totalTimeNeed = (totalSize * 1.0 / MiB) / 2;
    m_timeUsed = 0;

    QString backupDirMount = destPartInfoObj.value("mountpoint").toString();

    //每次备份都创建一个备份任务，任务执行完毕后自动删除
    m_rsyncTask = new RsyncTask;

    qint64 curMSecTime = QDateTime::currentMSecsSinceEpoch();
    m_startTime = QDateTime::fromMSecsSinceEpoch(curMSecTime).toString("yyyyMMdd-hhmmss");
    m_destDir = backupDir + "/" + m_startTime + "/localhost";

    connect(m_rsyncTask, &RsyncTask::progressChanged, [=](const QJsonObject &jsonObject) {
        int tmpProgress = jsonObject["progress"].toInt() * 0.7;
        int tmpRemainSecond = updateTimeRemain(tmpProgress);
        Response rsp(0, m_progress, tmpRemainSecond, -1, m_curOperateType, err, "", request.operateID, "");
        QString rspStr = Utils::JsonToQString(rsp.marshal());
        qWarning()<<"createUImg progressChanged, progress = "<<m_progress<<", remainSecond = "<<tmpRemainSecond<<", tmpProgress = "<<tmpProgress;
        Q_EMIT progressChanged(rspStr);
    });

    connect(m_rsyncTask, &RsyncTask::success, this,[this](const QJsonObject &jsonObject) {
        makeImgFile();
    }, Qt::QueuedConnection);

    connect(m_rsyncTask, &RsyncTask::error, [=](const QJsonObject &jsonObject) {
        // 失败场景下卸载之前挂载的SystemUpgradeMountPoint
        // unDoSystemUpgradeConfig();
        int errCode = jsonObject.value("errCode").toInt(-1);
        QString errMsg = jsonObject.value("errMsg").toString();
        Response rsp(errCode, 0, 0, -1, m_curOperateType, errMsg, "", request.operateID, "");
        QString rspStr = Utils::JsonToQString(rsp.marshal());
        qCritical()<<"createUImg error, rspStr = "<<rspStr.toLocal8Bit().data();
        Q_EMIT error(rspStr);
    });

    connect(m_rsyncTask, &QThread::finished, [=] {
        m_rsyncTask->deleteLater();
    });

    m_rsyncTask->setOptions({"-aAXHi", "--verbose", "--delete", "--force", "--sparse", "--stats", "--no-inc-recursive", "--delete-excluded", "--info=progress2", "--ignore-missing-args"});
    m_rsyncTask->setSourcePath("/");
    m_rsyncTask->setDestinationPath(m_destDir);
    m_rsyncTask->setCheckPath(m_destDir);
    QStringList sysBackupExcludes = {"/cdrom/*", "/dev/*", "/proc/*", "/run/*", "/mnt/*", "/media/*", "/sys/*", "/tmp/*", "/recovery/backup/*"};
    QString abRecoveryExcludes = "/etc/default/grub.d/11_deepin_ab_recovery.cfg";
    QStringList immutableExcludes;
    QDir mediaDir("/media");
    QString realMediaDir = mediaDir.canonicalPath();
    if (realMediaDir.startsWith("/var/media")) {
        immutableExcludes<<"/var/media/*"
            <<"/var/mnt/*"
            <<"/var/home/*/.config/browser/Default/virtual";
    }

    sysBackupExcludes<<abRecoveryExcludes<<immutableExcludes;

    QStringList varExcludes = {
        "/var/lib/deepin/developer-mode/*",
        "/var/lib/dde-daemon/fingerprint/fprint/*",
        "/var/lib/dde-daemon/fingerprint/huawei/*",
        "/var/lib/systemd/coredump/*",
        "/var/log/*",
        "/var/lib/tpm2-tss/system/*",
        "/var/deepin/uos-recovery/uos-recovery.db"
    };

    QStringList homeExcludes = {
        "/home/*/.config/browser/Default/virtual"
    };

    QStringList authExcludes = {
        "/usr/share/deepin-authentication/chara/charaInfo"
    };

    QStringList totalBindExcludes = varExcludes;
    totalBindExcludes << homeExcludes;

#ifdef QT_DEBUG
    QStringList debugExclude = {
            "/var/*",
            "/opt/*",
            "/home/*",
            "/usr/*",
            "/persistent/var/*",
            "/persistent/opt/*",
            "/persistent/home/*",
            "/persistent/usr/*",
            "/data/var/*",
            "/data/opt/*",
            "/data/home/*",
            "/data/usr/*"
    };

    sysBackupExcludes << debugExclude;  // 方便快速调试
#endif

    QStringList rsyncSysBackupFilters = this->getRsyncSystemBackupFilters();
    if (!rsyncSysBackupFilters.isEmpty()) {
        sysBackupExcludes << rsyncSysBackupFilters;
    }

    QStringList ostreeSysBackupFilters = {"/osroot/*", "/data/osroot/*", "/persistent/osroot/*",
                                          "/persistent/ostree/deploy/deepin/deploy",
                                          "/sysroot/persistent/ostree/deploy",
                                          "/sysroot/persistent/ostree/data",
                                          "/sysroot/ostree/deploy",
                                          "/persistent/lost+found",
                                          "/sysroot/lost+found",
                                          "/boot/lost+found",
                                          "/.cache"};
    sysBackupExcludes << ostreeSysBackupFilters;

    QString varDir = "/var";
    FSTabInfoList fstabInfos = FSTab::getFSTabFromFile("/etc/fstab");
    QMap<QString, QString> bindDirMap;
    FSTab::getFstabBindDir(fstabInfos, bindDirMap);
    sysBackupExcludes << QString(varDir + "/lib/lxcfs/*");
    if (bindDirMap.keys().contains(varDir)) {
        sysBackupExcludes << QString(bindDirMap.value(varDir) + "lib/lxcfs/*");
    }

    QStringList bindDirExcludes;
    for (auto it =  bindDirMap.begin(); it != bindDirMap.end(); ++it) {
        if (it.key() == "/var") {
            for (auto &itExclude : varExcludes) {
                itExclude.replace(QRegularExpression("^/var/"), it.value());
            }
            totalBindExcludes += varExcludes;
        } else if (it.key() == "/home") {
            for (auto &itExclude : homeExcludes) {
                itExclude.replace(QRegularExpression("^/home/"), it.value());
            }
            totalBindExcludes += homeExcludes;
        }

        if (it.value().endsWith("/")) {
            bindDirExcludes << it.value() + "*";
        } else {
            bindDirExcludes << it.value() + "/*";
        }
    }

    sysBackupExcludes << bindDirExcludes;
    sysBackupExcludes<<totalBindExcludes;
    sysBackupExcludes<<authExcludes;

    sysBackupExcludes << QString(backupDir + "/" + m_startTime);
    sysBackupExcludes << QString(backupDir + "/" + m_startTime + "/*");
    sysBackupExcludes << QString(backupDirMount + backupDir + "/" + m_startTime);
    sysBackupExcludes << QString(backupDirMount + backupDir + "/" + m_startTime + "/*");

    QStringList v20BackupExcludes = this->getV20SystemBackupExcludes();
    if (!v20BackupExcludes.isEmpty()) {
        sysBackupExcludes << v20BackupExcludes;
        QStringList tmpV20BackupExcludes = v20BackupExcludes;
        for (auto it =  bindDirMap.begin(); it != bindDirMap.end(); ++it) {
            if (it.key() == "/home") {
                for (auto &itExclude : tmpV20BackupExcludes) {
                    itExclude.replace(QRegularExpression("^/home/"), it.value());
                }
            } else if (it.key() == "/root") {
                for (auto &itExclude : tmpV20BackupExcludes) {
                    itExclude.replace(QRegularExpression("^/root/"), it.value());
                }
            }
        }
        sysBackupExcludes << tmpV20BackupExcludes;
        qWarning()<<"tmpV20BackupExcludes = "<<tmpV20BackupExcludes;
    }

    // 如果是非升级场景，则过滤掉隐藏根中的数据
    // if (!m_doSystemUpgradeConfig) {
    //     sysBackupExcludes << QString(SystemUpgradeMountPoint + "/*");
    // }

    m_rsyncTask->setExcludes(sysBackupExcludes);
    m_rsyncTask->enableDryRun(false);

    QString linkDest = backupDir + "last/";
    m_rsyncTask->setLinkDest(linkDest);
    QDir dir(m_destDir);
    if (!dir.exists()) {
        dir.mkpath(m_destDir);
    }

    QString tmpDir = backupDir + "/" + m_startTime;
    //Process::spawnCmd("setfacl", {"-m", "o:rwx", tmpDir});

    this->getInitBackupDirs();
    QString fstabPath = "/etc/fstab";
    Device::getGhostDiskInfo(fstabPath, m_diskInfoList);
    qDebug()<<"createUImg getDirSizeMap ...";
    this->getDirSizeMap(m_dirSizeMap);
    qDebug()<<"createUImg getDirSizeMap end";

    m_ghostLog.operateID = request.operateID;
    m_ghostLog.operateType = m_curOperateType;
    m_ghostLog.status = OperateStatus::Running;
    m_ghostLog.userName = request.username;
    m_ghostLog.time = curMSecTime;
    m_ghostLog.deviceUUID = request.destUUID;
    if (request.destPath.startsWith("/media/")) {
        m_ghostLog.isRemoveable = true;
        m_ghostLog.path = request.relativePath + "/" + m_startTime;
    } else {
        m_ghostLog.isRemoveable = false;
        m_ghostLog.path = tmpDir;
    }
    qWarning()<<"path: "<<m_ghostLog.path<<", deviceUUID: "<<m_ghostLog.deviceUUID;
    DBInterface::getInstance()->addGhostLog(m_ghostLog);

    if (!m_rsyncTask->buildArgumentsForBackup()) {
        qCritical()<<"createUImg buildArgumentsForBackup failed";
        QString errMsg = "build arguments failed";
        Response rsp(-2, 0, 0, -1, m_curOperateType, errMsg, "", request.operateID, "");
        QString rspStr = Utils::JsonToQString(rsp.marshal());
        qCritical()<<"createUImg error, rspStr = "<<rspStr.toLocal8Bit().data();
        Q_EMIT error(rspStr);
        return ErrorCode::ParamError;
    }

    m_imgStartTime = QDateTime::currentDateTime();
    m_rsyncTask->setOperateType(OperateType::GhostBackup);
    m_rsyncTask->start();

    return OK;
}

ErrorCode GhostUImg::removeUImgBackup(const RemoveUserDataBackupRequest &request)
{
    QString opID = request.operateID;
    m_pRemoveTaskMap[opID] = new RemoveTask;
    m_pRemoveTask = m_pRemoveTaskMap[opID];
    connect(m_pRemoveTask, &RemoveTask::error, [=] (const QJsonObject &jsonObject) {
        QJsonObject errJson = jsonObject;
        errJson.insert("operateType", static_cast<int>(OperateType::removeBackup));
        errJson.insert("operateID", request.operateID);
        errJson.insert("operateId", request.backupInfo.operateID);
        QString rspStr = Utils::JsonToQString(errJson);
        qCritical()<<"removeUImgBackup error, rspStr = "<<rspStr.toLocal8Bit().data();
        Q_EMIT error(rspStr);
    });

    connect(m_pRemoveTask, &RemoveTask::success, [=] (const QJsonObject &jsonObject) {
        GhostLog log;
        log.deviceUUID = request.backupInfo.backupDevUUID;
        log.operateID = request.backupInfo.operateID;
        log.time = request.backupInfo.startTime;
        log.path = request.backupInfo.backupPath;
        log.isRemoveable = request.backupInfo.backupDeviceRemovable;
        log.status = static_cast<OperateStatus> (request.backupInfo.status);
        log.userName = request.backupInfo.username;

        DBInterface::getInstance()->deleteGhostLog(log);
        QJsonObject msg = jsonObject;
        msg.insert("operateType", static_cast<int>(OperateType::removeBackup));
        msg.insert("operateId", request.backupInfo.operateID);
        msg.insert("operateID", request.operateID);
        Q_EMIT success(Utils::JsonToQString(msg));
    });

    connect(m_pRemoveTask, &QThread::finished, [=] {
        //   qInfo() << "task finish";
        if (nullptr != m_pRemoveTaskMap[opID]) {
            m_pRemoveTaskMap[opID]->deleteLater();
            m_pRemoveTaskMap.remove(opID);
        }
    });

    return m_pRemoveTask->remove(request.backupInfo);
}

void GhostUImg::clearUuidInRecoveryConf(const QString &recoveryConf)
{
    if (!QFile::exists(recoveryConf)) {
        qWarning()<<"clearUuidInRecoveryConf not exists recoveryConf = "<<recoveryConf;
        return;
    }

    QString emptyUuid = "";
    QSettings setConf(recoveryConf, QSettings::IniFormat);
    setConf.beginGroup(BACKUP_GROUP);
    setConf.setValue(BACKUP_DEVICE_UUID_KEY, emptyUuid);
    setConf.setValue(BACKUP_HISTORY_DEVICE_UUID_KEY, emptyUuid);
    setConf.endGroup();
}

void GhostUImg::makeImgFile()
{
    const QString srcDir = m_destDir.left(m_destDir.lastIndexOf("/"));
    qInfo()<<"makeImgFile, m_destDir: "<<m_destDir;
    QString ghostFileName = Utils::getGhostFileName(m_startTime);
    const QString imgFile = srcDir.left(srcDir.lastIndexOf("/") + 1) + ghostFileName;
    QString imgMd5File = imgFile + ".md5";
    // if (imgMd5File.contains(" ")) {
    //     imgMd5File = QString("\"%1\"").arg(imgMd5File);
    // }
    Process::spawnCmd("setfacl", {"-m", "o:rwx", imgFile});

    // ghost 安装后uuid会变，去掉配置文件里的uuid
    this->clearUuidInRecoveryConf(srcDir + "/localhost/etc/uos-recovery/uos-recovery.ini");

    // 保存设备信息
    if (!writeGhostDeviceInfo(srcDir)) {
        qCritical()<<"makeImgFile writeGhostDeviceInfo failed, return";
        QString errMsg = "makeImgFile writeGhostDeviceInfo failed";
        Response rsp(-3, 0, 0, -1, m_curOperateType, errMsg, "", m_curOperateID, "");
        QString rspStr = Utils::JsonToQString(rsp.marshal());
        Q_EMIT error(rspStr);
        return;
    }

    backUpConfFiles();// 备份配置文件到.back
    // unDoSystemUpgradeConfig();

    m_squashfsTask = new SquashfsTask;
    m_squashfsTask->setCheckPath(m_destDir);

    connect(m_squashfsTask, &SquashfsTask::progressChanged, [=](const QJsonObject &jsonObject) {
        int tmpProgress = 70 + jsonObject["progress"].toInt() * 0.25; // AFTER_MAKE_SQUASHFS_PROGRESS = 95
        int remainSecond = updateTimeRemain(tmpProgress);
        Response rsp(0, m_progress, remainSecond, -1, OperateType::GhostBackup, "", "", m_curOperateID, "");
        QString rspStr = Utils::JsonToQString(rsp.marshal());
        //qInfo()<<"SquashfsTask progressChanged, progress = "<<m_progress<<", remainSecond = "<<remainSecond;
        Q_EMIT progressChanged(rspStr);
    });

    connect(m_squashfsTask, &SquashfsTask::success, [=] {
        m_afterMakeSquashfs = 0;
        sleep(1);
        // qInfo() << "SquashfsTask::success, begin delete srcDir = " << srcDir;
        Process::spawnCmd("rm", {"-fr", srcDir});
        m_progress = 96;
        // qInfo() << "SquashfsTask::success, end delete srcDir = " << srcDir;
        DBInterface::getInstance()->deleteGhostLog(m_ghostLog);
        QString out;
        QString err;
        // 普通用户可以清理ghost文件
        Process::spawnCmd("chmod", {"o+rwx", imgFile});
        sync();
        sync();
        m_progress = 97;
        QString tmpImgFile = imgFile;
        // if (imgFile.contains(" ")) {
        //     tmpImgFile = QString("\"%1\"").arg(imgFile);
        // }
        //QString cmd = QString("md5sum %1 > %2").arg(tmpImgFile).arg(imgMd5File);
        bool ret = Process::spawnCmd("md5sum", {tmpImgFile}, out, err);
        if (!ret) {
            qCritical()<<"SquashfsTask::success, out = "<<out<<", err = "<<err;
        } else {
            QFile writeMd5File(imgMd5File);
            if (!writeMd5File.open(QIODevice::Truncate | QIODevice::Text | QIODevice::WriteOnly)) {
                qCritical()<<"SquashfsTask::success, writeMd5File open failed, imgMd5File = "<<imgMd5File;
            } else {
                writeMd5File.write(out.toStdString().c_str());
                writeMd5File.close();
            }
        }

        m_progress = 98;
        Process::spawnCmd("chmod", {"o+rwx", imgMd5File});
        ResultInfo retInfo(0, "success", "");
        this->reportEventLog(retInfo, OperateType::GhostBackup, RecoveryType::Rsync);
        sync();
        Response rsp(0, 100, 0, -1, OperateType::GhostBackup, "", "", m_curOperateID, "");
        QString rspStr = Utils::JsonToQString(rsp.marshal());
        qInfo()<<"SquashfsTask::success, rspStr = "<<rspStr.toLocal8Bit().data();
        m_afterMakeSquashfs = 1;
        Q_EMIT success(rspStr);
    });

    connect(m_squashfsTask, &SquashfsTask::error, [=](const QJsonObject &jsonObject) {
        m_afterMakeSquashfs = 0;
        m_progress = AFTER_MAKE_SQUASHFS_PROGRESS; // 95
        sleep(1);
        Process::spawnCmd("rm", {"-fr", srcDir});
        m_progress = 97;
        Process::spawnCmd("rm", {"-fr", imgFile});
        // qCritical() << "SquashfsTask::error, delete srcDir = " << srcDir;
        // qCritical() << "SquashfsTask::error, delete imgFile = " << imgFile;
        m_progress = 98;

        int errCode = jsonObject.value("errCode").toInt(-1);
        QString errMsg = jsonObject.value("errMsg").toString();
        ResultInfo retInfo(errCode, "failed", errMsg);
        this->reportEventLog(retInfo, OperateType::GhostBackup, RecoveryType::Rsync);
        Response rsp(errCode, 100, 0, -1, OperateType::GhostBackup, errMsg, "", m_curOperateID, "");
        QString rspStr = Utils::JsonToQString(rsp.marshal());
        qCritical()<<"SquashfsTask::error, rspStr = "<<rspStr.toLocal8Bit().data();
        m_afterMakeSquashfs = 1;
        Q_EMIT error(rspStr);
    });

    // connect(m_squashfsTask, &SquashfsTask::taskFinished, [=] {
    //     m_squashfsTask->deleteLater();
    //     m_subTimer->stop();
    // });

    connect(m_squashfsTask, &QThread::finished, [=] {
        if (nullptr != m_subTimer) {
            if (m_subTimer->isActive()) {
                m_subTimer->stop();
            }
        }
        m_squashfsTask->deleteLater();
        m_squashfsTask = nullptr;
    });

    // 压缩文件系统
    if (!m_squashfsTask->makeSquashfs(srcDir, imgFile)) {
        QString errMsg = "makeImgFile makeSquashfs failed";
        qWarning()<<"errMsg: "<<errMsg;
        Response rsp(-4, 0, 0, -1, m_curOperateType, errMsg, "", m_curOperateID, "");
        QString rspStr = Utils::JsonToQString(rsp.marshal());
        Q_EMIT error(rspStr);
        return;
    }

    if (!m_subTimer->isActive()) {
        m_subTimer->start(1000);
    }
}

int GhostUImg::updateTimeRemain(int progress)
{
    if (m_progress <= progress) {
        m_progress = progress;
    }

    int remainSecond = 0;
    QDateTime currDateTime = QDateTime::currentDateTime();
    qint64 elapsed = m_imgStartTime.msecsTo(currDateTime);
    double speed = -1.0;
    if (elapsed < 0) {
        qint64 elapsedSec = m_imgStartTime.secsTo(currDateTime);
        speed = (m_progress * 1.0) / elapsedSec;
    } else {
        speed = (m_progress * 1000.0) / elapsed;
    }
    if (0 < speed) {
        remainSecond = (100 - m_progress) / speed;
    }

    return remainSecond;
}

bool GhostUImg::doSystemUpgradeConfig()
{
    QString rootDev = "";
    if (!getRootDev(rootDev)) {
        return false;
    }

    QJsonArray deviceInfos;
    QString err = "";
    QStringList args;
    args<<rootDev<<"-lfpJ"<<"-o"<<"NAME,LABEL,MOUNTPOINT";
    if (!Utils::getLsblkJsonReturn(args, deviceInfos, err)) {
        return false;
    }

    bool doneConfig = true;
    for (int i = 0; i < deviceInfos.size(); i++) {
        QJsonObject infoItem = deviceInfos.at(i).toObject();
        if (infoItem.value("label").toString().contains("Root")) {
            if (infoItem.value("mountpoint").toString().isEmpty()) {
                // 挂载当前的未挂载的根分区
                QDir dir(SystemUpgradeMountPoint);
                if (!dir.exists()) {
                    dir.mkpath(SystemUpgradeMountPoint);
                }

                QString cmdLog = "";
                err = "";
                QString dev = infoItem.value("name").toString();
                doneConfig = Process::spawnCmd("mount", {dev, SystemUpgradeMountPoint}, cmdLog, err);
                break;
            }
        }
    }

    return doneConfig;
}

void GhostUImg::unDoSystemUpgradeConfig()
{
    QString cmd = QString("umount %1").arg(SystemUpgradeMountPoint);
    QString cmdLog = "";
    QString err = "";
    if (Process::spawnCmd("umount", {SystemUpgradeMountPoint}, cmdLog, err)) {
        cmd = QString("rm -rf %1").arg(SystemUpgradeMountPoint);
        cmdLog = "";
        err = "";
        Process::spawnCmd("rm", {"-rf", SystemUpgradeMountPoint}, cmdLog, err);
    }
}

bool GhostUImg::writeGhostDeviceInfo(const QString &filePath)
{
    // 保存设备信息到文件
    // 使用 uname -m 获取架构信息
    QString out;
    QString err;
    if (!Process::spawnCmd("uname", {"-m"}, out, err)) {
        qCritical()<<"writeGhostDeviceInfo spawnCmd failed to get uname, err: "<<err;
        return false;
    }
    QString platform = out.trimmed();

    // 不能使用 lspci | grep VGA 获取显卡型号信息，安装器环境里没有lspci这个命令，改用lshw命令去获取
    QString displayDriver;
    QString vga;
    if (Utils::getVGAFromLshw(vga)) {
        displayDriver = vga;
        qInfo()<<"writeGhostDeviceInfo, displayDriver = "<<displayDriver;
    }

    // 不能使用 cat /proc/cpuinfo |grep "model" 获取cpu信号信息, 部分国产机器获取不到想要的数据
    QString cpuModel;
    CpuInfo cpu;
    if (Utils::getCpuInfo(cpu)) {
        cpuModel = cpu.modelName;
        qInfo()<<"writeGhostDeviceInfo, cpuModel = "<<cpuModel;
    }

    QString fstabPath = "/etc/fstab";
    quint64 diskTotalSize = 0;
    quint64 curRootSize = 0;
    quint64 sysRootbSize = 0;
    quint64 efiSze = 0;
    quint64 recoveryPartitionUsedSizeBytes = 0;
    QJsonArray sysDiskJsonArray;
    for (auto &diskInfo : m_diskInfoList) {
        diskTotalSize += diskInfo.totalSizeBytes;
        QJsonObject jsonObject = diskInfo.marshal();
        sysDiskJsonArray.append(jsonObject);
        curRootSize += diskInfo.usedSizeBytes;
        if (diskInfo.recoveryPartitionUsedSizeBytes > 0) {
            recoveryPartitionUsedSizeBytes = diskInfo.recoveryPartitionUsedSizeBytes;
        }

        if (diskInfo.isRootb) {
            sysRootbSize = diskInfo.anotherSysRootUsedSizeBytes;
        }

        if (diskInfo.isEfi) {
            efiSze = diskInfo.efiUsedSizeBytes;
        }
    }

    curRootSize = curRootSize - recoveryPartitionUsedSizeBytes - sysRootbSize;
    AppVersion ghostVersion = AppVersion::getAppVersion();
    QJsonObject ghostVersionJson = ghostVersion.marshal();
    QString fstabFile = "/etc/fstab";
    QStringList sysMountPointList;
    bool isFullInstall = Device::isFullDiskInstall(fstabFile, sysMountPointList);
    // qint64 usedDeviceSize = getUsedDeviceSize(fstabPath);

    // 根据fstab获取bind信息
    QJsonArray bindInfosArray = getFstabInfos(fstabPath);

    // 判断引导模式
    bool isEFI = Utils::isEFIMode();

    // 保存以上信息到srcDir/info.json文件中
    GhostInfo ghostInfo;
    ghostInfo.platform = platform;
    ghostInfo.displayDriver = displayDriver;
    ghostInfo.cpuModel = cpuModel;
    ghostInfo.diskSize = diskTotalSize;
    ghostInfo.isEFI = isEFI;
    ghostInfo.isFullInstall = isFullInstall;
    ghostInfo.diskInfo = sysDiskJsonArray;
    ghostInfo.systemUpgradeInfo = getSystemUpgradeInfo();
    ghostInfo.ghostVersion = ghostVersionJson;
    ghostInfo.fstab = bindInfosArray;
    ghostInfo.dirSizeObj = getSysDirInfos(curRootSize, sysRootbSize, efiSze);
    ghostInfo.initOptApps = m_initBackupDirs;
    if ("aarch64" == platform) {
        bool isHuawei = false;
        Utils::getPlatformInfo(ghostInfo.hardwareType, ghostInfo.sysProductName, ghostInfo.huaweiTypeAlias, isHuawei);
        if (!isHuawei) {
            ghostInfo.hardwareType = "";
            ghostInfo.huaweiTypeAlias = "";
        }
    }
    QJsonObject infosObj = ghostInfo.marshal();

    QJsonDocument infosDoc(infosObj);
    QFile infosFile(filePath + "/info.json");
    if (!infosFile.open(QFile::WriteOnly)) {
        qInfo()<<"writeGhostDeviceInfo, infosFile failed : "<<filePath;
        return false;
    }

    infosFile.write(infosDoc.toJson());
    infosFile.close();

    // 按照partition_policy.json格式生成分区配置文件
    return Device::exportPartitionInfoByLsblk(filePath + "/partition_policy.json", {}, err);
}

void GhostUImg::getInitBackupDirs()
{
    m_initBackupDirs.clear();
    m_existInitAppDirs.clear();
    QStringList appPathList = {
            "/opt/apps/org.deepin.scanner",
            "/opt/apps/org.deepin.chineseime",
            "/opt/deepin-defender-vdbup",
            "/opt/xdroid"
    };

    for (auto & appPath : appPathList) {
        QFile file(appPath);
        if (file.exists()) {
            m_initBackupDirs += appPath + ";";
            m_existInitAppDirs<<appPath;
        }
    }

    if (!m_initBackupDirs.isEmpty()) {
        m_initBackupDirs = m_initBackupDirs.left(m_initBackupDirs.length() - 1);
    }
}

void GhostUImg::getDirSizeMap(QMap<QString, quint64> &dirSizeMap)
{
    dirSizeMap.clear();
    // QStringList sysBackupExcludes = this->getV20SystemBackupExcludes();
    QStringList homeDimExcludes;
    homeDimExcludes << "/home/*/.config/browser/Default/virtual";
    QStringList rootDimExcludes;
    // for (auto & exclude : sysBackupExcludes) {
    //     if (exclude.startsWith("/home/")) {
    //         homeDimExcludes << exclude;
    //     } else if (exclude.startsWith("/root/")) {
    //         rootDimExcludes << exclude;
    //     }
    // }

    QStringList varExcludes = {
            "/var/log/journal"
    };

    QStringList optExcludes;
    for (auto &appPath : m_existInitAppDirs) {
        if (appPath.startsWith("/opt/")) {
            optExcludes << appPath + "/*";
        }
    }

    QStringList persistentExcludes = {
            "/persistent/ostree/deploy/deepin/deploy/*",
            "/persistent/home/*",
            "/persistent/root/*"
    };

    QString errMsg;
    QStringList noExcludeDirs;
    QStringList dirList = {"/persistent", "/home", "/opt", "/var", "/root", "/usr/local", "/srv", "/boot", "/tmp"};
    for (auto &dir : dirList) {
        quint64 dirSizeBytes = 0;
        if ("/home" == dir) {
            noExcludeDirs = homeDimExcludes;
        } else if ("/root" == dir) {
            noExcludeDirs = rootDimExcludes;
        } else if ("/var" == dir) {
            noExcludeDirs = varExcludes;
        } else if ("/opt" == dir) {
            noExcludeDirs = optExcludes;
        } else if ("/persistent" == dir) {
            noExcludeDirs = persistentExcludes;
        } else {
            noExcludeDirs.clear();
        }
        if (!Utils::calculateDirSize(dir, noExcludeDirs, dirSizeBytes, errMsg)) {
            qWarning()<<"getSysDirInfos get "<<dir<<", failed, errMsg: "<<errMsg;
        }
        dirSizeMap[dir] = dirSizeBytes;
    }
}

QJsonObject GhostUImg::getSysDirInfos(const quint64 &sysRootSizeBytes, const quint64 &sysRootbSizeBytes, const quint64 &efiSizeBytes)
{
    SysDirSizeInfo sysDirSizeInfo;
    sysDirSizeInfo.boot = m_dirSizeMap["/boot"];
    sysDirSizeInfo.home = m_dirSizeMap["/home"];
    sysDirSizeInfo.opt = m_dirSizeMap["/opt"];
    sysDirSizeInfo.rootDir = m_dirSizeMap["/root"];
    sysDirSizeInfo.srv = m_dirSizeMap["/srv"];
    sysDirSizeInfo.var = m_dirSizeMap["/var"];
    sysDirSizeInfo.tmp = m_dirSizeMap["/tmp"];
    sysDirSizeInfo.usrLocal = m_dirSizeMap["/usr/local"];
    sysDirSizeInfo.persistent = m_dirSizeMap["/persistent"];
    sysDirSizeInfo.sysRoot = sysRootSizeBytes;
    sysDirSizeInfo.sysRootb = sysRootbSizeBytes;
    sysDirSizeInfo.efi = efiSizeBytes;

    QJsonObject sysDirInfoJson = sysDirSizeInfo.marshal();

    return sysDirInfoJson;
}

qint64 GhostUImg::getUsedDeviceSize(const QString &fstabPath)
{
    // 根据fstab中的设备id，找到对应的磁盘设备读取磁盘大小信息
    QStringList usedPartsUuids = {};
    FSTabInfoList fstabInfos = FSTab::getFSTabFromFile(fstabPath);
    for (FSTabInfoPtr fstabInfoItem : fstabInfos) {
        QString fstabDeviceItem = fstabInfoItem->device;
        if (!fstabDeviceItem.isEmpty()) {
            fstabDeviceItem = fstabDeviceItem.right(fstabDeviceItem.length() - fstabDeviceItem.indexOf("=") - 1);
            usedPartsUuids.append(fstabDeviceItem);
        }
    }

    QStringList usedDevicesName = {};
    QStringList usedLVMPartsName = {};
    Device usedDev;
    DeviceInfoList usedDevices = usedDev.getDeviceByLsblk();
    for (DeviceInfoPtr deviceItem : usedDevices) {
        if (deviceItem->uuid.isEmpty()) {
            continue;
        }
        if (usedPartsUuids.indexOf(deviceItem->uuid) != -1) {
            if (!deviceItem->type.compare("part")) {
                if (usedDevicesName.indexOf(deviceItem->pkname) == -1) {
                    usedDevicesName.append(deviceItem->pkname);
                }
            }
            if (!deviceItem->type.compare("lvm")) {
                if (usedLVMPartsName.indexOf(deviceItem->pkname) == -1) {
                    usedLVMPartsName.append(deviceItem->pkname);
                }
            }
        }
    }

    for (DeviceInfoPtr deviceItem : usedDevices) {
        if (usedLVMPartsName.contains(deviceItem->name)) {
            if (usedDevicesName.indexOf(deviceItem->pkname) == -1) {
                usedDevicesName.append(deviceItem->pkname);
            }
        }
    }

    qint64 usedDeviceSize = 0;
    for (QString usedDeviceItem : usedDevicesName) {
        usedDev.getDeviceByName(usedDeviceItem);
        usedDeviceSize += usedDev.getDeviceInfo()->sizeBytes;
    }

    return usedDeviceSize;
}

QJsonArray GhostUImg::getFstabInfos(const QString &fstabPath)
{
    QJsonArray bindInfosArray;
    FSTabInfoList fstabInfos = FSTab::getFSTabFromFile(fstabPath);
    for (FSTabInfoPtr fstabInfoItem : fstabInfos) {
        if (!fstabInfoItem->device.compare("")) {
            continue;
        }

        FstabItem fstab(fstabInfoItem->device, fstabInfoItem->mountPoint, fstabInfoItem->type,
                        fstabInfoItem->options, fstabInfoItem->dump, fstabInfoItem->pass);
        QJsonObject infosObj = fstab.marshal();
        bindInfosArray.append(infosObj);
    }

    return bindInfosArray;
}

bool GhostUImg::getRootDev(QString &rootDev)
{
    rootDev = "";

    QJsonArray fstabInfosArray = getFstabInfos("/etc/fstab");
    if (fstabInfosArray.size() < 0) {
        return false;
    }

    QString rootUuid = "";
    for (int i = 0; i < fstabInfosArray.size(); i++) {
        QJsonObject fstabInfoItem = fstabInfosArray.at(i).toObject();
        if (!fstabInfoItem.value("mountPoint").toString().compare("/")) {
            rootUuid = fstabInfoItem.value("device").toString();
            rootUuid = rootUuid.right(rootUuid.length() - rootUuid.indexOf("=") - 1);
            break;
        }
    }

    if (rootUuid.isEmpty()) {
        return false;
    }

    QJsonArray deviceInfos;
    QString err = "";
    QStringList args;
    args<<"-lfpJ"<<"-o"<<"PKNAME,UUID";
    if (!Utils::getLsblkJsonReturn(args, deviceInfos, err)) {
        return false;
    }

    for (int i = 0; i < deviceInfos.size(); i++) {
        QJsonObject infoItem = deviceInfos.at(i).toObject();
        if (!infoItem.value("uuid").toString().compare(rootUuid)) {
            rootDev = infoItem.value("pkname").toString();
            break;
        }
    }

    if (rootDev.isEmpty()) {
        return false;
    }

    return  true;
}


QJsonObject GhostUImg::getSystemUpgradeInfo()
{
    SystemUpgradeInfo sysUpgradeInfo;
    sysUpgradeInfo.isSystemUpgrade = m_doSystemUpgradeConfig;
    QJsonArray partInfo;
    QString err = "";
    QStringList args;
    args<<"-lfpJ"<<"-o"<<"NAME,UUID,MOUNTPOINT";
    if (!Utils::getLsblkJsonReturn(args, partInfo, err)) {
        return sysUpgradeInfo.marshal();
    }

    for (int i = 0; i < partInfo.size(); ++i) {
        QJsonObject infoObject = partInfo.at(i).toObject();
        QString mountPoint = infoObject.value("mountpoint").toString();
        if (!mountPoint.compare(SystemUpgradeMountPoint)) {
            sysUpgradeInfo.rootPath = infoObject.value("name").toString();
            sysUpgradeInfo.rootUuid = infoObject.value("uuid").toString();
            sysUpgradeInfo.mountPoint = SystemUpgradeMountPoint;
            break;
        }
    }

    return sysUpgradeInfo.marshal();
}

void GhostUImg::backUpConfFiles()
{
    QJsonArray deviceInfos;
    QString err = "";
    QStringList args;
    args<<"-lfpJ"<<"-o"<<"UUID,MOUNTPOINT";
    if (!Utils::getLsblkJsonReturn(args, deviceInfos, err)) {
        return;
    }

    QString ruleFilePath = "/etc/udev/rules.d/80-udisks-installer.rules";
    // 备份m_destDir/etc/udev/rules.d/80-udisks-installer.rules文件到.back
    replaceRootUuidContent(m_destDir + ruleFilePath,
                           m_destDir + ruleFilePath + ".back",
                           deviceInfos);

    QString abRecoveryConfFilePath = "/etc/deepin/ab-recovery.json";
    // 备份m_destDir/etc/deepin/ab-recovery.json文件到.back
    replaceRootUuidContent(m_destDir + abRecoveryConfFilePath,
                             m_destDir + abRecoveryConfFilePath + ".back",
                             deviceInfos);

    // if (m_doSystemUpgradeConfig) {
    //     // 备份m_destDir/SystemUpgradeMountPoint/etc/udev/rules.d/80-udisks-installer.rules到.back
    //     replaceRootUuidContent(m_destDir + SystemUpgradeMountPoint + ruleFilePath,
    //                            m_destDir + SystemUpgradeMountPoint + ruleFilePath + ".back",
    //                            deviceInfos);

    //     // 备份m_destDir/SystemUpgradeMountPoint/etc/deepin/ab-recovery.json到.back
    //     replaceRootUuidContent(m_destDir + SystemUpgradeMountPoint + abRecoveryConfFilePath,
    //                              m_destDir + SystemUpgradeMountPoint + abRecoveryConfFilePath + ".back",
    //                              deviceInfos);
    // }
}

void GhostUImg::replaceRootUuidContent(const QString &srcFilePath, const QString &destFilePath, const QJsonArray &devInfos)
{
    QFile ruleFile(srcFilePath);
    if (!ruleFile.open(QIODevice::Text | QIODevice::ReadOnly)) {
        return;
    }
    QString data = ruleFile.readAll();
    ruleFile.close();

    for (int i = 0; i < devInfos.size(); i++) {
        QJsonObject infoItem = devInfos.at(i).toObject();
        QString partUuid = infoItem.value("uuid").toString();
        QString partMountPoint = infoItem.value("mountpoint").toString();
        if (!partMountPoint.compare("/")) {
            data.replace(partUuid, "/target");
        } else {
            data.replace(partUuid, partMountPoint);
        }
    }

    QFile ruleFileBack(destFilePath);
    if (!ruleFileBack.open(QIODevice::Text | QIODevice::WriteOnly)) {
        return;
    }
    ruleFileBack.write(data.toLocal8Bit());
    ruleFileBack.close();

    QFile::remove(srcFilePath);
}

void GhostUImg::subTimeSlot()
{
    if (0 != m_afterMakeSquashfs) {
        return;
    }

    int progress = m_progress;
    int remainSecond = updateTimeRemain(progress);
    if (AFTER_MAKE_SQUASHFS_PROGRESS == progress) {
        m_remainSecond = remainSecond;
    }

    // qWarning()<<"lp: "<<m_lastProgress<<", pro: "<<progress<<", rs: "<<remainSecond<<", m_rs: "<<m_remainSecond<<", m_lrs: "<<m_lastRemainSecond;
    if (m_lastProgress == progress) {
        remainSecond = ++m_lastRemainSecond;
    } else if (m_lastProgress < progress) {
        m_lastProgress = progress;
        m_remainSecond = remainSecond;
        m_lastRemainSecond = remainSecond;
    }

    Response rsp(0, progress, remainSecond, -1, m_curOperateType, "", "", m_curOperateID, "");
    QString rspStr = Utils::JsonToQString(rsp.marshal());
    // if (progress > 90) {
    //     qWarning()<<"subTimeSlot, progress = "<<progress<<", remainSecond = "<<remainSecond;
    // }

    Q_EMIT progressChanged(rspStr);
}

QStringList GhostUImg::getRsyncSystemBackupFilters()
{
    QStringList totalExcludes;

    const QString uosRecoveryConf = "/" + UOS_RECOVERY_INI;
    QStringList sysBackupUuidList = Utils::getAllSystemBackupUUID(uosRecoveryConf);
    if (sysBackupUuidList.isEmpty()) {
        qWarning()<<"getRsyncSystemBackupFilters sysBackupUuidList isEmpty";
        return totalExcludes;
    }

    const QString sysBackupFilterPrefix = "/backup/system/snapshots/*";
    for (auto &uuid : sysBackupUuidList) {
        QString mountPoint;
        if(!Device::getMountPointByUUID(uuid, mountPoint)) {
            continue;
        }

        if (mountPoint == "/") {
            totalExcludes << sysBackupFilterPrefix;
        } else {
            totalExcludes << (mountPoint + sysBackupFilterPrefix);
        }
    }

    return totalExcludes;
}


#ifdef QT_DEBUG
QStringList GhostUImg::getRsyncSystemBackupFiltersTest()
{
    return this->getRsyncSystemBackupFilters();
}
#endif

QStringList GhostUImg::getV20SystemBackupExcludes()
{
    QStringList excludes;
    QString backupPath = "/recovery/backup/savePath.json";
    auto &obj = Utils::readPathfromJsonFile(backupPath);
    for (const auto &key : obj.keys()) {
        const QJsonObject &subObj = obj[key].toObject();
        for (const auto &subKey : subObj.keys()) {
            QString backupFilePath = subObj[subKey].toString();
            if (backupFilePath.startsWith("/media/")) {
                continue;
            }

            QDir dir(backupFilePath);
            if (!dir.exists()) {
                continue;
            }
            excludes << backupFilePath + "/*";
        }
    }

    return excludes;
}
