//
// Created by uos on 2022/2/16.
//

#include "Device.h"
#include "utils/Utils.h"
#include "utils/global.h"
#include "Process.h"
#include "FsTab.h"
#include <QDebug>
#include <QRegularExpression>
#include <QDir>
#include <QFileInfo>
#include <QJsonParseError>
#include <QJsonObject>
#include <QJsonArray>
#include <QJsonValue>
#include <QUuid>
#include <QSet>
#include <QStorageInfo>

DeviceInfoList Device::m_allDevice;

Device::Device()
{

}

Device::Device(const QString &devNameOrUUID)
{
    m_pDevice = findDevice(devNameOrUUID);
}

Device::~Device()
{

}

QString Device::getLsblkByOutput()
{
    QStringList args;
    args << "--bytes" << "--pairs" << "--output";
    args << "NAME,KNAME,LABEL,UUID,TYPE,FSTYPE,SIZE,FSSIZE,FSUSED,FSAVAIL,LOG-SEC,PHY-SEC,MOUNTPOINT,MODEL,RO,"
            "HOTPLUG,MAJ:MIN,PARTLABEL,PARTUUID,PKNAME,PATH,VENDOR,SERIAL,REV,ROTA";
    QString out;
    Process::spawnCmd("lsblk", args, out);

    return out;
}

DeviceInfoList Device::getDeviceInfoByLsblkOut(const QString &lsblkOut)
{
    static bool isImmutableSys = Utils::isImmutableSystem();
    QMap<QString, QString> diskNameMap;
    DeviceInfoList allDeviceInfo;
    QRegularExpression regExp(R"lit(NAME="(.*)" KNAME="(.*)" LABEL="(.*)" UUID="(.*)" TYPE="(.*)" FSTYPE="(.*)" SIZE="(.*)" FSSIZE="(.*)" FSUSED="(.*)" FSAVAIL="(.*)" LOG-SEC="(.*)" PHY-SEC="(.*)" MOUNTPOINT="(.*)" MODEL="(.*)" RO="([0-9]+)" HOTPLUG="([0-9]+)" MAJ[_:]MIN="([0-9:]+)" PARTLABEL="(.*)" PARTUUID="(.*)" PKNAME="(.*)" PATH="(.*)" VENDOR="(.*)" SERIAL="(.*)" REV="(.*)" ROTA="(.*)")lit");

#if QT_VERSION >= QT_VERSION_CHECK(5,15,0)
    QStringList lsblkOutList = lsblkOut.split("\n", Qt::SkipEmptyParts);
#else
    QStringList lsblkOutList = lsblkOut.split("\n", QString::SkipEmptyParts);
#endif

    for (QString &line : lsblkOutList) {
        QRegularExpressionMatch regExpMatch = regExp.match(line);
        if (!regExpMatch.hasMatch()) {
            continue;
        }

        //qInfo() << line;
        bool ok = false;
        int pos = 1;
        DeviceInfoPtr device(new DeviceInfo);
        device->name = regExpMatch.captured(pos++).trimmed();
        device->kname = regExpMatch.captured(pos++).trimmed();
        device->label = regExpMatch.captured(pos++).trimmed();
        device->uuid = regExpMatch.captured(pos++).trimmed();
        device->type = regExpMatch.captured(pos++).trimmed();
        device->fsType = regExpMatch.captured(pos++).trimmed().toLower();
        device->fsType = (device->fsType == "crypto_luks") ? "luks" : device->fsType;
        device->fsType = (device->fsType == "lvm2_member") ? "lvm2" : device->fsType;
        device->sizeBytes = regExpMatch.captured(pos++).trimmed().toLongLong();
        device->fsSizeBytes = regExpMatch.captured(pos++).trimmed().toLongLong();
        QString usedSize = regExpMatch.captured(pos++).trimmed();
        if (!usedSize.isEmpty()) {
            device->usedBytes = usedSize.toULongLong();
        }

        QString availableSize = regExpMatch.captured(pos++).trimmed();
        if (!availableSize.isEmpty()) {
            device->availableBytes = availableSize.toULongLong();
        }

        QString logSectorSize = regExpMatch.captured(pos++).trimmed();
        if (!logSectorSize.isEmpty()) {
            device->logSectorSize = logSectorSize.toULongLong();
        }

        QString phySectorSize = regExpMatch.captured(pos++).trimmed();
        if (!phySectorSize.isEmpty()) {
            device->phySectorSize = phySectorSize.toULongLong();
        }

        QString mountPoint = regExpMatch.captured(pos++).trimmed();
        if (isImmutableSys) {
            if ("/sysroot" == mountPoint) {
                mountPoint = "/";
            } else if (mountPoint.startsWith("/var/media/")) {
                mountPoint = mountPoint.right(mountPoint.length() - 4);
            } else if (mountPoint.startsWith("/run/media/")) {
                mountPoint = mountPoint.right(mountPoint.length() - 4);
            }
        }
        device->mountPoint = mountPoint;
        device->mode = regExpMatch.captured(pos++).trimmed();
        device->readOnly = (regExpMatch.captured(pos++).trimmed() == "1");
        device->removable = (regExpMatch.captured(pos++).trimmed() == "1");
        device->majMin = regExpMatch.captured(pos++).trimmed();
        int index = device->majMin.indexOf(":");
        device->major = device->majMin.left(index).toInt(&ok);
        if (!ok) {
            device->major = -1;
        }

        device->minor = device->majMin.right(device->majMin.length() - index - 1).toInt(&ok);
        if (!ok) {
            device->minor = -1;
        }
        // partlabel可能以空格开始或结束,不要trimmed()
        device->partLabel = regExpMatch.captured(pos++);
        device->partuuid = regExpMatch.captured(pos++).trimmed();
        device->pkname = regExpMatch.captured(pos++).trimmed();
        device->path = regExpMatch.captured(pos++).trimmed();
        device->vendor = regExpMatch.captured(pos++).trimmed();
        device->serial = regExpMatch.captured(pos++).trimmed();
        device->revision = regExpMatch.captured(pos++).trimmed();
        device->rota = regExpMatch.captured(pos++).trimmed().toInt(&ok);
        if (!ok) {
            device->rota = -1;
        }
        device->deviceName = QString("/dev/%1").arg(device->kname);
        if (!device->uuid.isEmpty()) {
            device->deviceByUUID = QString("/dev/disk/by-uuid/%1").arg(device->uuid);
        }
        if (!device->label.isEmpty()) {
            device->deviceByLabel = QString("/dev/disk/by-label/%1").arg(device->label);
        }
        if (!device->partuuid.isEmpty()) {
            device->deviceByPartUUID = QString("/dev/disk/by-partuuid/%1").arg(device->partuuid);
        }
        if (!device->partLabel.isEmpty()) {
            device->deviceByPartLabel = QString("/dev/disk/by-partlabel/%1").arg(device->partLabel);
        }

        if ("disk" == device->type) {
            //QString cmd = QString("smartctl -i %1 | grep Model").arg(device->deviceName);
            QString out;
            QString err;
            if (Process::spawnCmd("smartctl", {"-i", device->deviceName}, out, err)) {
#if QT_VERSION >= QT_VERSION_CHECK(5,15,0)
                QStringList outList = out.split("\n", Qt::SkipEmptyParts);
#else
                QStringList outList = out.split("\n", QString::SkipEmptyParts);
#endif
                for (QString &line : outList) {
                    if (line.contains("Device Model:") || line.contains("Model Number:")) {
                        QString modelName = line.right(line.length() - line.lastIndexOf(":") - 1);
                        device->diskName = modelName.trimmed();
                        diskNameMap[device->kname] = device->diskName;
                    }
                }
            } else {
                device->diskName = device->mode;
                diskNameMap[device->kname] = device->diskName;
            }
        } else {
            if (diskNameMap.contains(device->pkname)) {
                device->diskName = diskNameMap[device->pkname];
            }
        }

        allDeviceInfo.append(device);
    }

    return allDeviceInfo;
}

DeviceInfoList Device::getOrganizedDiskList(DeviceInfoList &diskList, DeviceInfoList &cryptDevList,
    DeviceInfoList &noCryptDevList, DeviceInfoList &partitionList, DeviceInfoList& lvmList,
    DeviceInfoList &fileCryptDevList, DeviceInfoList &dmDevList)
{
    DeviceInfoList &fullFamilyDiskList = diskList;
    for (auto &diskDev : fullFamilyDiskList) {
        for (auto &cryptDev : cryptDevList) {
            if (!(cryptDev->type == "part" && cryptDev->fsType == "luks" && cryptDev->pkname == diskDev->name)) {
                continue;
            }
            cryptDev->parent = diskDev;

            for (auto &fileCryptDev : fileCryptDevList) {
                if (!(fileCryptDev->type == "crypt" && fileCryptDev->fsType != "lvm2" && fileCryptDev->pkname == cryptDev->kname)) {
                    continue;
                }
                fileCryptDev->parent = cryptDev;
                cryptDev->children.append(fileCryptDev);
            }

            for (auto &lvm2Dev : cryptDevList) {
                if (!(lvm2Dev->type == "crypt" && lvm2Dev->fsType == "lvm2" && lvm2Dev->pkname == cryptDev->kname)) {
                    continue;
                }
                lvm2Dev->parent = cryptDev;

                for (auto &lvmMember : lvmList) {
                    if (lvmMember->pkname == lvm2Dev->kname) {
                        lvmMember->parent = lvm2Dev;
                        lvm2Dev->children.append(lvmMember);
                    }
                }

                if (lvm2Dev->children.size() > 1) {
                    std::sort(lvm2Dev->children.begin(), lvm2Dev->children.end(), deviceInfoChildrenCompare);
                }
                cryptDev->children.append(lvm2Dev);
            }
            diskDev->children.append(cryptDev);
        }

        for (auto &noCryptDev : noCryptDevList) {
            if (noCryptDev->pkname != diskDev->name) {
                continue;
            }
            noCryptDev->parent = diskDev;

            for (auto &lvmMember : lvmList) {
                if (lvmMember->pkname == noCryptDev->kname) {
                    lvmMember->parent = noCryptDev;
                    noCryptDev->children.append(lvmMember);
                }
            }
            if (noCryptDev->children.size() > 1) {
                std::sort(noCryptDev->children.begin(), noCryptDev->children.end(), deviceInfoChildrenCompare);
            }
            diskDev->children.append(noCryptDev);
        }

        for (auto &partitionDev : partitionList) {
            for (auto &dmDev : dmDevList) {
                if (dmDev->pkname == partitionDev->name) {
                    dmDev->parent = partitionDev;
                    partitionDev->children.append(dmDev);
                    qWarning()<<"find partidion child: dmName: "<<dmDev->name<<", partion: "<<partitionDev->name;
                }
            }
            if (partitionDev->pkname == diskDev->name) {
                partitionDev->parent = diskDev;
                diskDev->children.append(partitionDev);
            }
        }
        if (diskDev->children.size() > 1) {
            std::sort(diskDev->children.begin(), diskDev->children.end(), deviceInfoChildrenCompare);
        }
    }

    return fullFamilyDiskList;
}

bool deviceInfoChildrenCompare(const DeviceInfoPtr left, const DeviceInfoPtr right)
{
    if (nullptr == left || nullptr == right) {
        return false;
    }

    if (left->major == right->major && left->minor < right->minor) {
        return true;
    }

    return false;
}

DeviceInfoList Device::getAllDeviceByLsblk()
{
    QString lsblkOut = Device::getLsblkByOutput();
    DeviceInfoList allDeviceInfo = Device::getDeviceInfoByLsblkOut(lsblkOut);

    //lvm 从/dev/mapper/  中获取映射名
    QDir mapperDir("/dev/mapper/");
    for (auto file : mapperDir.entryInfoList()) {
        if (file.fileName() == "control") {
            continue;
        }

        if (file.isSymLink()) {
            QString mapperDevice = file.symLinkTarget();
            //qInfo() << "mapperDevice=" << mapperDevice;
            for (auto dev : allDeviceInfo) {
                if (dev->deviceName == mapperDevice) {
                    dev->mappedName = file.fileName();
                    dev->symlink = file.absoluteFilePath();
                    //qInfo() << "mapperdName" << dev->mappedName;
                    //qInfo() << "symlink" << dev->symlink;
                }
            }
        }
    }

    Device::findChildByDmsetup(allDeviceInfo);

    return allDeviceInfo;
}

QStringList Device::getSysDevicesByFstab(const QString &fstabFile)
{
    static FSTabInfoList fstabList = FSTab::getFSTabFromFile(fstabFile);
    static QStringList sysDeviceUuidList = FSTab::getUuidListFromFstab(fstabList);

    DeviceInfoList diskList;
    DeviceInfoList cryptDevList;
    DeviceInfoList noCryptDevList;
    DeviceInfoList partitionList;
    DeviceInfoList lvmList;
    DeviceInfoList fileCryptDevList;
    DeviceInfoList dmDevList;
    Device::getAllTypeDevice(diskList, cryptDevList, noCryptDevList, partitionList, lvmList, fileCryptDevList, dmDevList);

    QStringList sysDeviceList;
    for (auto &diskDev : diskList) {
        for (auto &cryptDev : cryptDevList) {
            if (!(cryptDev->type == "part" && cryptDev->fsType == "luks" && cryptDev->pkname == diskDev->name)) {
                continue;
            }

            for (auto &lvm2Dev : cryptDevList) {
                if (!(lvm2Dev->type == "crypt" && lvm2Dev->fsType == "lvm2" && lvm2Dev->pkname == cryptDev->kname)) {
                    continue;
                }

                for (auto &lvmMember : lvmList) {
                    if (lvmMember->pkname == lvm2Dev->kname) {
                        if (sysDeviceUuidList.contains(lvmMember->uuid) && !sysDeviceList.contains(diskDev->name)) {
                            sysDeviceList.append(diskDev->name);
                            break;
                        }
                    }
                }
            }
        }

        for (auto &noCryptDev : noCryptDevList) {
            if (noCryptDev->pkname != diskDev->name) {
                continue;
            }

            for (auto &lvmMember : lvmList) {
                if (lvmMember->pkname == noCryptDev->kname) {
                    if (sysDeviceUuidList.contains(lvmMember->uuid) && !sysDeviceList.contains(diskDev->name)) {
                        sysDeviceList.append(diskDev->name);
                        break;
                    }
                }
            }
        }

        for (auto &partitionDev : partitionList) {
            if (partitionDev->pkname == diskDev->name) {
                if (sysDeviceUuidList.contains(partitionDev->uuid) && !sysDeviceList.contains(diskDev->name)) {
                    sysDeviceList.append(diskDev->name);
                }
            }
        }
    }

    sysDeviceList.removeDuplicates();

    return sysDeviceList;
}

DeviceInfoList Device::getDeviceByLsblk()
{
    m_allDevice.clear();
    m_allDevice = Device::getAllDeviceByLsblk();
    return m_allDevice;
}

bool Device::exportPartitionInfoByLsblk(const QString &filepath, const QStringList &deviceList, QString &err)
{
    // 使用lsblk获取信息，从json解析分区信息
    QJsonArray deviceArray;
    QStringList args;
    args<<"-JOb";
    if (!Utils::getLsblkJsonReturn(args, deviceArray, err)) {
        qWarning()<<"exportPartitionInfoByLsblk, getLsblkJsonReturn err : "<<err;
        return false;
    }

    if (Utils::filterDevice(deviceArray) == 0) {
        qWarning()<<"exportPartitionInfoByLsblk, filterDevice failed";
        return false;
    }

    err = "";
    QMap<SVGInfo, QList<SLVMInfo>> lvmInfos;
//    QMap<QString, SVGInfo> vgNames;
//    QMap<QString, SLVMInfo> lvNames;
//    if (!Device::getVGNamesInfo(vgNames, err)) {
//        err = "exportPartitionInfoByLsblk getVGnames failed vgNames is empty !";
//        return false;
//    }

    DeviceInfoList diskList;
    DeviceInfoList cryptDevList;
    DeviceInfoList noCryptDevList;
    DeviceInfoList partitionList;
    DeviceInfoList lvmList;
    DeviceInfoList fileCryptDevList;
    DeviceInfoList dmDevList;
    Device::getAllTypeDevice(diskList, cryptDevList, noCryptDevList, partitionList, lvmList, fileCryptDevList, dmDevList);

    QMap<QString, QString> crypt2Partition;  // </dev/mapper/luks_crypt0, /dev/sda3>
    for (auto &crypt : cryptDevList) {
        for (auto &luks : cryptDevList) {
            if (luks->pkname == crypt->kname) {
                crypt2Partition[luks->path] = crypt->path;
                break;
            }
        }
    }

    QStringList vgNameList;
    if (!Device::getVGNames(vgNameList)) {
        qCritical()<<"exportPartitionInfoByLsblk getVGNames failed";
        return false;
    }

    QMap<QString, QString> pvVgMap;
    if (!Device::getPvDisplay(pvVgMap)) {
        qCritical()<<"exportPartitionInfoByLsblk getPvDisplay failed";
        return false;
    }

    QString fstabFile = "/etc/fstab";
    QStringList sysDevList = Device::getSysDevicesByFstab(fstabFile);

    QMap<QString, SVGInfo> vgNames;
    for (const QString &vg : vgNameList) {
        for (const QString &pv : pvVgMap.keys()) {
            if (vg != pvVgMap[pv]) {
                continue;
            }
            if (pv.startsWith("/dev/mapper/luks_crypt")) {
                SVGInfo &vgInfo = vgNames[vg];
                vgInfo.name = vg;
                vgInfo.pvIds.append(crypt2Partition[pv]);
                continue;
            }
            // deal with no encrypt partition
            for (const QString &sysDev : sysDevList) {
                if (pv.startsWith("/dev/" + sysDev)) {
                    SVGInfo &vgInfo = vgNames[vg];
                    vgInfo.name = vg;
                    vgInfo.pvIds.append(pv);
                    break;
                }
            }
        }
    }

    QMap<QString, SLVMInfo> lvNames;
    if (!cryptDevList.isEmpty()) {
        for (const QString &vg : vgNameList) {
            for (auto lvmPartition : lvmList) {
                QString vgName = lvmPartition->name.left(lvmPartition->name.indexOf("-"));
                if (vg == vgName) {
                    SVGInfo &vgInfo = vgNames[vg];
                    vgInfo.name = vg;
                    vgInfo.size += lvmPartition->sizeBytes / MiB;

                    SLVMInfo &lvmInfo = lvNames[lvmPartition->name];
                    lvmInfo.label = lvmPartition->label;
                    if (lvmInfo.label.isEmpty()) {
                        if ("swap" == lvmPartition->fsType) {
                            lvmInfo.label = "SWAP"; // 分区label是空的会导致调用安装器分区工具失败
                        }
                    }
                    lvmInfo.size = lvmPartition->sizeBytes / MiB;
                    lvmInfo.mountPoint = lvmPartition->mountPoint;
                    lvmInfo.filesystem = lvmPartition->fsType;
                    lvmInfo.usage = true;
                    lvmInfo.vgName = vg;
                }
            }
        }
    }

    QMap<QString, QList<SPartitionInfo>> devicesInfos;
    for (int i = 0; i < deviceArray.size(); i++) {
        // 初始化SDeviceInfo、SPartitionInfo列表
        QJsonObject deviceObj = deviceArray.at(i).toObject();
        QString devicePath = deviceObj.value("path").toString();
        QList<SPartitionInfo> partInfoItems;
        QJsonArray devicePartArray = deviceObj.value("children").toArray();
        for (int j = 0; j < devicePartArray.size(); j++ ) {
            QJsonObject devicePartObj = devicePartArray.at(j).toObject();
            SPartitionInfo partInfoItem = Device::newPartitionByJObj(devicePath, devicePartObj);
            partInfoItem.device = devicePath;
            partInfoItem.deviceSize = deviceObj.value("size").toVariant().toLongLong() / MiB;

            // 更新vgnames和lvnames
            Device::updateVGNames(devicePartObj, vgNames, lvNames);
            Device::updatePartIsMergeVG(vgNames, partInfoItem);

            partInfoItems.append(partInfoItem);
        }

        // 使用fdisk命令获取分区的起止位置信息
        if (!Device::updatePartStartEndPoint(devicePath, partInfoItems)) {
            err = "exportPartitionInfoByLsblk updatePartStartEndPoint failed !";
            qWarning()<<"exportPartitionInfoByLsblk, updatePartStartEndPoint failed";
            return false;
        }

        if (!Device::updatePartType(devicePath, partInfoItems)) {
            err = "exportPartitionInfoByLsblk updatePartType failed !";
            qWarning()<<"exportPartitionInfoByLsblk, updatePartType failed";
            return false;
        }

        // 根据分区起始位置做一次排序处理
        devicesInfos.insert(devicePath, partInfoItems);
    }

    // 根据vgnames和lvnames更新m_lvmInfos
    Device::updateLvmInfos(devicesInfos, vgNames, lvNames, lvmInfos);

    if (deviceList.size() == 0) {
        // 根据fstab将当前系统用到的设备信息选取出来
        Device::updateDeviceInfoMapByFstab(devicesInfos);
    } else {
        for (QString deviceName : devicesInfos.keys()) {
            if (deviceList.indexOf(deviceName) == -1) {
                devicesInfos.remove(deviceName);
            }
        }
    }

    // 将分区信息导出到partition_policy.json文件
    Device::writeDeviceInfo(filepath, devicesInfos, lvmInfos);
    return true;
}

void Device::calculateDiskSpace()
{
    QStringList args = {"-T", "-B1"};
    QString out;
    if (Process::spawnCmd("df", args, out)) {

        /*  sample output
            文件系统       类型            1B-块         已用         可用 已用% 挂载点
            udev           devtmpfs   8074424320            0   8074424320    0% /dev
            tmpfs          tmpfs      1623896064      3158016   1620738048    1% /run
            /dev/nvme0n1p5 btrfs    147065929728 122901000192  23372861440   85% /
            tmpfs          tmpfs      8119472128    238022656   7881449472    3% /dev/shm
            tmpfs          tmpfs         5242880         4096      5238784    1% /run/lock
            tmpfs          tmpfs      8119472128            0   8119472128    0% /sys/fs/cgroup
            /dev/nvme0n1p3 ext4       1551745024    131887104   1322553344   10% /boot
            tmpfs          tmpfs      1623891968        40960   1623851008    1% /run/user/1000
            /dev/sda3      fuseblk  355953799168 224915378176 131038420992   64% /media/uos/本地磁盘
            /dev/sda1      fuseblk  322123595776  56340246528 265783349248   18% /media/uos/0BD418FA279A9BA3
            /dev/sda2      fuseblk  322123591680  20298776576 301824815104    7% /media/uos/8B78E6D0A657E529
            /dev/nvme0n1p1 fuseblk  106680532992  32996925440  73683607552   31% /media/uos/44BA472DBA471B36
         * */
        auto lines = out.split("\n");
        int linuNum = 0;
        for (const auto& line : lines) {
        //    qInfo() << line;
            if (++linuNum == 1 || line.trimmed().length() == 0) {
                continue;
            }
#if QT_VERSION >= QT_VERSION_CHECK(5,15,0)
            auto cols = line.split(" ", Qt::SkipEmptyParts);
#else
            auto cols = line.split(" ", QString::SkipEmptyParts);
#endif
            if (cols.size() != 7) {
                continue;
            }
            auto deviceInfo = findDevice(cols[0].trimmed());
            if (deviceInfo.isNull()) {
                continue;
            }
            int i = 1;
            for (auto col : cols) {
                switch (i++) {
                    case 1:
                    case 2:
                        break;
                    case 3:
                        deviceInfo->fsSizeBytes = col.trimmed().toLongLong();
                        break;
                    case 4:
                        deviceInfo->usedBytes = col.trimmed().toLongLong();
                        break;
                    case 5:
                        deviceInfo->availableBytes = col.trimmed().toLongLong();
                        break;
                    case 6:
                        deviceInfo->usedPercent = col.trimmed();
                        break;
                    case 7:
                        break;
                }

            }

        }
    }
}

bool Device::mount(const QString &mountPoint)
{
    if (m_pDevice.isNull() || m_pDevice->type != "part") {
        return false;
    }

    if (!m_pDevice->mountPoint.isEmpty() && m_pDevice->mountPoint != mountPoint) {
        return false;
    }
    QString out, err;
    bool ret = Process::spawnCmd("mount",
                      QStringList() << m_pDevice->deviceName << mountPoint,
                      out, err);
    if (ret) {
        m_pDevice->mountPoint = mountPoint;
    }
    return ret;
}

bool Device::umount()
{
    if (m_pDevice.isNull() || m_pDevice->type != "part") {
        return false;
    }
    if (m_pDevice->mountPoint.isEmpty()) {
        return true;
    }
    QString out, err;
    bool ret = Process::spawnCmd("umount",
                                 QStringList() << m_pDevice->mountPoint,
                                 out, err);
    if (ret) {
        m_pDevice->mountPoint.clear();
    }
    return ret;
}

void Device::getDeviceByName(const QString &name)
{
    auto deviceInfo = findDevice(name);
    if (!deviceInfo.isNull()) {
        m_pDevice = deviceInfo;
    }

}

void Device::getDeviceByUUID(const QString &uuid)
{
    auto deviceInfo = findDevice(uuid);
    if (!deviceInfo.isNull()) {
        m_pDevice = deviceInfo;
    }
}


void Device::findChildByDmsetup(DeviceInfoList &deviceList)
{
    QString out;
    QStringList args = {"deps", "-o", "blkdevname"};
    if (Process::spawnCmd("dmsetup", args, out)) {
        for (auto line : out.split("\n")) {
            //qInfo() << line;
            if (line.trimmed().length() == 0) {
                continue;
            }
            QRegularExpression regExp(R"lit(([^:]*)\:.*\((.*)\))lit");
            QRegularExpressionMatch match = regExp.match(line);
            if (!match.hasMatch()) {
                continue;
            }
            //qInfo() << "regExp.cap(1)" << regExp.cap(1).trimmed();
            //qInfo() << "regExp.cap(2)" << regExp.cap(2).trimmed();
            QString childName = match.captured(1).trimmed();
            QString parentName = match.captured(2).trimmed();
            DeviceInfoPtr parent;
            auto parentIter = std::find_if(m_allDevice.begin(), m_allDevice.end(), [=](DeviceInfoPtr devItem){ return devItem->kname == parentName; });
            if (parentIter != m_allDevice.end()) {
                parent = (*parentIter);
            }

            DeviceInfoPtr child;
            auto childIter = std::find_if(m_allDevice.begin(), m_allDevice.end(), [=](DeviceInfoPtr devItem){ return devItem->mappedName == childName; });
            if (childIter != m_allDevice.end()) {
                child = (*childIter);
            }

            if (!parent.isNull() && !child.isNull()) {
                child->pkname = parent->kname;
            //    qInfo() << "child.kname=" << child->kname;
            //   qInfo() << "child.pkname=" << parent->kname;
            }
        }
    }
}

DeviceInfoPtr Device::findDevice(const QString &devAilas)
{
    for (auto dev : m_allDevice) {
        if (dev->deviceName.startsWith("/dev/loop")) {
            continue; // dim 转换异常场景下，会导致判断root uuid错误，识别到loop里的uuid
        }

        if (dev->fsSizeBytes == 0 && !dev->fsType.isEmpty()) {
            continue;
        }

        if (dev->deviceName.startsWith("/dev/dm-")) {
            QDir mapperDir("/dev/mapper/");
            for (auto file : mapperDir.entryInfoList()) {
                if (file.fileName() == "control") {
                    continue;
                }

                if (file.isSymLink()) {
                    QString fullName = file.path() + "/" + file.fileName();
                    QString mapperDevice = file.symLinkTarget();
                    if ((dev->deviceName == mapperDevice) && (devAilas == fullName)) {
                        return dev;
                    }
                }
            }
        }

        if (dev->deviceName == devAilas) {
            return dev;
        } else if (dev->name == devAilas) {
            return dev;
        } else if (dev->kname == devAilas) {
            return dev;
        } else if (dev->uuid == devAilas) {
            return dev;
        } else if (dev->label == devAilas) {
            return dev;
        } else if (dev->partuuid == devAilas) {
            return dev;
        } else if (dev->partLabel == devAilas) {
            return dev;
        } else if (dev->deviceByUUID == devAilas) {
            return dev;
        } else if (dev->deviceByLabel == devAilas) {
            return dev;
        } else if (dev->deviceByPartLabel == devAilas) {
            return dev;
        } else if (dev->deviceByPartUUID == devAilas) {
            return dev;
        } else if (dev->mappedName == devAilas) {
            return dev;
        } else if (dev->mountPoint == devAilas) {
            return dev;
        }

    }
    return DeviceInfoPtr();
}

QString Device::size()
{
    if (m_pDevice->sizeBytes < GiB) {
        return QString().asprintf("%.1f MB", m_pDevice->sizeBytes / MiB);
    } else if (m_pDevice->sizeBytes > 0) {
        return QString().asprintf("%.1f GB", m_pDevice->sizeBytes / GiB);
    }
    return QString();
}

QString Device::used()
{
    return (m_pDevice->usedBytes == 0) ? "" : QString().asprintf("%.1f GB", m_pDevice->usedBytes / GiB);
}

QString Device::free()
{
    return (m_pDevice->availableBytes == 0) ? "" : QString().asprintf("%.1f GB", m_pDevice->availableBytes / GiB);
}

DeviceInfoPtr Device::getDeviceInfo()
{
    return m_pDevice;
}

bool Device::isLVM(const QStringList &sysUUIDList, bool &isEncrypted, bool &isFileMgrEncrypted, DeviceInfoList &fileCryptDevList)
{
    bool islvm = false;
    isEncrypted = false;
    isFileMgrEncrypted = false;
    DeviceInfoList diskList;
    DeviceInfoList cryptDevList;
    DeviceInfoList noCryptDevList;
    DeviceInfoList partitionList;
    DeviceInfoList lvmList;
    DeviceInfoList dmDevList;
    fileCryptDevList.clear();
    Device::getAllTypeDevice(diskList, cryptDevList, noCryptDevList, partitionList, lvmList,
                             fileCryptDevList, dmDevList);
    DeviceInfoList orgDevList = Device::getOrganizedDiskList(diskList, cryptDevList, noCryptDevList, partitionList,
                                                             lvmList, fileCryptDevList, dmDevList);

    QSet<QString> checkUUIDSet;
    for (auto &lvm : lvmList) {
        if (sysUUIDList.contains(lvm->uuid)) {
            checkUUIDSet.insert(lvm->uuid);
            islvm = true;
            if (!isEncrypted && nullptr != lvm->parent) {
                if ("crypt" == lvm->parent->type && "lvm2" == lvm->parent->fsType) {
                    isEncrypted = true;
                }
            }
        }
    }

    for (auto &fileCryptDev : fileCryptDevList) {
        if (sysUUIDList.contains(fileCryptDev->uuid)) {
            isFileMgrEncrypted = true;
            break;
        }
    }

    if (!cryptDevList.isEmpty() && !isFileMgrEncrypted) {
        int sysUUIDSize = sysUUIDList.size();
        for (auto &dev : partitionList) {
            if ("part" == dev->type) {
                if (sysUUIDList.contains(dev->uuid)) {
                    checkUUIDSet.insert(dev->uuid);
                }
            }
        }

        if (!checkUUIDSet.isEmpty() && checkUUIDSet.size() < sysUUIDSize) {
            isFileMgrEncrypted = true;
        }
    }

    if (isFileMgrEncrypted) {
        isEncrypted = true;
    }

    return islvm;
}

bool Device::isLinuxFilesystem(const DeviceInfoPtr &dev)
{
    QStringList fstype = {"ext2", "ext3", "ext4", "f2fs", "reiserfs", "reiser4",
                          "xfs", "jfs", "zfs", "zfs_member", "btrfs", "lvm", "lvm2_member",
                          "luks", "crypt", "crypto_luks"};

    return fstype.contains(dev->fsType);
}

bool Device::isFsTypeSupported(const QString &fsType)
{
    // 系统备份支持的文件系统类型
    const QStringList fstype = {"ext4", "btrfs", "xfs", "reiserfs"};

    return fstype.contains(fsType);
}

bool Device::isFsTypeSupportedDataBackup(const QString &fsType)
{
    // 数据备份支持的文件系统类型
    const QStringList fstype = {"ext4", "btrfs", "xfs", "reiserfs"};

    return fstype.contains(fsType);
}

bool Device::isFsTypeSupportedGhost(const QString &fsType)
{
    const QStringList type = {"ext4", "btrfs", "xfs"};

    return type.contains(fsType);
}

bool Device::isDisk(const DeviceInfoPtr &dev)
{
    if (dev->type == "disk" && !dev->mode.isEmpty()) {
        return true;
    }
    return false;
}

SPartitionInfo Device::newPartitionByJObj(const QString &deviceName, const QJsonObject &partitionInfoObj)
{
    SPartitionInfo partInfoItem;
    partInfoItem.size           = partitionInfoObj.value("size").toVariant().toLongLong() / MiB;
    partInfoItem.id             = QUuid::createUuid().toString();
    partInfoItem.label          = partitionInfoObj.value("label").toString();
    partInfoItem.usage          = true;
    partInfoItem.partitionType  = EPartitionType::PartitionTypePrimary;
    partInfoItem.device         = deviceName;
    partInfoItem.index          = partitionInfoObj.value("path").toString().replace(deviceName, "").replace(QRegularExpression("[A-Za-z]"), "").toInt();
    partInfoItem.path           = partitionInfoObj.value("path").toString();
    partInfoItem.filesystem     = partitionInfoObj.value("fstype").toString();
    partInfoItem.mountPoint     = partitionInfoObj.value("mountpoint").toString();
    partInfoItem.startPoint     = 0;
    partInfoItem.endPoint       = 0;
    partInfoItem.sectorSize     = partitionInfoObj.value("log-sec").toVariant().toLongLong();
    partInfoItem.phySectorSize  = partitionInfoObj.value("phy-sec").toVariant().toLongLong();
    partInfoItem.isStartPoint   = true;
    partInfoItem.deviceSize     = 0;
    partInfoItem.isEFIPartition = false;
    partInfoItem.isMergeVG      = false;
    return partInfoItem;
}

bool Device::updatePartStartEndPoint(const QString &deviceInfo, QList<SPartitionInfo> &partitionInfos)
{
    qInfo()<<"updatePartStartEndPoint, deviceInfo: "<<deviceInfo;
    QString cmd = QString("fdisk -l -o Device,Start,End,Type | grep %1").arg(deviceInfo);
    QString out;
    QString err;
    QStringList partInfoStringList;
    QStringList args;
    args <<"-l"<<"-o"<<"Device,Start,End,Type";
    if (!Process::spawnCmd("fdisk", args, out, err)) {
        qWarning() << "fdisk -l failed, err: "<<err;
        return false;
    }

#if QT_VERSION >= QT_VERSION_CHECK(5,15,0)
    QStringList outList = out.split("\n", Qt::SkipEmptyParts);
#else
    QStringList outList = out.split("\n", QString::SkipEmptyParts);
#endif
    for (QString &line : outList) {
        if (line.contains(deviceInfo)) {
            partInfoStringList<<line;
        }
    }

    // 更新SPartitionInfo列表中的信息
    for (int i = 0; i < partitionInfos.size(); i++) {
        for (QString partInfoItem : partInfoStringList) {
            if (partInfoItem.contains(partitionInfos.at(i).path)) {
#if QT_VERSION >= QT_VERSION_CHECK(5,15,0)
                QStringList partInfoListItem = partInfoItem.split(" ", Qt::SkipEmptyParts);
#else
                QStringList partInfoListItem = partInfoItem.split(" ", QString::SkipEmptyParts);
#endif
                partInfoItem = partInfoListItem.join(" ");

                int pathIndex = partInfoItem.indexOf(" ");
                int startPointIndex = partInfoItem.indexOf(" ", pathIndex + 1);
                if (startPointIndex != -1) {
                    bool toLongLongOK = false;
                    QString startPointStr = partInfoItem.left(startPointIndex);
                    startPointStr = startPointStr.right(startPointStr.length() - pathIndex - 1);
                    partitionInfos[i].startPoint = startPointStr.toLongLong(&toLongLongOK);
                    if (!toLongLongOK) {
                        qWarning() << QString("set startPoint failed: %1 to toLongLong failed.").arg(startPointStr);
                    }

                    int endPointIndex = partInfoItem.indexOf(" ", startPointIndex + startPointStr.length() + 1);
                    if (endPointIndex != -1) {
                        QString endPointStr = partInfoItem.left(endPointIndex);
                        endPointStr = endPointStr.right(endPointStr.length() - startPointIndex - 1);
                        partitionInfos[i].endPoint = endPointStr.toLongLong(&toLongLongOK);
                        if (!toLongLongOK) {
                            qWarning() << QString("set endPoint failed: %1 to toLongLong failed.").arg(endPointStr);
                        }
                    }
                }

                // 设置是否是EFI分区
                if (partInfoItem.contains("EFI")) {
                    partitionInfos[i].isEFIPartition = true;
                }

                break;
            }
        }
    }

    return true;
}

void Device::updateLvmInfos(QMap<QString, QList<SPartitionInfo>> devs, QMap<QString, SVGInfo> &vgNames, QMap<QString, SLVMInfo> &lvNames, QMap<SVGInfo, QList<SLVMInfo>> &lvmInfos)
{
    // 更新每个vg下的lvm分区信息，和空闲空间信息
    for (QString vgName : vgNames.keys()) {
        qint64 lvTotalSize = 0;
        vgNames[vgName].id = QUuid::createUuid().toString();
        // 更新vg的pvids信息
        QMap<QString, QList<SPartitionInfo>>::iterator deviceIter;
        for (deviceIter = devs.begin(); deviceIter != devs.end(); ++deviceIter) {
            for (SPartitionInfo partInfo : deviceIter.value()) {
                if (vgNames[vgName].pvIds.indexOf(partInfo.path) != -1) {
                    vgNames[vgName].pvIds.removeOne(partInfo.path);
                    vgNames[vgName].pvIds.append(partInfo.id);
                }
            }
        }

        // 找到vg对应的lvm分区信息
        for (QString lvName : lvNames.keys()) {
            if (lvName.contains(vgName)) {
                lvNames[lvName].id     = QUuid::createUuid().toString();
                lvNames[lvName].label  = lvNames[lvName].label.replace(QString("%1-").arg(vgName), "");
                lvNames[lvName].vgName = vgName;
                lvTotalSize += lvNames[lvName].size;
                lvmInfos[vgNames[vgName]].append(lvNames[lvName]);
            }
        }

        // 获取vg中的pe的size，将其过滤掉不作为空闲空间使用
        qint64 peSize = 1;
        QString err;
        if (!getLVMPESize(vgName, peSize, err)) {
            peSize = 1;
        }

        // 添加vg空闲空间信息, 空间大小以M为单位
        qint64 vgFreeSize = vgNames[vgName].size - lvTotalSize;
        if (vgFreeSize > peSize) {
            SLVMInfo freeSpaceInfo;
            freeSpaceInfo.id         = QUuid::createUuid().toString();
            freeSpaceInfo.size       = vgFreeSize;
            freeSpaceInfo.label      = "";
            freeSpaceInfo.usage      = false;
            freeSpaceInfo.vgName     = vgName;
            freeSpaceInfo.filesystem = "";
            freeSpaceInfo.mountPoint = "";
            lvmInfos[vgNames[vgName]].append(freeSpaceInfo);
        }
    }
}

bool Device::updateDeviceInfoMapByFstab(QMap<QString, QList<SPartitionInfo>> &devicesInfos)
{
    QString fstabFile = "/etc/fstab";
    static QStringList sysDevices = Device::getSysDevicesByFstab(fstabFile);

    for (QString deviceName : devicesInfos.keys()) {
        QString devName = deviceName;
        if (sysDevices.indexOf(devName.replace("/dev/", "")) == -1) {
            devicesInfos.remove(deviceName);
        }
    }

    return true;
}

bool Device::getVGNamesInfo(QMap<QString, SVGInfo> &vgNames, QString &err)
{
    return false;
}

void Device::updatePartIsMergeVG(QMap<QString, SVGInfo> &vgNames, SPartitionInfo &partitionInfo)
{
    for (QString vgNameItem : vgNames.keys()) {
        if (vgNames[vgNameItem].pvIds.contains(partitionInfo.path)) {
            partitionInfo.isMergeVG = true;
            break;
        }
    }
}

void Device::updateVGNames(const QJsonObject &partInfoObj, QMap<QString, SVGInfo> &vgNames, QMap<QString, SLVMInfo> &lvNames)
{
    if (partInfoObj.value("fstype").toString().compare("LVM2_member")) {
        return;
    }

    for (QString vgName : vgNames.keys()) {
        if (vgNames[vgName].pvIds.indexOf(partInfoObj.value("path").toString()) != -1) {
            vgNames[vgName].size += partInfoObj.value("size").toVariant().toLongLong() / MiB;
        }
    }

    // 添加lvm分区信息到lvnames
    QJsonArray lvmPartArray = partInfoObj.value("children").toArray();
    for (int h = 0; h < lvmPartArray.size(); h++) {
        QJsonObject lvmPartObj = lvmPartArray.at(h).toObject();
        QString lvmPath = lvmPartObj.value("name").toString();
        lvNames[lvmPath].label      = lvmPath;
        lvNames[lvmPath].filesystem = lvmPartObj.value("fstype").toString();
        lvNames[lvmPath].mountPoint = lvmPartObj.value("mountpoint").toString();
        lvNames[lvmPath].size       = lvmPartObj.value("size").toVariant().toLongLong() / MiB;
        lvNames[lvmPath].usage      = true;
    }
}

bool Device::getLVMPESize(const QString &vgName, qint64 &peSize, QString &err)
{
    peSize = 0;
    QString out;
    if (!Process::spawnCmd("vgdisplay", {vgName}, out, err)) {
        qWarning()<<"getLVMPESize failed, err: "<<err;
        return false;
    }

#if QT_VERSION >= QT_VERSION_CHECK(5,15,0)
    QStringList outList = out.split("\n", Qt::SkipEmptyParts);
#else
    QStringList outList = out.split("\n", QString::SkipEmptyParts);
#endif

    for (QString line : outList) {
        if (!(line.contains("PE") && line.contains("Size"))) {
            continue;
        }

        line = line.trimmed();
        if (!line.startsWith("PE ")) {
            continue;
        }
        QRegularExpression rx("(\\d+\\.\\d+|(\\d+))");
        QRegularExpressionMatch match = rx.match(line);
        if (match.hasMatch()) {
            peSize = qint64(match.captured(0).toDouble());
        } else {
            peSize = 0;
        }
        break;
    }

    qInfo() << "vgName: " << vgName << "  PE Size:" << peSize;
    return true;
}

bool Device::updatePartType(const QString &deviceInfo, QList<SPartitionInfo> &partitionInfos)
{
    QString devicePartInfoString = "";
    QString cmd = QString("parted %1 print | grep -E \"primary|extended|logical\"").arg(deviceInfo);

    QString out;
    QString err;
    if (!Process::spawnCmd("parted", {deviceInfo, "print"}, out, err)) {
        qWarning() << "parted failed, deviceInfo: "<<deviceInfo<<", err: "<<err;
        return false;
    }

#if QT_VERSION >= QT_VERSION_CHECK(5,15,0)
    QStringList outList = out.split("\n", Qt::SkipEmptyParts);
#else
    QStringList outList = out.split("\n", QString::SkipEmptyParts);
#endif
    QStringList partInfoStringList;
    for (QString &line : outList) {
        if (line.contains("primary") || line.contains("extended") || line.contains("logical")) {
            partInfoStringList << line.trimmed();
        }
    }

    // 将解析的分区type信息设置到SPartitionInfo列表中
    for (int i = 0; i < partitionInfos.size(); i++) {
        for (QString partInfoItem : partInfoStringList) {
#if QT_VERSION >= QT_VERSION_CHECK(5,15,0)
            QStringList partInfoListItem = partInfoItem.split(" ", Qt::SkipEmptyParts);
#else
            QStringList partInfoListItem = partInfoItem.split(" ", QString::SkipEmptyParts);
#endif
            partInfoItem = partInfoListItem.join(" ");

            int pathIndex = partInfoItem.indexOf(" ");
            QString partIndexStr = partInfoItem.left(pathIndex);
            bool toIntOK = false;
            int partIndex = partIndexStr.toInt(&toIntOK);
            if (toIntOK && (partIndex == partitionInfos.at(i).index)) {
                if (partInfoItem.contains("primary")) {
                    partitionInfos[i].partitionType = EPartitionType::PartitionTypePrimary;
                } else if (partInfoItem.contains("extended")) {
                    partitionInfos[i].partitionType = EPartitionType::PartitionTypeExtension;
                    // 计算扩展分区的大小
                    qint64 oneMSectors =  1 * MiB / partitionInfos[i].sectorSize; //CommonFunc::mToSectors(1, partitionInfos[i].sectorSize);
                    qint64 subSectors = partitionInfos.at(i).endPoint - partitionInfos.at(i).startPoint - 1;
                    partitionInfos[i].size = subSectors / oneMSectors;
                } else if (partInfoItem.contains("logical")) {
                    partitionInfos[i].partitionType = EPartitionType::PartitionTypeLogical;
                }
            }
        }
    }

    return true;
}

bool Device::writeDeviceInfo(const QString &filepath, const QMap<QString, QList<SPartitionInfo>> &devicesInfos, QMap<SVGInfo, QList<SLVMInfo>> &lvmInfos)
{
    QJsonArray partitionOperate;
    for (QString deviceKey : devicesInfos.keys()) {
        for (SPartitionInfo partInfo : devicesInfos.value(deviceKey)) {
            QJsonObject partitionItem;
            partitionItem.insert("id", partInfo.id);
            partitionItem.insert("type", "partition");
            partitionItem.insert("operate", "new");
            partitionItem.insert("device", deviceKey);
            if (!partInfo.filesystem.compare("LVM2_member")) {
                partitionItem.insert("filesystem", "lvm2 pv");
            } else if (!partInfo.filesystem.compare("crypto_LUKS")) {
                partitionItem.insert("filesystem", "crypto_luks");
            } else {
                partitionItem.insert("filesystem", partInfo.filesystem);
            }
            partitionItem.insert("mountPoint", partInfo.mountPoint);
            partitionItem.insert("label", partInfo.label);
            partitionItem.insert("startPoint", partInfo.startPoint);
            partitionItem.insert("endPoint", partInfo.endPoint);
            partitionItem.insert("sectorSize", partInfo.sectorSize);
            partitionItem.insert("phySectorSize", partInfo.phySectorSize);
            partitionItem.insert("deviceSize", partInfo.deviceSize);
            partitionItem.insert("isstartpoint", true);
            partitionItem.insert("size", partInfo.size);
            partitionItem.insert("index", -1);
            partitionItem.insert("isneedformat", true);
            switch (partInfo.partitionType) {
                case EPartitionType::PartitionTypeLogical: {
                    partitionItem.insert("partType", "logical");
                }
                break;
                case EPartitionType::PartitionTypeExtension: {
                    partitionItem.insert("partType", "extended");
                }
                break;
                case EPartitionType::PartitionTypePrimary: {
                    partitionItem.insert("partType", "primary");
                }
                break;
                default: {
                    partitionItem.insert("partType", "primary");
                }
                break;
            }
            partitionOperate.append(partitionItem);
        }
    }

    // 保存lvm分区信息
    for (SVGInfo vgInfo : lvmInfos.keys()) {
        QJsonObject vgPartItem;
        vgPartItem.insert("id", vgInfo.id);
        vgPartItem.insert("name", vgInfo.name);
        vgPartItem.insert("operate", "new");
        QJsonArray pvs;
        for (QString pvId : vgInfo.pvIds) {
            pvs.append(pvId);
        }
        vgPartItem.insert("pv", pvs);
        vgPartItem.insert("size", vgInfo.size);
        vgPartItem.insert("type", "VG");
        partitionOperate.append(vgPartItem);

        for (SLVMInfo lvInfo : lvmInfos.value(vgInfo)) {
            // 要过滤掉空闲空间
            if (!lvInfo.usage) {
                continue;
            }
            QJsonObject lvPartItem;
            lvPartItem.insert("filesystem", lvInfo.filesystem);
            lvPartItem.insert("id", lvInfo.id);
            lvPartItem.insert("label", lvInfo.label);
            lvPartItem.insert("mountPoint", lvInfo.mountPoint);
            lvPartItem.insert("operate", "new");
            lvPartItem.insert("size", lvInfo.size);
            lvPartItem.insert("type", "LVM");
            lvPartItem.insert("vg", lvInfo.vgName);
            lvPartItem.insert("isneedformat", true);
            partitionOperate.append(lvPartItem);
        }
    }

    QJsonDocument devicePartInfo(partitionOperate);
    QFile partPolicyInfoJson(filepath);
    if (!partPolicyInfoJson.open(QFile::WriteOnly)) {
        return false;
    }

    partPolicyInfoJson.write(devicePartInfo.toJson());
    partPolicyInfoJson.close();

    return true;
}

int deviceCompare (const DevicePtr left, const DevicePtr right)
{
    if (nullptr == left->getDeviceInfo() || nullptr == right->getDeviceInfo()) {
        return 0;
    }

    if (left->getDeviceInfo()->availableBytes < right->getDeviceInfo()->availableBytes) {
        return 1;
    }

    return -1;
}

bool Device::getMountPointByUUID(const QString &uuid, QString &mountPointPath)
{
    bool retCode = false;
    QStringList args;
    args <<"--output"<<"UUID,PATH";
    QString out;
    if (!Process::spawnCmd("lsblk", args, out)) {
        qInfo()<<Q_FUNC_INFO<<", spawnCmd failed, args = "<<args<<", out = "<<out;
        return retCode;
    }

#if QT_VERSION >= QT_VERSION_CHECK(5,15,0)
    QStringList outList = out.split("\n", Qt::SkipEmptyParts);
#else
    QStringList outList = out.split("\n", QString::SkipEmptyParts);
#endif
    QString devPath;
    for (QString line : outList) {
        if (!line.contains(uuid)) {
            continue;
        }
        line = line.trimmed();
        int index = line.indexOf("/");
        devPath = line.right(line.length() - index);
        break;
    }

    static bool isImmutableSys = Utils::isImmutableSystem();
    if (!devPath.isEmpty()) {
        QList<QStorageInfo> storeInfoList = QStorageInfo::mountedVolumes();
        for (auto &storeInfo : storeInfoList) {
            if (devPath == storeInfo.device()) {
                mountPointPath = storeInfo.rootPath();
                if (isImmutableSys) {
                    if (mountPointPath.startsWith("/var/media/")) {
                        mountPointPath = mountPointPath.right(mountPointPath.length() - 4);
                    } else if (mountPointPath.startsWith("/run/media/")) {
                        mountPointPath = mountPointPath.right(mountPointPath.length() - 4);
                    }
                }
                retCode = true;
                break;
            }
        }
    }

    return retCode;
}

bool Device::getAllMediaMountPoint(QList<QString> &mountPointList)
{
    bool retCode = false;
    static bool isImmutableSys = Utils::isImmutableSystem();
    mountPointList.clear();
    QList<QStorageInfo> storeInfoList = QStorageInfo::mountedVolumes();
    for (auto &storeInfo : storeInfoList) {
        if (storeInfo.isValid()) {
            QString mountPoint = storeInfo.rootPath();
            if (isImmutableSys) {
                if (mountPoint.startsWith("/var/media/")) {
                    mountPoint = mountPoint.right(mountPoint.length() - 4);
                } else if (mountPoint.startsWith("/run/media/")) {
                    mountPoint = mountPoint.right(mountPoint.length() - 4);
                }
            }
            if (!mountPoint.startsWith("/media/")) {
                continue;
            }
            mountPointList.push_back(mountPoint);
            retCode = true;
        }
    }

    return retCode;
}

bool Device::getUUIDByPartitionPath(const QString &path, QString &uuid)
{
    uuid.clear();
    QString error;
    QString out;
    QStringList args;
    args <<"--output"<<"UUID,PATH";
    if (!Process::spawnCmd("lsblk", args, out, error)) {
        qInfo()<<Q_FUNC_INFO<<"getUUIDByPartitionPath failed, args = "<<args<<", error = "<<error;
        return false;
    }

#if QT_VERSION >= QT_VERSION_CHECK(5,15,0)
    QStringList outList = out.split("\n", Qt::SkipEmptyParts);
#else
    QStringList outList = out.split("\n", QString::SkipEmptyParts);
#endif
    QString devPath;
    for (QString &line : outList) {
        if (!line.contains(path)) {
            continue;
        }
        line = line.trimmed();
        int index = line.indexOf("/");
        devPath = line.right(line.length() - index);
        if (path == devPath) {
            uuid = line.left(index).trimmed();
            break;
        }
    }

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

    return true;
}

bool Device::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;
}

int deviceInfoCompareMore (const DeviceInfoPtr left, const DeviceInfoPtr right)
{
    return right->availableBytes < left->availableBytes;
}

int deviceInfoTotalSizeCompare(const DeviceInfoPtr &left, const DeviceInfoPtr &right)
{
    return left->sizeBytes < right->sizeBytes;
}

bool Device::findSuitablePartition(quint64 dimTotalBytes, QString &partitionMountPoint, quint64 thresholdBytes,
                                   bool removable)
{
    partitionMountPoint = "";
    QString innerSpacePath = ""; // 内置系统磁盘分区，带空格的路径
    QString innerMediaNoSpacePath = ""; // 内置非系统磁盘，分区路径不带空格
    QString innerMediaSpacePath = ""; // 内置非系统磁盘，分区路径带空格
    QString mediaNoSpacePath = ""; // 外接磁盘，分区路径不带空格
    QString mediaSpacePath = ""; // 外接磁盘，分区路径带空格
    quint64 needBytes = dimTotalBytes + thresholdBytes;
    DeviceInfoList devList = Device::getDeviceByLsblk();
    // 先按照可用空间排序，优先选择可用空间大的
    std::sort(devList.begin(), devList.end(), deviceInfoCompareMore);
    for (auto dev : devList) {
        if (dev->availableBytes < needBytes || !Device::isFsTypeSupported(dev->fsType)) {
            continue;
        }

        if (!removable) { // 默认过滤掉外接设备, hotplug
            if (dev->removable) {
                continue;
            }
        }

        QString devMountPoint = dev->mountPoint;
        if (devMountPoint.startsWith("/media/")) {
            if (!Device::isDeviceMountedReadOnly(devMountPoint)) {
                if (devMountPoint.contains(" ")) {
                    if (dev->removable) {
                        if (mediaSpacePath.isEmpty()) {
                            mediaSpacePath = devMountPoint;
                        }
                    } else {
                        if (innerMediaSpacePath.isEmpty()) {
                            innerMediaSpacePath = devMountPoint;
                        }
                    }
                } else {
                    if (dev->removable) {
                        if (mediaNoSpacePath.isEmpty()) {
                            mediaNoSpacePath = devMountPoint;
                        }
                    } else {
                        if (innerMediaNoSpacePath.isEmpty()) {
                            innerMediaNoSpacePath = devMountPoint;
                        }
                    }
                }
            }
            continue;
        } else {
            if(devMountPoint.contains(" ")) {
                if (innerSpacePath.isEmpty()) {
                    innerSpacePath = devMountPoint;
                    //    partitionMountPoint = innerSpacePath; // 测试带空格的分区路径
                    //    return true;
                }
                continue;
            } else if ("/" == devMountPoint || "/boot" == devMountPoint) {
                continue;
            }
        }

        // 找到系统磁盘分区，且分区挂载点路径不带空格，最佳选择
        partitionMountPoint = devMountPoint; // best choose, inner disk partition, has no empty space
        return true;
    }

    /* 匹配策略：
     * 1. 内置系统盘分区挂载点路径不带空格的，第1优先级
     * 2. 内置磁盘优先级 > 外接磁盘，并且内置磁盘分区挂载点路径不带空格的 > 带空格的
     * 3. 内置磁盘分区挂载点路径都带空格的场景下：系统盘 > 内置挂载的磁盘
     * 4. 外接磁盘：分区挂载点路径不带空格的 > 带空格的
     * 5. 各种场景只保存分区可用空间最大的挂载点路径
    */
    if (!innerMediaNoSpacePath.isEmpty()) {
        partitionMountPoint = innerMediaNoSpacePath;
    } else if (!innerSpacePath.isEmpty()) {
        partitionMountPoint = innerSpacePath;
    } else if (!innerMediaSpacePath.isEmpty()) {
        partitionMountPoint = innerMediaSpacePath;
    } else if (!mediaNoSpacePath.isEmpty()) {
        partitionMountPoint = mediaNoSpacePath;
    } else if (!mediaSpacePath.isEmpty()) {
        partitionMountPoint = mediaSpacePath;
    }

    return !partitionMountPoint.isEmpty();
}

bool Device::getVGNames(QStringList &vgNameList)
{
    vgNameList.clear();
    QString error;
    QString out;
    //QString cmd = "vgdisplay | grep \"VG Name\" | awk -F \" \" '{print $3}'";
    QStringList args;
    args <<"-c";
    if (!Process::spawnCmd("vgdisplay", args, out, error)) {
        qCritical()<<"Device::getVGNames failed, error = "<<error;
        return false;
    }

#if QT_VERSION >= QT_VERSION_CHECK(5,15,0)
    QStringList outList = out.split("\n", Qt::SkipEmptyParts);
#else
    QStringList outList = out.split("\n", QString::SkipEmptyParts);
#endif

    for (QString &line : outList) {
        line = line.trimmed();
        int index = line.indexOf(":");
        QString vgName = line.left(index);
        vgNameList<<vgName;
    }

    return true;
}

bool Device::getPvDisplay(QMap<QString, QString> &pvVgMaps)
{
    pvVgMaps.clear();
    QString error;
    QString out;
    if (!Process::spawnCmd("pvdisplay", {"-c"}, out, error)) {
        qCritical()<<"Device::getPvDisplay failed, error = "<<error;
        return false;
    }

#if QT_VERSION >= QT_VERSION_CHECK(5,15,0)
    QStringList outList = out.split("\n", Qt::SkipEmptyParts);
#else
    QStringList outList = out.split("\n", QString::SkipEmptyParts);
#endif

    for (QString &line : outList) {
        line = line.trimmed();
        int index = line.indexOf(":");
        QString pvName = line.left(index);
        if (!pvName.startsWith("/dev/")) {
            continue;
        }
        QString vgName = line.right(line.length() - index -1);
        index = vgName.indexOf(":");
        vgName = vgName.left(index);
        pvVgMaps.insert(pvName, vgName);
    }

    return true;
}

bool Device::getLvPathsByVgName(const QString &vgName, QStringList &lvPathList)
{
    lvPathList.clear();
    QString error;
    QString out;
    if (!Process::spawnCmd("lvdisplay", {"-c"}, out, error)) {
        qCritical()<<"Device::getLvPathByVgName failed, error = "<<error;
        return false;
    }

#if QT_VERSION >= QT_VERSION_CHECK(5,15,0)
    QStringList outList = out.split("\n", Qt::SkipEmptyParts);
#else
    QStringList outList = out.split("\n", QString::SkipEmptyParts);
#endif

    for (QString &line : outList) {
        line = line.trimmed();
        int index = line.indexOf(":");
        QString lvPath = line.left(index);
        if (!lvPath.startsWith("/dev/")) {
            continue;
        }
        QString vg = line.right(line.length() - index -1);
        index = vg.indexOf(":");
        vg = vg.left(index);
        if (vgName == vg) {
            lvPathList << lvPath;
        }
    }

    return true;
}

bool Device::lvRemove(const QString &lvPath)
{
    QString error;
    QString out;
    //QString cmd = QString("lvremove %1 --yes").arg(lvPath);
    if (!Process::spawnCmd("lvremove", {lvPath, "--yes"}, out, error)) {
        qCritical()<<"Device::lvRemove failed, error = "<<error;
        return false;
    }

    if (!error.isEmpty()) {
        qCritical()<<"Device::lvRemove error = "<<error;
        return false;
    }

    return true;
}

bool Device::deleteLVM(const QStringList &devices, const QMap<QString, QString> &pvVgMaps)
{
    if (devices.isEmpty()) {
        return true;
    }

    for (const QString &device : devices) {
        for (const QString &pv : pvVgMaps.keys()) {
            if (!pv.startsWith(device)) {
                continue;
            }

            QStringList lvPathList;
            if (!Device::getLvPathsByVgName(pvVgMaps[pv], lvPathList)) {
                return false;
            }

            for (const QString &lvPath : lvPathList) {
                if (!Device::lvRemove(lvPath)) {
                    return false;
                }
            }
        }
    }

    return true;
}

bool Device::vgRemove(const QString &vgName)
{
    QString error;
    QString out;
    QString cmd = QString("vgremove %1 --yes").arg(vgName);
    if (!Process::spawnCmd("vgremove", {vgName, "--yes"}, out, error)) {
        qCritical()<<"Device::vgRemove failed, cmd = "<<cmd<<", error = "<<error;
        return false;
    }

    if (!error.isEmpty()) {
        qCritical()<<"Device::vgRemove error = "<<error;
        return false;
    }

    return true;
}

bool Device::deleteVG(const QStringList &devices, const QMap<QString, QString> &pvVgMaps)
{
    if (devices.isEmpty() || pvVgMaps.isEmpty()) {
        return true;
    }

    for (const QString &device : devices) {
        for (const QString &pv : pvVgMaps.keys()) {
            if (!pv.startsWith(device)) {
                continue;
            }

            if (!Device::vgRemove(pvVgMaps[pv])) {
                return false;
            }
        }
    }

    return true;
}

bool Device::renameVG(const QStringList &sysVgNameList, QMap<QString, QString> &vgMap)
{
    bool needRename = false;
    int num = 1;
    QString vgName = "group";
    for (auto vgKey : vgMap.keys()) {
        if (sysVgNameList.contains(vgKey)) {
            do {
                vgName = QString("group%2").arg(num++);
            } while (sysVgNameList.contains(vgName));
            vgMap[vgKey] = vgName;
            needRename = true;
        }
    }

    return needRename;
}

void Device::getAllTypeDevice(DeviceInfoList &diskList, DeviceInfoList &cryptDevList,
                      DeviceInfoList &noCryptDevList, DeviceInfoList &partitionList, DeviceInfoList &lvmList,
                      DeviceInfoList &fileCryptDevList, DeviceInfoList &dmDevList)
{
    diskList.clear();
    cryptDevList.clear();
    noCryptDevList.clear();
    partitionList.clear();
    lvmList.clear();
    fileCryptDevList.clear();
    dmDevList.clear();

    DeviceInfoList usedDevices = Device::getAllDeviceByLsblk();
    for (auto &dev : usedDevices) {
        if (dev->type == "disk") {
            diskList.append(dev);
        } else if ((dev->type == "part" && dev->fsType == "luks") || (dev->type == "crypt" && dev->fsType == "lvm2")) {
            cryptDevList.append(dev); // 全盘加密场景
        } else if (dev->type == "part" && dev->fsType == "lvm2") {
            noCryptDevList.append(dev); // 普通LVM
        } else if (dev->type == "part") {
            partitionList.append(dev);
        } else if (dev->type == "lvm") {
            lvmList.append(dev);
        } else if (dev->type == "crypt" && dev->fsType != "lvm2") {
            fileCryptDevList.append(dev);
        } else if (dev->type == "dm") {
            dmDevList.append(dev);
        }
    }
}

bool Device::isMultiFullDiskEncrypt(const QString &fstabFile)
{
    static FSTabInfoList fstabList = FSTab::getFSTabFromFile(fstabFile);
    QSet<QString> mountBindPointSets;
    for (auto &info : fstabList) {
        if (info->isEmptyLine || info->isComment) {
            continue;
        }

        if (info->options.contains("bind")) {
            QString mountBindPoint = info->device.left(info->device.lastIndexOf("/"));
            mountBindPointSets.insert(mountBindPoint);
        }
    }

    if (1 != mountBindPointSets.size()) {
        return false; // not full disk install
    }

    static QStringList sysDeviceUuidList = FSTab::getUuidListFromFstab(fstabList);
    DeviceInfoList diskList;
    DeviceInfoList cryptDevList;
    DeviceInfoList noCryptDevList;
    DeviceInfoList partitionList;
    DeviceInfoList lvmList;
    DeviceInfoList fileCryptDevList;
    DeviceInfoList dmDevList;
    Device::getAllTypeDevice(diskList, cryptDevList, noCryptDevList, partitionList, lvmList, fileCryptDevList, dmDevList);

    QStringList sysDeviceList;
    for (auto &diskDev : diskList) {
        for (auto &cryptDev : cryptDevList) {
            if (!(cryptDev->type == "part" && cryptDev->fsType == "luks" && cryptDev->pkname == diskDev->name)) {
                continue;
            }

            for (auto &lvm2Dev : cryptDevList) {
                if (!(lvm2Dev->type == "crypt" && lvm2Dev->fsType == "lvm2" && lvm2Dev->pkname == cryptDev->kname)) {
                    continue;
                }

                for (auto &lvmMember : lvmList) {
                    if (lvmMember->pkname == lvm2Dev->kname) {
                        if (sysDeviceUuidList.contains(lvmMember->uuid) && !sysDeviceList.contains(diskDev->name)) {
                            sysDeviceList.append(diskDev->name);
                            break;
                        }
                    }
                }
            }
        }
    }

    sysDeviceList.removeDuplicates();

    return sysDeviceList.size() > 1;
}

bool Device::isFullDiskInstall(const QString &fstabFile, QStringList &sysMountPointList)
{
    sysMountPointList.clear();
    static FSTabInfoList fstabList = FSTab::getFSTabFromFile(fstabFile);
    QSet<QString> mountBindPointSets;
    for (auto &info : fstabList) {
        if (info->isEmptyLine || info->isComment) {
            continue;
        }

        if (info->device.startsWith("UUID=")) {
            sysMountPointList.append(info->mountPoint);
        }

        if (info->options.contains("bind")) {
            QString mountBindPoint = info->device.left(info->device.lastIndexOf("/"));
            mountBindPointSets.insert(mountBindPoint);
        }
    }

    if (1 == mountBindPointSets.size()) {
        QString mountBindPoint = *(mountBindPointSets.begin());
        if (sysMountPointList.contains(mountBindPoint)) {
            return true; // full disk install
        }
    } else {
        if (sysMountPointList.contains("/persistent")) {
            if (sysMountPointList.contains("/sysroot")) {
                return true;
            }
        }
    }

    return false;
}

bool AppVersion::operator<(const AppVersion &version)
{
    if (this->major < version.major) {
        return true;
    } else if (this->minor < version.minor) {
        return true;
    } else if (this->patch < version.patch) {
        return true;
    } else if (this->build < version.build) {
        return true;
    } else if (this->update < version.update) {
        return true;
    }

    return false;
}

AppVersion& AppVersion::operator=(const AppVersion &version)
{
    if (this != &version) {
        this->major = version.major;
        this->minor = version.minor;
        this->patch = version.patch;
        this->build = version.build;
        this->update = version.update;
    }

    return *this;
}

QJsonObject AppVersion::marshal()
{
    QJsonObject jsonObject;
    jsonObject.insert("major", major);
    jsonObject.insert("minor", minor);
    jsonObject.insert("patch", patch);
    jsonObject.insert("build", build);
    jsonObject.insert("update", update);

    return jsonObject;
}

void AppVersion::unmarshal(const QJsonObject &jsonObject)
{
    major = jsonObject.value("major").toInt(-1);
    minor = jsonObject.value("minor").toInt(-1);
    patch = jsonObject.value("patch").toInt(-1);
    build = jsonObject.value("build").toInt(-1);
    update = jsonObject.value("update").toInt(-1);
}

AppVersion AppVersion::getAppVersion()
{
    AppVersion version;
    //QString cmd = QString("uos-recovery-tool --version | awk '{print $2}'");
    QString output = "";
    QString err;
    if (!Process::spawnCmd("uos-recovery-tool", {"--version"}, output, err)) {
        qWarning()<<"getAppVersion failed, err = "<<err;
        return version;
    }
    output = output.trimmed();
#if QT_VERSION >= QT_VERSION_CHECK(5,15,0)
    QStringList outList = output.split(" ", Qt::SkipEmptyParts);
#else
    QStringList outList = output.split(" ", QString::SkipEmptyParts);
#endif
    output = outList[1];
    if (output.isEmpty()) {
        return version;
    }

    bool ok = false;
    int index = output.indexOf(".");
    version.major = output.left(index).toInt(&ok);
    if (!ok) {
        version.major = -1;
    }

    output = output.right(output.length() - index - 1);
    index = output.indexOf(".");
    version.minor = output.left(index).toInt(&ok);
    if (!ok) {
        version.minor = -1;
    }

    output = output.right(output.length() - index - 1);
    index = output.indexOf(".");
    ok = false;
    if (-1 != index) {
        version.patch = output.left(index).toInt(&ok);
    } else { // 6.0.15+u021
        index = output.indexOf("+");
        if (-1 != index) {
            version.patch = output.left(index).toInt(&ok);
        } else { // 6.0.15
            version.patch = output.toInt(&ok);
            output = "";
        }
    }
    if (!ok) {
        version.patch = -1;
    }

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

    output = output.right(output.length() - index - 1);
    index = output.indexOf(".");
    if (-1 != index) {
        version.build = output.left(index).toInt(&ok);
    } else {
        index = output.indexOf("+");
        if (-1 != index) {
            version.build = output.left(index).toInt(&ok);
        } else {
            version.build = output.toInt(&ok);
        }
    }
    if (!ok) {
        version.build = -1;
    }

    index = output.indexOf("u");
    if (-1 != index) {
        output = output.right(output.length() - index - 1);
        version.update = output.toInt(&ok);
        if (!ok) {
            version.build = -1;
        }
    }

    return version;
}

QJsonObject PartitionInfo::marshal()
{
    QJsonObject jsonObject;
    jsonObject.insert("deviceName", deviceName);
    jsonObject.insert("kName", kName);
    jsonObject.insert("pkName", pkName);
    jsonObject.insert("uuid", uuid);
    jsonObject.insert("label", label);
    jsonObject.insert("type", type);
    jsonObject.insert("fsType", fsType);
    jsonObject.insert("mountPoint", mountPoint);
    jsonObject.insert("totalSizeBytes", QString("%1").arg(totalSizeBytes));
    jsonObject.insert("usedSizeBytes", QString("%1").arg(usedSizeBytes));
    jsonObject.insert("logSectorSize", logSectorSize);
    jsonObject.insert("phySectorSize", phySectorSize);
    jsonObject.insert("rota", rota);
    jsonObject.insert("isEncrypt", isEncrypt);
    jsonObject.insert("isLvm", isLvm);

    return jsonObject;
}

void PartitionInfo::unmarshal(const QJsonObject &jsonObject)
{
    deviceName = jsonObject.value("deviceName").toString();
    kName = jsonObject.value("kName").toString();
    pkName = jsonObject.value("pkName").toString();
    uuid = jsonObject.value("uuid").toString();
    label = jsonObject.value("label").toString();
    type = jsonObject.value("type").toString();
    fsType = jsonObject.value("fsType").toString();
    mountPoint = jsonObject.value("mountPoint").toString();

    bool ok = false;
    totalSizeBytes = jsonObject.value("totalSizeBytes").toString().toULongLong(&ok);
    if (!ok) {
        totalSizeBytes = 0;
    }

    usedSizeBytes = jsonObject.value("usedSizeBytes").toString().toULongLong(&ok);
    if (!ok) {
        usedSizeBytes = 0;
    }

    logSectorSize = jsonObject.value("logSectorSize").toInt(-1);
    phySectorSize = jsonObject.value("phySectorSize").toInt(-1);
    rota = jsonObject.value("rota").toInt(-1);
    isEncrypt = jsonObject.value("isEncrypt").toBool();
    isLvm = jsonObject.value("isLvm").toBool();
}

QJsonObject SysDirSizeInfo::marshal()
{
    QJsonObject jsonObject;
    jsonObject.insert("/", QString("%1").arg(sysRoot));
    jsonObject.insert("/rootb", QString("%1").arg(sysRootb));
    jsonObject.insert("/boot/efi", QString("%1").arg(efi));
    jsonObject.insert("/root", QString("%1").arg(rootDir));
    jsonObject.insert("/home", QString("%1").arg(home));
    jsonObject.insert("/opt", QString("%1").arg(opt));
    jsonObject.insert("/var", QString("%1").arg(var));
    jsonObject.insert("/tmp", QString("%1").arg(tmp));
    jsonObject.insert("/boot", QString("%1").arg(boot));
    jsonObject.insert("/srv", QString("%1").arg(srv));
    jsonObject.insert("/usr/local", QString("%1").arg(usrLocal));
    jsonObject.insert("/persistent", QString("%1").arg(persistent));

    return jsonObject;
}

void SysDirSizeInfo::unmarshal(const QJsonObject &jsonObject)
{
    bool ok = false;
    sysRoot = jsonObject.value("/").toString().toULongLong(&ok);
    if (!ok) {
        sysRoot = 0;
    }

    sysRootb = jsonObject.value("/rootb").toString().toULongLong(&ok);
    if (!ok) {
        sysRootb = 0;
    }

    efi = jsonObject.value("/boot/efi").toString().toULongLong(&ok);
    if (!ok) {
        efi = 0;
    }

    rootDir = jsonObject.value("/root").toString().toULongLong(&ok);
    if (!ok) {
        rootDir = 0;
    }

    home = jsonObject.value("/home").toString().toULongLong(&ok);
    if (!ok) {
        home = 0;
    }

    opt = jsonObject.value("/opt").toString().toULongLong(&ok);
    if (!ok) {
        opt = 0;
    }

    var = jsonObject.value("/var").toString().toULongLong(&ok);
    if (!ok) {
        var = 0;
    }

    tmp = jsonObject.value("/tmp").toString().toULongLong(&ok);
    if (!ok) {
        tmp = 0;
    }

    boot = jsonObject.value("/boot").toString().toULongLong(&ok);
    if (!ok) {
        boot = 0;
    }

    srv = jsonObject.value("/srv").toString().toULongLong(&ok);
    if (!ok) {
        srv = 0;
    }

    usrLocal = jsonObject.value("/usr/local").toString().toULongLong(&ok);
    if (!ok) {
        usrLocal = 0;
    }

    persistent = jsonObject.value("/persistent").toString().toULongLong(&ok);
    if (!ok) {
        persistent = 0;
    }
}

QList<PartitionInfo> GhostDiskInfo::getPartitionList()
{
    QList<PartitionInfo> partList;
    int size = partitionList.size();
    for (int i = 0; i < size; ++i) {
        QJsonObject obj = partitionList.at(i).toObject();
        PartitionInfo p;
        p.unmarshal(obj);
        partList.append(p);
    }

    return partList;
}

QJsonObject GhostDiskInfo::marshal()
{
    QJsonObject jsonObject;
    jsonObject.insert("deviceName", deviceName);
    jsonObject.insert("vendor", vendor);
    jsonObject.insert("mode", mode);
    jsonObject.insert("totalSizeBytes", QString("%1").arg(totalSizeBytes));
    jsonObject.insert("allocTotalSizeBytes", QString("%1").arg(allocTotalSizeBytes));
    jsonObject.insert("usedSizeBytes", QString("%1").arg(usedSizeBytes));
    jsonObject.insert("lastPartitionTotalSizeBytes", QString("%1").arg(lastPartitionTotalSizeBytes));
    jsonObject.insert("recoveryPartitionUsedSizeBytes", QString("%1").arg(recoveryPartitionUsedSizeBytes));
    jsonObject.insert("curSysRootUsedSizeBytes", QString("%1").arg(curSysRootUsedSizeBytes));
    jsonObject.insert("anotherSysRootUsedSizeBytes", QString("%1").arg(anotherSysRootUsedSizeBytes));
    jsonObject.insert("efiUsedSizeBytes", QString("%1").arg(efiUsedSizeBytes));
    jsonObject.insert("mediaType", mediaType);
    jsonObject.insert("isEncrypt", isEncrypt);
    jsonObject.insert("isLvm", isLvm);
    jsonObject.insert("isVirtualDisk", isVirtualDisk);
    jsonObject.insert("isRemovable", isRemovable);
    jsonObject.insert("isCurRoot", isCurRoot);
    jsonObject.insert("isRootb", isRootb);
    jsonObject.insert("isEfi", isEfi);
    jsonObject.insert("partitionList", partitionList);

    return jsonObject;
}

void GhostDiskInfo::unmarshal(const QJsonObject &jsonObject)
{
    bool ok = false;
    deviceName = jsonObject.value("deviceName").toString();
    vendor = jsonObject.value("vendor").toString();
    mode = jsonObject.value("mode").toString();
    totalSizeBytes = jsonObject.value("totalSizeBytes").toString().toULongLong(&ok);
    if (!ok) {
        totalSizeBytes = 0;
    }

    allocTotalSizeBytes = jsonObject.value("allocTotalSizeBytes").toString().toULongLong(&ok);
    if (!ok) {
        allocTotalSizeBytes = 0;
    }

    usedSizeBytes = jsonObject.value("usedSizeBytes").toString().toULongLong(&ok);
    if (!ok) {
        usedSizeBytes = 0;
    }

    lastPartitionTotalSizeBytes = jsonObject.value("lastPartitionTotalSizeBytes").toString().toULongLong(&ok);
    if (!ok) {
        lastPartitionTotalSizeBytes = 0;
    }

    recoveryPartitionUsedSizeBytes = jsonObject.value("recoveryPartitionUsedSizeBytes").toString().toULongLong(&ok);
    if (!ok) {
        recoveryPartitionUsedSizeBytes = 0;
    }

    curSysRootUsedSizeBytes = jsonObject.value("curSysRootUsedSizeBytes").toString().toULongLong(&ok);
    if (!ok) {
        curSysRootUsedSizeBytes = 0;
    }

    anotherSysRootUsedSizeBytes = jsonObject.value("anotherSysRootUsedSizeBytes").toString().toULongLong(&ok);
    if (!ok) {
        anotherSysRootUsedSizeBytes = 0;
    }

    efiUsedSizeBytes = jsonObject.value("efiUsedSizeBytes").toString().toULongLong(&ok);
    if (!ok) {
        efiUsedSizeBytes = 0;
    }

    mediaType = jsonObject.value("mediaType").toInt(-1);
    isEncrypt = jsonObject.value("isEncrypt").toBool();
    isLvm = jsonObject.value("isLvm").toBool();
    isVirtualDisk = jsonObject.value("isVirtualDisk").toBool();
    isRemovable = jsonObject.value("isRemovable").toBool();
    isCurRoot = jsonObject.value("isCurRoot").toBool();
    isRootb = jsonObject.value("isRootb").toBool();
    isEfi = jsonObject.value("isEfi").toBool();

    if (jsonObject.contains("partitionList")) {
        QJsonValue val = jsonObject.value("partitionList");
        if (val.isArray()) {
            partitionList = val.toArray();
        }
    }
}

bool GhostDiskInfo::operator<(const GhostDiskInfo &diskInfo)
{
    return this->totalSizeBytes < diskInfo.totalSizeBytes;
}

void Device::fillGhostDiskInfo(const DeviceInfoPtr &devInfoPtr, GhostDiskInfo &diskInfo)
{
    diskInfo.totalSizeBytes = devInfoPtr->sizeBytes;
    diskInfo.deviceName = devInfoPtr->deviceName;
    diskInfo.isRemovable = devInfoPtr->removable;
    if (devInfoPtr->removable) {
        if (devInfoPtr->vendor.startsWith("VBOX")) {
            diskInfo.isVirtualDisk = true;
        }
    } else {
        if (devInfoPtr->mode.startsWith("VBOX")) {
            diskInfo.isVirtualDisk = true;
        }
    }
    diskInfo.mediaType = devInfoPtr->rota;
    diskInfo.vendor = devInfoPtr->vendor;
    diskInfo.mode = devInfoPtr->mode;
    diskInfo.lastPartitionTotalSizeBytes = devInfoPtr->sizeBytes; // need refresh
    diskInfo.allocTotalSizeBytes = 0; // need refresh
    diskInfo.usedSizeBytes = 0; // need refresh
    diskInfo.isEncrypt = false; // need refresh
    diskInfo.isLvm = false; // need refresh
}

void Device::getGhostDiskInfo(const QString &fstabFile, QList<GhostDiskInfo> &diskInfoList)
{
    diskInfoList.clear();
    static FSTabInfoList fstabList = FSTab::getFSTabFromFile(fstabFile);
    static QStringList sysDeviceUuidList = FSTab::getUuidListFromFstab(fstabList);

    DeviceInfoList diskList;
    DeviceInfoList cryptDevList;
    DeviceInfoList noCryptDevList;
    DeviceInfoList partitionList;
    DeviceInfoList lvmList;
    DeviceInfoList fileCryptDevList;
    DeviceInfoList dmDevList;
    Device::getAllTypeDevice(diskList, cryptDevList, noCryptDevList, partitionList, lvmList,
                             fileCryptDevList, dmDevList);
    DeviceInfoList orgDevList = Device::getOrganizedDiskList(diskList, cryptDevList, noCryptDevList, partitionList,
                                                             lvmList, fileCryptDevList, dmDevList);
    QSet<QString> encryptPartitionUUIDSet;
    QSet<QString> lvmUUIDSet;

    for (auto &diskDev : diskList) {
        GhostDiskInfo diskInfo;
        quint64 allocTotalSizeBytes = 0;
        quint64 usedSizeBytes = diskDev->usedBytes;
        quint64 lastPartitionTotalSizeBytes = 0;
        quint64 lastPartitionUsedSizeBytes = 0;
        bool isEncrypt = false;
        bool isLvm = false;
        bool isSysDisk = false;
        for (auto &cryptDev : cryptDevList) {
            if (!(cryptDev->type == "part" && cryptDev->fsType == "luks" && cryptDev->pkname == diskDev->name)) {
                continue;
            }

            for (auto &lvm2Dev : cryptDevList) {
                if (!(lvm2Dev->type == "crypt" && lvm2Dev->fsType == "lvm2" && lvm2Dev->pkname == cryptDev->kname)) {
                    continue;
                }

                for (auto &lvmMember : lvmList) {
                    if (lvmMember->pkname == lvm2Dev->kname) {
                        if (sysDeviceUuidList.contains(lvmMember->uuid)) {
                            isSysDisk = true;
                            allocTotalSizeBytes += lvmMember->sizeBytes;
                            usedSizeBytes += lvmMember->usedBytes;
                            isEncrypt = true;
                            isLvm = true;
                            encryptPartitionUUIDSet.insert(lvmMember->uuid);
                        }
                    }
                }
            }
        }

        for (auto &noCryptDev : noCryptDevList) {
            if (noCryptDev->pkname != diskDev->name) {
                continue;
            }

            for (auto &lvmMember : lvmList) {
                if (lvmMember->pkname == noCryptDev->kname) {
                    if (sysDeviceUuidList.contains(lvmMember->uuid)) {
                        isSysDisk = true;
                        allocTotalSizeBytes += lvmMember->sizeBytes;
                        usedSizeBytes += lvmMember->usedBytes;
                        isLvm = true;
                        lvmUUIDSet.insert(lvmMember->uuid);
                    }
                }
            }
        }

        for (auto &partitionDev : partitionList) {
            if (partitionDev->pkname == diskDev->name) {
                if (sysDeviceUuidList.contains(partitionDev->uuid)) {
                    isSysDisk = true;
                    allocTotalSizeBytes += partitionDev->sizeBytes;
                    usedSizeBytes += partitionDev->usedBytes;
                }

                if (partitionDev->fsSizeBytes == 0 && !partitionDev->children.isEmpty()) {
                    for (auto &child : partitionDev->children) {
                        if (sysDeviceUuidList.contains(child->uuid)) {
                            isSysDisk = true;
                            allocTotalSizeBytes += child->sizeBytes;
                            usedSizeBytes += child->usedBytes;
                            qWarning()<<"find child dm partition, dmName: "<<child->name<<", usedsize: "<<child->usedBytes;
                        }
                    }
                }
            }
        }

        if (isSysDisk) {
            if (!diskDev->children.isEmpty()) {
                DeviceInfoPtr &lastPartition = diskDev->children.last();
                while (nullptr != lastPartition) {
                    if (lastPartition->children.isEmpty()) {
                        break;
                    }
                    lastPartition = lastPartition->children.last();
                }
                lastPartitionTotalSizeBytes = lastPartition->sizeBytes;
                lastPartitionUsedSizeBytes = lastPartition->usedBytes;
                if ("swap" == lastPartition->fsType) {
                    lastPartitionUsedSizeBytes = lastPartition->sizeBytes;
                }

                for (DeviceInfoPtr &device : diskDev->children) {
                    PartitionInfo p;
                    p.deviceName = device->deviceName;
                    p.kName = device->kname;
                    p.pkName = device->pkname;
                    p.uuid = device->uuid;
                    p.label = device->label;
                    p.type = device->type;
                    p.fsType = device->fsType;
                    p.mountPoint = device->mountPoint;
                    p.totalSizeBytes = device->sizeBytes;
                    p.usedSizeBytes = device->usedBytes;
                    p.logSectorSize = device->logSectorSize;
                    p.phySectorSize = device->phySectorSize;
                    p.rota = device->rota;
                    if (encryptPartitionUUIDSet.contains(p.uuid)) {
                        p.isEncrypt = true;
                    }
                    if (lvmUUIDSet.contains(p.uuid)) {
                        p.isLvm = true;
                    }
                    diskInfo.partitionList.append(p.marshal());

                    if ("/boot/efi" == p.mountPoint) {
                        diskInfo.efiUsedSizeBytes = p.usedSizeBytes;
                        diskInfo.isEfi = true;
                    }

                    if ("/" == p.mountPoint) {
                        diskInfo.curSysRootUsedSizeBytes = p.usedSizeBytes;
                        diskInfo.isCurRoot = true;
                    }

                    if ("/SystemUpgradeMountPoint" == p.mountPoint) {
                        diskInfo.anotherSysRootUsedSizeBytes = p.usedSizeBytes;
                        diskInfo.isRootb = true;
                    }

                    if ("/recovery" == p.mountPoint || "Backup" == p.label) {
                        diskInfo.recoveryPartitionUsedSizeBytes = p.usedSizeBytes;
                    }
                }
            }

            Device::fillGhostDiskInfo(diskDev, diskInfo);
            diskInfo.allocTotalSizeBytes = allocTotalSizeBytes;
            diskInfo.usedSizeBytes = usedSizeBytes;
            diskInfo.lastPartitionTotalSizeBytes = lastPartitionTotalSizeBytes;
            diskInfo.isEncrypt = isEncrypt;
            diskInfo.isLvm = isLvm;
            diskInfoList.append(diskInfo);
        }
    }
}

QJsonObject SystemUpgradeInfo::marshal()
{
    QJsonObject jsonObject;
    jsonObject.insert("rootUuid", rootUuid);
    jsonObject.insert("rootPath", rootPath);
    jsonObject.insert("mountPoint", mountPoint);
    jsonObject.insert("isSystemUpgrade", isSystemUpgrade);

    return jsonObject;
}

void SystemUpgradeInfo::unmarshal(const QJsonObject &jsonObject)
{
    rootUuid = jsonObject.value("rootUuid").toString();
    rootPath = jsonObject.value("rootPath").toString();
    mountPoint = jsonObject.value("mountPoint").toString();
    isSystemUpgrade = jsonObject.value("isSystemUpgrade").toBool();
}

FstabItem::FstabItem()
        : device(""),
          mountPoint(""),
          type(""),
          options(""),
          dump(""),
          pass("")
{}

FstabItem::FstabItem(const QString &devUUID, const QString &mnt, const QString &devType, const QString &option,
          const QString &devDump, const QString &devPass)
          : device(devUUID),
            mountPoint(mnt),
            type(devType),
            options(option),
            dump(devDump),
            pass(devPass)

{}

FstabItem& FstabItem::operator=(const FstabItem &other)
{
    if (this != &other) {
        this->device = other.device;
        this->mountPoint = other.mountPoint;
        this->type = other.type;
        this->options = other.options;
        this->dump = other.dump;
        this->pass = other.pass;
    }

    return *this;
}

QJsonObject FstabItem::marshal()
{
    QJsonObject jsonObject;
    jsonObject.insert("device", device);
    jsonObject.insert("mountPoint", mountPoint);
    jsonObject.insert("type", type);
    jsonObject.insert("options", options);
    jsonObject.insert("dump", dump);
    jsonObject.insert("pass", pass);

    return jsonObject;
}

void FstabItem::unmarshal(const QJsonObject &jsonObject)
{
    device = jsonObject.value("device").toString();
    mountPoint = jsonObject.value("mountPoint").toString();
    type = jsonObject.value("type").toString();
    options = jsonObject.value("options").toString();
    dump = jsonObject.value("dump").toString();
    pass = jsonObject.value("pass").toString();
}

QJsonObject GhostInfo::marshal()
{
    QJsonObject jsonObject;
    jsonObject.insert("platform", platform);
    jsonObject.insert("hardwareType", hardwareType);
    jsonObject.insert("sysProductName", sysProductName);
    jsonObject.insert("huaweiTypeAlias", huaweiTypeAlias);
    jsonObject.insert("cpuModel", cpuModel);
    jsonObject.insert("displayDriver", displayDriver);
    jsonObject.insert("diskSize", QString("%1").arg(diskSize));
    jsonObject.insert("isEFI", isEFI);
    jsonObject.insert("isFullInstall", isFullInstall);
    jsonObject.insert("systemUpgradeInfo", systemUpgradeInfo);
    jsonObject.insert("ghostVersion", ghostVersion);
    jsonObject.insert("diskInfo", diskInfo);
    jsonObject.insert("fstab", fstab);
    jsonObject.insert("dirSize", dirSizeObj);
    jsonObject.insert("initOptApps", initOptApps);

    return jsonObject;
}

void GhostInfo::unmarshal(const QJsonObject &jsonObject)
{
    platform = jsonObject.value("platform").toString();
    if (jsonObject.contains("hardwareType")) {
        hardwareType = jsonObject.value("hardwareType").toString();
    }
    if (jsonObject.contains("sysProductName")) {
        sysProductName = jsonObject.value("sysProductName").toString();
    }
    if (jsonObject.contains("huaweiTypeAlias")) {
        huaweiTypeAlias = jsonObject.value("huaweiTypeAlias").toString();
    }
    cpuModel = jsonObject.value("cpuModel").toString();
    displayDriver = jsonObject.value("displayDriver").toString();
    isEFI = jsonObject.value("isEFI").toBool();
    isFullInstall = jsonObject.value("isFullInstall").toBool();

    bool ok = false;
    diskSize = jsonObject.value("diskSize").toString().toULongLong(&ok);
    if (!ok) {
        diskSize = 0;
    }

    if (jsonObject.contains("systemUpgradeInfo")) {
        systemUpgradeInfo = jsonObject.value("systemUpgradeInfo").toObject();
    }

    if (jsonObject.contains("ghostVersion")) {
        ghostVersion = jsonObject.value("ghostVersion").toObject();
    }

    if (jsonObject.contains("diskInfo")) {
        QJsonValue val = jsonObject.value("diskInfo");
        if (val.isArray()) {
            diskInfo = val.toArray();
        }
    }

    if (jsonObject.contains("fstab")) {
        QJsonValue val = jsonObject.value("fstab");
        if (val.isArray()) {
            fstab = val.toArray();
        }
    }

    if (jsonObject.contains("dirSize")) {
        dirSizeObj = jsonObject.value("dirSize").toObject();
    }

    if (jsonObject.contains("initOptApps")) {
        initOptApps = jsonObject.value("initOptApps").toString();
    }
}

SystemUpgradeInfo GhostInfo::getSystemUpgradeInfo()
{
    SystemUpgradeInfo sysUpgrade;
    if (!systemUpgradeInfo.isEmpty()) {
        sysUpgrade.unmarshal(systemUpgradeInfo);
    }

    return sysUpgrade;
}

AppVersion GhostInfo::getGhostVersion()
{
    AppVersion ghostVer;
    if (!ghostVersion.isEmpty()) {
        ghostVer.unmarshal(ghostVersion);
    }

    return ghostVer;
}

QList<GhostDiskInfo> GhostInfo::getGhostDiskInfo() const
{
    QList<GhostDiskInfo> ghostDiskInfoList;
    int sysDiskNum = diskInfo.size();
    for (int i = 0; i < sysDiskNum; ++i) {
        QJsonObject sysDisk = diskInfo.at(i).toObject();
        GhostDiskInfo gDisk;
        gDisk.unmarshal(sysDisk);
        ghostDiskInfoList.append(gDisk);
    }

    return ghostDiskInfoList;
}

QList<FstabItem> GhostInfo::getFstabItemList()
{
    QList<FstabItem> fstabItemList;
    int fstabItemNum = fstab.size();
    for (int i = 0; i < fstabItemNum; ++i) {
        QJsonObject fstabItemObj = fstab.at(i).toObject();
        FstabItem fstabItem;
        fstabItem.unmarshal(fstabItemObj);
        fstabItemList.append(fstabItem);
    }

    return fstabItemList;
}
