/* common/KMBuildinsUtils.cpp
 *
 * Copyright (C) 2023 OpenKylin, Inc
 */

#include "KMBuildinsUtils.h"

#include <gio/gunixinputstream.h>
#include <gio/gunixoutputstream.h>
#include <string.h>

#include <algorithm>
#include <cstdlib>
#include <regex>
#include <set>

#include "config.h"
#include "common/KMChecksum.h"
#include "common/KMDirFd.h"
#include "common/KMFileEnumerator.h"
#include "common/KMFileInfo.h"
#include "common/KMLogger.h"
#include "common/KMOstreeCore.h"
#include "common/KMOstreeRepoFile.h"
#include "common/KMUtil.h"
#include "common/kmtranslation.h"
#include "common/kmvariant.h"

/**
 * findInstalledPref:
 * @brief : 根据入参条件判断app或runtime、sdk是否安装
 * @param : [in] pref，形如"org.videolan.VLC"或"runtime/org.freedesktop.Sdk/x86_64/22.08"
 * @param : [in] kinds，开明包安装目录类型，app还是runtime等
 * @param : [in] defaultArch，默认查询架构，如果pref里面没有架构信息则默认采用defaultArch
 * @param : [in] defaultBranch，默认查询版本，如果pref里面没有版本信息则默认采用defaultBranch
 * @param : [out] outRef，如果查到已经安装，则返回KMRef里的信息
 * @param : [out] errMsg，保存错误信息
 * @return: 如果在系统级安装目录（当前仅支持系统级安装目录）中查找到安装信息则返回系统安装目录构造的KMStorageDir对象；否则，返回一个空的KMStorageDir对象
 */
KMStorageDir KMBuildinsUtils::findInstalledPref(const std::string &pref, KaimingKinds kinds, const std::string &defaultArch, const std::string &defaultBranch, KMRef &outRef, std::string &errMsg)
{
    KMRef ref = KMBuildinsUtils::splitPartialRefArg(pref, defaultArch, defaultBranch, kinds, errMsg);
    if (ref.id.empty())
    {
        return KMStorageDir();
    }

    KMStorageDir systemDir = KMStorageDir::getSystemDefaultDir();
    outRef = systemDir.findInstalledRef(ref.id, ref.branch, ref.arch, kinds);
    if (outRef.id.empty())
    {
        errMsg = "Not find installed " + KMUtil::dupPref(ref);
        return KMStorageDir();
    }

    return systemDir;
}

/**
 * splitPartialRefArg:
 * @brief : 解析参数pref中的信息，获取里面的id、arch、branch等信息
 * @param : [in] pref，形如"org.videolan.VLC"或"runtime/org.freedesktop.Sdk/x86_64/22.08"
 * @param : [in] defaultArch，默认查询架构，如果pref里面没有架构信息则默认采用defaultArch
 * @param : [in] defaultBranch，默认查询版本，如果pref里面没有版本信息则默认采用defaultBranch
 * @param : [out] kinds，开明包安装目录类型，app还是runtime等
 * @param : [out] errMsg，保存错误信息
 * @return: 返回id、arch、branch等信息
 */
KMRef KMBuildinsUtils::splitPartialRefArg(const std::string &pref, const std::string &defaultArch, const std::string &defaultBranch, KaimingKinds &kinds, std::string &errMsg)
{
    KMRef ref;

    size_t idStart = 0;
    if (KMUtil::startsWith(pref, "app/"))
    {
        idStart = strlen("app/");
        kinds = KaimingKinds::KAIMING_KINDS_APP;
        ref.kind = BASE_TYPE_APP;
    }
    else if (KMUtil::startsWith(pref, "runtime/"))
    {
        idStart = strlen("runtime/");
        kinds = KaimingKinds::KAIMING_KINDS_RUNTIME;
        ref.kind = BASE_TYPE_RUNTIME;
    }
    size_t idEnd = pref.find("/", idStart);
    size_t idLen = (std::string::npos != idEnd) ? idEnd - idStart : pref.length() - idStart;
    std::string id = pref.substr(idStart, idLen);
    if (!isValidAppName(id, errMsg))
    {
        return ref;
    }

    size_t archStart = std::string::npos;
    size_t archEnd = std::string::npos;
    if (std::string::npos != idEnd)
    {
        archStart = idEnd + 1;
        archEnd = pref.find("/", archStart);
    }
    std::string arch;
    if (archStart != archEnd)
    {
        size_t archLen = (std::string::npos != archEnd) ? archEnd - archStart : pref.length() - archStart;
        arch = pref.substr(archStart, archLen);
    }
    if (arch.empty())
    {
        arch = defaultArch;
    }

    size_t branchStart = std::string::npos;
    size_t branchEnd = std::string::npos;
    if (std::string::npos != archEnd)
    {
        branchStart = archEnd + 1;
        branchEnd = pref.find("/", branchStart);
    }
    std::string branch;
    if (branchStart != branchEnd)
    {
        size_t branchLen = (std::string::npos != branchEnd) ? branchEnd - branchStart : pref.length() - branchStart;
        branch = pref.substr(branchStart, branchLen);
    }
    if (branch.empty())
    {
        branch = defaultBranch;
    }
    if (!branch.empty() && !isValidBranch(branch, errMsg))
    {
        return ref;
    }

    ref.id = id;
    ref.arch = arch;
    ref.branch = branch;

    return ref;
}

/**
 * prefToKMRef:
 * @brief : 将pref转换为KMRef对象
 * @param : [in] kind，开明包安装目录类型，app还是runtime等
 * @param : [in] pref，形如"org.freedesktop.Sdk/x86_64/22.08"或"runtime/org.freedesktop.Sdk/x86_64/22.08"
 * @return: 解析pref转换成的KMRef对象
 */
KMRef KMBuildinsUtils::prefToKMRef(const KaimingKinds &kind, const std::string &pref)
{
    KMRef ref;

    size_t idStart = 0;
    if (KMUtil::startsWith(pref, "app/"))
    {
        idStart = strlen("app/");
        ref.kind = BASE_TYPE_APP;
    }
    else if (KMUtil::startsWith(pref, "runtime/"))
    {
        idStart = strlen("runtime/");
        ref.kind = BASE_TYPE_RUNTIME;
    }
    else
    {
        if (kind & KaimingKinds::KAIMING_KINDS_APP)
        {
            ref.kind = BASE_TYPE_APP;
        }
    }
    size_t idEnd = pref.find("/", idStart);
    size_t idLen = (std::string::npos != idEnd) ? idEnd - idStart : pref.length() - idStart;
    std::string id = pref.substr(idStart, idLen);
    std::string errMsg;
    if (!isValidAppName(id, errMsg))
    {
        return ref;
    }

    size_t archStart = std::string::npos;
    size_t archEnd = std::string::npos;
    if (std::string::npos != idEnd)
    {
        archStart = idEnd + 1;
        archEnd = pref.find("/", archStart);
    }
    std::string arch;
    if (archStart != archEnd)
    {
        size_t archLen = (std::string::npos != archEnd) ? archEnd - archStart : pref.length() - archStart;
        arch = pref.substr(archStart, archLen);
    }

    size_t branchStart = std::string::npos;
    size_t branchEnd = std::string::npos;
    if (std::string::npos != archEnd)
    {
        branchStart = archEnd + 1;
        branchEnd = pref.find("/", branchStart);
    }
    std::string branch;
    if (branchStart != branchEnd)
    {
        size_t branchLen = (std::string::npos != branchEnd) ? branchEnd - branchStart : pref.length() - branchStart;
        branch = pref.substr(branchStart, branchLen);
    }
    if (!branch.empty() && !isValidBranch(branch, errMsg))
    {
        return ref;
    }

    ref.id = id;
    ref.arch = arch;
    ref.branch = branch;

    return ref;
}

/**
 * isValidAppName:
 * @string: The string to check
 *
 * Checks if @string is a valid application name.
 *
 * App names are composed of 3 or more elements separated by a period
 * ('.') character. All elements must contain at least one character.
 *
 * Each element must only contain the ASCII characters
 * "[A-Z][a-z][0-9]_-". Elements may not begin with a digit.
 * Additionally "-" is only allowed in the last element.
 *
 * App names must not begin with a '.' (period) character.
 *
 * App names must not exceed 255 characters in length.
 *
 * The above means that any app name is also a valid DBus well known
 * bus name, but not all DBus names are valid app names. The difference are:
 * 1) DBus name elements may contain '-' in the non-last element.
 * 2) DBus names require only two elements
 *
 * Returns: %TRUE if valid, %FALSE otherwise.
 */
bool KMBuildinsUtils::isValidAppName(const std::string &appName, std::string &errMsg)
{
    if (appName.length() > 255)
    {
        errMsg = _("Name can't be longer than 255 characters");
        return false;
    }

    std::regex regexStr("(\\w+\\.)+(\\w+\\.)+([\\w-]+)$");
    std::smatch matchResult;
    if (std::regex_match(appName, matchResult, regexStr))
    {
        std::regex regexDigit("^\\d.*");
        std::smatch matchDigit;
        for (auto element : matchResult)
        {
            std::string str = element.str();
            if (std::regex_match(str, matchDigit, regexDigit))
            {
                errMsg = _("Name is wrong! Elements may not begin with a digit.");
                return false;
            }
        }
    }
    else
    {
        errMsg = _("Name is wrong! Rules:\n"
                   "\t1）Names are composed of 3 or more elements separated by a period('.') character.\n"
                   "\t2) All elements must contain at least one character.\n"
                   "\t3) Each element must only contain the ASCII characters '[A-Z][a-z][0-9]_-'. Elements may not begin with a digit. Additionally '-' is only allowed in the last element.");
        return false;
    }

    return true;
}

/**
 * isValidBranch:
 * @string: The string to check
 * @string: return location for an error
 *
 * Checks if @string is a valid branch name.
 *
 * Branch names must only contain the ASCII characters
 * "[A-Z][a-z][0-9]_-.".
 * Branch names may not begin with a period.
 * Branch names must contain at least one character.
 *
 * Returns: %TRUE if valid, %FALSE otherwise.
 */
bool KMBuildinsUtils::isValidBranch(const std::string &branch, std::string &errMsg)
{
    std::regex regexStr("[\\w-\\.]+");
    std::smatch matchResult;
    if (std::regex_match(branch, matchResult, regexStr))
    {
        if (KMUtil::startsWith(branch, "."))
        {
            errMsg = _("Branch can't start with a period('.')");
            return false;
        }
    }
    else
    {
        errMsg = _("Branch is wrong! Rules:\n"
                   "\t1）Branch names must only contain the ASCII characters '[A-Z][a-z][0-9]_-.'\n"
                   "\t2) Branch names may not begin with a period.\n"
                   "\t3) Branch names must contain at least one character.");
        return false;
    }

    return true;
}

/**
 * @brief : 根据metadata信息获取extension列表
 * @param : [in] metadata, metadata信息
 * @param : [in] defaultArch，默认架构
 * @param : [in] defaultBranch，默认分支
 * @return: extension列表
 */
std::vector<KMExtension> KMBuildinsUtils::listExtensions(const std::shared_ptr<KMMetaData> &metadata, const std::string &defaultArch, const std::string &defaultBranch)
{
    std::vector<KMExtension> list;
    const std::vector<KMExtension> &extensions = metadata->getExtensions();

    KMStorageDir dir = KMStorageDir::getSystemDefaultDir();

    for (KMExtension extension : extensions)
    {
        std::string extensionNameWithTag(extension.name);
        std::string tag;
        KMBuildinsUtils::parseExtensionWithTag(extensionNameWithTag, extension.name, tag);

        extension.ref = KMUtil::constructRef(KaimingKinds::KAIMING_KINDS_RUNTIME, extension.name, defaultArch, defaultBranch);

        if (extension.versions.empty())
        {
            if (!extension.version.empty())
            {
                extension.ref.branch = extension.version;
                dir.addExtension(extension, list);
            }
            else
            {
                dir.addExtension(extension, list);
            }
        }
        else
        {
            for (const std::string &version : extension.versions)
            {
                extension.ref.branch = version;
                dir.addExtension(extension, list);
            }
        }
    }

    return list;
}

/**
 * parseExtensionWithTag：
 * @brief : 解析带tag的extension
 * @param : [in] extension, metadata文件中配置的extension等
 * @param : [out] name, 解析后得到的externsion的id
 * @param : [out] tag, 解析后得到的tag
 */
void KMBuildinsUtils::parseExtensionWithTag(const std::string &extension, std::string &name, std::string &tag)
{
    std::size_t pos = extension.find_first_of("@");
    if (std::string::npos != pos)
    {
        name = extension.substr(0, pos);
        /* Everything after the @ */
        tag = extension.substr(pos + 1);
        return;
    }

    name = extension;
    tag = "";
}

/**
 * isValidInitialNameCharacter:
 * @brief : 是否合法的命名字符
 * @param : [in] c, 待判断字符
 * @param : [in] allowDash，是否运行横线连接符'-'
 */
bool KMBuildinsUtils::isValidInitialNameCharacter(int c, bool allowDash)
{
    return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c == '_') || (allowDash && c == '-');
}

bool KMBuildinsUtils::isValidNameCharacter(int c, bool allowDash)
{
    return KMBuildinsUtils::isValidInitialNameCharacter(c, allowDash) || (c >= '0' && c <= '9');
}

/**
 * checkRequiredVersion:
 * @brief : 校验kaiming包版本要求
 * @param : [in] required_versions，要求的版本号列表
 * @param : [out] errMsg，错误信息
 * @return: true-满足开明包版本要求；false-不满足版本要求
 * @note  :
 *
 */
bool KMBuildinsUtils::checkRequiredVersion(const std::vector<std::string> &requiredVersions, std::string &errMsg)
{
    /* We handle handle multiple version requirements here. Each requirement must
     * be in the form major.minor.micro, and if the kaiming version matches the
     * major.minor part, t must be equal or later in the micro. If the major.minor part
     * doesn't exactly match any of the specified requirements it must be larger
     * than the maximum specified requirement.
     *
     * For example, specifying
     *   required-kaiming=1.6.2;1.4.2;1.0.2;
     * would allow kaiming versions:
     *  1.7.0, 1.6.2, 1.6.3, 1.4.2, 1.4.3, 1.0.2, 1.0.3
     * but not:
     *  1.6.1, 1.4.1 or 1.2.100.
     *
     * The goal here is to be able to specify a version (like 1.6.2 above) where a feature
     * was introduced, but also allow backports of said feature to earlier version series.
     *
     * Earlier versions that only support specifying one version will only look at the first
     * element in the list, so put the largest version first.
     */

    int maxRequiredMajor = 0, maxRequiredMinor = 0;
    std::string maxRequiredVersion = "0.0";

    int currentMajor = 0, currentMinor = 0, currentMicro = 0;
    if (sscanf(PACKAGE_VERSION, "%d.%d.%d", &currentMajor, &currentMinor, &currentMicro) != 3)
    {
        errMsg = _("Invalid kaiming version : ") + std::string(PACKAGE_VERSION);
        return false;
    }

    for (const std::string &requiredVersion : requiredVersions)
    {
        int requiredMajor, requiredMinor, requiredMicro;
        if (sscanf(requiredVersion.c_str(), "%d.%d.%d", &requiredMajor, &requiredMinor, &requiredMicro) != 3)
        {
            errMsg = _("Invalid required-kaiming argument : ") + requiredVersion;
            return false;
        }
        else
        {
            /* If kaiming is in the same major.minor series as the requirement, do a micro check */
            if (requiredMajor == currentMajor && requiredMinor == currentMinor)
            {
                if (requiredMicro <= currentMicro)
                {
                    return true;
                }
                else
                {
                    errMsg = _("Needs a later kaiming version ") + requiredVersion;
                    return false;
                }
            }

            /* Otherwise, keep track of the largest major.minor that is required */
            if ((requiredMajor > maxRequiredMajor) || (requiredMajor == maxRequiredMajor && requiredMinor > maxRequiredMinor))
            {
                maxRequiredMajor = requiredMajor;
                maxRequiredMinor = requiredMinor;
                maxRequiredVersion = requiredVersion;
            }
        }
    }

    if (maxRequiredMajor > currentMajor || (maxRequiredMajor == currentMajor && maxRequiredMinor > currentMinor))
    {
        errMsg = _("Needs a later kaiming version ") + maxRequiredVersion;
        return false;
    }

    return true;
}

/**
 * repoResolveRev:
 * @brief : 根据给定参数查找校验和，并在outRev中返回
 * @param : [in] repo, ostree仓库
 * @param : [in] collectionId，ostree仓库的collectionId
 * @param : [in] remoteName
 * @param : [in] refName
 * @param : [in] allowNoent
 * @param : [out] outRev，校验和
 * @param : [out] errMsg，错误信息
 */
bool KMBuildinsUtils::repoResolveRev(KMOstreeRepo *repo, const std::string &collectionId, const std::string &remoteName, const std::string &refName, bool allowNoent, std::string &outRev,
                                     std::string &errMsg)
{
    /* Wrapper that uses ostree_repo_resolve_collection_ref() and on failure falls
     * back to using ostree_repo_resolve_rev() for backwards compatibility. This
     * means we support refs/heads/, refs/remotes/, and refs/mirrors/. */

    if (!collectionId.empty())
    {
        /* Do a version check to ensure we have these:
         * https://github.com/ostreedev/ostree/pull/1821
         * https://github.com/ostreedev/ostree/pull/1825 */
#if OSTREE_CHECK_VERSION(2019, 2)
        const OstreeCollectionRef ostreeCollectionRef = {
            .collection_id = (char *)(collectionId.c_str()),
            .ref_name = (char *)(refName.c_str()),
        };
        OstreeRepoResolveRevExtFlags flags = remoteName.empty() ? OSTREE_REPO_RESOLVE_REV_EXT_LOCAL_ONLY : OSTREE_REPO_RESOLVE_REV_EXT_NONE;
        if (repo->resolveCollectionRef(&ostreeCollectionRef, allowNoent, flags, outRev))
            return true;
#endif // !OSTREE_CHECK_VERSION
    }

    /* There may be several remotes with the same branch (if we for
     * instance changed the origin) so prepend the current origin to
     * make sure we get the right one */
    bool ret = false;
    if (remoteName.empty())
    {
        ret = repo->resolveRevExt(refName, allowNoent, OSTREE_REPO_RESOLVE_REV_EXT_NONE, outRev);
    }
    else
    {
        std::string refspec = remoteName + ":" + refName;
        ret = repo->resolveRev(refspec, allowNoent, outRev);
    }

    if (!ret)
        errMsg = repo->errorMessage();

    return ret;
}

/**
 * mtreeEnsureDirMetadata:
 * @brief : 创建根目录元数据
 */
bool KMBuildinsUtils::mtreeEnsureDirMetadata(KMOstreeRepo *repo, KMOstreeMutableTree *tree, std::string &errMsg)
{
    KMFileInfo fileInfo("/");
    fileInfo.setFileType(G_FILE_TYPE_DIRECTORY);
    fileInfo.setAttribute("unix::uid", 0);
    fileInfo.setAttribute("unix::gid", 0);
    fileInfo.setAttribute("unix::mode", 040755);

    KMVariant dirmeta = KMOstreeCore::createDirectoryMetadata(fileInfo, nullptr);
    g_autofree guchar *csum = nullptr;
    if (!repo->writeMetadata(OSTREE_OBJECT_TYPE_DIR_META, nullptr, dirmeta, &csum))
    {
        errMsg = repo->errorMessage();
        return false;
    }

    std::string checksum = KMOstreeCore::checksumFromBytes(csum);
    tree->setMetadataChecksum(checksum.c_str());

    return true;
}

/**
 * pathMatchPrefix:
 * @brief : 判断路径string是否匹配pattern前缀，返回匹配结束后的部分字符串
 * @param : [in] pattern, 前缀模式
 * @param : [in] string, 路径
 * @return: 返回匹配结束后的部分字符串，不匹配返回null；
 */
const char *KMBuildinsUtils::pathMatchPrefix(const char *pattern, const char *string)
{
    char c, test;
    const char *tmp;

    while (*pattern == '/')
    {
        pattern++;
    }

    while (*string == '/')
    {
        string++;
    }

    while (true)
    {
        switch (c = *pattern++)
        {
        case 0:
            if (*string == '/' || *string == 0)
            {
                return string;
            }
            return nullptr;

        case '?':
            if (*string == '/' || *string == 0)
            {
                return nullptr;
            }
            string++;
            break;

        case '*':
            c = *pattern;

            while (c == '*')
            {
                c = *++pattern;
            }

            /* special case * at end */
            if (c == 0)
            {
                tmp = strchr(string, '/');
                if (tmp != nullptr)
                {
                    return tmp;
                }
                return string + strlen(string);
            }
            else if (c == '/')
            {
                string = strchr(string, '/');
                if (string == nullptr)
                {
                    return nullptr;
                }
                break;
            }

            while ((test = *string) != 0)
            {
                tmp = KMBuildinsUtils::pathMatchPrefix(pattern, string);
                if (tmp != nullptr)
                {
                    return tmp;
                }
                if (test == '/')
                {
                    break;
                }
                string++;
            }
            return nullptr;

        default:
            if (c != *string)
            {
                return nullptr;
            }
            string++;
            break;
        }
    }

    return nullptr; /* Should not be reached */
}

#define OSTREE_GIO_FAST_QUERYINFO ("standard::name,standard::type,standard::size,standard::is-symlink,standard::symlink-target," \
                                   "unix::device,unix::inode,unix::mode,unix::uid,unix::gid,unix::rdev")

/**
 * repoCollectSizes:
 * @brief : 获取仓库安装、下载大小
 * @param : [in] repo, ostree仓库
 * @param : [in] file, 里面封装了OstreeRepoFile对象指针
 * @param : [in] fileInfo，里面封装了GFileInfo对象指针
 * @param : [out] installedSize， 返回安装大小
 * @param : [out] downloadSize，返回下载dax
 * @param : [ount] errMsg，错误信息
 * @return: 返回操作是否成功
 */
bool KMBuildinsUtils::repoCollectSizes(KMOstreeRepo &repo, KMFile &file, KMFileInfo *fileInfo, std::uint64_t &installedSize, std::uint64_t &downloadSize, std::string &errMsg)
{
    if (fileInfo != nullptr && fileInfo->operator bool() && fileInfo->getFileType() == G_FILE_TYPE_REGULAR)
    {
        KMOstreeRepoFile repoFile(file);
        const char *checksum = repoFile.getChecksum();
        std::uint64_t fileSize = fileInfo->getSize();

        installedSize += ((fileSize + 511) / 512) * 512;

        std::uint64_t objSize = 0;
        if (!repo.queryObjectStorageSize(OSTREE_OBJECT_TYPE_FILE, checksum, objSize))
        {
            /* Ostree does not look at the staging directory when querying storage
                 size, so may return a NOT_FOUND error here. We work around this
                 by loading the object and walking back until we find the original
                 fd which we can fstat(). */
            if (repo.errorCode() != G_IO_ERROR_NOT_FOUND)
            {
                errMsg = repo.errorMessage();
                return false;
            }

            KMInputStream input;
            KMFileInfo tmpFileinfo;
            KMVariant tmpXattrs;
            if (!repo.loadFile(checksum, input, tmpFileinfo, tmpXattrs))
            {
                errMsg = repo.errorMessage();
                return false;
            }

            GInputStream *baseInput = input.get();
            while (G_IS_FILTER_INPUT_STREAM(baseInput))
                baseInput = g_filter_input_stream_get_base_stream(G_FILTER_INPUT_STREAM(baseInput));

            if (!G_IS_UNIX_INPUT_STREAM(baseInput))
            {
                errMsg = "Unable to find size of commit " + std::string(checksum) + ", not an unix stream";
                return false;
            }

            int fd = g_unix_input_stream_get_fd(G_UNIX_INPUT_STREAM(baseInput));
            struct stat stbuf;
            if (::fstat(fd, &stbuf) != 0)
            {
                errMsg = "Can't find commit size: " + std::string(checksum);
                return false;
            }

            objSize = stbuf.st_size;
        }

        downloadSize += objSize;
    }

    if (fileInfo == NULL || !fileInfo->operator bool() || fileInfo->getFileType() == G_FILE_TYPE_DIRECTORY)
    {
        KMFileEnumerator enumerator = file.children(OSTREE_GIO_FAST_QUERYINFO, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS);
        if (!enumerator)
        {
            errMsg = file.errorMessage();
            return false;
        }

        KMFileInfo childInfoTmp(nullptr);
        KMFile child;
        while (childInfoTmp = enumerator.nextFile())
        {
            const std::string &name = childInfoTmp.getName();
            child = file.getChild(name);

            if (!KMBuildinsUtils::repoCollectSizes(repo, child, &childInfoTmp, installedSize, downloadSize, errMsg))
            {
                return false;
            }
        }
    }

    return true;
}

bool KMBuildinsUtils::commitGetExtraDataSources(KMVariant &commitv, KMVariant &extraDataSources, std::string &errMsg)
{
    KMVariant commitMetadata = commitv.getChildValue(0);
    extraDataSources = commitMetadata.lookupValue("xa.extra-data-sources", "a(ayttays)");
    if (!extraDataSources)
    {
        errMsg = _("No extra data sources");
        return false;
    }

    return true;
}

bool KMBuildinsUtils::repoGetExtraDataSources(KMOstreeRepo &repo, const std::string &rev, KMVariant &extraDataSources, std::string &errMsg)
{
    KMVariant commitv;
    if (!repo.loadVariant(OSTREE_OBJECT_TYPE_COMMIT, rev.c_str(), commitv))
    {
        errMsg = repo.errorMessage();
        return false;
    }

    return KMBuildinsUtils::commitGetExtraDataSources(commitv, extraDataSources, errMsg);
}

void KMBuildinsUtils::repoParseExtraDataSources(KMVariant &extraDataSources, int index, const char **name, std::uint64_t &downloadSize, std::uint64_t &installedSize, const guchar **sha256,
                                                const char **uri)
{
    KMTrace("repoParseExtraDataSources invoke begin");

    g_autoptr(GVariant) sha256Variant = nullptr;
    extraDataSources.getChild(index, "(^aytt@ay&s)", name, &downloadSize, &installedSize, &sha256Variant, uri);

    downloadSize = GUINT64_FROM_BE(downloadSize);

    installedSize = GUINT64_FROM_BE(installedSize);

    if (sha256)
    {
        *sha256 = ostree_checksum_bytes_peek(sha256Variant);
    }

    KMTrace("repoParseExtraDataSources invoke end");
}

void KMBuildinsUtils::repoCollectExtraDataSizes(KMOstreeRepo &repo, const std::string &rev, std::uint64_t &installedSize, std::uint64_t &downloadSize)
{
    KMTrace("KMBuildinsUtils::repoCollectExtraDataSizes invoke begin");

    std::string errMsg;
    KMVariant extraDataSources;
    if (KMBuildinsUtils::repoGetExtraDataSources(repo, rev, extraDataSources, errMsg))
    {
        return;
    }

    std::size_t nExtraData = extraDataSources.childrenCount();
    if (nExtraData == 0)
    {
        return;
    }

    for (int i = 0; i < nExtraData; i++)
    {
        std::uint64_t extraDownloadSize = 0;
        std::uint64_t extraInstalledSize = 0;

        KMBuildinsUtils::repoParseExtraDataSources(extraDataSources, i, nullptr, extraDownloadSize, extraInstalledSize, nullptr, nullptr);

        installedSize += extraInstalledSize;
        downloadSize += extraDownloadSize;
    }

    KMTrace("KMBuildinsUtils::repoCollectExtraDataSizes invoke end");
}

std::uint32_t KMBuildinsUtils::repoGetSummaryHistoryLength(KMOstreeRepo &repo)
{
    GKeyFile *config = repo.getConfig();

    int length = g_key_file_get_integer(config, "kaiming", "sumary-history-length", NULL);

    if (length <= 0)
    {
        return KAIMING_SUMMARY_HISTORY_LENGTH_DEFAULT;
    }

    return length;
}

typedef struct
{
    std::uint64_t installedSize;
    std::uint64_t downloadSize;
    std::string metadataContents;
    std::vector<std::string> subsets;
    KMVariant sparseData;
    std::size_t commitSize;
    std::uint64_t commitTimestamp;
} CommitData;

static std::map<std::string, CommitData> populateCommitDataCache(KMOstreeRepo &repo, KMVariant indexVariant)
{
    KMTrace("populateCommitDataCache invoke begin");

    VarSummaryIndexRef index = varSummaryIndexFromGvariant(indexVariant.get());
    VarMetadataRef indexMetadata = varSummaryIndexGetMetadata(index);
    VarSummaryIndexSubsummariesRef subsummaries = varSummaryIndexGetSubsummaries(index);
    gsize subsummariesLSize = varSummaryIndexSubsummariesGetLength(subsummaries);
    guint32 cacheVersion = GUINT32_FROM_LE(varMetadataLookupUint32(indexMetadata, "xa.cache-version", 0));
    std::map<std::string, CommitData> commitDataCache;

    if (cacheVersion < KAIMING_XA_CACHE_VERSION)
    {
        KMDebug("Old summary cache version " + std::to_string(cacheVersion) + ", not using cache");
        return commitDataCache;
    }

    for (gsize i = 0; i < subsummariesLSize; i++)
    {
        VarSummaryIndexSubsummariesEntryRef entry = varSummaryIndexSubsummariesGetAt(subsummaries, i);
        VarSubsummaryRef subsummary = varSummaryIndexSubsummariesEntryGetValue(entry);
        gsize checksumBytesLen;
        const guchar *checksumBytes = varSubsummaryPeekChecksum(subsummary, &checksumBytesLen);
        if (G_UNLIKELY(checksumBytesLen != OSTREE_SHA256_DIGEST_LEN))
        {
            KMDebug("Invalid checksum for digested summary, not using cache");
            return commitDataCache;
        }
        std::string digest = KMOstreeCore::checksumFromBytes(checksumBytes);

        std::string name = varSummaryIndexSubsummariesEntryGetKey(entry);
        std::size_t pos = name.find_last_of('-');
        std::string subset;

        if (std::string::npos != pos)
        {
            subset = name.substr(0, pos);
        }

        std::string errMsg;
        KMVariant summaryVGvariant = KMBuildinsUtils::repoLoadDigestedSummary(repo, digest, errMsg);
        if (!summaryVGvariant)
        {
            KMDebug("Failed to load digested summary " + digest + ", not using cache");
            return commitDataCache;
        }

        // 请注意，索引引用的所有摘要都是新格式的
        VarSummaryRef summary;
        VarRefMapRef refMap;
        gsize nRefs;

        summary = varSummaryFromGvariant(summaryVGvariant.get());
        refMap = varSummaryGetRefMap(summary);
        nRefs = varRefMapGetLength(refMap);

        for (gsize j = 0; j < nRefs; j++)
        {
            VarRefMapEntryRef e = varRefMapGetAt(refMap, j);
            const char *ref = varRefMapEntryGetRef(e);
            if (!KMBuildinsUtils::isAppOrRuntimeOrAppstreamRef(ref))
            {
                continue;
            }

            VarRefInfoRef info = varRefMapEntryGetInfo(e);
            gsize commitBytesLen;
            const guchar *commitBytes = varRefInfoPeekChecksum(info, &commitBytesLen);
            if (G_UNLIKELY(commitBytesLen != OSTREE_SHA256_DIGEST_LEN))
            {
                continue;
            }

            VarMetadataRef commitMetadata = varRefInfoGetMetadata(info);
            VarVariantRef xaDataVariant;
            if (!varMetadataLookup(commitMetadata, "xa.data", NULL, &xaDataVariant) || !isVarVariantType(xaDataVariant, G_VARIANT_TYPE("(tts)")))
            {
                KMDebug(std::string("Missing xa.data for ref ") + ref + ", not using cache");
                return commitDataCache;
            }

            VarCacheDataRef xaData;
            xaData = varCacheDataFromVariant(xaDataVariant);

            guint64 commitSize = varRefInfoGetCommitSize(info);

            std::string rev = KMOstreeCore::checksumFromBytes(commitBytes);
            CommitData revData;
            if (commitDataCache.find(rev) == commitDataCache.end())
            {
                KMVariantBuilder sparseBuilder("a{sv}");

                revData.installedSize = varCacheDataGetInstalledSize(xaData);
                revData.downloadSize = varCacheDataGetDownloadSize(xaData);
                revData.metadataContents = varCacheDataGetMetadata(xaData);
                revData.commitSize = commitSize;
                revData.commitTimestamp = GUINT64_FROM_BE(varMetadataLookupUint64(commitMetadata, OSTREE_COMMIT_TIMESTAMP2, 0));

                bool hasSparse = false;
                /* Get sparse data */
                gsize len = varMetadataGetLength(commitMetadata);
                for (gsize k = 0; k < len; k++)
                {
                    VarMetadataEntryRef m = varMetadataGetAt(commitMetadata, k);
                    const char *m_key = varMetadataEntryGetKey(m);
                    if (!g_str_has_prefix(m_key, "ot.") && !g_str_has_prefix(m_key, "ostree.") && strcmp(m_key, "xa.data") != 0)
                    {
                        VarVariantRef v = varMetadataEntryGetValue(m);
                        GVariant *vv = varVariantDupToGVaraint(v);
                        sparseBuilder.add("{sv}", m_key, g_variant_get_child_value(vv, 0));
                        hasSparse = true;
                    }
                }

                if (hasSparse)
                {
                    revData.sparseData = sparseBuilder.end();
                }
                commitDataCache.insert({ rev, revData });
            }
            else
            {
                revData = commitDataCache.at(rev);
            }

            if (!subset.empty())
            {
                if (!KMUtil::contains(revData.subsets, subset))
                {
                    revData.subsets.push_back(subset);
                }
            }
        }
    }

    KMTrace("populateCommitDataCache invoke end");
    return commitDataCache;
}

static bool readCommitData(KMOstreeRepo &repo, const std::string &ref, const std::string &checksum, CommitData &commitData, std::string &errMsg)
{
    KMTrace("readCommitData invoke begin");

    KMFile root;
    std::string commit;
    if (!repo.readCommit(ref.c_str(), root, commit))
    {
        errMsg = repo.errorMessage();
        return false;
    }

    KMVariant commit_v;
    if (!repo.loadCommit(commit.c_str(), commit_v, nullptr))
    {
        errMsg = repo.errorMessage();
        return false;
    }

    KMVariant commitMetadata = commit_v.getChildValue(0);
    std::string metadataContents;
    KMFile metadata;
    if (!commitMetadata.lookup("xa.metadata", metadataContents))
    {
        metadata = root.getChild("metadata");
        if (!metadata.loadContents(metadataContents))
        {
            metadataContents = "";
        }
    }

    std::uint64_t installedSize = 0;
    std::uint64_t downloadSize = 0;
    if (commitMetadata.lookup("xa.installed-size", installedSize) && commitMetadata.lookup("xa.download-size", downloadSize))
    {
        installedSize = GUINT64_FROM_BE(installedSize);
        downloadSize = GUINT64_FROM_BE(downloadSize);
    }
    else
    {
        if (!KMBuildinsUtils::repoCollectSizes(repo, root, nullptr, installedSize, downloadSize, errMsg))
        {
            return false;
        }
    }

    std::vector<std::string> subsets;
    commitMetadata.lookup("xa.subsets", subsets);

    KMBuildinsUtils::repoCollectExtraDataSizes(repo, checksum, installedSize, downloadSize);

    commitData.installedSize = installedSize;
    commitData.downloadSize = downloadSize;
    commitData.metadataContents = metadataContents;
    commitData.subsets = subsets;
    commitData.commitSize = commit_v.getSize();
    commitData.commitTimestamp = KMOstreeCore::getTimestampOfCommit(commit_v);

    std::string eol, eolRebase;
    commitMetadata.lookup(OSTREE_COMMIT_META_KEY_ENDOFLIFE, eol);
    commitMetadata.lookup(OSTREE_COMMIT_META_KEY_ENDOFLIFE_REBASE, eolRebase);
    int tokenType = -1;
    if (commitMetadata.lookup("xa.token-type", tokenType))
    {
        tokenType = GINT32_FROM_LE(tokenType);
    }

    KMVariant extraDataSources;
    std::uint64_t totalExtraDataDownloadSize = 0;
    int nExtraData = 0;
    if (KMBuildinsUtils::commitGetExtraDataSources(commit_v, extraDataSources, errMsg))
    {
        nExtraData = extraDataSources.childrenCount();
        for (int i = 0; i < nExtraData; i++)
        {
            std::uint64_t extraDownloadSize = 0;
            std::uint64_t extraInstalledSize = 0;
            KMBuildinsUtils::repoParseExtraDataSources(extraDataSources, i, nullptr, extraDownloadSize, extraInstalledSize, nullptr, nullptr);
            totalExtraDataDownloadSize += extraDownloadSize;
        }
    }

    if (!eol.empty() || !eolRebase.empty() || tokenType >= 0 || nExtraData > 0)
    {
        KMVariantBuilder builder("a{sv}");
        if (!eol.empty())
        {
            builder.add("{sv}", KAIMING_SPARSE_CACHE_KEY_ENDOFLINE, g_variant_new_string(eol.c_str()));
        }
        if (!eolRebase.empty())
        {
            builder.add("{sv}", KAIMING_SPARSE_CACHE_KEY_ENDOFLINE_REBASE, g_variant_new_string(eolRebase.c_str()));
        }
        if (tokenType >= 0)
        {
            builder.add("{sv}", KAIMING_SPARSE_CACHE_KEY_TOKEN_TYPE, g_variant_new_int32(GINT32_TO_LE(tokenType)));
        }
        if (nExtraData >= 0)
        {
            builder.add("{sv}", KAIMING_SPARSE_CACHE_KEY_EXTRA_DATA_SIZE, g_variant_new("(ut)", GUINT32_TO_LE(nExtraData), GUINT64_TO_LE(totalExtraDataDownloadSize)));
        }

        commitData.sparseData = builder.end();
    }

    KMTrace("readCommitData invoke end");
    return true;
}

static void addSummaryMetadata(KMOstreeRepo &repo, KMVariantBuilder &metadataBuilder)
{
    KMTrace("addSummaryMetadata invoke begin");

    GKeyFile *config = repo.getConfig();

    g_autofree char *title = nullptr;
    g_autofree char *comment = nullptr;
    g_autofree char *description = nullptr;
    g_autofree char *homepage = nullptr;
    g_autofree char *icon = nullptr;
    g_autofree char *redirectUrl = nullptr;
    g_autofree char *defaultBranch = nullptr;
    g_autofree char *remoteModeStr = nullptr;
    g_autofree char *authenticatorName = nullptr;
    g_autofree char *gpgKeys = nullptr;
    g_auto(GStrv) configKeys = nullptr;
    int authenticatorInstall = -1;
    gboolean deployCollectionId = FALSE;
    gboolean deploySideloadCollectionId = FALSE;
    gboolean tombstoneCommits = FALSE;

    if (config)
    {
        remoteModeStr = g_key_file_get_string(config, "core", "mode", nullptr);
        tombstoneCommits = g_key_file_get_boolean(config, "core", "tombstone-commits", nullptr);

        title = g_key_file_get_string(config, "kaiming", "title", nullptr);
        comment = g_key_file_get_string(config, "kaiming", "comment", nullptr);
        description = g_key_file_get_string(config, "kaiming", "description", nullptr);
        homepage = g_key_file_get_string(config, "kaiming", "homepage", nullptr);
        icon = g_key_file_get_string(config, "kaiming", "icon", nullptr);
        defaultBranch = g_key_file_get_string(config, "kaiming", "default-branch", nullptr);
        gpgKeys = g_key_file_get_string(config, "kaiming", "gpg-keys", nullptr);
        redirectUrl = g_key_file_get_string(config, "kaiming", "redirect-url", nullptr);
        deploySideloadCollectionId = g_key_file_get_boolean(config, "kaiming", "deploy-sideload-collection-id", nullptr);
        deployCollectionId = g_key_file_get_boolean(config, "kaiming", "deploy-collection-id", nullptr);
        authenticatorName = g_key_file_get_string(config, "kaiming", "authenticator-name", nullptr);
        if (g_key_file_has_key(config, "kaiming", "authenticator-install", nullptr))
        {
            authenticatorInstall = g_key_file_get_boolean(config, "kaiming", "authenticator-install", nullptr);
        }

        configKeys = g_key_file_get_keys(config, "kaiming", nullptr, nullptr);
    }

    std::string collectionId = repo.getCollectionId();

    metadataBuilder.add("{sv}", "ostree.summary.mode", g_variant_new_string(remoteModeStr ? remoteModeStr : "bare"));
    metadataBuilder.add("{sv}", "ostree.summary.tombstone-commits", g_variant_new_boolean(tombstoneCommits));
    metadataBuilder.add("{sv}", "ostree.summary.indexed-deltas", g_variant_new_boolean(TRUE));
    metadataBuilder.add("{sv}", "ostree.summary.last-modified", g_variant_new_uint64(GUINT64_TO_BE(g_get_real_time() / G_USEC_PER_SEC)));

    if (!collectionId.empty())
    {
        metadataBuilder.add("{sv}", "ostree.summary.collection-id", g_variant_new_string(collectionId.c_str()));
    }

    if (title)
    {
        metadataBuilder.add("{sv}", "xa.title", g_variant_new_string(title));
    }

    if (comment)
    {
        metadataBuilder.add("{sv}", "xa.comment", g_variant_new_string(comment));
    }

    if (description)
    {
        metadataBuilder.add("{sv}", "xa.description", g_variant_new_string(description));
    }

    if (homepage)
    {
        metadataBuilder.add("{sv}", "xa.homepage", g_variant_new_string(homepage));
    }

    if (icon)
    {
        metadataBuilder.add("{sv}", "xa.icon", g_variant_new_string(icon));
    }

    if (redirectUrl)
    {
        metadataBuilder.add("{sv}", "xa.redirect-url", g_variant_new_string(redirectUrl));
    }

    if (defaultBranch)
    {
        metadataBuilder.add("{sv}", "xa.default-branch", g_variant_new_string(defaultBranch));
    }

    if (deployCollectionId && !collectionId.empty())
    {
        metadataBuilder.add("{sv}", OSTREE_META_KEY_DEPLOY_COLLECTION_ID, g_variant_new_string(collectionId.c_str()));
    }
    else if (deploySideloadCollectionId && !collectionId.empty())
    {
        metadataBuilder.add("{sv}", "xa.deploy-collection-id", g_variant_new_string(collectionId.c_str()));
    }
    else if (deployCollectionId)
    {
        g_debug("Ignoring deploy-collection-id=true because no collection ID is set.");
    }

    if (authenticatorName)
    {
        metadataBuilder.add("{sv}", "xa.authenticator-name", g_variant_new_string(authenticatorName));
    }

    if (authenticatorInstall != -1)
    {
        metadataBuilder.add("{sv}", "xa.authenticator-install", g_variant_new_boolean(authenticatorInstall));
    }

    metadataBuilder.add("{sv}", "xa.cache-version", g_variant_new_uint32(GUINT32_TO_LE(KAIMING_XA_CACHE_VERSION)));

    if (configKeys != NULL)
    {
        for (int i = 0; configKeys[i] != NULL; i++)
        {
            const char *key = configKeys[i];
            g_autofree char *xaKey = NULL;
            g_autofree char *value = NULL;

            if (!g_str_has_prefix(key, "authenticator-options."))
            {
                continue;
            }

            value = g_key_file_get_string(config, "kaiming", key, NULL);
            if (value == NULL)
            {
                continue;
            }

            xaKey = g_strconcat("xa.", key, NULL);
            metadataBuilder.add("{sv}", xaKey, g_variant_new_string(value));
        }
    }

    if (gpgKeys)
    {
        guchar *decoded;
        gsize decodedLen;

        gpgKeys = g_strstrip(gpgKeys);
        decoded = g_base64_decode(gpgKeys, &decodedLen);

        metadataBuilder.add("{sv}", "xa.gpg-keys", g_variant_new_from_data(G_VARIANT_TYPE("ay"), decoded, decodedLen, TRUE, (GDestroyNotify)g_free, decoded));
    }

    KMTrace("addSummaryMetadata invoke end");
}

static std::vector<std::string> mapKeys(const std::map<std::string, string> &mapOfStringPair)
{
    std::vector<std::string> keys;

    for (auto &pair : mapOfStringPair)
    {
        keys.push_back(pair.first);
    }

    return keys;
}

static char *appstreamRefGetSubset(const char *ref)
{
    if (!g_str_has_prefix(ref, "appstream2/"))
    {
        return NULL;
    }

    const char *rest = ref + strlen("appstream2/");
    const char *dash = strrchr(rest, '-');
    if (dash == NULL)
    {
        return NULL;
    }

    return g_strndup(rest, dash - rest);
}

static void variantDictMerge(GVariantDict *dict, GVariant *toMerge)
{
    GVariantIter iter;
    gchar *key;
    GVariant *value;

    if (toMerge)
    {
        g_variant_iter_init(&iter, toMerge);
        while (g_variant_iter_next(&iter, "{sv}", &key, &value))
        {
            g_variant_dict_insert_value(dict, key, value);
            g_variant_unref(value);
            g_free(key);
        }
    }
}

static void ostreeParseDeltaName(const std::string &deltaName, std::string &from, std::string &to)
{
    std::vector<std::string> parts = KMUtil::splitString(deltaName, "-");

    if (parts.size() >= 2)
    {
        from = parts.at(0);
        to = parts.at(1);
    }
    else
    {
        from = "";
        to = deltaName;
    }
}

static std::string staticDeltaPathBase(const std::string &dir, const std::string &from, const std::string &to)
{
    unsigned char csumTo[OSTREE_SHA256_DIGEST_LEN];
    char toB64[44];
    unsigned char csumToCopy[OSTREE_SHA256_DIGEST_LEN];
    std::string ret(dir);

    KMOstreeCore::checksumInplaceToBytes(to.c_str(), csumTo);
    KMOstreeCore::checksumB64InplaceFromBytes(csumTo, toB64);
    KMOstreeCore::checksumB64InplaceToBytes(toB64, csumToCopy);

    assert(memcmp(csumTo, csumToCopy, OSTREE_SHA256_DIGEST_LEN) == 0);

    if (!from.empty())
    {
        unsigned char csumFrom[OSTREE_SHA256_DIGEST_LEN];
        char fromB64[44];
        KMOstreeCore::checksumInplaceToBytes(from.c_str(), csumFrom);
        KMOstreeCore::checksumB64InplaceFromBytes(csumFrom, fromB64);

        ret += fromB64[0];
        ret += fromB64[1];
        ret += '/';
        ret += fromB64 + 2;
        ret += '-';
    }

    ret += toB64[0];
    ret += toB64[1];
    if (from.empty())
    {
        ret += '/';
    }
    ret += toB64 + 2;

    return ret;
}

static std::string ostreeGetRelativeStaticDeltaPath(const std::string &from, const std::string &to, const std::string &target)
{
    std::string ret = staticDeltaPathBase("deltas/", from, to);

    if (!target.empty())
    {
        KMUtil::buildFilename(ret, target);
    }

    return ret;
}

static std::string ostreeGetRelativeStaticDeltaSuperblockPath(const std::string &from, const std::string &to)
{
    return ostreeGetRelativeStaticDeltaPath(from, to, "superblock");
}

static KMVariant ostreeRepoStaticDeltaSuperblockDigest(KMOstreeRepo &repo, const std::string &from, const std::string &to, std::string &errMsg)
{
    std::string superblock = ostreeGetRelativeStaticDeltaSuperblockPath(from, to);

    int flags = O_RDONLY | O_CLOEXEC | O_NOCTTY;
    int fd = TEMP_FAILURE_RETRY(::openat(repo.getDfd(), superblock.c_str(), flags));
    if (fd == -1)
    {
        errMsg = "Failed to openat ostree file : " + superblock;
        return KMVariant();
    }

    KMFileGuard fg(fd);
    g_autoptr(GBytes) superblockContent = KMUtil::readAllBytesByFd(fd, errMsg);
    if (!superblockContent)
    {
        errMsg = "Failed to read ostree file : " + superblock;
        return KMVariant();
    }

    unsigned char digest[OSTREE_SHA256_DIGEST_LEN];
    std::size_t len = 0;
    KMChecksum checksum(G_CHECKSUM_SHA256);
    checksum.update((unsigned char *)g_bytes_get_data(superblockContent, NULL), g_bytes_get_size(superblockContent));
    len = sizeof digest;
    checksum.getDigest(digest, &len);

    return g_variant_new_from_data(G_VARIANT_TYPE("ay"), g_memdup(digest, len), len, FALSE, g_free, FALSE);
}

static KMVariant generateSummary(KMOstreeRepo &repo, bool compatFormat, const std::map<std::string, std::string> &refs, const std::map<std::string, CommitData> &commitDataCache,
                                 const std::vector<std::string> &deltaNames, const char *subset, const char **summaryArches, std::string &errMsg)
{
    KMTrace("generateSummary invoke begin");

    KMVariantBuilder metadataBuilder("a{sv}");
    KMVariantBuilder refsBuilder("a(s(taya{sv}))");
    KMVariantBuilder refDataBuilder("a{s(tts)}");
    KMVariantBuilder refSparseDataBuilder("a{sa{sv}}");

    // 在新的格式中，它会出现在摘要索引中
    if (compatFormat)
    {
        addSummaryMetadata(repo, metadataBuilder);
    }

    std::vector<std::string> orderedKeys = mapKeys(refs);
    std::sort(orderedKeys.begin(), orderedKeys.end());

    std::set<std::string> summaryArches_ht;
    if (summaryArches)
    {
        for (int i = 0; summaryArches[i] != NULL; i++)
        {
            const char *arch = summaryArches[i];
            summaryArches_ht.insert(arch);
        }
    }

    // 计算要保留的提交
    std::set<std::string> commits;
    for (const std::string &ref : orderedKeys)
    {
        std::string rev = refs.at(ref);

        if (summaryArches)
        {
            /* NOTE: Non-arched (unknown) refs get into all summary versions */
            std::string arch = KMBuildinsUtils::getArchForRef(ref);
            if (!arch.empty() && summaryArches_ht.end() == summaryArches_ht.find(arch))
            {
                continue; // 过滤
            }
        }

        bool found = commitDataCache.find(rev) != commitDataCache.end();
        CommitData revData;
        if (found)
        {
            revData = commitDataCache.at(rev);
        }

        if (subset && *subset != 0)
        {
            /* 子集摘要保留了appstream2/$subset-$arch，并且没有appstream/ compat分支 */
            if (KMUtil::startsWith(ref, "appstream/"))
            {
                continue; /* 子集中没有兼容分支 */
            }
            else if (KMUtil::startsWith(ref, "appstream2/"))
            {
                g_autofree char *refSubset = appstreamRefGetSubset(ref.c_str());
                if (refSubset == NULL)
                {
                    continue; /* 非子集，忽略 */
                }

                if (strcmp(subset, refSubset) != 0)
                {
                    continue; /* 不同的子集，忽略 */
                }

                /* 否则，保留 */
            }
            else if (found)
            {
                if (revData.subsets.empty() || KMUtil::contains(revData.subsets, subset))
                {
                    continue; /* Ref不在此子集中 */
                }
            }
        }
        else
        {
            /* 非子集，保留除子集以外的所有内容appstream-refs */
            g_autofree char *refSubset = appstreamRefGetSubset(ref.c_str());
            if (refSubset != NULL)
            {
                continue; /* 非子集，忽略 */
            }
        }

        commits.insert(rev);
    }

    // 创建参考文献列表、元数据和稀疏数据
    for (const std::string &ref : orderedKeys)
    {
        std::string rev = refs.at(ref);
        if (commits.end() == commits.find(rev))
        {
            continue; // 筛选出提交（按arch和subset）
        }

        bool found = commitDataCache.find(rev) != commitDataCache.end();
        CommitData revData;
        if (found && KMBuildinsUtils::isAppOrRuntimeOrAppstreamRef(ref))
        {
            revData = commitDataCache.at(rev);
        }

        std::uint64_t commitSize;
        std::uint64_t commitTimestamp;
        if (found)
        {
            commitSize = revData.commitSize;
            commitTimestamp = revData.commitTimestamp;
        }
        else
        {
            KMVariant commitObj;
            if (!repo.loadVariant(OSTREE_OBJECT_TYPE_COMMIT, rev.c_str(), commitObj))
            {
                errMsg = repo.errorMessage();
                return KMVariant();
            }
            commitSize = commitObj.getSize();
            commitTimestamp = KMOstreeCore::getTimestampOfCommit(commitObj);
        }

        KMVariantDict commitMetadataDict;
        if (!compatFormat && found)
        {
            commitMetadataDict.insert("xa.data", g_variant_new("(tts)", GUINT64_TO_BE(revData.installedSize), GUINT64_TO_BE(revData.downloadSize), revData.metadataContents.c_str()));
            variantDictMerge(commitMetadataDict.get(), revData.sparseData.get());
        }

        // 对于新的格式摘要，我们使用较短的时间戳名称以节省空间
        commitMetadataDict.insert(compatFormat ? OSTREE_COMMIT_TIMESTAMP : OSTREE_COMMIT_TIMESTAMP2, GUINT64_TO_BE(commitTimestamp));

        refsBuilder.addValue(g_variant_new("(s(t@ay@a{sv}))", ref.c_str(), commitSize, KMOstreeCore::checksumToBytes_v(rev).get(), commitMetadataDict.end().get()));

        if (compatFormat && found)
        {
            refDataBuilder.add("{s(tts)}", ref.c_str(), GUINT64_TO_BE(revData.installedSize), GUINT64_TO_BE(revData.downloadSize), revData.metadataContents.c_str());
            if (revData.sparseData)
            {
                refSparseDataBuilder.add("{s@a{sv}}", ref.c_str(), revData.sparseData.get());
            }
        }
    }

    if (!deltaNames.empty())
    {
        KMVariantDict deltasDict;
        for (const std::string &deltaName : deltaNames)
        {
            std::string from, to;
            ostreeParseDeltaName(deltaName, from, to);

            // 只将增量保持为摘要中的引用（即，不经过arch过滤或随机）
            if (commits.end() == commits.find(to))
            {
                continue;
            }

            KMVariant digest = ostreeRepoStaticDeltaSuperblockDigest(repo, from, to, errMsg);
            if (!digest)
            {
                return digest;
            }

            deltasDict.insert(deltaName, digest);
        }

        metadataBuilder.add("{sv}", "ostree.static-deltas", deltasDict.end().get());
    }

    if (compatFormat)
    {
        /*
            注意：xa.cache不需要支持所列refs的集合ID，因为xa.cache元数据存储在ostree元数据ref上，该元数据本身与集合ID强绑定，
            因此集合ID绑定到xa.cache中的所有refs。如果客户端使用的是摘要文件（而不是ostree元数据分支）中的xa.cache数据，那么它们太旧了，无论如何都不关心集合ID。
        */
        metadataBuilder.add("{sv}", "xa.cache", refDataBuilder.end().get());
        metadataBuilder.add("{sv}", "xa.sparse-cache", refSparseDataBuilder.end().get());
    }
    else
    {
        metadataBuilder.add("{sv}", "xa.summary-version", g_variant_new_uint32(GUINT32_TO_LE(KAIMING_XA_SUMMARY_VERSION)));
    }

    KMVariantBuilder summaryBuilder(OSTREE_SUMMARY_GVARIANT_STRING);
    summaryBuilder.addValue(refsBuilder.end());
    summaryBuilder.addValue(metadataBuilder.end());

    KMTrace("generateSummary invoke end");
    return summaryBuilder.end();
}

static std::string repoSaveDigestedSummary(KMOstreeRepo &repo, const std::string &name, KMVariant &summary, std::string &errMsg)
{
    KMTrace("repoSaveDigestedSummary invoke begin");

    int repoDfd = repo.getDfd();

    if (!KMDirFd::mkdirPAt(repoDfd, "summaries", 0775))
    {
        errMsg = "Failed to mkdir summaries in repo.";
        return "";
    }

    KMChecksum checksum(G_CHECKSUM_SHA256);
    checksum.update((const unsigned char *)summary.getData(), summary.getSize());
    std::string digest = checksum.getString();

    std::string filename = digest + ".gz";
    std::string path = KMUtil::buildFilename("summaries", filename);

    // 检查预先存在的（未截断的）副本，避免重新写入
    struct stat stbuf;
    if (::fstatat(repoDfd, path.c_str(), &stbuf, 0) == 0 && stbuf.st_size != 0)
    {
        g_debug("Reusing digested summary at %s for %s", path.c_str(), name.c_str());
        return digest;
    }

    g_autoptr(GBytes) data = NULL;
    g_autoptr(GBytes) compressedData = NULL;
    g_autoptr(GError) error = NULL;

    data = summary.getDataAsBytes();
    compressedData = KMBuildinsUtils::zlibCompressBytes(data, -1, &error);
    if (compressedData == NULL)
    {
        errMsg = error->message;
        return "";
    }

    if (!KMUtil::replaceFileContentsWithPermsAt(repoDfd, path.c_str(), (const unsigned char *)(g_bytes_get_data(compressedData, NULL)), g_bytes_get_size(compressedData), -1, -1, -1,
                                                repo.getDisableFsync() ? FILE_REPLACE_NODATASYNC : FILE_REPLACE_DATASYNC_NEW, errMsg))
    {
        return "";
    }

    g_debug("Wrote digested summary at %s for %s", path.c_str(), name.c_str());

    KMTrace("repoSaveDigestedSummary invoke end");
    return digest;
}

static bool repoSaveDigestedSummaryDelta(KMOstreeRepo &repo, const std::string &fromDigest, const std::string &toDigest, GBytes *delta, std::string &errMsg)
{
    KMTrace("repoSaveDigestedSummaryDelta invoke begin");

    int repoDfd = repo.getDfd();

    if (!KMDirFd::mkdirPAt(repoDfd, "summaries", 0775))
    {
        errMsg = "Failed to mkdir summaries in repo.";
        return false;
    }

    std::string filename = fromDigest + "-" + toDigest + ".delta";
    std::string path = KMUtil::buildFilename("summaries", filename);

    // 检查先前存在的相同大小的副本，避免重新写入
    struct stat stbuf;
    if (::fstatat(repoDfd, path.c_str(), &stbuf, 0) == 0 && stbuf.st_size == g_bytes_get_size(delta))
    {
        g_debug("Reusing digested summary-diff for %s", filename.c_str());
        return true;
    }

    if (!KMUtil::replaceFileContentsWithPermsAt(repoDfd, path.c_str(), (const unsigned char *)(g_bytes_get_data(delta, NULL)), g_bytes_get_size(delta), -1, -1, -1,
                                                repo.getDisableFsync() ? FILE_REPLACE_NODATASYNC : FILE_REPLACE_DATASYNC_NEW, errMsg))
    {
        return false;
    }

    g_debug("Wrote digested summary delta at %s", path.c_str());

    KMTrace("repoSaveDigestedSummaryDelta invoke end");
    return true;
}

static KMVariant readDigestedSummary(KMOstreeRepo &repo, const std::string &digest, std::map<std::string, KMVariant> &digestedSummaryCache, std::string &errMsg)
{
    KMTrace("readDigestedSummary invoke begin");

    if (digestedSummaryCache.end() != digestedSummaryCache.find(digest))
    {
        return digestedSummaryCache.at(digest);
    }

    KMVariant loaded = KMBuildinsUtils::repoLoadDigestedSummary(repo, digest, errMsg);

    if (loaded)
    {
        digestedSummaryCache.insert({ digest, loaded });
    }

    KMTrace("readDigestedSummary invoke end");
    return loaded;
}

typedef enum
{
    DIFF_OP_KIND_RESUSE_OLD,
    DIFF_OP_KIND_SKIP_OLD,
    DIFF_OP_KIND_DATA,
} DiffOpKind;

typedef struct
{
    DiffOpKind kind;
    gsize size;
} DiffOp;

typedef struct
{
    const guchar *oldData;
    const guchar *newData;

    GArray *ops;
    GArray *data;

    gsize lastOldOffset;
    gsize lastNewOffset;
} DiffData;

static gsize matchBytesAtStart(const guchar *data1, gsize data1Len, const guchar *data2, gsize data2Len)
{
    gsize len = 0;
    gsize maxLen = MIN(data1Len, data2Len);

    while (len < maxLen)
    {
        if (*data1 != *data2)
        {
            break;
        }
        data1++;
        data2++;
        len++;
    }

    return len;
}

static gsize matchBytesAtEnd(const guchar *data1, gsize data1Len, const guchar *data2, gsize data2Len)
{
    gsize len = 0;
    gsize maxLen = MIN(data1Len, data2Len);

    data1 += data1Len - 1;
    data2 += data2Len - 1;

    while (len < maxLen)
    {
        if (*data1 != *data2)
        {
            break;
        }
        data1--;
        data2--;
        len++;
    }

    return len;
}

static DiffOp *diffEnsureOp(DiffData *data, DiffOpKind kind)
{
    if (data->ops->len == 0 || g_array_index(data->ops, DiffOp, data->ops->len - 1).kind != kind)
    {
        DiffOp op = { kind, 0 };
        g_array_append_val(data->ops, op);
    }

    return &g_array_index(data->ops, DiffOp, data->ops->len - 1);
}

static void diffEmitReuse(DiffData *data, gsize size)
{
    if (size == 0)
    {
        return;
    }

    DiffOp *op = diffEnsureOp(data, DIFF_OP_KIND_RESUSE_OLD);
    op->size += size;
}

static void diffEmitSkip(DiffData *data, gsize size)
{
    if (size == 0)
    {
        return;
    }

    DiffOp *op = diffEnsureOp(data, DIFF_OP_KIND_SKIP_OLD);
    op->size += size;
}

static void diffEmitData(DiffData *data, gsize size, const guchar *newData)
{
    if (size == 0)
    {
        return;
    }

    DiffOp *op = diffEnsureOp(data, DIFF_OP_KIND_DATA);
    op->size += size;

    g_array_append_vals(data->data, newData, size);
}

static void diffConsumeBlock2(DiffData *data, gsize consumeOldOffset, gsize consumeOldSize, gsize produceNewOffset, gsize produceNewSize)
{
    /* 从$consume_old_offset中消耗了$consumer_old_size字节，以$produce_new_size产生$produced_new_ssize字节 */
    /* 首先，复制块的任何匹配前缀的旧数据 */

    gsize prefixLen = matchBytesAtStart(data->oldData + consumeOldOffset, consumeOldSize, data->newData + produceNewOffset, produceNewSize);
    diffEmitReuse(data, prefixLen);

    consumeOldSize -= prefixLen;
    consumeOldOffset += prefixLen;

    produceNewSize -= prefixLen;
    produceNewOffset += prefixLen;

    /* 然后,找到其余的匹配后缀 */
    gsize suffixLen = matchBytesAtEnd(data->oldData + consumeOldOffset, consumeOldSize, data->newData + produceNewOffset, produceNewSize);

    /* 跳过源数据直到后缀匹配 */
    diffEmitSkip(data, consumeOldSize - suffixLen);

    /* 复制新数据直到后缀匹配 */
    diffEmitData(data, produceNewSize - suffixLen, data->newData + produceNewOffset);

    diffEmitReuse(data, suffixLen);
}

static void diffConsumeBlock(DiffData *data, gssize consumeOldOffset, gsize consumeOldSize, gssize produceNewOffset, gsize produceNewSize)
{
    if (consumeOldOffset == -1)
        consumeOldOffset = data->lastOldOffset;
    if (produceNewOffset == -1)
        produceNewOffset = data->lastNewOffset;

    /* 从$consumer_old_offset消耗了$consumer_old_size字节到以$produce_new_size生成$produte-new_size字节，
        但是，虽然发出的块是有序的，但它们可能不会覆盖每个字节，所以分别发出inbetwen块。 */

    if (consumeOldOffset != data->lastOldOffset || produceNewOffset != data->lastNewOffset)
        diffConsumeBlock2(data, data->lastOldOffset, consumeOldOffset - data->lastOldOffset, data->lastNewOffset, produceNewOffset - data->lastNewOffset);

    diffConsumeBlock2(data, consumeOldOffset, consumeOldSize, produceNewOffset, produceNewSize);

    data->lastOldOffset = consumeOldOffset + consumeOldSize;
    data->lastNewOffset = produceNewOffset + produceNewSize;
}

static GBytes *diffEncode(DiffData *data, GError **error)
{
    g_autoptr(GOutputStream) mem = g_memory_output_stream_new_resizable();
    g_autoptr(GDataOutputStream) out = g_data_output_stream_new(mem);
    gsize opsCount = 0;

    g_data_output_stream_set_byte_order(out, G_DATA_STREAM_BYTE_ORDER_LITTLE_ENDIAN);

    /* Header */
    if (!g_output_stream_write_all(G_OUTPUT_STREAM(out), KAIMING_SUMMARY_DIFF_HEADER, 4, NULL, NULL, error))
    {
        return NULL;
    }

    /* 编写操作计数占位符 */
    if (!g_data_output_stream_put_uint32(out, 0, NULL, error))
    {
        return NULL;
    }

    for (gsize i = 0; i < data->ops->len; i++)
    {
        DiffOp *op = &g_array_index(data->ops, DiffOp, i);
        gsize size = op->size;

        while (size > 0)
        {
            /* We leave a nibble at the top for the op */
            guint32 opdata = (guint64)size & 0x0fffffff;
            size -= opdata;

            opdata = opdata | ((0xf & op->kind) << 28);

            if (!g_data_output_stream_put_uint32(out, opdata, NULL, error))
            {
                return NULL;
            }
            opsCount++;
        }
    }

    /* 然后添加数据 */
    if (data->data->len > 0 && !g_output_stream_write_all(G_OUTPUT_STREAM(out), data->data->data, data->data->len, NULL, NULL, error))
    {
        return NULL;
    }

    /* 行动计数中的后补丁 */
    if (!g_seekable_seek(G_SEEKABLE(out), 4, G_SEEK_SET, NULL, error))
    {
        return NULL;
    }

    if (!g_data_output_stream_put_uint32(out, opsCount, NULL, error))
    {
        return NULL;
    }

    if (!g_output_stream_close(G_OUTPUT_STREAM(out), NULL, error))
    {
        return NULL;
    }

    return g_memory_output_stream_steal_as_bytes(G_MEMORY_OUTPUT_STREAM(mem));
}

static GBytes *summaryGenerateDiff(KMVariant oldContent, KMVariant newContent, std::string &errMsg)
{
    KMTrace("summaryGenerateDiff invoke begin");

    GVariant *oldVariant = oldContent.get();
    GVariant *newVariant = newContent.get();

    VarSummaryRef newSumaryRef, oldSumaryRef;
    VarRefMapRef newRefs, oldRefs;
    VarRefMapEntryRef newEntry, oldEntry;
    gsize newLen, oldLen;
    int newInt, oldInt;
    const char *oldRef, *newRef;
    g_autoptr(GArray) ops = g_array_new(FALSE, TRUE, sizeof(DiffOp));
    g_autoptr(GArray) dataBytes = g_array_new(FALSE, TRUE, 1);
    g_autoptr(GBytes) diffUncompressed = nullptr;
    GBytes *diffCompressed = nullptr;
    DiffData data = {
        (const guchar *)g_variant_get_data(oldVariant),
        (const guchar *)g_variant_get_data(newVariant),
        ops,
        dataBytes,
    };

    newSumaryRef = varSummaryFromGvariant(newVariant);
    oldSumaryRef = varSummaryFromGvariant(oldVariant);

    newRefs = varSummaryGetRefMap(newSumaryRef);
    oldRefs = varSummaryGetRefMap(oldSumaryRef);

    newLen = varRefMapGetLength(newRefs);
    oldLen = varRefMapGetLength(oldRefs);

    newInt = oldInt = 0;
    while (newInt < newLen && oldInt < oldLen)
    {
        if (newInt == newLen)
        {
            /* Just old left */
            oldEntry = varRefMapGetAt(oldRefs, oldInt);
            oldRef = varRefMapEntryGetRef(oldEntry);
            oldInt++;
            diffConsumeBlock(&data, -1, 0, (const guchar *)newEntry.base - (const guchar *)newSumaryRef.base, newEntry.size);
        }
        else if (oldInt == oldLen)
        {
            /* Just new left */
            newEntry = varRefMapGetAt(newRefs, newInt);
            newRef = varRefMapEntryGetRef(newEntry);
            diffConsumeBlock(&data, (const guchar *)oldEntry.base - (const guchar *)oldSumaryRef.base, oldEntry.size, -1, 0);

            newInt++;
        }
        else
        {
            newEntry = varRefMapGetAt(newRefs, newInt);
            newRef = varRefMapEntryGetRef(newEntry);

            oldEntry = varRefMapGetAt(oldRefs, oldInt);
            oldRef = varRefMapEntryGetRef(oldEntry);

            int cmp = strcmp(newRef, oldRef);
            if (cmp == 0)
            {
                /* same ref */
                diffConsumeBlock(&data, (const guchar *)oldEntry.base - (const guchar *)oldSumaryRef.base, oldEntry.size, (const guchar *)newEntry.base - (const guchar *)newSumaryRef.base,
                                 newEntry.size);
                oldInt++;
                newInt++;
            }
            else if (cmp < 0)
            {
                /* new added */
                diffConsumeBlock(&data, -1, 0, (const guchar *)newEntry.base - (const guchar *)newSumaryRef.base, newEntry.size);
                newInt++;
            }
            else
            {
                /* oldSumaryRef removed */
                diffConsumeBlock(&data, (const guchar *)oldEntry.base - (const guchar *)oldSumaryRef.base, oldEntry.size, -1, 0);
                oldInt++;
            }
        }
    }

    /* Flush till the end */
    diffConsumeBlock2(&data, data.lastOldOffset, oldSumaryRef.size - data.lastOldOffset, data.lastNewOffset, newSumaryRef.size - data.lastNewOffset);

    g_autoptr(GError) error = NULL;
    diffUncompressed = diffEncode(&data, &error);
    if (diffUncompressed == NULL)
    {
        errMsg = error->message;
        return NULL;
    }

    diffCompressed = KMBuildinsUtils::zlibCompressBytes(diffUncompressed, 9, &error);
    if (diffCompressed == NULL)
    {
        errMsg = error->message;
    }

    KMTrace("summaryGenerateDiff invoke end");
    return diffCompressed;
}

static bool addToHistory(KMOstreeRepo &repo, KMVariantBuilder &historyBuilder, VarChecksumRef &oldDigest_vv, KMVariant currentDigest_v, KMVariant currentContent,
                         std::map<std::string, KMVariant> &digestedSummaryCache, std::uint32_t &historyLen, std::uint32_t maxHistoryLength, std::string &errMsg)
{
    KMTrace("addToHistory invoke begin");

    // 限制历史记录长度
    if (historyLen >= maxHistoryLength)
    {
        return true;
    }

    // 避免在历史中重复（以防subsummary中没有任何变化）
    KMVariant oldDigestVariant = varChecksumDupToGvariant(oldDigest_vv);
    if (oldDigestVariant.get() == currentDigest_v.get())
    {
        return true;
    }

    std::string oldDigest = KMOstreeCore::checksumFromBytesVariant(oldDigestVariant);
    KMVariant oldContent = readDigestedSummary(repo, oldDigest, digestedSummaryCache, errMsg);
    if (!oldContent)
    {
        return true;
    }

    g_autoptr(GBytes) subsummaryDiff = NULL;
    subsummaryDiff = summaryGenerateDiff(oldContent, currentContent, errMsg);
    if (subsummaryDiff == NULL)
    {
        return false;
    }

    std::string currentDigest = KMOstreeCore::checksumFromBytesVariant(currentDigest_v);

    if (!repoSaveDigestedSummaryDelta(repo, oldDigest, currentDigest, subsummaryDiff, errMsg))
    {
        return false;
    }

    historyLen += 1;
    historyBuilder.addValue(oldDigestVariant);

    KMTrace("addToHistory invoke end");
    return true;
}

static KMVariant generateSummaryIndex(KMOstreeRepo &repo, KMVariant oldIndexVariant, std::map<std::string, std::string> &summaries, std::map<std::string, KMVariant> &digestedSummaries,
                                      std::map<std::string, KMVariant> &digestedSummaryCache, const std::vector<std::string> &gpgKeyIds, const std::string &gpgHomeDir, std::string &errMsg)
{
    KMTrace("generateSummaryIndex invoke begin");

    KMVariantBuilder metadataBuilder("a{sv}");
    KMVariantBuilder subsummaryBuilder("a{s(ayaaya{sv})}");

    addSummaryMetadata(repo, metadataBuilder);

    std::vector<std::string> orderedSummaries = mapKeys(summaries);
    std::sort(orderedSummaries.begin(), orderedSummaries.end());

    for (const std::string &subsummary : orderedSummaries)
    {
        const std::string &digest = summaries.at(subsummary);
        KMVariant digest_v = KMOstreeCore::checksumToBytes_v(digest);

        KMVariant subsummaryContent = readDigestedSummary(repo, digest, digestedSummaryCache, errMsg);
        if (!subsummaryContent)
        {
            return KMVariant();
        }

        KMVariantBuilder historyBuilder("aay");
        if (oldIndexVariant)
        {
            VarSummaryIndexRef oldIndex = varSummaryIndexFromGvariant(oldIndexVariant.get());
            VarSummaryIndexSubsummariesRef oldSubsummaries = varSummaryIndexGetSubsummaries(oldIndex);
            VarSubsummaryRef oldSubsummary;
            std::uint32_t historyLen = 0;

            if (varSummaryIndexSubsummariesLookup(oldSubsummaries, subsummary.c_str(), NULL, &oldSubsummary))
            {
                VarChecksumRef parent = varSubsummaryGetChecksum(oldSubsummary);
                std::uint32_t maxHistoryLength = KMBuildinsUtils::repoGetSummaryHistoryLength(repo);

                // 将当前添加为历史上第一个
                if (!addToHistory(repo, historyBuilder, parent, digest_v, subsummaryContent, digestedSummaryCache, historyLen, maxHistoryLength, errMsg))
                {
                    return KMVariant();
                }

                // 添加以前的历史记录
                VarArrayofChecksumRef history = varSubsummaryGetHistory(oldSubsummary);
                gsize len = varArrayofChecksumGetLength(history);
                for (gsize i = 0; i < len; i++)
                {
                    VarChecksumRef c = varArrayofChecksumGetAt(history, i);
                    if (!addToHistory(repo, historyBuilder, c, digest_v, subsummaryContent, digestedSummaryCache, historyLen, maxHistoryLength, errMsg))
                    {
                        return KMVariant();
                    }
                }
            }
        }

        KMVariantDict subsummaryMetadataBuilder;
        subsummaryBuilder.add("{s(@ay@aay@a{sv})}", subsummary.c_str(), digest_v.get(), historyBuilder.end().get(), subsummaryMetadataBuilder.end().get());
    }

    KMVariantBuilder indexBuilder(KAIMING_SUMMARY_INDEX_GVARIANT_STRING);
    indexBuilder.addValue(subsummaryBuilder.end());
    indexBuilder.addValue(metadataBuilder.end());

    KMTrace("generateSummaryIndex invoke end");
    return indexBuilder.end();
}

static bool repoSaveSummaryIndex(KMOstreeRepo &repo, KMVariant index, const std::string &indexDigest, GBytes *indexSig, std::string &errMsg)
{
    KMTrace("repoSaveSummaryIndex invoke begin");

    int repoDfd = repo.getDfd();

    if (!index)
    {
        if (::unlinkat(repoDfd, "summary.idx", 0) != 0 && G_UNLIKELY(errno != ENOENT))
        {
            // errMsg = "Failed to unlinkat summary.idx";
            errMsg = ::strerror(errno);
            return false;
        }

        if (::unlinkat(repoDfd, "summary.idx.sig", 0) != 0 && G_UNLIKELY(errno != ENOENT))
        {
            // errMsg = "Failed to unlinkat summary.idx.sig";
            errMsg = ::strerror(errno);
            return false;
        }
    }

    FileReplaceFlags flags = FILE_REPLACE_INCREASING_MTIME;
    if (repo.getDisableFsync())
    {
        flags = (FileReplaceFlags)(flags | FILE_REPLACE_NODATASYNC);
    }
    else
    {
        flags = (FileReplaceFlags)(flags | FILE_REPLACE_DATASYNC_NEW);
    }

    if (indexSig)
    {
        std::string path = "summaries/" + indexDigest + ".idx.sig";
        if (!KMDirFd::mkdirPAt(repoDfd, "summaries", 0775))
        {
            errMsg = "Failed to mkdirPAt summaries";
            return false;
        }

        if (!KMUtil::replaceFileContentsWithPermsAt(repoDfd, path.c_str(), (const unsigned char *)(g_bytes_get_data(indexSig, NULL)), g_bytes_get_size(indexSig), -1, -1, -1, flags, errMsg))
        {
            return false;
        }
    }

    if (!KMUtil::replaceFileContentsWithPermsAt(repoDfd, "summary.idx", (const unsigned char *)(index.getData()), index.getSize(), -1, -1, -1, flags, errMsg))
    {
        return false;
    }

    // 兼容早期不必要的无索引summary.idx.sig文件。保留一段时间，直到每个人都更新
    if (indexSig)
    {
        if (!KMUtil::replaceFileContentsWithPermsAt(repoDfd, "summary.idx.sig", (const unsigned char *)(g_bytes_get_data(indexSig, NULL)), g_bytes_get_size(indexSig), -1, -1, -1, flags, errMsg))
        {
            return false;
        }
    }
    else
    {
        if (::unlinkat(repoDfd, "summary.idx.sig", 0) != 0 && G_UNLIKELY(errno != ENOENT))
        {
            // errMsg = "Failed to unlinkat summary.idx.sig";
            errMsg = ::strerror(errno);
            return false;
        }
    }

    KMTrace("repoSaveSummaryIndex invoke end");
    return true;
}

static bool repoSaveCompatSummary(KMOstreeRepo &repo, KMVariant summary, time_t &outOldSigMtime, std::string &errMsg)
{
    KMTrace("repoSaveCompatSummary invoke begin");

    int repoDfd = repo.getDfd();

    FileReplaceFlags flags = FILE_REPLACE_INCREASING_MTIME;
    if (repo.getDisableFsync())
    {
        flags = (FileReplaceFlags)(flags | FILE_REPLACE_NODATASYNC);
    }
    else
    {
        flags = (FileReplaceFlags)(flags | FILE_REPLACE_DATASYNC_NEW);
    }

    if (!KMUtil::replaceFileContentsWithPermsAt(repoDfd, "summary", (const unsigned char *)(summary.getData()), summary.getSize(), -1, -1, -1, flags, errMsg))
    {
        return false;
    }

    struct stat stbuf;
    if (::fstatat(repoDfd, "summary.sig", &stbuf, AT_SYMLINK_NOFOLLOW) == 0)
    {
        outOldSigMtime = stbuf.st_mtime;
    }

    if (::unlinkat(repoDfd, "summary.sig", 0) != 0 && G_UNLIKELY(errno != ENOENT))
    {
        // errMsg = "Failed to unlinkat summary.idx.sig";
        errMsg = ::strerror(errno);
        return false;
    }

    KMTrace("repoSaveCompatSummary invoke end");
    return true;
}

static bool gcDigestedSummaries(KMOstreeRepo &repo, const std::string &indexDigest, const std::string &oldIndexDigest, std::map<std::string, KMVariant> &digestedSummaries,
                                std::map<std::string, KMVariant> &digestedSummarieCache, std::string &errMsg)
{
    KMTrace("gcDigestedSummaries invoke begin");

    int repoDfd = repo.getDfd();

    KMDirFdIterator iter;
    if (!iter.initAt(repoDfd, "summaries", false))
    {
        if (G_LIKELY(errno == ENOENT))
        {
            return true;
        }

        errMsg = ::strerror(errno);
        return false;
    }

    struct dirent *dent;
    while (true)
    {
        bool remove = false;
        if (!iter.nextDentEnsureDtype(&dent))
        {
            errMsg = ::strerror(errno);
            return false;
        }

        if (dent == NULL)
        {
            break;
        }

        if (dent->d_type != DT_REG)
        {
            continue;
        }

        // 如果是意外类型，请保留它
        const char *ext = strchr(dent->d_name, '.');
        if (ext != NULL)
        {
            if (strcmp(ext, ".gz") == 0 && strlen(dent->d_name) == 64 + 3)
            {
                std::string sha256(dent->d_name);
                sha256 = sha256.substr(0, 64);

                // 保留所有引用的摘要
                if (digestedSummarieCache.end() != digestedSummarieCache.find(sha256))
                {
                    g_debug("Keeping referenced summary %s", dent->d_name);
                    continue;
                }

                /* Remove rest */
                remove = true;
            }
            else if (strcmp(ext, ".delta") == 0)
            {
                const char *dash = strchr(dent->d_name, '-');
                if (dash != NULL && dash < ext && (ext - dash) == 1 + 64)
                {
                    g_autofree char *to_sha256 = g_strndup(dash + 1, 64);
                    std::string sha256(to_sha256);

                    // 只将增量保存到生成的摘要中
                    if (digestedSummaries.end() != digestedSummaries.find(sha256))
                    {
                        g_debug("Keeping delta to generated summary %s", dent->d_name);
                        continue;
                    }

                    /* Remove rest */
                    remove = true;
                }
            }
            else if (strcmp(ext, ".idx.sig") == 0)
            {
                g_autofree char *digestdup = g_strndup(dent->d_name, strlen(dent->d_name) - strlen(".idx.sig"));
                std::string digest(digestdup);

                // 始终保持最新
                if (digest == indexDigest)
                {
                    continue;
                }

                // 总是保持前一个，以避免某些竞赛
                if (digest == oldIndexDigest)
                {
                    continue;
                }

                /* Remove rest */
                remove = true;
            }
        }

        if (remove)
        {
            g_debug("Removing old digested summary file %s", dent->d_name);
            if (::unlinkat(iter.fd(), dent->d_name, 0) != 0)
            {
                errMsg = ::strerror(errno);
                return false;
            }
        }
        else
        {
            g_debug("Keeping unexpected summary file %s", dent->d_name);
        }
    }

    KMTrace("gcDigestedSummaries invoke end");
    return true;
}

/**
 * @brief : 根据ref字符串获取arch
 * @param : [in] ref, 形如：app/${id}/${arch}/${branch}
 * @return: 是否是app、runtime、appstream的ref
 */
bool KMBuildinsUtils::isAppOrRuntimeOrAppstreamRef(const std::string &ref)
{
    return KMUtil::startsWith(ref, "appstream/") || KMUtil::startsWith(ref, "appstream2/") || KMUtil::startsWith(ref, "app/") || KMUtil::startsWith(ref, "runtime/");
}

GBytes *KMBuildinsUtils::zlibDecompressBytes(GBytes *bytes, GError **error)
{
    KMTrace("KMBuildinsUtils::zlibDecompressBytes invoke begin");

    g_autoptr(GZlibDecompressor) decompressor = NULL;
    g_autoptr(GOutputStream) out = NULL;
    g_autoptr(GOutputStream) mem = NULL;

    mem = g_memory_output_stream_new_resizable();

    decompressor = g_zlib_decompressor_new(G_ZLIB_COMPRESSOR_FORMAT_GZIP);
    out = g_converter_output_stream_new(mem, G_CONVERTER(decompressor));

    if (!g_output_stream_write_all(out, g_bytes_get_data(bytes, NULL), g_bytes_get_size(bytes), NULL, NULL, error))
    {
        return NULL;
    }

    if (!g_output_stream_close(out, NULL, error))
    {
        return NULL;
    }

    KMTrace("KMBuildinsUtils::zlibDecompressBytes invoke end");
    return g_memory_output_stream_steal_as_bytes(G_MEMORY_OUTPUT_STREAM(mem));
}

GBytes *KMBuildinsUtils::zlibCompressBytes(GBytes *bytes, int level, GError **error)
{
    KMTrace("KMBuildinsUtils::zlibCompressBytes invoke begin");

    g_autoptr(GZlibCompressor) compressor = NULL;
    g_autoptr(GOutputStream) out = NULL;
    g_autoptr(GOutputStream) mem = NULL;

    mem = g_memory_output_stream_new_resizable();

    compressor = g_zlib_compressor_new(G_ZLIB_COMPRESSOR_FORMAT_GZIP, level);
    out = g_converter_output_stream_new(mem, G_CONVERTER(compressor));

    if (!g_output_stream_write_all(out, g_bytes_get_data(bytes, NULL), g_bytes_get_size(bytes), NULL, NULL, error))
    {
        return NULL;
    }

    if (!g_output_stream_close(out, NULL, error))
    {
        return NULL;
    }

    KMTrace("KMBuildinsUtils::zlibCompressBytes invoke end");
    return g_memory_output_stream_steal_as_bytes(G_MEMORY_OUTPUT_STREAM(mem));
}

KMVariant KMBuildinsUtils::repoLoadDigestedSummary(KMOstreeRepo &repo, const std::string &digest, std::string &errMsg)
{
    KMTrace("KMBuildinsUtils::repoLoadDigestedSummary invoke begin");

    std::string filename = digest + ".gz";
    std::string path = KMUtil::buildFilename("summaries", filename);

    KMFileGuard fg = ::openat(repo.getDfd(), path.c_str(), O_RDONLY | O_CLOEXEC);
    if (fg.fd() < 0)
    {
        errMsg = "Failed to openat file : " + path;
        return KMVariant();
    }

    g_autoptr(GMappedFile) mfile = NULL;
    g_autoptr(GError) error = NULL;
    mfile = g_mapped_file_new_from_fd(fg.fd(), FALSE, &error);
    if (!mfile)
    {
        errMsg = error->message;
        return KMVariant();
    }

    g_autoptr(GBytes) bytes = NULL;
    g_autoptr(GBytes) compressedBytes = NULL;
    compressedBytes = g_mapped_file_get_bytes(mfile);
    bytes = KMBuildinsUtils::zlibDecompressBytes(compressedBytes, &error);
    if (bytes == NULL)
    {
        errMsg = error->message;
        return KMVariant();
    }

    KMTrace("KMBuildinsUtils::repoLoadDigestedSummary invoke end");
    return g_variant_new_from_bytes(OSTREE_SUMMARY_GVARIANT_FORMAT, bytes, TRUE);
}

KMVariant KMBuildinsUtils::repoLoadSummaryIndex(KMOstreeRepo &repo, std::string &errMsg)
{
    KMTrace("KMBuildinsUtils::repoLoadSummaryIndex invoke begin");

    KMFileGuard fg = ::openat(repo.getDfd(), "summary.idx", O_RDONLY | O_CLOEXEC);
    if (fg.fd() < 0)
    {
        errMsg = "Failed to openat repo's 'summary.idx' file";
        return KMVariant();
    }

    g_autoptr(GMappedFile) mfile = NULL;
    g_autoptr(GBytes) bytes = NULL;
    g_autoptr(GError) error = NULL;

    mfile = g_mapped_file_new_from_fd(fg.fd(), false, &error);
    if (!mfile)
    {
        errMsg = error->message;
        return KMVariant();
    }

    bytes = g_mapped_file_get_bytes(mfile);

    KMTrace("KMBuildinsUtils::repoLoadSummaryIndex invoke end");
    return g_variant_new_from_bytes(KAIMING_SUMMARY_INDEX_GVARIANT_FORMAT, bytes, TRUE);
}

/**
 * @brief : 根据ref字符串获取arch
 * @param : [in] ref, 形如：app/${id}/${arch}/${branch}
 * @return: arch
 */
std::string KMBuildinsUtils::getArchForRef(const std::string &ref)
{
    if (KMUtil::startsWith(ref, "appstream/") || KMUtil::startsWith(ref, "appstream2/"))
    {
        std::size_t pos = ref.find_first_of('/'); // 根据上述检查，保证存在
        std::string rest = ref.substr(pos + 1);   // 子集appstream引用为appstream2/$subset-$arch
        pos = rest.find_last_of('-');
        if (std::string::npos != pos)
            rest = rest.substr(pos + 1);

        return rest;
    }
    else if (KMUtil::startsWith(ref, "app/") || KMUtil::startsWith(ref, "runtime/"))
    {
        std::size_t pos = ref.find_first_of('/'); // 根据上述检查，保证存在
        std::string slash = ref.substr(pos + 1);
        pos = slash.find_first_of('/'); // Skip id
        if (std::string::npos == pos)
            return "";
        slash = slash.substr(pos + 1);

        pos = slash.find_first_of('/'); // trip branch
        if (std::string::npos == pos)
            return "";

        return slash.substr(0, pos);
    }

    return "";
}

/*
更新@repo摘要文件中的元数据，然后重新签署该文件。
如果repo设置了集合ID，则将元数据额外存储在众所周知的分支中的无内容提交上，这是广播每个repo元数据的首选方式（不赞成将其放在摘要文件中，但为了向后兼容而保留）。

请注意，集合ID有两个键：collection-id和ostree.deploy-collection-id。如果客户端当前没有为此远程配置集合ID，它将*仅*从ostree.deloy-collection-ID更新其配置。
这允许分阶段部署基于集合的存储库。客户端只会将其配置从未设置更新为已设置的集合ID一次（否则集合ID的安全属性将被破坏）。
*/
bool KMBuildinsUtils::repoUpdate(KMOstreeRepo &repo, KMRepoUpdateFlags flags, const std::vector<std::string> &gpgKeyIds, const std::string &gpgHomeDir, std::string &errMsg)
{
    KMTrace("KMBuildinsUtils::repoUpdate invoke begin");

    GKeyFile *config = repo.getConfig();

    std::map<std::string, std::string> refs;
    if (!repo.listRefsExt(nullptr, (OstreeRepoListRefsExtFlags)(OSTREE_REPO_LIST_REFS_EXT_EXCLUDE_REMOTES | OSTREE_REPO_LIST_REFS_EXT_EXCLUDE_MIRRORS), refs))
    {
        errMsg = repo.errorMessage();
        return false;
    }

    std::string tmpErrMsg;
    KMVariant oldIndex = KMBuildinsUtils::repoLoadSummaryIndex(repo, tmpErrMsg);
    std::map<std::string, CommitData> commitDataCache;
    if (oldIndex)
    {
        commitDataCache = populateCommitDataCache(repo, oldIndex);
    }

    std::vector<std::string> deltaNames;
    if (!repo.listStaticDeltaNames(deltaNames))
    {
        errMsg = repo.errorMessage();
        return false;
    }

    g_auto(GStrv) summaryArches = NULL;
    if (config)
    {
        summaryArches = g_key_file_get_string_list(config, "kaiming", "summary-arches", NULL, NULL);
    }

    std::map<std::string, std::string> summaries;
    std::map<std::string, KMVariant> digestedSummaries;
    std::map<std::string, KMVariant> digestedSummaryCache;
    std::set<std::string> arches;
    std::set<std::string> subsets;
    subsets.insert("");

    for (auto &pair : refs)
    {
        const std::string &ref = pair.first;
        const std::string &checksum = pair.second;
        std::string arch = KMBuildinsUtils::getArchForRef(ref);

        if (!arch.empty())
        {
            arches.insert(arch);
        }

        CommitData revData;
        if (KMBuildinsUtils::isAppOrRuntimeOrAppstreamRef(ref))
        {
            if (commitDataCache.end() == commitDataCache.find(checksum))
            {
                if (!readCommitData(repo, ref, checksum, revData, errMsg))
                {
                    return false;
                }

                commitDataCache.insert({ checksum, revData });
            }
            else
            {
                revData = commitDataCache.at(checksum);
            }

            for (const std::string &subset : revData.subsets)
            {
                subsets.insert(subset);
            }
        }
    }

    KMVariant compatSummary = generateSummary(repo, true, refs, commitDataCache, deltaNames, "", (const char **)summaryArches, errMsg);
    if (!compatSummary)
    {
        return false;
    }

    bool disableIndex = (flags & KAIMING_REPO_UPDATE_FLAG_DISABLE_INDEX) != 0;
    KMVariant summaryIndex;
    if (!disableIndex)
    {
        for (const std::string &subset : subsets)
        {
            for (const std::string &arch : arches)
            {
                std::string name;
                if (subset.empty())
                {
                    name = arch;
                }
                else
                {
                    name = subset + "-" + arch;
                }

                const char *archVariant[] = { arch.c_str(), NULL };
                KMVariant archSummary = generateSummary(repo, false, refs, commitDataCache, std::vector<std::string>(), subset.c_str(), archVariant, errMsg);
                if (!archSummary)
                {
                    return false;
                }

                std::string digest = repoSaveDigestedSummary(repo, name, archSummary, errMsg);
                if (digest.empty())
                {
                    return false;
                }

                digestedSummaries.insert({ digest, archSummary });
                // 具有生成的摘要的Prime摘要缓存
                digestedSummaryCache.insert({ digest, archSummary });
                summaries.insert({ name, digest });
            }
        }

        summaryIndex = generateSummaryIndex(repo, oldIndex, summaries, digestedSummaries, digestedSummaryCache, gpgKeyIds, gpgHomeDir, errMsg);
        if (!summaryIndex)
        {
            return false;
        }
    }

    if (!repo.staticDeltaReindex(OSTREE_STATIC_DELTA_INDEX_FLAGS_NONE, nullptr))
    {
        errMsg = repo.errorMessage();
        return false;
    }

    g_autoptr(GBytes) indexSig = NULL;
    if (summaryIndex && !gpgKeyIds.empty())
    {
        g_autoptr(GBytes) indexBytes = summaryIndex.getDataAsBytes();

        if (!repo.gpgSignData(indexBytes, nullptr, gpgKeyIds, gpgHomeDir, &indexSig))
        {
            errMsg = repo.errorMessage();
            return false;
        }
    }

    std::string indexDigest;
    if (summaryIndex)
    {
        KMChecksum checksum(G_CHECKSUM_SHA256);
        checksum.update((const unsigned char *)summaryIndex.getData(), summaryIndex.getSize());
        indexDigest = checksum.getString();
    }

    std::string oldIndexDigest;
    if (oldIndex)
    {
        KMChecksum checksum(G_CHECKSUM_SHA256);
        checksum.update((const unsigned char *)oldIndex.getData(), oldIndex.getSize());
        oldIndexDigest = checksum.getString();
    }

    // 在替换之前先释放内存映射的摘要索引文件，以避免cifs等文件系统出现故障
    oldIndex.clear();

    if (!repoSaveSummaryIndex(repo, summaryIndex, indexDigest, indexSig, errMsg))
    {
        return false;
    }

    time_t oldCompatSigMtime = 0;
    if (!repoSaveCompatSummary(repo, compatSummary, oldCompatSigMtime, errMsg))
    {
        return false;
    }

    if (!gpgKeyIds.empty())
    {
        if (!repo.addGpgSignatureSummary(gpgKeyIds, gpgHomeDir))
        {
            errMsg = repo.errorMessage();
            return false;
        }

        if (oldCompatSigMtime != 0)
        {
            int repoDfd = repo.getDfd();
            struct stat stbuf;

            // 确保我们更新（以秒为单位）
            if (::fstatat(repoDfd, "summary.sig", &stbuf, AT_SYMLINK_NOFOLLOW) == 0 && stbuf.st_mtime <= oldCompatSigMtime)
            {
                struct timespec ts[2] = { { 0, UTIME_OMIT }, { oldCompatSigMtime + 1, 0 } };
                (void)::utimensat(repoDfd, "summary.sig", ts, AT_SYMLINK_NOFOLLOW);
            }
        }
    }

    if (!disableIndex && !gcDigestedSummaries(repo, indexDigest, oldIndexDigest, digestedSummaries, digestedSummaryCache, errMsg))
    {
        return false;
    }

    KMTrace("KMBuildinsUtils::repoUpdate invoke end : true");
    return true;
}
