/* kmcreateusb.cpp
 *
 * Copyright (C) 2024 OpenKylin, Inc
 */

#include "KMCreateUsb.h"

#include <filesystem>

#include "common/KMLogger.h"
#include "common/KMOSTreeHandler.h"
#include "common/KMParseDeploy.h"
#include "kmcreateusbutils.h"

using namespace std;
namespace fs = std::filesystem;

#define DEFAULT_COLLECTION_ID "kylin.kaiming.export"

class KMCreateUsb::Options : public KMOptions
{
public:
    Options() = default;
    virtual ~Options() = default;

protected:
    /**
     * @brief 命令行参数解析后操作
     */
    void preParseHook() override;

    /**
     * @brief 命令行参数解析后操作
     */
    void postParseHook() override;

private:
    /**
     * @brief 命令行参数添加描述选项
     */
    void addOptions();

    /**
     * @brief 命令行参数检查项
     */
    void checkOptions(const opt::variables_map &varsMap);

public:
    bool m_bHelp;
    bool m_bRuntime;
    bool m_bApp;
    bool m_bAllowPartial;
    bool m_bSystem;
    bool m_bUser;
    string m_strArch;
    string m_strDestRepo;
    string m_strMountPath;
    vector<string> m_vecRefs;
    vector<string> m_vecInstallation;
};

void KMCreateUsb::Options::preParseHook()
{
    addOptions();
}

/**
 * @brief 命令行参数解析后操作
 */
void KMCreateUsb::Options::postParseHook()
{
    checkOptions(m_varsMap);

    if (m_bHelp)
    {
        showUsage();
        exit(EXIT_SUCCESS);
    }

    if (m_strMountPath.empty())
    {
        KMError("MOUNT-PATH must be specified");
        exit(EXIT_FAILURE);
    }
}

/**
 * @brief 命令行参数添加描述选项
 */
void KMCreateUsb::Options::addOptions()
{
    m_optionsDescription = std::make_unique<opt::options_description>(_("\nUsage:\n \tkaiming export [OPTION…] PATH [REF…] - Copy apps or runtimes onto removable media "));

    opt::options_description helpOptions(_("Help Options"));
    helpOptions.add_options()("help,h", _("Show help options"));

    opt::options_description appOptions(_("Application Options"));
    appOptions.add_options()("app", _("Look for app with the specified name"))("arch=ARCH", opt::value<string>(&m_strArch), _("Arch to copy"))("destination-repo=DEST", opt::value<string>(&m_strDestRepo), _("Use custom repository directory within the mount"))(
      "runtime",
      _("Look for runtime with the specified name"))("allow-partial", _("Allow partial commits in the created repo"))("installation", opt::value<vector<string>>(&m_vecInstallation), _("Work on a non-default system-wide installation"));

    opt::options_description positionalOptions(_("Positional Options"));
    positionalOptions.add_options()("MOUNT-PATH", opt::value<string>(&m_strMountPath), _("Mount path"))("REF...", opt::value<vector<string>>(&m_vecRefs), _("REF..."));
    m_optionsDescription->add(helpOptions).add(appOptions).add(positionalOptions);

    m_optionsPositionalDescription->add("MOUNT-PATH", 1);
    m_optionsPositionalDescription->add("REF...", -1);
}

/**
 * @brief 命令行参数检查项
 */
void KMCreateUsb::Options::checkOptions(const opt::variables_map &varsMap)
{
    checkOption(varsMap, "help", "h", m_bHelp);
    checkOption(varsMap, "app", nullptr, m_bApp);
    checkOption(varsMap, "runtime", nullptr, m_bRuntime);
    checkOption(varsMap, "allow-partial", nullptr, m_bAllowPartial);
}

class KMCreateUsb::Private
{
public:
    std::unique_ptr<Options> m_kmOptions;
};

REGISTER_SUBCOMMAND_DYNCREATE(export, KMCreateUsb)

KMCreateUsb::KMCreateUsb()
    : d(make_unique<Private>())
{
    d->m_kmOptions = make_unique<Options>();
}

KMCreateUsb::~KMCreateUsb() { }

/**
 * @brief 子命令运行
 * @param argc 参数个数
 * @param argv 参数数组
 * @return int 0 成功
 */
int KMCreateUsb::despose(int argc, char **argv)
{
    KMDebug("KMCreateUsb::despose invoke begin");

    // 环境检查
    if (!checkEnv(argc, argv))
    {
        return -1;
    }

    // 导出应用程序或运行时
    return createUsb() ? 0 : -1;
}

/**
 * @brief 数据解析与环境检查
 * @return true 成功 false 失败
 */
bool KMCreateUsb::checkEnv(int argc, char **argv)
{
    // 参数解析
    d->m_kmOptions->parseCommandLine(argc, argv);

    // 检查挂载路径是否存在
    std::error_code ec;
    if (!fs::exists(fs::path(d->m_kmOptions->m_strMountPath), ec) && !fs::create_directories(fs::path(d->m_kmOptions->m_strMountPath), ec))
    {
        KMError("create directory " + d->m_kmOptions->m_strMountPath + " fail ");
        return false;
    }

    return isExistExport() ? false : true;
}

/**
 * @brief 是否已导出
 * @return true 存在 false 不存在
 */
bool KMCreateUsb::isExistExport()
{
    if (!fs::exists(fs::path(d->m_kmOptions->m_strMountPath + "/.ostree/repo")))
    {
        return false;
    }

    for (auto ref : d->m_kmOptions->m_vecRefs)
    {
        if (fs::exists(fs::path(d->m_kmOptions->m_strMountPath + "/.ostree/repo/refs/mirrors/kylin.kaiming.export/app/" + ref)))
        {
            KMInfo(ref + " repo仓库已导出");
            return true;
        }

        if (fs::exists(fs::path(d->m_kmOptions->m_strMountPath + "/.ostree/repo/refs/mirrors/kylin.kaiming.export/runtime/" + ref)))
        {
            KMInfo(ref + " repo仓库已导出");
            return true;
        }
    }

    return false;
}

/**
 * @brief 将应用程序或运行时复制到可移动媒体上
 * @return bool true 成功
 */
bool KMCreateUsb::createUsb()
{
    vector<KMStorageDir> vecDir;
    if (!getInstallKmdir(vecDir))
    {
        KMError("No installation directory specified");
        return false;
    }

    KaimingKinds defaultKinds;
    kaimingkindsFromRools(d->m_kmOptions->m_bApp, d->m_kmOptions->m_bRuntime, defaultKinds);

    g_autoptr(GHashTable) allRefs = g_hash_table_new_full(ostree_collection_ref_hash, ostree_collection_ref_equal, (GDestroyNotify)ostree_collection_ref_free, (GDestroyNotify)commitAndSubpathsFree);
    g_autoptr(GHashTable) allRemoteArch = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)g_strfreev);
    g_autoptr(GHashTable) allCollectionIds = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);

    KMStorageDir dirWithRef;
    OstreeRepo *srcRepo;
    string remote;

    for (auto ref : d->m_kmOptions->m_vecRefs)
    {
        KaimingKinds matchedKinds;
        if (!getKMKinds(ref, matchedKinds))
        {
            matchedKinds = defaultKinds;
        }

        string id{}, arch{}, branch{};
        splitName(ref, id, arch, branch);

        if (arch.empty())
        {
            arch = d->m_kmOptions->m_strArch;
        }

        // TODO: 补充id名称有效性判断
        // 后续增加优化

        KMStorageDir installDir;
        KMRef installRef;
        if (!getDirAndRef(ref, vecDir, matchedKinds, installDir, installRef))
        {
            return false;
        }

        if (!static_cast<bool>(dirWithRef))
        {
            dirWithRef = installDir;
        }
        else if (dirWithRef.getBasePath() != installDir.getBasePath())
        {
            KMError("Refs must all be in the same installation .");
            return false;
        }

        if (arch.empty())
        {
            arch = installRef.arch;
        }

        if (branch.empty())
        {
            branch = installRef.branch;
        }

        string commit, runtime;
        if (!getDeployInfo(dirWithRef, installRef, remote, commit, runtime))
        {
            return false;
        }

        if (!doUpdateHashTab(allRefs, allCollectionIds, dirWithRef, installRef, &srcRepo))
        {
            return false;
        }

        // 哈希表保存remote arch 信息
        updateRemoteArchHashTab(allRemoteArch, remote, arch);

        /* Add dependencies and related refs */
        if (!(installRef.kind == BASE_TYPE_RUNTIME) && !addRuntime(allRefs, allCollectionIds, dirWithRef, installRef))
        {
            return false;
        }
        
        if (!addRelated(allRefs, allCollectionIds, dirWithRef, installRef))
        {
            return false;
        }
    }

    /* Add ostree-metadata and appstream refs for each collection ID */
    addMetaAndAppstream(allRefs, allCollectionIds, allRemoteArch, srcRepo);

    return doCreateUsb(allRefs, srcRepo, d->m_kmOptions->m_strMountPath);
}

/**
 * @brief 添加应用相关信息
 * @return bool true 成功 false 失败
 */
bool KMCreateUsb::addRelated(GHashTable *allRefs, GHashTable *allCollectionIds, KMStorageDir &dir, KMRef &ref)
{
    std::shared_ptr<KMDeploy> deploy = dir.loadDeployed(ref);
    const std::vector<KMExtension> &extensions = deploy->m_metadata->getExtensions();
    for (const auto &ext : extensions)
    {
        if (ext.isUnmaintained)
        {
            continue;
        }

        if (ext.name.empty())
        {
            continue;
        }

        vector<KMRef> vRefs = dir.findFuzzyMatchingRef(ext.name, KAIMING_KINDS_ALL, dir.getAllInstalledRefs(KAIMING_KINDS_ALL));

        for (auto extRef : vRefs)
        {
            if (extRef.id.empty())
            {
                continue;
            }

            OstreeRepo *srcRepo = nullptr;
            doUpdateHashTab(allRefs, allCollectionIds, dir, extRef, &srcRepo);
        }
    }
    return true;
}

/**
 * @brief 添加运行时信息
 * @param allRefs 仓库哈希表
 * @param allCollectionIds id哈希表
 * @param ref KMRef结构体
 * @return bool true 成功 false 失败
 */
bool KMCreateUsb::addRuntime(GHashTable *allRefs, GHashTable *allCollectionIds, KMStorageDir &dir, KMRef &ref)
{
    string remote, commit, runtime;
    if (!getDeployInfo(dir, ref, remote, commit, runtime))
    {
        return false;
    }

    // 获取runtime ref
    KMRef runtimeRef;
    if (!getInstalledKMRef(runtime, dir, KAIMING_KINDS_ALL, runtimeRef))
    {
        return false;
    }

    // 更新哈希表
    OstreeRepo *srcRepo = nullptr;
    if (!doUpdateHashTab(allRefs, allCollectionIds, dir, runtimeRef, &srcRepo))
    {
        return false;
    }

    // 添加相关内容
    if (!addRelated(allRefs, allCollectionIds, dir, runtimeRef))
    {
        return false;
    }

    return true;
}

/**
 * @brief 真正的导出功能
 * @param allRefs 哈希表
 * @param srcRepo 源仓库
 * @param mountRootPath 挂载根路径
 * @param statBuf stat属性结构
 * @param mountFd 文件句柄
 * @return true 成功 false 失败
 */
bool KMCreateUsb::doCreateUsb(GHashTable *allRefs, OstreeRepo *srcRepo, string mountRootPath)
{
    // 验证refs有效性
    HASH_TABLE_FOREACH(allRefs, OstreeCollectionRef *, collection_ref)
    {
        GError *error = nullptr;
        if (!ostree_validate_collection_id(collection_ref->collection_id, &error))
        {
            return false;
        }

        if (!ostree_validate_rev(collection_ref->ref_name, &error))
        {
            return false;
        }
    }

    // ostree 导出功能
    if (!ostreeCreateUsb(allRefs, srcRepo, mountRootPath))
    {
        return false;
    }

    return true;
}

/**
 * @brief 真正的导出功能
 * @param allRefs 哈希表
 * @param srcRepo 源仓库
 * @param mountRootPath 挂载根路径
 * @param statBuf stat属性结构
 * @param mountFd 文件句柄
 * @return true 成功 false 失败
 */
bool KMCreateUsb::ostreeCreateUsb(GHashTable *allRefs, OstreeRepo *srcRepo, string mountRootPath)
{
    struct stat statBuf;
    if (stat(mountRootPath.c_str(), &statBuf) != 0)
    {
        KMError("stat " + mountRootPath + "fail .");
        return false;
    }

    string optDestRepo = d->m_kmOptions->m_strDestRepo;
    string destRepoPath = (!optDestRepo.empty()) ? optDestRepo : ".ostree/repo";
    string dstRepoDir = fs::path(d->m_kmOptions->m_strMountPath) / destRepoPath;
    std::error_code ec;
    if (!fs::exists(dstRepoDir, ec) && !fs::create_directories(dstRepoDir, ec))
    {
        KMError("create directory " + dstRepoDir + " fail ");
        return false;
    }

    GError *error = nullptr;
    GCancellable *cancellable = nullptr;
    OstreeRepoMode mode = OSTREE_REPO_MODE_ARCHIVE;
    OstreeRepo *pOstreeDestRepo = ostree_repo_create_at(-1, dstRepoDir.c_str(), mode, nullptr, cancellable, &error);

    if (pOstreeDestRepo == nullptr)
    {
        return false;
    }

    struct stat destRepoStbuf;

    if (fstat(ostree_repo_get_dfd(pOstreeDestRepo), &destRepoStbuf) == -1)
    {
        return false;
    }

    if (destRepoStbuf.st_dev != statBuf.st_dev)
    {
        KMError("--destination-repo must be a descendent of MOUNT-PATH .");
        return false;
    }

    if (ostree_repo_equal(srcRepo, pOstreeDestRepo))
    {
        KMError("--destination-repo must not be the source repository .");
        return false;
    }

    if (!ostree_repo_is_writable(pOstreeDestRepo, &error))
    {
        KMError("Cannot write to repository .");
        return false;
    }

    KMInfo("is pulling down app and runtime, please wait ...");

    /* Copy across all of the collection–refs to the destination repo. We have to
     * do it one ref at a time in order to get the subpaths right. */

    HASH_TABLE_FOREACH_KV(allRefs, OstreeCollectionRef *, ostreeCollectionRef, CommitAndSubpaths *, commitSubpath)
    {
        GVariantBuilder refsBuilder;
        g_variant_builder_init(&refsBuilder, G_VARIANT_TYPE("a(sss)"));
        g_variant_builder_add(&refsBuilder, "(sss)", ostreeCollectionRef->collection_id, ostreeCollectionRef->ref_name, commitSubpath->commit ? commitSubpath->commit : "");

        GVariantBuilder builder;
        g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}"));
        g_variant_builder_add(&builder, "{s@v}", "collection-refs", g_variant_new_variant(g_variant_builder_end(&refsBuilder)));

        if (commitSubpath->subpaths != nullptr)
        {
            g_variant_builder_add(&builder, "{s@v}", "subdirs", g_variant_new_variant(g_variant_new_strv((const char *const *)commitSubpath->subpaths, -1)));
        }

        OstreeRepoPullFlags flags = OSTREE_REPO_PULL_FLAGS_MIRROR;
        g_variant_builder_add(&builder, "{s@v}", "flags", g_variant_new_variant(g_variant_new_int32(flags)));
        g_variant_builder_add(&builder, "{s@v}", "depth", g_variant_new_variant(g_variant_new_int32(0)));
        g_autoptr(GVariant) opts = nullptr;
        opts = g_variant_ref_sink(g_variant_builder_end(&builder));

        KMOSTreeHandler ostreeManager(dstRepoDir);
        if (!ostreeManager.pullWithOptions(g_file_get_uri(ostree_repo_get_path(srcRepo)), ostreeCollectionRef->ref_name, opts, nullptr))
        {
            return false;
        }
    }

    KMInfo("is generating summary file .");

    /* Ensure a summary file is present to make it easier to look up commit checksums. */
    if (!ostree_repo_regenerate_summary(pOstreeDestRepo, nullptr, cancellable, &error))
    {
        return false;
    }

    KMInfo("export sucess .");

    return createSyslink(destRepoPath, destRepoStbuf);
}

/**
 * @brief 创建软连接
 * @param destRepoPath 目的仓库路径
 * @param destRepoStbuf 目的路径属性结构
 * @return true 成功 false 失败
 */
bool KMCreateUsb::createSyslink(const string &destRepoPath, const struct stat &destRepoStbuf)
{
    if (destRepoPath.compare("ostree/repo") == 0 || destRepoPath.compare(".ostree/repo") == 0)
    {
        return true;
    }

    std::error_code ec;
    fs::path linkDstRepoDir = fs::path(d->m_kmOptions->m_strMountPath) / ".ostree/repos.d";
    if (fs::exists(linkDstRepoDir))
    {
        fs::remove_all(linkDstRepoDir, ec);
    }

    if (!fs::create_directories(linkDstRepoDir, ec))
    {
        KMError("create directory " + linkDstRepoDir.string() + " fail ");
        return false;
    }

    fs::path targetRepo = fs::path(d->m_kmOptions->m_strMountPath) / destRepoPath;
    fs::path linkRepo = linkDstRepoDir / "00-generated";
    fs::create_symlink(targetRepo, linkRepo, ec);

    return true;
}

/**
 * @brief 获取deployData信息
 * @param dir 目录
 * @param ref KMRef 结构
 * @param remote 远程名
 * @param commit 提交名
 * @param runtime 运行时
 * @return true 成功 false 失败
 */
bool KMCreateUsb::getDeployInfo(KMStorageDir &dir, KMRef &ref, string &remote, string &commit, string &runtime)
{
    KMParseDeploy parseDeploy;
    GBytes *pDeployData = parseDeploy.getDeployData(dir.getDeployedDir(ref) + "/deploy");
    if (pDeployData == nullptr)
    {
        KMError("get deploy data fail .");
        return false;
    }

    remote = parseDeploy.getOriginFromDeployData(pDeployData);
    commit = parseDeploy.getCommitFromDeployData(pDeployData);
    runtime = parseDeploy.getRuntimeFromDeployData(pDeployData);
    return true;
}

/**
 * @brief 操作更新哈希表
 * @param allRefs 哈希表
 * @param allCollectionIds 哈希表
 * @param dir 目录
 * @param ref KMRef 结构
 * @param runtime 运行时
 * @param repo 指针
 * @return true 成功 false 失败
 */
bool KMCreateUsb::doUpdateHashTab(GHashTable *allRefs, GHashTable *allCollectionIds, KMStorageDir &dir, KMRef &ref, OstreeRepo **repo)
{
    KMParseDeploy parseDeploy;
    GBytes *pDeployData = parseDeploy.getDeployData(dir.getDeployedDir(ref) + "/deploy");
    if (pDeployData == nullptr)
    {
        KMError("get deploy data fail .");
        return false;
    }

    string remote = parseDeploy.getOriginFromDeployData(pDeployData);
    string commit = parseDeploy.getCommitFromDeployData(pDeployData);
    string collectionId = DEFAULT_COLLECTION_ID;

    KMOSTreeHandler ostreeHandler(dir.getBasePath() + "/repo");
    *repo = ostreeHandler.getRepo();

    char **ppSubpaths = getSubpathsFromDeploySubpaths((char **)parseDeploy.getSubpathsFromDeployData(pDeployData));
    if (ppSubpaths != nullptr && !d->m_kmOptions->m_bAllowPartial)
    {
        KMWarn("Warning: Ref is partially installed. Use --allow-partial to suppress this message .");
    }

    if (ostreeHandler.isExtraData(commit))
    {
        return false;
    }

    // 更新ref,collcetionid哈希表
    updateHashTab(allRefs, allCollectionIds, ref, collectionId, remote, commit, ppSubpaths);

    return true;
}

/**
 * @brief 更新RemoteArch哈希表
 * @param remote 远程
 * @param arch 架构
 */
void KMCreateUsb::updateRemoteArchHashTab(GHashTable *allRemoteArch, const string &remote, const string &arch)
{
    // 哈希表保存remote arch 信息
    const char **arches = (const char **)g_hash_table_lookup(allRemoteArch, remote.c_str());
    if (arches == nullptr)
    {
        GPtrArray *archesArray = g_ptr_array_new();
        g_ptr_array_add(archesArray, g_strdup(arch.c_str()));
        g_ptr_array_add(archesArray, nullptr);
        g_hash_table_insert(allRemoteArch, g_strdup(remote.c_str()), g_ptr_array_free(archesArray, false));
    }
    else
    {
        GPtrArray *archesArray = g_ptr_array_new();
        for (const char **iter = arches; *iter != nullptr; ++iter)
        {
            g_ptr_array_add(archesArray, g_strdup(*iter));
        }
        g_ptr_array_add(archesArray, g_strdup(arch.c_str()));
        g_ptr_array_add(archesArray, nullptr);
        g_hash_table_replace(allRemoteArch, g_strdup(remote.c_str()), g_ptr_array_free(archesArray, false));
    }
}

/**
 * @brief 更新哈希表
 */
void KMCreateUsb::updateHashTab(GHashTable *allRefs, GHashTable *allCollectionIds, KMRef &ref, const string &refCollectionId, const string &remote, const string &commit, char **ppSubpaths)
{
    const char *pRemote = (const char *)g_hash_table_lookup(allCollectionIds, refCollectionId.c_str());
    if (pRemote == nullptr)
    {
        g_hash_table_insert(allCollectionIds, g_strdup(refCollectionId.c_str()), g_strdup(remote.c_str()));
    }
    CommitAndSubpaths *commitSubpath = commitAndSubpathsNew(commit.c_str(), ppSubpaths);
    string refName = ref.kind + "/" + ref.id + "/" + ref.arch + "/" + ref.branch;
    OstreeCollectionRef *collectionRef = ostree_collection_ref_new(refCollectionId.c_str(), refName.c_str());
    g_hash_table_insert(allRefs, KMUtil::stealPointer(&collectionRef), commitSubpath);
}

/**
 * @brief 添加ostree-metadata和appstream
 * @param allRefs 哈希表
 * @param allCollectionIds 哈希表
 * @param remoteArch 哈希表
 * @param OstreeRepo* 指针
 */
void KMCreateUsb::addMetaAndAppstream(GHashTable *allRefs, GHashTable *allCollectionIds, GHashTable *remoteArch, OstreeRepo *srcRepo)
{
    HASH_TABLE_FOREACH_KV(allCollectionIds, const char *, collectionId, const char *, remoteName)
    {
        // TODO: 获取remoteState,无对应接口，暂不实现

        OstreeCollectionRef *pMetadataCollectionRef = ostree_collection_ref_new(collectionId, OSTREE_REPO_METADATA_REF);
        if (ostree_repo_resolve_collection_ref(srcRepo, pMetadataCollectionRef, false, OSTREE_REPO_RESOLVE_REV_EXT_NONE, nullptr, nullptr, nullptr))
        {
            g_hash_table_insert(allRefs, KMUtil::stealPointer(&pMetadataCollectionRef), commitAndSubpathsNew(nullptr, nullptr));
        }

        /* Add whatever appstream data is available for each arch */
        const char **pRemoteArches = (const char **)g_hash_table_lookup(remoteArch, remoteName);
        for (const char **iter = pRemoteArches; iter != nullptr && *iter != nullptr; ++iter)
        {
            const char *pCurrentArch = *iter;

            /* Try to update the appstream data, but don't fail on error because we
             * want this to work offline. */
            // g_autoptr(GPtrArray) pAppstreamDirs = g_ptr_array_new();
            // g_ptr_array_add(pAppstreamDirs, dirWithRef);
            // if (!update_appstream(pAppstreamDirs, remoteName, pCurrentArch , 0, true, nullptr,nullptr)) {
            //     KMWarn("Warning: Couldn't update appstream data for remote .");
            // }

            /* Copy the appstream data if it exists. It's optional because without it
             * the USB will still be useful to the kaiming CLI even if GNOME Software
             * wouldn't display the contents. */
            g_autofree char *pCommit = nullptr;
            g_autoptr(GError) pAppstreamError = nullptr;
            g_autofree char *pAppstreamRefspec = g_strdup_printf("%s:appstream/%s", remoteName, pCurrentArch);
            g_autofree char *pAppstreamRef = g_strdup_printf("appstream/%s", pCurrentArch);
            OstreeCollectionRef *pAppstreamCollectionRef = ostree_collection_ref_new(collectionId, pAppstreamRef);
            if (ostree_repo_resolve_rev(srcRepo, pAppstreamRefspec, false, &pCommit, &pAppstreamError))
            {
                g_hash_table_insert(allRefs, KMUtil::stealPointer(&pAppstreamCollectionRef), commitAndSubpathsNew(pCommit, nullptr));
            }

            /* Copy the appstream2 data if it exists. */
            g_autofree char *pCommit2 = nullptr;
            g_autoptr(GError) pAppstreamError2 = nullptr;
            g_autofree char *pAppstream2Rrefspec = g_strdup_printf("%s:appstream2/%s", remoteName, pCurrentArch);
            g_autofree char *pAppstream2Rref = g_strdup_printf("appstream2/%s", pCurrentArch);
            OstreeCollectionRef *pAppstream2CollectionRef = ostree_collection_ref_new(collectionId, pAppstream2Rref);
            if (ostree_repo_resolve_rev(srcRepo, pAppstream2Rrefspec, false, &pCommit2, &pAppstreamError2))
            {
                g_hash_table_insert(allRefs, KMUtil::stealPointer(&pAppstream2CollectionRef), commitAndSubpathsNew(pCommit2, nullptr));
            }
        }
    }
}

/**
 * @brief 获取导出的目录
 * @param vecDir 仓库目录集合
 * @return bool ture 成功 false 失败
 */
bool KMCreateUsb::getInstallKmdir(vector<KMStorageDir> &vecDir)
{
    if (d->m_kmOptions->m_bSystem || (!d->m_kmOptions->m_bUser && d->m_kmOptions->m_vecInstallation.empty()))
    {
        vecDir.emplace_back(KMStorageDir::getSystemDefaultDir());
    }

    if (d->m_kmOptions->m_bUser || (!d->m_kmOptions->m_bSystem && d->m_kmOptions->m_vecInstallation.empty()))
    {
        vecDir.emplace_back(KMStorageDir::getUserDir());
    }

    if (!d->m_kmOptions->m_vecInstallation.empty())
    {
        // TODO 待后续添加优化
        // for (auto const& install : d->m_kmOptions->m_vecInstallation)
        //     vecDir.emplace_back(KMStorageDir dir(d->m_kmOptions->m_vecIns));
    }

    if (vecDir.empty())
    {
        KMError("No installation directory specified");
        return false;
    }

    for (auto &dir : vecDir)
    {
        if (!dir.ensureRepo(true))
        {
            return false;
        }
    }

    return true;
}

/**
 * @brief 获取已安装的目录和ref
 * @param name 名称
 * @param vecDir KMStorageDir目录
 * @param kinds 查询类型
 * @param installDir 返回安装目录
 * @param installRef 返回KMRef结构
 * @return bool ture 成功 false 失败
 */
bool KMCreateUsb::getDirAndRef(const string &name, vector<KMStorageDir> &vecDir, const KaimingKinds &defaultKinds, KMStorageDir &installDir, KMRef &installRef)
{
    string id{}, arch{}, branch{};
    splitName(name, id, arch, branch);

    if (arch.empty())
    {
        arch = d->m_kmOptions->m_strArch;
    }

    // TODO: 补充id名称有效性判断
    // 后续增加优化

    vector<KMStorageDir> vecDirWithRef;
    for (auto &dir : vecDir)
    {
        KMRef tmpRef = dir.findInstalledRef(id, branch, arch, defaultKinds);
        if (!tmpRef.id.empty())
        {
            installRef = tmpRef;
            vecDirWithRef.emplace_back(dir);
        }
    }

    if (vecDirWithRef.empty())
    {
        KMError("No match anywhere " + id);
        return false;
    }

    if (vecDirWithRef.size() > 1)
    {
        KMError("Ref found in multiple installations .You must specify one .");
        return false;
    }

    installDir = vecDirWithRef.front();

    return true;
}

/**
 * @brief 获取已安装的ref
 * @param name 名称
 * @param dir KMStorageDir目录类型
 * @param kinds 查询类型
 * @param ref 返回的KMRef结构
 * @return bool ture 成功 false 失败
 */
bool KMCreateUsb::getInstalledKMRef(const string &name, KMStorageDir &dir, const KaimingKinds &kinds, KMRef &ref)
{
    string id{}, branch{}, arch{};
    splitName(name, id, arch, branch);

    // 获取安装的ref
    ref = dir.findInstalledRef(id, branch, arch, kinds);
    if (ref.id.empty())
    {
        return false;
    }
    return true;
}

/**
 * @brief 根据app和runtime判断KaimingKinds值
 * @param app 是否为应用程序
 * @param runtime 是否为运行时
 * @return KaimingKinds值
 */
bool KMCreateUsb::kaimingkindsFromRools(bool app, bool runtime, KaimingKinds &kinds)
{
    int kind = 0;
    if (app)
    {
        kind |= KAIMING_KINDS_APP;
    }

    if (runtime)
    {
        kind |= KAIMING_KINDS_RUNTIME;
    }

    if (kind == 0)
    {
        kind = KAIMING_KINDS_APP | KAIMING_KINDS_RUNTIME;
    }

    kinds = static_cast<KaimingKinds>(kind);

    return true;
}

/**
 * @brief 分解名称，获取对应的id,arch,branch
 * @param name 名称
 * @param id arch brach
 * @return bool ture 成功 false 失败
 */
bool KMCreateUsb::splitName(const string &name, string &id, string &arch, string &branch)
{
    string appName = name;
    if (splits(appName, "/", id) != -1)
    {
        if (splits(appName, "/", arch) != -1)
        {
            splits(appName, "/", branch);
        }
    }
    return true;
}

/**
 * @brief 获取KaimingKinds类型
 * @param name 名称
 * @param kinds 类型
 * @return bool ture 成功 false 失败
 */
bool KMCreateUsb::getKMKinds(const string &name, KaimingKinds &kinds)
{
    string ref = name;
    if (ref.compare(0, strlen("app/"), "app/") == 0)
    {
        ref = ref.substr(strlen("app/"), ref.size() - strlen("app/"));
        kinds = KAIMING_KINDS_APP;
        return true;
    }
    else if (ref.compare(0, strlen("runtime/"), "runtime/") == 0)
    {
        ref = ref.substr(strlen("runtime/"), ref.size() - strlen("runtime/"));
        kinds = KAIMING_KINDS_RUNTIME;
        return true;
    }

    return false;
}
