/* common/KMOstreeRepoCommitModifier.h
 *
 * Copyright (C) 2023 OpenKylin, Inc
 */

#include "KMOstreeRepoCommitModifier.h"

class KMOstreeRepoCommitModifier::Private
{
public:
    Private(OstreeRepoCommitModifierFlags flags, OstreeRepoCommitFilter commitFilter, gpointer userData, GDestroyNotify destroyNotify);
    ~Private();

    OstreeRepoCommitModifier *m_modifier = nullptr;
};

KMOstreeRepoCommitModifier::Private::Private(OstreeRepoCommitModifierFlags flags, OstreeRepoCommitFilter commitFilter, gpointer userData, GDestroyNotify destroyNotify)
{
    m_modifier = ostree_repo_commit_modifier_new(flags, commitFilter, userData, destroyNotify);
}

KMOstreeRepoCommitModifier::Private::~Private()
{
    // if (m_modifier)
    //     g_object_unref(m_modifier);
}

KMOstreeRepoCommitModifier::KMOstreeRepoCommitModifier(OstreeRepoCommitModifierFlags flags, OstreeRepoCommitFilter commitFilter, gpointer userData, GDestroyNotify destroyNotify)
    : d(std::make_shared<Private>(flags, commitFilter, userData, destroyNotify))
{
}

KMOstreeRepoCommitModifier::KMOstreeRepoCommitModifier(const KMOstreeRepoCommitModifier &other)
{
    d = other.d;
}

KMOstreeRepoCommitModifier::~KMOstreeRepoCommitModifier() { }

KMOstreeRepoCommitModifier &KMOstreeRepoCommitModifier::operator=(const KMOstreeRepoCommitModifier &other)
{
    if (this != &other && d->m_modifier != other.d->m_modifier)
    {
        d = other.d;
    }

    return *this;
}

OstreeRepoCommitModifier *KMOstreeRepoCommitModifier::get()
{
    return d->m_modifier;
}

class KMOstreeRepo::Private
{
public:
    Private(const std::string &path);
    ~Private();

    void clearError()
    {
        if (m_error)
        {
            g_error_free(m_error);
        }
    }

    OstreeRepo *m_repo = nullptr;
    GFile *m_repofile = nullptr;
    std::string m_path;
    GError *m_error = nullptr;
    bool m_isStartTransaction = false;
};

KMOstreeRepo::Private::Private(const std::string &path)
{
    m_path = path;
    m_repofile = g_file_new_for_path(path.c_str());

    // 创建位于路径path的OSTree存储库的访问者对象
    m_repo = ostree_repo_new(m_repofile);
}

KMOstreeRepo::Private::~Private()
{
    if (m_repo)
    {
        g_object_unref(m_repo);
    }

    if (m_repofile)
    {
        g_object_unref(m_repofile);
    }

    clearError();
}

KMOstreeRepo::KMOstreeRepo(const std::string &path)
    : d(std::make_shared<Private>(path))
{
}

KMOstreeRepo::~KMOstreeRepo()
{
    // 异常或忘记提交或中断事务场景，中断事务
    if (d->m_isStartTransaction)
    {
        abortTransaction();
    }
}

OstreeRepo *KMOstreeRepo::get()
{
    if (d)
    {
        return d->m_repo;
    }

    return nullptr;
}

bool KMOstreeRepo::create(OstreeRepoMode mode)
{
    d->clearError();
    return ostree_repo_create(d->m_repo, mode, nullptr, &d->m_error);
}

bool KMOstreeRepo::open()
{
    d->clearError();
    return ostree_repo_open(d->m_repo, nullptr, &d->m_error);
}

std::string KMOstreeRepo::getCollectionId() const
{
    const gchar *collectionId = ostree_repo_get_collection_id(d->m_repo);
    return collectionId == nullptr ? "" : collectionId;
}

bool KMOstreeRepo::setCollectionId(const std::string &collectionId)
{
    d->clearError();
    return ostree_repo_set_collection_id(d->m_repo, collectionId.c_str(), &d->m_error);
}

bool KMOstreeRepo::resolveCollectionRef(const OstreeCollectionRef *ref, bool allowNoent, OstreeRepoResolveRevExtFlags flags, std::string &tmpOutRev)
{
    g_autofree char *parent = nullptr;

    d->clearError();
    bool ret = ostree_repo_resolve_collection_ref(d->m_repo, ref, allowNoent, flags, &parent, nullptr, &d->m_error);

    if (ret && parent)
    {
        tmpOutRev = parent;
    }

    return ret;
}

bool KMOstreeRepo::resolveRev(const std::string &refspec, bool allowNoent, std::string &tmpOutRev)
{
    g_autofree char *parent = nullptr;

    d->clearError();
    bool ret = ostree_repo_resolve_rev(d->m_repo, refspec.c_str(), allowNoent, &parent, &d->m_error);

    if (ret && parent)
    {
        tmpOutRev = parent;
    }

    return ret;
}

bool KMOstreeRepo::resolveRevExt(const std::string &refspec, bool allowNoent, OstreeRepoResolveRevExtFlags flags, std::string &tmpOutRev)
{
    g_autofree char *parent = nullptr;

    d->clearError();
    bool ret = ostree_repo_resolve_rev_ext(d->m_repo, refspec.c_str(), allowNoent, flags, &parent, &d->m_error);

    if (ret && parent)
    {
        tmpOutRev = parent;
    }

    return ret;
}

void KMOstreeRepo::setDisableFsync(bool disable)
{
    ostree_repo_set_disable_fsync(d->m_repo, disable);
}

bool KMOstreeRepo::getDisableFsync()
{
    return ostree_repo_get_disable_fsync(d->m_repo);
}

/*
启动或恢复事务。为了写入回购，您需要启动一个交易。您可以使用ostree_repo_commit_transaction（）完成事务，也可以使用ostee_repo_abort_transaction（）中止事务。
目前，如果在ostree_repo_commit_transaction（）期间中断，事务可能会导致目标存储库中的部分提交或数据，并且进一步写入refs当前也不是原子的。
OstreRepo的每个实例一次最多可以有一个交易在回购中处于活动状态；然而，让多个线程在单个OstreRepo实例上写入对象是安全的，只要它们的生存期受事务限制即可。
锁定：获取共享锁；通过提交或中止多线程释放：此函数*不*MT安全；一次只能激活一个事务。
 */
bool KMOstreeRepo::prepareTransaction(bool *outTransactionResume)
{
    d->clearError();

    gboolean transaction_resume = 0;
    bool ret = ostree_repo_prepare_transaction(d->m_repo, &transaction_resume, nullptr, &d->m_error);
    if (outTransactionResume)
    {
        *outTransactionResume = transaction_resume != 0;
    }

    d->m_isStartTransaction = ret;

    return ret;
}

/*
完成事务。使用ostree_repo_transaction_set_ref（）或ostree_repo _transaction_set_refspec（）设置的任何ref都将被写入。
请注意，如果多个线程正在执行写入操作，那么在调用此函数之前，所有此类线程都必须已终止。
锁定：释放ostree_repo_prepare_transaction（）获取的共享锁多线程：此函数不是多线程安全；一次只能激活一个事务。
*/
bool KMOstreeRepo::commitTransaction(OstreeRepoTransactionStats *outStats)
{
    d->clearError();

    bool ret = ostree_repo_commit_transaction(d->m_repo, outStats, nullptr, &d->m_error);
    d->m_isStartTransaction = !ret;

    return ret;
}

/*
中止活动事务；任何暂存对象和ref更改都将被丢弃。如果您选择不调用ostree_repo_commit_transaction（），则必须*调用它。当不在事务中时调用此函数将不执行任何操作并成功返回。
*/
bool KMOstreeRepo::abortTransaction()
{
    d->clearError();
    bool ret = ostree_repo_abort_transaction(d->m_repo, nullptr, &d->m_error);
    d->m_isStartTransaction = !ret;

    return ret;
}

/*
不赞成使用此函数，而赞成使用ostree_repo_devino_cache_new（），它允许在硬链接签出文件之间建立精确的映射，并在ostree_rebo_checkout_at（）和ostree_repo _write_directory_to_tree（）之间建立它们的校验和。
当调用ostree_repo_write_directory_to_tree（）时，它必须计算所有文件的校验和。如果您的提交包含来自签出的硬链接，则此函数将构建设备编号和索引节点与其校验和的映射。
创建此映射需要前期成本，因为这将扫描整个对象目录。如果您的提交主要由到现有ostree对象的硬链接组成，那么这将大大加快速度，所以在调用ostree_repo_write_directory_to_tree（）或类似对象之前先调用它。但是，ostree_repo_devino_cache_new（）更好，因为它可以避免扫描所有对象。
多线程：此功能*不*MT安全。
*/
bool KMOstreeRepo::scanHardlinks()
{
    d->clearError();
    return ostree_repo_scan_hardlinks(d->m_repo, nullptr, &d->m_error);
}

/*
存储元数据对象对象。将校验和返回为outCsum。
如果expected_checksum不为NULL，请根据计算的校验和进行验证。
*/
bool KMOstreeRepo::writeMetadata(OstreeObjectType objtype, const char *expectedChecksum, KMVariant object, unsigned char **outCsum)
{
    d->clearError();

    return ostree_repo_write_metadata(d->m_repo, objtype, expectedChecksum, object.get(), outCsum, nullptr, &d->m_error);
}

// 将dir和所有子级的对象存储到存储库中，将生成的文件系统层次结构覆盖到mtree中。
bool KMOstreeRepo::writeDirectoryToMtree(const std::string &dir, KMOstreeMutableTree &mtree, KMOstreeRepoCommitModifier &modifier)
{
    d->clearError();

    KMFile file(dir);
    return ostree_repo_write_directory_to_mtree(d->m_repo, file.get(), mtree.get(), modifier.get(), nullptr, &d->m_error);
}

// 存储流式传输的内容对象object_input，总长度为length。实际校验和将作为out_csum返回。
bool KMOstreeRepo::writeContent(const char *expectedChecksum, KMInputStream &object_input, std::uint64_t length, unsigned char **outCSum)
{
    d->clearError();

    return ostree_repo_write_content(d->m_repo, expectedChecksum, object_input.get(), length, outCSum, nullptr, &d->m_error);
}

// 将mtree的所有元数据对象写入repo；结果outFile指向mtree表示的OSTREE_OBECT_TYPE_DIR_TREE对象。
bool KMOstreeRepo::writeMtree(KMOstreeMutableTree &mtree, KMFile &outFile)
{
    d->clearError();

    GFile *file = nullptr;
    bool ret = ostree_repo_write_mtree(d->m_repo, mtree.get(), &file, nullptr, &d->m_error);

    if (file)
    {
        outFile = file;
    }

    return ret;
}

std::string KMOstreeRepo::errorMessage() const
{
    if (d->m_error)
    {
        return d->m_error->message;
    }
    else
    {
        return "";
    }
}

int KMOstreeRepo::errorCode() const
{
    if (d->m_error)
    {
        return d->m_error->code;
    }
    else
    {
        return -1;
    }
}

// 在应用任何压缩后，返回带有校验和sha256的对象的大小（以字节为单位）。
bool KMOstreeRepo::queryObjectStorageSize(OstreeObjectType objtype, const char *sha256, std::uint64_t &outSize)
{
    d->clearError();

    return ostree_repo_query_object_storage_size(d->m_repo, objtype, sha256, &outSize, nullptr, &d->m_error);
}

// 加载内容对象，将其分解为三部分：实际内容（对于常规文件）、元数据和扩展属性。
bool KMOstreeRepo::loadFile(const char *checksum, KMInputStream &outInput, KMFileInfo &outFileInfo, KMVariant &outXattrs)
{
    d->clearError();

    GInputStream *input = nullptr;
    GFileInfo *tmpFileInfo = nullptr;
    GVariant *xattrs = nullptr;

    bool ret = ostree_repo_load_file(d->m_repo, checksum, &input, &tmpFileInfo, &xattrs, nullptr, &d->m_error);
    if (ret)
    {
        outInput = input;
        outFileInfo = tmpFileInfo;
        outXattrs = xattrs;
    }

    return ret;
}

/**
 * @brief : 写一个提交元数据对象，引用root_contentschecksum和root_metadata_checksum。
 * @param : [in] parent, ASCII SHA256 checksum for parent, or NULL for none.
 * @param : [in] subject, Subject.
 * @param : [in] body, Body.
 * @param : [in] metadata, GVariant of type a{sv}, or NULL for none.
 * @param : [in] root, The tree to point the commit to
 * @param : [in] time, The time to use to stamp the commit
 * @param : [out] tmpOutCommit, Resulting ASCII SHA256 checksum for commit.
 * @return: true-写成功; false-写失败
 */
bool KMOstreeRepo::writeCommitWithTime(const std::string &parent, const char *subject, const char *body, KMVariant metadata, KMFile root, std::uint64_t time, std::string &tmpOutCommit)
{
    d->clearError();

    const char *p_parent = nullptr;
    if (!parent.empty())
    {
        p_parent = parent.c_str();
    }

    g_autofree char *commit = nullptr;
    bool ret = ostree_repo_write_commit_with_time(d->m_repo, p_parent, subject, body, metadata.get(), OSTREE_REPO_FILE(root.get()), time, &commit, nullptr, &d->m_error);
    if (ret && commit)
    {
        tmpOutCommit = commit;
    }

    return ret;
}

bool KMOstreeRepo::writeCommit(const std::string &parent, const char *subject, const char *body, KMVariant metadata, KMFile root, std::string &tmpOutCommit)
{
    d->clearError();

    const char *p_parent = nullptr;
    if (!parent.empty())
    {
        p_parent = parent.c_str();
    }

    g_autofree char *commit = nullptr;
    bool ret = ostree_repo_write_commit(d->m_repo, p_parent, subject, body, metadata.get(), OSTREE_REPO_FILE(root.get()), &commit, nullptr, &d->m_error);
    if (ret && commit)
    {
        tmpOutCommit = commit;
    }

    return ret;
}

// 将GPG签名添加到提交中。
bool KMOstreeRepo::signCommit(const char *tmpCommitChecksum, const char *keyId, const char *homedir)
{
    d->clearError();

    return ostree_repo_sign_commit(d->m_repo, tmpCommitChecksum, keyId, homedir, nullptr, &d->m_error);
}

/*
如果校验和不为NULL，则将其记录为名为ref的本地ref的目标。否则，如果校验和为NULL，则记录应删除引用。

更改不会立即写入，而是在使用ostree_repo_commit_transaction（）完成事务时写入。如果使用ostree_repo_abort_transaction（）中止事务，则不会对存储库进行任何更改。

多线程：自v2017.15起，此功能是MT安全的。
*/
void KMOstreeRepo::transactionSetCollectionRef(const OstreeCollectionRef *ref, const char *checksum)
{
    ostree_repo_transaction_set_collection_ref(d->m_repo, ref, checksum);
}

/**
如果校验和不为NULL，则将其记录为名为ref的ref的目标；如果提供了remote，则ref将显示为源自该remote。否则，如果校验和为NULL，则记录应删除引用。

当使用ostree_repo_commit_transaction（）完成事务时，将写入更改；该函数负责在更新ref之前写入所有对象（例如校验和引用的提交）。如果使用ostree_repo_abort_transaction（）中止事务，则不会对存储库进行ref更改。

然而，请注意，当前写*multiple*refs并不是真正的原子；如果进程或系统在ostree_repo_commit_transaction（）期间终止，则可能只有一些ref被更新。你的申请应该小心处理这种情况。

多线程：自v2017.15起，此功能是MT安全的。
 * @param : [in] remote, A remote for the ref.
 * @param : [in] ref, The ref to write
 * @param : [in] checksum, The checksum to point it to.
*/
void KMOstreeRepo::transactionSetRef(const char *remote, const char *ref, const char *checksum)
{
    ostree_repo_transaction_set_ref(d->m_repo, remote, ref, checksum);
}

// 存储库配置；不要修改。
GKeyFile *KMOstreeRepo::getConfig()
{
    return ostree_repo_get_config(d->m_repo);
}

/**
 * @brief : 如果refspec_prefix为NULL，则列出所有本地和远程refspec，并在out_all_refs中列出它们的当前值。否则，只列出前缀为refspec_prefix的refspec。
 *          out_all_refs将作为从refspecs（包括远程名称）到校验和的映射返回。与ostree_repo_list_refs（）不同，refspec_prefix不会从哈希表中的refspec中删除。
 * @param : [in] refspecPrefix, 仅列出与此前缀匹配的引用。
 * @param : [in] flags, 控制列表行为的选项
 * @param : [out] outRefs, 从refspec到校验和的映射。
 * @return: 是否成功
 */
bool KMOstreeRepo::listRefsExt(const char *refspecPrefix, OstreeRepoListRefsExtFlags flags, std::map<std::string, std::string> &outRefs)
{
    d->clearError();

    g_autoptr(GHashTable) refs = nullptr;
    bool ret = ostree_repo_list_refs_ext(d->m_repo, refspecPrefix, &refs, flags, nullptr, &d->m_error);
    if (ret && refs)
    {
        // 创建一个迭代器
        GHashTableIter iter;
        g_hash_table_iter_init(&iter, refs);
        gpointer key, value;

        // 遍历GHashTable中的所有成员
        while (g_hash_table_iter_next(&iter, &key, &value))
        {
            outRefs.insert({ (char *)key, (char *)value });
        }
    }

    return ret;
}

// 返回存储库根的文件描述符。在某些情况下，应用程序直接访问存储库是很有用的；例如，将内容写入repo/tmp可确保内容位于同一文件系统中。另一种情况是检测存储库上的mtime（查看是否写入了ref）。
int KMOstreeRepo::getDfd()
{
    return ostree_repo_get_dfd(d->m_repo);
}

// 此函数同步枚举存储库中的所有静态增量，并以deltaNames形式返回结果。
bool KMOstreeRepo::listStaticDeltaNames(std::vector<std::string> &deltaNamesVector)
{
    d->clearError();

    g_autoptr(GPtrArray) deltaNames = NULL;
    bool ret = ostree_repo_list_static_delta_names(d->m_repo, &deltaNames, nullptr, &d->m_error);
    if (ret && deltaNames)
    {
        std::string deltaName;
        for (guint i = 0; i < deltaNames->len; i++)
        {
            deltaName = (char *)deltaNames->pdata[i];
            deltaNamesVector.push_back(deltaName);
        }
    }

    return ret;
}

/**
 * @brief : 将rev的内容加载到root中
 * @param : [in] ref, Ref或ASCII校验和
 * @param : [out] root, 与根相对应的OstreeRepoFile。
 * @param : [out] outCommit, 已解析的提交校验和。
 */
bool KMOstreeRepo::readCommit(const char *ref, KMFile &root, std::string &outCommit)
{
    d->clearError();

    GFile *file = nullptr;
    g_autofree char *commit = nullptr;
    bool ret = ostree_repo_read_commit(d->m_repo, ref, &file, &commit, nullptr, &d->m_error);
    if (ret && commit)
    {
        root = file;
        outCommit = commit;
    }

    return ret;
}

/**
 * @brief : ostree_repo_load_variant的一个版本，专门用于提交，能够返回扩展的状态信息。目前唯一的扩展状态是OSTREE_REPO_COMMIT_STATE_PARTIAL，这意味着只有提交的子路径可用。
 * @param : [in] checksum, 提交校验和
 * @param : [out] outCommit，Commit.
 * @param : [out] Commit state.
 */
bool KMOstreeRepo::loadCommit(const char *checksum, KMVariant &outCommit, OstreeRepoCommitState *outState)
{
    d->clearError();

    GVariant *commit_v = nullptr;
    bool ret = ostree_repo_load_commit(d->m_repo, checksum, &commit_v, outState, &d->m_error);
    if (ret && commit_v)
    {
        outCommit = commit_v;
    }

    return ret;
}

/**
 * @brief : 加载objtype类型的元数据对象sha256，将结果存储在outMetadata中。
 * @param : [in] objtype, 需要的对象类型
 * @param : [in] sha256, 校验和字符串
 * @param : [out] outMetadata, 元数据对象
 * @return: 操作是否成功
 */
bool KMOstreeRepo::loadVariant(OstreeObjectType objtype, const char *sha256, KMVariant &outMetadata)
{
    d->clearError();

    GVariant *metadata_v = nullptr;
    bool ret = ostree_repo_load_variant(d->m_repo, objtype, sha256, &metadata_v, &d->m_error);
    if (ret && metadata_v)
    {
        outMetadata = metadata_v;
    }

    return ret;
}

/**
 * @brief : 特定提交的增量索引列出了下载该提交时可以使用的所有现有增量。此操作为特定提交（如果opt_to_commit为非NULL）或现有增量可访问的所有提交（如果pti_to_commit为NULL）重新生成这些索引。这通常是在ostree_repo_regenerate_summary（）中更新摘要时自动调用的。
 * @param : [in] flags, 影响索引操作的标志
 * @param : [in] optToCommit, 目标提交的ASCII SHA256校验和，或NULL以索引所有目标
 * @return: 是否成功
 */
bool KMOstreeRepo::staticDeltaReindex(OstreeStaticDeltaIndexFlags flags, const char *optToCommit)
{
    d->clearError();

    return ostree_repo_static_delta_reindex(d->m_repo, flags, optToCommit, nullptr, &d->m_error);
}

/**
 * @brief : 使用gpgKeyIds中指定的密钥对给定数据进行签名。类似于ostree_repo_add_gpg_signature_summary（），但可以用于任何数据。您可以使用ostree_repo_gpg_verify_data（）来验证签名。
 * @param : [in] data, Data as a GBytes
 * @param : [in] oldSignatures, 要附加到的现有签名（或NULL）。
 * @param : [in] gpgKeyIds, 以NULL结尾的GPG键数组。
 * @param : [in] gpgHomeDir, GPG主目录，或NULL。
 */
bool KMOstreeRepo::gpgSignData(GBytes *data, GBytes *oldSignatures, const std::vector<std::string> &gpgKeyIds, const std::string &gpgHomeDir, GBytes **outSignatures)
{
    d->clearError();

    const gchar *homedir = nullptr;
    if (!gpgHomeDir.empty())
    {
        homedir = gpgHomeDir.c_str();
    }

    if (gpgKeyIds.empty())
    {
        return ostree_repo_gpg_sign_data(d->m_repo, data, oldSignatures, nullptr, homedir, outSignatures, nullptr, &d->m_error);
    }
    else
    {
        std::vector<const char *> keyIds;
        for (auto &keyId : gpgKeyIds)
        {
            keyIds.push_back(keyId.c_str());
        }
        keyIds.push_back(nullptr);

        return ostree_repo_gpg_sign_data(d->m_repo, data, oldSignatures, keyIds.data(), homedir, outSignatures, nullptr, &d->m_error);
    }
}

/**
 * @brief : 将GPG签名添加到摘要文件中。
 * @param : [in] gpgKeyIds, 以NULL结尾的GPG键数组。
 * @param : [in] gpgHomeDir, GPG主目录，或NULL。
 */
bool KMOstreeRepo::addGpgSignatureSummary(const std::vector<std::string> &gpgKeyIds, const std::string &gpgHomeDir)
{
    d->clearError();

    const gchar *homedir = nullptr;
    if (!gpgHomeDir.empty())
    {
        homedir = gpgHomeDir.c_str();
    }

    if (gpgKeyIds.empty())
    {
        return ostree_repo_add_gpg_signature_summary(d->m_repo, nullptr, homedir, nullptr, &d->m_error);
    }
    else
    {
        std::vector<const char *> keyIds;
        for (auto &keyId : gpgKeyIds)
        {
            keyIds.push_back(keyId.c_str());
        }
        keyIds.push_back(nullptr);

        return ostree_repo_add_gpg_signature_summary(d->m_repo, keyIds.data(), homedir, nullptr, &d->m_error);
    }
}
