//
// Created by 张雪明 <zhangxueming@uniontech.com> on 2023/10/18.
//
#include "WorkerThread.h"
#include <QDir>
#include <QFile>
#include <QSettings>
#include <QStorageInfo>
#include <QJsonDocument>
#include <QRegularExpression>
#include <QSaveFile>
#include <QDateTime>
#include <QDebug>
#include <udisks2-qt6/ddiskdevice.h>
#include <udisks2-qt6/dblockdevice.h>
#include <udisks2-qt6/ddiskmanager.h>
#include <udisks2-qt6/dblockpartition.h>
#include <DSysInfo>
#include "utils/Process.h"
#include "utils/Utils.h"

static const QMap<FsType, QString> FS_TYPE_MAP{
        { FsType::Empty, "" },
        { FsType::Btrfs, "btrfs" },
        { FsType::EFI, "efi" },
        { FsType::Ext2, "ext2" },
        { FsType::Ext3, "ext3" },
        { FsType::Ext4, "ext4" },
        { FsType::F2fs, "f2fs" },
        { FsType::Fat16, "fat16" },
        { FsType::Fat32, "fat32" },
        { FsType::VFat, "vfat" },
        { FsType::Hfs, "hfs" },
        { FsType::HfsPlus, "hfsplus" },
        { FsType::Jfs, "jfs" },
        { FsType::NTFS, "ntfs" },
        { FsType::Nilfs2, "nilfs2" },
        { FsType::LVM2PV, "lvm2 pv" },
        { FsType::Reiser4, "reiser4" },
        { FsType::Reiserfs, "reiserfs" },
        { FsType::Unknown, "unknown" },
        { FsType::Xfs, "xfs" },
        { FsType::Recovery, "recovery" }
};

enum class SwapType {
    Partition,
    File,
};

struct SwapItem {
    QString filename;
    SwapType type;
    qint64 size;
    qint64 used;
    int priority;
};

typedef QList<SwapItem> SwapItemList;

QString ReadFile(const QString& path) {
    QFile file(path);
    if (file.exists()) {
        if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
            qWarning() << "ReadFile() failed to open" << path;
            return "";
        }
        const QString &str = QString::fromUtf8(file.readAll());
        file.close();
        return str;
    } else {
        qWarning() << "ReadFileContent() file not found: " << path;
        return "";
    }
}
// Parse /proc/swaps file.
SwapItemList ParseSwaps() {
    SwapItemList result;
    const QString content(ReadFile("/proc/swaps"));

    for (const QString& line : content.split('\n')) {
        if ((!line.isEmpty()) && (!line.startsWith("Filename"))) {
            const QStringList parts(line.split(QRegularExpression("\\s+")));
            if (parts.length() == 5) {
                SwapItem item = {
                        parts.at(0),
                        parts.at(1) == "partition" ? SwapType::Partition : SwapType::File,
                        parts.at(2).toLongLong() * 1000,  // from kilobytes
                        parts.at(3).toLongLong() * 1000,  // from kilobytes
                        parts.at(4).toInt(),
                };
                result.append(item);
            }
        }
    }

    return result;
}

// Size units defined in bytes
// See https://en.wikipedia.org/wiki/Kibibyte
const qint64 kKibiByte = 1024;
const qint64 kMebiByte = kKibiByte * kKibiByte;
const qint64 kGibiByte = kMebiByte * kKibiByte;
const qint64 kTebiByte = kGibiByte * kKibiByte;
const qint64 kPebiByte = kTebiByte * kKibiByte;
const qint64 kExbiByte = kPebiByte * kKibiByte;

QString RegexpLabel(const QString& pattern, const QString& str) {
    QRegularExpression reg(pattern, QRegularExpression::MultilineOption);
    QRegularExpressionMatch match = reg.match(str);
    if (match.hasMatch()) {
        return match.captured(1);
    } else {
        return QString();
    }
}

qint64 ParseBtrfsUnit(const QString& value) {
    const float pref = RegexpLabel("^(\\d+\\.?\\d+)", value).toFloat();
    if (value.contains("KiB")) {
        return static_cast<qint64>(pref * kKibiByte);
    }
    if (value.contains("MiB")) {
        return static_cast<qint64>(pref * kMebiByte);
    }
    if (value.contains("GiB")) {
        return static_cast<qint64>(pref * kGibiByte);
    }
    if (value.contains("TiB")) {
        return static_cast<qint64>(pref * kTebiByte);
    }
    if (value.contains("PiB")) {
        return static_cast<qint64>(pref * kPebiByte);
    }
    return -1;
}

bool ReadBtrfsUsage(const QString& path, qint64& freespace, qint64& total) {
    QString output;
    QString err;
    if (!Process::spawnCmd("btrfs", {"filesystem", "show", path}, output, err)) {
        qCritical()<<"ReadBtrfsUsage err: "<<err<<", output: "<<output;
        return false;
    }

    QString total_str, used_str;
    for (const QString& line : output.split('\n')) {
        if (line.contains(path)) {
            total_str = RegexpLabel("size\\s*([^\\s]*)\\s", line);
        } else if (line.contains("Total devices")) {
            used_str = RegexpLabel("used\\s*([^\\s]*)", line);
        }
    }

    total = ParseBtrfsUnit(total_str);
    freespace = total - ParseBtrfsUnit(used_str);
    return (total > -1 && freespace > -1);
}


bool ReadExt2Usage(const QString& path, qint64& freespace, qint64& total) {
    QString output;
    QString err;
    if (!Process::spawnCmd("dumpe2fs", {"-h", path}, output, err)) {
        qCritical()<<"ReadExt2Usage failed to call dumpe2fs, output = "<<output<<", err = "<<err<<", path = "<<path;
        return false;
    }

    int block_size = 0;
    qint64 total_blocks = 0;
    qint64 free_blocks = 0;
    for (const QString& line : output.split('\n')) {
        if (line.contains("Block count:")) {
            const QString m = RegexpLabel("Block count:\\s+(\\d+)", line);
            if (!m.isEmpty()) {
                total_blocks = m.toLongLong();
            }
        } else if (line.contains("Free blocks:")) {
            const QString m = RegexpLabel("Free blocks:\\s+(\\d+)", line);
            if (!m.isEmpty()) {
                free_blocks = m.toLongLong();
            }
        } else if (line.contains("Block size:")) {
            const QString m = RegexpLabel("Block size:\\s+(\\d+)", line);
            if (!m.isEmpty()) {
                block_size = m.toInt();
            }
        }
    }

    freespace = block_size * free_blocks;
    total = block_size * total_blocks;
    return true;
}

bool ReadFat16Usage(const QString& path, qint64& freespace, qint64& total) {
    QString output, err;
    Process::spawnCmd("dosfsck", {"-n", "-v", path}, output, err);
    // NOTE(xushaohua): `dosfsck` returns 1 on success, so we check its error
    // message instead.
    if (!err.isEmpty()) {
        qWarning() << "dosfsck failed:" << err;
        return false;
    }

    int cluster_size = 0;
    qint64 start_byte = 0;
    qint64 total_clusters = 0;
    qint64 used_clusters = 0;

    for (const QString& line : output.split('\n')) {
        if (line.contains("bytes per cluster")) {
            cluster_size = line.trimmed().split(' ').at(0).trimmed().toInt();
        } else if (line.contains("Data area starts at")) {
            start_byte = line.split(' ').at(5).toLongLong();
        } else if (line.contains(path)) {
            const QStringList parts = line.split(' ').at(3).split('/');
            total_clusters = parts.at(1).toLongLong();
            used_clusters = parts.at(0).toLongLong();
        }
    }

    total = total_clusters * cluster_size;
    freespace = total - start_byte - used_clusters * cluster_size;
    return true;
}

bool ReadJfsUsage(const QString& path, qint64& freespace, qint64& total) {
    QString output;
    QString err;
    //const QString param(QString("echo dm | jfs_debugfs %1").arg(path));
    if (!Process::spawnSubCmd("jfs_debugfs", {path}, "echo", {"dm"},output, err)) {
        qCritical()<<"ReadJfsUsage failed to call jfs_debugfs, output = "<<output<<", err = "<<err<<", path = "<<path;
        return false;
    }

    int block_size = 0;
    qint64 total_blocks = 0;
    qint64 free_blocks = 0;
    for (const QString& line : output.split('\n')) {
        if (line.startsWith("Aggregate Block Size:")) {
            block_size = line.split(':').at(1).trimmed().toInt();
        } else if (line.contains("dn_mapsize:")) {
            const QString item = RegexpLabel("dn_mapsize:\\s*([^\\s]+)", line);
            total_blocks = item.toLongLong(nullptr, 16);
        } else if (line.contains("dn_nfree:")) {
            const QString item = RegexpLabel("dn_nfree:\\s*([^\\s]+)", line);
            free_blocks = item.toLongLong(nullptr, 16);
        }
    }

    if (free_blocks > 0 && total_blocks > 0 && block_size > 0) {
        freespace = free_blocks * block_size;
        total = total_blocks * block_size;
        return true;
    } else {
        return false;
    }
}

bool ReadLinuxSwapUsage(const QString& path, qint64& freespace, qint64& total) {
    const SwapItemList swap_items = ParseSwaps();
    // If this swap partition is used, read from /proc/swaps.
    for (const SwapItem& item : swap_items) {
        if (item.filename == path) {
            total = item.size;
            freespace = item.size - item.used;
            return true;
        }
    }

    // If it is not used, it is totally free.
    freespace = 0;
    total = 0;
    return true;
}

bool ReadNilfs2Usage(const QString& path, qint64& freespace, qint64& total) {
    QString output;
    QString err;
    if (!Process::spawnCmd("nilfs-tune", {"-l", path}, output, err)) {
        qCritical()<<"ReadNilfs2Usage failed to call nilfs-tune, output = "<<output<<", err = "<<err<<", path = "<<path;
        return false;
    }

    int block_size = 0;
    qint64 free_blocks = 0;
    for (const QString& line : output.split('\n')) {
        if (line.startsWith("Block size:")) {
            block_size = line.split(':').last().trimmed().toInt();
        } else if (line.startsWith("Device size:")) {
            total = line.split(':').last().trimmed().toLongLong();
        } else if (line.startsWith("Free blocks count:")) {
            free_blocks = line.split(':').last().trimmed().toLongLong();
        }
    }

    if (free_blocks > 0 && block_size > 0) {
        freespace = free_blocks * block_size;
        return true;
    } else {
        return false;
    }
}

bool ReadNTFSUsage(const QString& path, qint64& freespace, qint64& total) {
    QString output;
    QString err;
    if (!Process::spawnCmd("ntfsinfo", {"-mf", path}, output, err)) {
        qCritical()<<"ReadNTFSUsage failed to call ntfsinfo, output = "<<output<<", err = "<<err<<", path = "<<path;
        return false;
    }

    int cluster_size = 0;
    qint64 free_clusters = 0;
    qint64 total_clusters = 0;
    for (const QString& line : output.split('\n')) {
        if (line.contains("Cluster Size:")) {
            const QString m = RegexpLabel("Cluster Size:\\s+(\\d+)", line);
            if (!m.isEmpty()) {
                cluster_size = m.toInt();
            }
        } else if (line.contains("Volume Size in Clusters:")) {
            const QString m = RegexpLabel("Volume Size in Clusters:\\s+(\\d+)",
                                          line);
            if (!m.isEmpty()) {
                total_clusters = m.toLongLong();
            }
        } else if (line.contains("Free Clusters:")) {
            const QString m = RegexpLabel("Free Clusters:\\s+(\\d+)", line);
            if (!m.isEmpty()) {
                free_clusters = m.toLongLong();
            }
        }
    }

    if (free_clusters > 0 && total_clusters > 0 && cluster_size > 0) {
        freespace = cluster_size * free_clusters;
        total = cluster_size * total_clusters;
        return true;
    } else {
        return false;
    }
}

bool ReadReiser4Usage(const QString& path, qint64& freespace, qint64& total) {
    QString output;
    QString err;
    if (!Process::spawnCmd("debugfs.reiser4", {"--force", "--yes", path}, output, err)) {
        qCritical()<<"ReadReiser4Usage failed to call debugfs.reiser4, output = "<<output<<", err = "<<err<<", path = "<<path;
        return false;
    }

    int block_size = 0;
    qint64 free_blocks = 0;
    qint64 total_blocks = 0;
    for (const QString& line : output.split('\n')) {
        if (line.startsWith("blksize:")) {
            block_size = line.split(':').at(1).trimmed().toInt();
        } else if (line.startsWith("blocks:")) {
            total_blocks = line.split(':').at(1).trimmed().toLongLong();
        } else if (line.startsWith("free blocks:")) {
            free_blocks = line.split(':').at(1).trimmed().toLongLong();
        }
    }

    if (free_blocks > 0 && total_blocks > 0 && block_size > 0) {
        total = total_blocks * block_size;
        freespace = free_blocks * block_size;
        return true;
    } else {
        return false;
    }
}

bool ReadReiserfsUsage(const QString& path, qint64& freespace, qint64& total) {
    QString output;
    QString err;
    if (!Process::spawnCmd("debugreiserfs", {"-d", path}, output, err)) {
        qCritical()<<"failed to call debugreiserfs, output = "<<output<<", err = "<<err<<", path = "<<path;
        return false;
    }

    qint64 total_blocks = 0;
    qint64 free_blocks = 0;
    int block_size = 0;
    for (const QString& line : output.split('\n')) {
        if (line.startsWith("Count of blocks on the device:")) {
            total_blocks = line.split(':').last().trimmed().toLongLong();
        } else if (line.startsWith("Blocksize:")) {
            block_size = line.split(':').last().trimmed().toInt();
        } else if (line.startsWith("Free blocks (count of blocks")) {
            free_blocks = line.split(':').last().trimmed().toLongLong();
        }
    }

    if (free_blocks > 0 && total_blocks > 0 && block_size > 0) {
        freespace = free_blocks * block_size;
        total = total_blocks * block_size;
        return true;
    } else {
        return false;
    }
}

bool ReadXfsUsage(const QString& path, qint64& freespace, qint64& total) {
    QString output;
    QString err;
    if (!Process::spawnCmd("xfs_db", {"-c sb", "-c print", "-r", path}, output, err)) {
        qCritical()<<"ReadXfsUsage failed to call xfs_db, output = "<<output<<", err = "<<err<<", path = "<<path;
        return false;
    }

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

    int block_size = 0;
    qint64 total_blocks = 0;
    qint64 free_blocks = 0;
    for (const QString& line : output.split('\n')) {
        if (line.contains("fdblocks")) {
            free_blocks = line.split('=').last().trimmed().toLongLong();
        } else if (line.contains("dblocks")) {
            total_blocks = line.split('=').last().trimmed().toLongLong();
        } else if (line.contains("blocksize")) {
            block_size = line.split('=').last().trimmed().toInt();
        }
    }

    if (free_blocks > 0 && total_blocks > 0 && block_size > 0) {
        freespace = free_blocks * block_size;
        total = total_blocks * block_size;
        return true;
    } else {
        return false;
    }
}

// Unused file system.
const char kFsUnused[] = "unused";

QDebug& operator<<(QDebug& debug, const FsType& fs_type) {
    debug << GetFsTypeName(fs_type);
    return debug;
}

FsType GetFsTypeByName(const QString& name) {
    const QString lower = name.toLower();

    if (lower.isEmpty() || lower == kFsUnused) return FsType::Empty;
    if (lower.startsWith("linux-swap")) return FsType::LinuxSwap;

    return FS_TYPE_MAP.key(lower, FsType::Unknown);
}

QString GetFsTypeName(FsType fs_type) {
    switch (fs_type) {
        case FsType::LinuxSwap: {
            return "linux-swap";
        }
        default: {
            return FS_TYPE_MAP.value(fs_type);
        }
    }
}

/**
 * @brief 允许的文件系统类型
 */
const QStringList AllowFsTypes {
        "ext4",
        "xfs",
        "btrfs",
        "reiserfs"
};

WorkerThread::WorkerThread(ActionType actionType)
    : m_actionType(actionType)
{

}

WorkerThread::~WorkerThread()
{}

void WorkerThread::run()
{
    switch (m_actionType) {
        case ActionType::ManualBackup:
        case ActionType::SystemBackup: {
            V20Backup(m_backupReq);
            break;
        }
        case ActionType::ManualRestore:
        case ActionType::SystemRestore: {
            v20Restore(m_restoreReq);
            break;
        }
        case ActionType::CancelBackup:
        case ActionType::CancelRestore: {
            this->cancelBackupOrRestore20();
            break;
        }
        default:
            break;
    }
}

void WorkerThread::cancelBackupOrRestore20()
{
    QString liveFlagPath = "/live.flag";
    if (QFile::exists(liveFlagPath)) {
        if (!QFile::remove(liveFlagPath)) {
            qWarning()<<"cancelBackupOrRestore, remove liveFlagPath failed!";
            Q_EMIT error(static_cast<int> (ErrorType::DeleteFileError));
            return;
        }
    }

    //获取recovery分区的uuid
    const QString &recoveryPath{ "/etc/deepin/system-recovery.conf" };
    QSettings settings(recoveryPath, QSettings::IniFormat);
    const QString recoveryUUID{ settings.value("UUID").toString() };
    if (recoveryUUID.isEmpty()) {
        qCritical() << QObject::tr("Cannot open %1").arg(recoveryPath);
        Q_EMIT error(static_cast<int> (ErrorType::FileError));
        return;
    }

    const QStringList &devices = DDiskManager::blockDevices({});
    QString recoveryMountPoint = getMountPoint(devices, recoveryUUID);
    if (recoveryMountPoint.isEmpty()) {
        qCritical() << QObject::tr("Cannot find the recovery partition");
        Q_EMIT error(static_cast<int> (ErrorType::NotFoundRecoveryPartition));
    }

    QString recoveryJsonPath = QString("%1/backup/recovery.json").arg(recoveryMountPoint);
    if (QFile::exists(recoveryJsonPath)) {
        if (!QFile::remove(recoveryJsonPath)) {
            qWarning()<<"cancelBackupOrRestore, remove failed! recoveryJsonPath: "<<recoveryJsonPath;
            Q_EMIT error(static_cast<int> (ErrorType::DeleteFileError));
            return;
        }
    }

    qInfo()<<"cancelBackupOrRestore20 success";
    Q_EMIT success();
}

void WorkerThread::setBackupRequest(const V20BackupReq &request)
{
    m_backupReq = request;
}

void WorkerThread::setSystemRestoreRequest(const V20RestoreReq &request)
{
    m_restoreReq = request;
}

void WorkerThread::V20Backup(const V20BackupReq &req)
{
    ErrorType errCode = check(req.backupPath, static_cast<ActionType> (req.actionType), req.language, false, false);
    if (ErrorType::NoError != errCode) {
        Q_EMIT error(static_cast<int> (errCode));
    } else {
        Q_EMIT success();
    }
}

void WorkerThread::v20Restore(const V20RestoreReq &req)
{
    ErrorType errCode = check(req.backupDir, static_cast<ActionType> (req.actionType), req.language, false, req.formatData);
    if (ErrorType::NoError != errCode) {
        Q_EMIT error(static_cast<int> (errCode));
    } else {
        Q_EMIT success();
    }
}

ErrorType WorkerThread::check(const QString &backupPath, ActionType actionType, const QString &lang,
                              bool isAutoReboot, bool formatData)
{
    QString absolutePath = backupPath;
    QString coverBackupFilePath;
    // 解析路径如果是#结尾说明是增量备份文件
    if (absolutePath.endsWith('#')) {
        coverBackupFilePath = absolutePath.left(absolutePath.length() - 1);
        int index = coverBackupFilePath.lastIndexOf('/');
        absolutePath = coverBackupFilePath.left(index);
    }

    // 对路径进行特殊处理
    if (!absolutePath.startsWith('/')) {
        absolutePath = QString("%1/%2").arg(QDir::currentPath()).arg(absolutePath);
    }

    //去除末尾的"/"
    if (absolutePath.size() > 1) {
        while (absolutePath.endsWith('/')) {
            int index = absolutePath.lastIndexOf('/');
            absolutePath = absolutePath.left(index);
        }
    }
    qWarning()<<"absolutePath = "<<absolutePath;

    //获取recovery分区的uuid
    const QString &recoveryPath{ "/etc/deepin/system-recovery.conf" };
    QSettings settings(recoveryPath, QSettings::IniFormat);
    const QString recoveryUUID{ settings.value("UUID").toString() };
    if (recoveryUUID.isEmpty()) {
        qCritical() << QObject::tr("Cannot open %1").arg(recoveryPath);
        return ErrorType::FileError;
    }

    const QStringList &devices = DDiskManager::blockDevices({});
    QString recoveryMountPoint = getMountPoint(devices, recoveryUUID);
    if (recoveryMountPoint.isEmpty()) {
        qCritical() << QObject::tr("Cannot find the recovery partition");
        return ErrorType::NotFoundRecoveryPartition;
    }

    QString bootUUID = getUUID(QString("%1/backup/boot.info").arg(recoveryMountPoint));
    QString rootUUID = getUUID("/");
    QString dataUUID = getUUID(QString("%1/backup/_dde_data.info").arg(recoveryMountPoint));
    QString dataMountPoint = getMountPoint(devices, dataUUID);
    QString relativeUUID;
    QString relativePath;
    QString rootDiskDrive;
    QString dataDiskDrive;

    // 对relativeUUID和relativePath进行处理
    if (actionType == ActionType::ManualBackup || actionType == ActionType::SystemBackup || actionType == ActionType::ManualRestore) {
        if (backupPath.isEmpty() || absolutePath.isEmpty() || !QDir(absolutePath).exists()) {
            qCritical() << QObject::tr("Path not set or not exists!");
            return ErrorType::PathError;
        }
        // QString output;
        // Process::spawnCmd("lsblk", {"-o", "MOUNTPOINT", "-nlp"}, output);
        // QTextStream steam(&output);
        QString line;
        QStringList mountList;

        // while (steam.readLineInto(&line)) {
        //     if (!line.isEmpty()) {
        //         mountList << line;
        //     }
        // }

        QList<QStorageInfo> storeInfoList = QStorageInfo::mountedVolumes();
        for (auto &storeInfo : storeInfoList) {
            line = storeInfo.rootPath();
            if (!line.isEmpty()) {
                mountList << line;
            }
        }

        std::sort(mountList.begin(), mountList.end(), [=] (const QString& v1, const QString& v2) {
            return v1.length() > v2.length();
        });

        relativePath = absolutePath;
        // FIXME(justforlxz): 这里特殊处理一下，因为/home和/data/home是bind关系
        const QStringList &bindPaths {"/home", "/root", "/var", "/opt"};
        for (const QString &path : bindPaths) {
            if (relativePath.startsWith(path)) {
                // const QString &dataMountPoint = getMountPoint(dataUUID);
                relativePath = dataMountPoint + relativePath;
                break;
            }
        }

        for (const QString& mount : mountList) {
            if (QString(relativePath).startsWith(mount)) {
                relativeUUID = getUUID(mount);
                relativePath.remove(0, mount.length());
                break;
            }
        }
    }

    QString fsType;
    //备份时，对目标磁盘位置、文件系统和磁盘空间进行判断
    if (actionType == ActionType::ManualBackup || actionType == ActionType::SystemBackup) {
        QString mountPointCheck = checkMountPoint(absolutePath);
        if ("PathError" == mountPointCheck) {
            return ErrorType::PathError;
        }
        //对路径位置进行判断，全盘备份必须选择移动磁盘
        if (actionType == ActionType::ManualBackup && !absolutePath.startsWith("/media")) {
            qWarning() << QObject::tr("The storage location cannot be in the source disk.");
            return ErrorType::LocationError;
        }

        // 如果是系统备份，则不能选择根分区和boot分区
        if (actionType == ActionType::SystemBackup &&
            (mountPointCheck == "/" || mountPointCheck == "/boot" ||
            absolutePath.startsWith("/opt") || absolutePath.startsWith(dataMountPoint + "/opt") ||
            absolutePath.startsWith("/var") || absolutePath.startsWith(dataMountPoint + "/var"))) {
            qWarning() << QObject::tr("This directory cannot be used to store backup files, please select a different directory.");
            return ErrorType::BackupPathError;
        }

        if (this->isDeviceMountedReadOnly(absolutePath)) {
            qWarning() << QObject::tr("Cannot back up data to the read-only device");
            return ErrorType::DeviceReadOnly;
        }

        fsType = getAbsolutePathFsType(devices, absolutePath);
        // 如果文件类型不被支持则返回错误
        if (!AllowFsTypes.contains(fsType.simplified().toLower())) {
            qWarning() << QObject::tr("The file system is not supported for backup. Please select one in ext4, btrfs, xfs, reiserfs format.") << fsType;
            return ErrorType::FsError;
        }

        // get root disk drive
        for (const QString &path : devices) {
            QScopedPointer<DBlockDevice> device(DDiskManager::createBlockDevice(path));
            if (device->idUUID() == rootUUID) {
                rootDiskDrive = device->drive();
                break;
            }
        }

        //get data disk drive
        for (const QString &path : devices) {
            QScopedPointer<DBlockDevice> device(DDiskManager::createBlockDevice(path));
            if (device->idUUID() == dataUUID) {
                dataDiskDrive = device->drive();
                break;
            }
        }

        qint64 usage = 0;
        const qint64 reservedSize = 1.5 * 1024 * 1024 * 1024;
        //check disk usage
        if (actionType == ActionType::ManualBackup) {
            qint64 freeSpace = 0;
            qint64 total = 0;
            qWarning()<<"rootDiskDrive: "<<rootDiskDrive<<", dataDiskDrive: "<<dataDiskDrive;
            for (const QString &path : devices) {
                QScopedPointer<DBlockDevice> device(DDiskManager::createBlockDevice(path));
                // 过滤掉非系统盘和数据盘的而且文件系统不为空和swap的分区
                QString drive = device->drive();
                QString idType = device->idType();
                if (idType.isEmpty() || (drive != rootDiskDrive && drive != dataDiskDrive)) {
                    continue;
                }

                QString partitionPath = device->device().replace('\x00', "");
                if (device->idType() == "swap") {
                    QString arch = Utils::getKernelArch();
                    if (!arch.isEmpty()) {
                        if (0 != arch.compare("amd64", Qt::CaseInsensitive)) {
                            qint64 swapSize = 0;
                            if (Utils::getSwapPartitionSize(partitionPath, swapSize)) {
                                usage += swapSize;
                                qWarning()<<"ManualBackup, partitionPath = "<<partitionPath<<", size: "<<swapSize;
                            }
                        }
                    }
                    continue;
                }

                FsType type = GetFsTypeByName(device->idType());
                this->ReadUsage(partitionPath, type, freeSpace, total);
                qWarning()<<"ManualBackup, partitionPath = "<<partitionPath<<", freeSpace = "<<freeSpace<<", total = "<<total;
                usage += total - freeSpace;
            }
        }

        //check boot and system usage，and check opt and var useage
        if (actionType == ActionType::SystemBackup) {
            qint64 freeSpace = 0;
            qint64 total = 0;
            for (const QString &path : devices) {
                QScopedPointer<DBlockDevice> device(DDiskManager::createBlockDevice(path));
                if (device->idUUID() == bootUUID || device->idUUID() == rootUUID) {
                    QString partitionPath = device->device().replace('\x00', "");
                    FsType type = GetFsTypeByName(device->idType());
                    this->ReadUsage(partitionPath, type, freeSpace, total);
                    qWarning()<<"SystemBackup, partitionPath = "<<partitionPath<<", freeSpace = "<<freeSpace<<", total = "<<total;
                    usage += total - freeSpace;
                }
            }
            usage += getPathUsageSpace("/opt");
            usage += getPathUsageSpace("/var");
        }

        usage += reservedSize;

        qint64 abPathFreeSpace = getPathFreeSpace(absolutePath);
        qWarning()<<"SystemBackup, usage = "<<usage<<", abPathFreeSpace = "<<abPathFreeSpace;
        if (abPathFreeSpace < usage) {
            qWarning() << QObject::tr("A minimum of %2 bytes is required for backup. Free space available: %1.")
                    .arg(abPathFreeSpace).arg(usage);
            return ErrorType::SpaceError;
        }
    }

    //如果是自定义还原，则对还原文件进行校验
    if (actionType == ActionType::ManualRestore) {
        if (QDir(absolutePath).entryList({"*.dim"}).isEmpty()) {
            qWarning() << QObject::tr("Invalid path");
            return ErrorType::PathError;
        }
        for (const QString &file : QDir(absolutePath).entryList()) {
            if (!file.endsWith(".dim")) continue;
            if (!this->checkMd5Valid(QString("%1/%2").arg(absolutePath).arg(file))) {
                qWarning() << QObject::tr("The backup file is invalid.");
                return ErrorType::MD5Error;
            }
        }
    }
    //如果是系统还原，则对初始化备份文件进行校验
    if (actionType == ActionType::SystemRestore) {
        if (!this->checkMd5Valid("/recovery/backup/system.dim")) {
            qWarning() << QObject::tr("The initial backup file is invalid.");
            return ErrorType::MD5Error;
        }
    }

    QString curLang = lang;
    if (lang.isEmpty()) {
        curLang = QLocale::system().name();
    }
    const QDateTime& currentTime = QDateTime::currentDateTime();
    const QString& timeDirectory = currentTime.toString("yyyy-MM-dd-hh-mm-ss");
    const QString& type = [=]() -> QString {
        if (actionType == ActionType::SystemRestore) {
            return "SystemRestore";
        }

        if (actionType == ActionType::ManualRestore) {
            return "ManualRestore";
        }

        if (actionType == ActionType::ManualBackup) {
            return "ManualBackup";
        }

        if (actionType == ActionType::SystemBackup) {
            return "SystemBackup";
        }
        Q_UNREACHABLE();
    }();

    QJsonObject object{
            { "name", "restore" },
            { "message", "restore boot and root partition" },
            { "total", 7 },
            { "ask", true },
            { "locale", curLang },
            { "type", type },
            { "auto_reboot", isAutoReboot },
            { "tasks",
                    QJsonArray{
                            QJsonObject{ { "message", "starting restore boot partition" },
                                         { "enable", actionType == ActionType::SystemRestore && !bootUUID.isEmpty() },
                                         { "command", "restore-partitions" },
                                         { "args", QJsonArray{ QString("UUID:%1").arg(recoveryUUID),
                                                               "backup/boot.dim",
                                                               QString("UUID:%1").arg(bootUUID) } } },
                            QJsonObject{ { "message", "starting restore root partition" },
                                         { "enable", actionType == ActionType::SystemRestore },
                                         { "command", "restore-partitions" },
                                         { "args", QJsonArray{ QString("UUID:%1").arg(recoveryUUID),
                                                               "backup/system.dim",
                                                               QString("UUID:%1").arg(rootUUID) } } },
                            QJsonObject{ { "message", "starting backup root & boot partition" },
                                         { "enable", actionType == ActionType::SystemBackup },
                                         { "command", "create-backup-image" },
                                         { "args", QJsonArray{ QString("UUID:%1").arg(bootUUID),
                                                               QString("UUID:%1").arg(relativeUUID),
                                                               QString("%1/%2").arg(relativePath).arg(timeDirectory),
                                                               "boot.dim"
                                         } } },
                            QJsonObject{ { "message", "starting backup '/opt' and '/var' of data partition" },
                                         { "enable", actionType == ActionType::SystemBackup },
                                         { "command", "create-backup-directory" },
                                         { "args", QJsonArray{ QString("UUID:%1").arg(dataUUID),
                                                               QString("UUID:%1").arg(relativeUUID),
                                                               QString("%1/%2").arg(relativePath).arg(timeDirectory)
                                         } } },
                            QJsonObject{ { "message", "starting backup root partition" },
                                         { "enable", actionType == ActionType::ManualBackup || actionType == ActionType::SystemBackup},
                                         { "command", "create-backup-image" },
                                         { "args", QJsonArray{ QString("UUID:%1").arg(rootUUID),
                                                               QString("UUID:%1").arg(relativeUUID),
                                                               QString("%1/%2").arg(relativePath).arg(timeDirectory),
                                                               "system.dim"
                                         } } },
                            QJsonObject{ { "message", "starting backup data partition" },
                                         { "enable", actionType == ActionType::ManualBackup && rootDiskDrive != dataDiskDrive},
                                         { "command", "create-backup-image" },
                                         { "args", QJsonArray{ QString("UUID:%1").arg(dataUUID),
                                                               QString("UUID:%1").arg(relativeUUID),
                                                               QString("%1/%2").arg(relativePath).arg(timeDirectory),
                                                               "data.dim"
                                         } } },
                            QJsonObject{ { "message", "starting restore root partition" },
                                         { "enable", actionType == ActionType::ManualRestore },
                                         { "command", "restore-partitions" },
                                         { "args", QJsonArray{ QString("UUID:%1").arg(relativeUUID),
                                                               QString("%1").arg(relativePath)
                                         } } },
                            QJsonObject{ { "message", "formating data partition" },
                                         { "progress", false },
                                         { "enable", formatData },
                                         { "command", "refresh-data-partition" } }
                    }
            }
    };

    // 清理禁用的任务
    QJsonArray tasks = object["tasks"].toArray();
    for (auto it = tasks.begin(); it != tasks.end();) {
        QJsonObject obj{ it->toObject() };
        if (!obj["enable"].toBool()) {
            it = tasks.erase(it);
        } else {
            ++it;
        }
    }

    // 更新task的名字
    for (int i = 0; i != tasks.size(); ++i) {
        QJsonObject object = tasks[i].toObject();
        object["name"] = QString("task-%1").arg(i + 1);
        tasks.replace(i, object);
    }

    object["tasks"] = tasks;
    object["total"] = tasks.size();

    QJsonDocument doc;
    doc.setObject(object);

    QSaveFile file(QString("%1/backup/recovery.json").arg(recoveryMountPoint));
    file.open(QIODevice::WriteOnly | QIODevice::Text);
    file.write(doc.toJson());
    file.commit();

    if (actionType == ActionType::SystemBackup) {
        const QString &filePath = QString("%1/backup/savePath.json").arg(recoveryMountPoint);
        QJsonObject obj = Utils::readPathfromJsonFile(filePath);
        QJsonObject subjsonObj = obj[relativeUUID].toObject();
        const QString &createTime = QString::number(currentTime.toSecsSinceEpoch());

        do {
            if (coverBackupFilePath.isEmpty()) {
                break;
            }

            // Process::spawnCmd("rm", {"-rf", QString("%1").arg(coverBackupFilePath)});
            const QStringList &devices = DDiskManager::blockDevices({});
            QString mountPoint;
            for (const auto &path : devices) {
                QScopedPointer<DBlockDevice> device(DDiskManager::createBlockDevice(path));
                if (device->idUUID() != relativeUUID) {
                    continue;
                }
                device->setWatchChanges(true);
                mountPoint = device->mountPoints().first();
                break;
            }

            if (mountPoint.isEmpty()) {
                break;
            }

            for (const auto &subKey : subjsonObj.keys()) {
                const QString &path = mountPoint + subjsonObj[subKey].toString();
                //在同一块分区中
                if (path == coverBackupFilePath) {
                    // subjsonObj.remove(subKey);
                    break;
                }
            }
        }while (false);

        //如果realtiveUUID存在则更新,否则插入
        subjsonObj[createTime] = relativePath + "/" +timeDirectory;
        obj[relativeUUID] = subjsonObj;
        writePathToJsonFile(recoveryMountPoint, obj);
    }

    //给live系统调用，创建文件则表示重启进live系统
    QString liveFlag = this->getLiveFlagPath();
    Process::spawnCmd("touch", {"/live.flag"});
    qInfo() << QObject::tr("Configuration successful, please reboot your system");
    return ErrorType::NoError;
}

QString WorkerThread::getLiveFlagPath()
{
    int majorVer = Dtk::Core::DSysInfo::majorVersion().toInt();
    QString liveFlagPath = "/live.flag";
    QString newLiveFlagPath = "/.flag.d/live.flag";
    if (majorVer > 20) {
        return newLiveFlagPath;
    }

    if (majorVer == 20) {
        int minorVer = Dtk::Core::DSysInfo::minorVersion().toInt();
        if (minorVer > 1070) {
            return newLiveFlagPath;
        }

        if (minorVer == 1070) {
            const int ver1070u1BuildVer1 = 101; // 1070u1: 101.100
            QString buildVer = Dtk::Core::DSysInfo::buildVersion();
            int ver1 = buildVer.left(buildVer.indexOf(".")).toInt();
            if (ver1 > ver1070u1BuildVer1) {
                return newLiveFlagPath;
            }
        }
    }

    return liveFlagPath;
}

bool WorkerThread::isDeviceMountedReadOnly(const QString &path)
{
    //QString path = "/media/zxm/zxm  $$/a   $$ 8+888_";
    QStorageInfo storageInfo(path);
    QString displayName = storageInfo.displayName(); // mountPoint
    bool readOnly = storageInfo.isReadOnly();

    if (!readOnly) {
        // filearmor 域管场景, 域管将外接设备从ro 改成 rw 挂载，但是由filearmor来控制，没有提供接口判断
        QString uuid = QUuid::createUuid().toString();
        QString testWFile = path + "/.writeable_" + uuid;
        QFile tmp(testWFile);
        if (!tmp.open(QIODevice::Text | QIODevice::WriteOnly | QIODevice::Truncate)) {
            return true;
        }

        tmp.remove();
        tmp.close();
    }

    return readOnly;
}

bool WorkerThread::checkMd5Valid(const QString &path)
{
    QString output, error;
    Process::spawnCmd("deepin-clone", {"--dim-info", path}, output, error);

    return !error.contains("invalid file");
}

int WorkerThread::checkBackupV20(const V20BackupReq &request)
{



    return 0;
}

QString WorkerThread::getUUID(const QString &path)
{
    QFile file(path);
    if (file.exists() && file.open(QIODevice::ReadOnly | QIODevice::Text)) {
        return file.readAll().simplified();
    }

    const QStringList &devices = DDiskManager::blockDevices({});
    for (const QString &devicePath : devices) {
        QScopedPointer<DBlockDevice> device(DDiskManager::createBlockDevice(devicePath));
        device->setWatchChanges(true);
        for (const QString &mountPoint : device->mountPoints()) {
            if (mountPoint == path) {
                return device->idUUID();
            }
        }
    }

    return QString();
}

qint64 WorkerThread::getPathFreeSpace(const QString &path)
{
    return QStorageInfo (path).bytesAvailable();
}

qint64 WorkerThread::getPathUsageSpace(const QString &path)
{
    QString output;
    QString err;
    if (!Process::spawnCmd("du", {"--max-depth=0", path}, output, err)) {
        qCritical()<<"getPathUsageSpace failed to call du, output = "<<output<<", err = "<<err<<", path = "<<path;
        return 0;
    }

    QTextStream steam(&output);
    QString line;
    while (steam.readLineInto(&line)) {
        const QStringList& list = line.split(' ');
        if (list.last() == path) {
            return list.first().toUInt();
        }
    }

    return 0;
}

QString WorkerThread::getMountPoint(const QStringList &devices, const QString &uuid)
{
    QString mountPoint;
    for (const QString &path : devices) {
        QScopedPointer<DBlockDevice> device(DDiskManager::createBlockDevice(path));
        if (device->idUUID() == uuid) {
            device->setWatchChanges(true);
            if (!device->mountPoints().isEmpty()) {
                mountPoint = device->mountPoints().first();
            } else {
                mountPoint = device->mount({});
            }
            break;
        }
    }

    return mountPoint;
}

QString WorkerThread::getAbsolutePathFsType(const QStringList &devices, const QString &path)
{
    for (const QString &devPath : devices) {
        QScopedPointer<DBlockDevice> device(DDiskManager::createBlockDevice(devPath));
        device->setWatchChanges(true);
        for (const QString &mountPoint : device->mountPoints()) {
            //挂载点肯定不能为根分区和boot分区
            if (mountPoint == "/" || mountPoint == "/boot") {
                continue;
            }

            if (path.startsWith(mountPoint)) {
                return device->idType();
            }
        }
    }

    return QString();
}

QString WorkerThread::checkMountPoint(const QString &path)
{
    if (path.isEmpty()) {
        return "Error";
    }

    // QString path1 = "/media/zxm/zxm  $$/a   $$ 8+888_";
    QStorageInfo storageInfo(path);
    QString displayName = storageInfo.displayName();
    qInfo()<<"checkMountPoint, displayName = "<<displayName<<", path = "<<path;
    if (displayName.isEmpty()) { // not exist?
        return "PathError";
    }

    // return displayName;
    QString output;
    Process::spawnCmd("df", {path}, output);
    QTextStream stream(&output);
    QString line;
    while (stream.readLineInto(&line)) {
        line = line.simplified();
        if (line.startsWith("/dev")) {
            return line.split(' ').last();
        }
    }

    return "Error";
}

int WorkerThread::writePathToJsonFile(const QString &mountPoint, const QJsonObject &obj)
{
    QFile file(QString("%1/backup/savePath.json").arg(mountPoint));
    if (!file.open(QIODevice::ReadWrite)) {
        qWarning() << "writePathToJsonFile: file open failed!";
        return -1;
    }

    QJsonDocument jsonDoc;
    jsonDoc.setObject(obj);
    // Indented:表示自动添加/n回车符
    file.write(jsonDoc.toJson(QJsonDocument::Indented));
    file.close();

    return 0;
}

bool WorkerThread::ReadUsage(const QString& partition_path, FsType fs_type, qint64& freespace, qint64& total)
{
    bool ok = false;
    switch (fs_type) {
        case FsType::Btrfs: {
            ok = ReadBtrfsUsage(partition_path, freespace, total);
            break;
        }
        case FsType::Ext2:
        case FsType::Ext3:
        case FsType::Ext4: {
            ok = ReadExt2Usage(partition_path, freespace, total);
            break;
        }
        case FsType::EFI:
        case FsType::Fat16:
        case FsType::Fat32:
        case FsType::VFat: {
            ok = ReadFat16Usage(partition_path, freespace, total);
            break;
        }
        case FsType::Hfs:
        case FsType::HfsPlus: {
            break;
        }
        case FsType::Jfs: {
            ok = ReadJfsUsage(partition_path, freespace, total);
            break;
        }
        case FsType::LinuxSwap: {
            ok = ReadLinuxSwapUsage(partition_path, freespace, total);
            break;
        }
        case FsType::Nilfs2: {
            ok = ReadNilfs2Usage(partition_path, freespace, total);
            break;
        }
        case FsType::NTFS: {
            ok = ReadNTFSUsage(partition_path, freespace, total);
            break;
        }
        case FsType::Reiser4: {
            ok = ReadReiser4Usage(partition_path, freespace, total);
            break;
        }
        case FsType::Reiserfs: {
            ok = ReadReiserfsUsage(partition_path, freespace, total);
            break;
        }
        case FsType::Xfs: {
            ok = ReadXfsUsage(partition_path, freespace, total);
            break;
        }
        default: {
            ok = false;
            break;
        }
    }

    if (!ok) {
        freespace = -1;
        total = -1;
        qWarning() << "Failed to read usage:" << partition_path;
    }
    return ok;
}