From 563ddb11d9da8fa91b0eee06ca96145c8a5f8dd0 Mon Sep 17 00:00:00 2001 From: Nikita Shlyago <85184722+nikkitashl@users.noreply.github.com> Date: Wed, 7 Sep 2022 15:18:14 +0300 Subject: [PATCH] Some refactor #207 --- .clang-format | 1 + remote_helper/CMakeLists.txt | 10 +- remote_helper/engines/base_engine.h | 53 + remote_helper/engines/engine_factory.cpp | 21 + remote_helper/engines/engine_factory.h | 21 + remote_helper/engines/ipfs/ipfs_engine.cpp | 706 +++++++++++++ remote_helper/engines/ipfs/ipfs_engine.h | 61 ++ remote_helper/engines/ipfs/types.cpp | 79 ++ remote_helper/engines/ipfs/types.h | 66 ++ remote_helper/{ => git}/git_utils.cpp | 126 +-- remote_helper/{ => git}/git_utils.h | 182 ++-- remote_helper/{ => git}/object_collector.cpp | 392 +++---- remote_helper/{ => git}/object_collector.h | 342 +++--- remote_helper/remote_helper.cpp | 985 +----------------- remote_helper/unittests/helper_tests.cpp | 4 +- remote_helper/utils.cpp | 101 +- remote_helper/utils.h | 34 + remote_helper/wallets/base_client.h | 67 ++ .../beam_wallet_client.cpp} | 450 ++++---- .../beam_wallet_client.h} | 224 ++-- remote_helper/wallets/client_factory.cpp | 26 + remote_helper/wallets/client_factory.h | 22 + 22 files changed, 2125 insertions(+), 1848 deletions(-) create mode 100644 remote_helper/engines/base_engine.h create mode 100644 remote_helper/engines/engine_factory.cpp create mode 100644 remote_helper/engines/engine_factory.h create mode 100644 remote_helper/engines/ipfs/ipfs_engine.cpp create mode 100644 remote_helper/engines/ipfs/ipfs_engine.h create mode 100644 remote_helper/engines/ipfs/types.cpp create mode 100644 remote_helper/engines/ipfs/types.h rename remote_helper/{ => git}/git_utils.cpp (96%) rename remote_helper/{ => git}/git_utils.h (95%) rename remote_helper/{ => git}/object_collector.cpp (96%) rename remote_helper/{ => git}/object_collector.h (96%) create mode 100644 remote_helper/wallets/base_client.h rename remote_helper/{wallet_client.cpp => wallets/beam_wallet_client.cpp} (65%) rename remote_helper/{wallet_client.h => wallets/beam_wallet_client.h} (72%) create mode 100644 remote_helper/wallets/client_factory.cpp create mode 100644 remote_helper/wallets/client_factory.h diff --git a/.clang-format b/.clang-format index 97c0eae3..2fec8ce7 100644 --- a/.clang-format +++ b/.clang-format @@ -1,6 +1,7 @@ --- BasedOnStyle: Google Language: Cpp +ColumnLimit : 100 AccessModifierOffset: -4 AllowShortFunctionsOnASingleLine: None AllowShortIfStatementsOnASingleLine: false diff --git a/remote_helper/CMakeLists.txt b/remote_helper/CMakeLists.txt index f5f3b4a4..bd536814 100644 --- a/remote_helper/CMakeLists.txt +++ b/remote_helper/CMakeLists.txt @@ -7,10 +7,14 @@ add_executable (${TARGET_NAME} remote_helper.cpp) add_library(helper_lib STATIC) target_sources(helper_lib PRIVATE - git_utils.cpp - object_collector.cpp + git/git_utils.cpp + git/object_collector.cpp utils.cpp - wallet_client.cpp + wallets/beam_wallet_client.cpp + engines/ipfs/ipfs_engine.cpp + engines/engine_factory.cpp + wallets/client_factory.cpp + engines/ipfs/types.cpp ) target_include_directories(helper_lib PUBLIC ${LIBGIT2_INCLUDES}) diff --git a/remote_helper/engines/base_engine.h b/remote_helper/engines/base_engine.h new file mode 100644 index 00000000..31d25b5a --- /dev/null +++ b/remote_helper/engines/base_engine.h @@ -0,0 +1,53 @@ +// Copyright 2021-2022 SOURC3 Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include +#include +#include + +#include "utils.h" + +struct IWalletClient; + +struct IEngine { + enum struct CommandResult { Ok, Failed, Batch }; + + explicit IEngine(IWalletClient& client) : client_(client) { + } + + virtual ~IEngine() = default; + + virtual CommandResult DoCommand(std::string_view command, + std::vector& args) = 0; + +protected: + IWalletClient& client_; + + struct BaseOptions { + enum struct SetResult { InvalidValue, Ok, Unsupported }; + + static constexpr uint32_t kInfiniteDepth = + (uint32_t)std::numeric_limits::max(); + sourc3::ReporterType progress; + int64_t verbosity = 0; + uint32_t depth = kInfiniteDepth; + + virtual ~BaseOptions() = default; + + virtual SetResult Set(std::string_view option, + std::string_view value) = 0; + }; +}; diff --git a/remote_helper/engines/engine_factory.cpp b/remote_helper/engines/engine_factory.cpp new file mode 100644 index 00000000..0b095c44 --- /dev/null +++ b/remote_helper/engines/engine_factory.cpp @@ -0,0 +1,21 @@ +// Copyright 2021-2022 SOURC3 Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "engine_factory.h" + +#include "engines/ipfs/ipfs_engine.h" + +std::unique_ptr CreateEngine(IWalletClient& client) { + return std::make_unique(client); +} diff --git a/remote_helper/engines/engine_factory.h b/remote_helper/engines/engine_factory.h new file mode 100644 index 00000000..8b5c3275 --- /dev/null +++ b/remote_helper/engines/engine_factory.h @@ -0,0 +1,21 @@ +// Copyright 2021-2022 SOURC3 Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include + +#include "base_engine.h" + +std::unique_ptr CreateEngine(IWalletClient& client); diff --git a/remote_helper/engines/ipfs/ipfs_engine.cpp b/remote_helper/engines/ipfs/ipfs_engine.cpp new file mode 100644 index 00000000..66f1d607 --- /dev/null +++ b/remote_helper/engines/ipfs/ipfs_engine.cpp @@ -0,0 +1,706 @@ +// Copyright 2021-2022 SOURC3 Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "ipfs_engine.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "utils.h" +#include "contract_state.hpp" +#include "wallets/base_client.h" +#include "engines/ipfs/types.h" + +using namespace std; +using namespace sourc3; + +namespace { +struct ObjectWithContent : public sourc3::GitObject { + std::string ipfs_hash; + std::string content; + + ObjectWithContent(int8_t type, git_oid hash, std::string ipfs_hash, std::string content) + : ipfs_hash(std::move(ipfs_hash)), content(std::move(content)) { + this->hash = std::move(hash); + this->type = type; + data_size = static_cast(content.size()); + } +}; + +std::string GetStringFromIPFS(const std::string& hash, IWalletClient& wallet_client) { + auto res = ParseJsonAndTest(wallet_client.LoadObjectFromIPFS(hash)); + if (!res.as_object().contains("result")) { + throw std::runtime_error{"No result in resulting JSON, probably error"}; + } + auto d = res.as_object()["result"].as_object()["data"].as_array(); + ByteBuffer buf; + buf.reserve(d.size()); + for (auto&& v : d) { + buf.emplace_back(static_cast(v.get_int64())); + } + return ByteBufferToString(buf); +} + +std::vector GetObjectsFromTreeMeta(const TreeMetaBlock& tree, + sourc3::IProgressReporter& progress, + IWalletClient& client) { + std::vector objects; + auto tree_content = GetStringFromIPFS(tree.hash.ipfs, client); + objects.emplace_back(GIT_OBJECT_TREE, std::move(tree.hash.oid), std::move(tree.hash.ipfs), + std::move(tree_content)); + for (auto&& [oid, hash] : tree.entries) { + auto file_content = GetStringFromIPFS(hash, client); + objects.emplace_back(GIT_OBJECT_BLOB, std::move(oid), std::move(hash), + std::move(file_content)); + progress.AddProgress(1); + } + return objects; +} + +std::vector GetAllObjects(const std::string& root_ipfs_hash, + sourc3::IProgressReporter& progress, + IWalletClient& client) { + std::vector objects; + CommitMetaBlock commit(GetStringFromIPFS(root_ipfs_hash, client)); + auto commit_content = GetStringFromIPFS(commit.hash.ipfs, client); + objects.emplace_back(GIT_OBJECT_COMMIT, std::move(commit.hash.oid), std::move(commit.hash.ipfs), + std::move(commit_content)); + progress.AddProgress(1); + TreeMetaBlock tree(GetStringFromIPFS(commit.tree_meta_hash, client)); + auto tree_objects = GetObjectsFromTreeMeta(tree, progress, client); + std::move(tree_objects.begin(), tree_objects.end(), std::back_inserter(objects)); + for (auto&& parent_hash : commit.parent_hashes) { + auto parent_objects = GetAllObjects(parent_hash.ipfs, progress, client); + std::move(parent_objects.begin(), parent_objects.end(), std::back_inserter(objects)); + } + return objects; +} + +std::vector GetUploadedObjects(const std::vector& refs, + IWalletClient& client, + sourc3::ReporterType reporter_type) { + std::vector objects; + auto progress = + MakeProgress("Enumerate uploaded objects", client.GetUploadedObjectCount(), reporter_type); + for (const auto& ref : refs) { + auto ref_objects = GetAllObjects(ref.ipfs_hash, *progress, client); + std::move(ref_objects.begin(), ref_objects.end(), std::back_inserter(objects)); + } + progress->Done(); + return objects; +} + +using Metas = unordered_map>; + +struct ObjectsAndMetas { + std::vector objects; + Metas metas; +}; + +ObjectsAndMetas GetAllObjectsWithMeta(const std::string& root_ipfs_hash, + sourc3::IProgressReporter& progress, IWalletClient& client) { + std::vector objects; + unordered_map> metas; + CommitMetaBlock commit(GetStringFromIPFS(root_ipfs_hash, client)); + metas[root_ipfs_hash] = commit; + auto commit_content = GetStringFromIPFS(commit.hash.ipfs, client); + objects.emplace_back(GIT_OBJECT_COMMIT, std::move(commit.hash.oid), std::move(commit.hash.ipfs), + std::move(commit_content)); + progress.AddProgress(1); + TreeMetaBlock tree(GetStringFromIPFS(commit.tree_meta_hash, client)); + metas[commit.tree_meta_hash] = tree; + auto tree_objects = GetObjectsFromTreeMeta(tree, progress, client); + std::move(tree_objects.begin(), tree_objects.end(), std::back_inserter(objects)); + for (auto&& parent_hash : commit.parent_hashes) { + auto [parent_objects, parent_metas] = + GetAllObjectsWithMeta(parent_hash.ipfs, progress, client); + std::move(parent_objects.begin(), parent_objects.end(), std::back_inserter(objects)); + for (auto&& [key, value] : parent_metas) { + metas[std::move(key)] = std::move(value); + } + } + return {std::move(objects), std::move(metas)}; +} + +ObjectsAndMetas GetUploadedObjectsWithMetas(const std::vector& refs, + IWalletClient& client, + sourc3::ReporterType reporter_type) { + std::vector objects; + unordered_map> metas; + auto progress = + MakeProgress("Enumerate uploaded objects", client.GetUploadedObjectCount(), reporter_type); + for (const auto& ref : refs) { + auto [ref_objects, ref_metas] = GetAllObjectsWithMeta(ref.ipfs_hash, *progress, client); + std::move(ref_objects.begin(), ref_objects.end(), std::back_inserter(objects)); + for (auto&& [key, value] : ref_metas) { + metas[std::move(key)] = std::move(value); + } + } + progress->Done(); + return {std::move(objects), std::move(metas)}; +} + +std::set GetOidsFromObjects(const std::vector& objects) { + std::set oids; + for (const auto& object : objects) { + oids.insert(object.hash); + } + return oids; +} + +struct OidHasher { + std::hash hasher; + size_t operator()(const git_oid& oid) const { + return hasher(ToString(oid)); + } +}; + +using HashMapping = std::unordered_map; + +std::string CreateRefsFile(const std::vector& refs, const HashMapping& mapping) { + std::string file; + for (const auto& ref : refs) { + if (mapping.count(ref.target) == 0) { + throw std::runtime_error{"Some refs are not used!"}; + } + file += ref.name + "\t" + mapping.at(ref.target) + "\t" + ToString(ref.target) + "\n"; + } + return file; +} + +std::vector ParseRefs(const std::string& refs_file) { + std::vector refs; + if (refs_file.empty()) { + return refs; + } + + std::istringstream ss(refs_file); + std::string ref_name; + std::string target_ipfs; + std::string target_oid; + while (ss >> ref_name) { + if (ref_name.empty()) { + break; + } + ss >> target_ipfs; + ss >> target_oid; + refs.push_back(Ref{std::move(ref_name), target_ipfs, FromString(target_oid)}); + } + return refs; +} + +HashMapping ParseRefHashed(const std::string& refs_file) { + HashMapping mapping; + if (refs_file.empty()) { + return mapping; + } + + std::istringstream ss(refs_file); + std::string ref_name; + std::string target_ipfs; + std::string target_oid; + while (ss >> ref_name) { + if (ref_name.empty()) { + break; + } + ss >> target_ipfs; + ss >> target_oid; + mapping[FromString(target_oid)] = std::move(target_ipfs); + } + return mapping; +} + +std::unique_ptr GetCommitMetaBlock(const git::Commit& commit, + const HashMapping& oid_to_meta, + const HashMapping& oid_to_ipfs) { + auto block = std::make_unique(); + git_commit* raw_commit = *commit; + const auto* commit_id = git_commit_id(raw_commit); + block->hash.oid = *commit_id; + block->hash.ipfs = oid_to_ipfs.at(*commit_id); + if (oid_to_meta.count(*git_commit_tree_id(raw_commit)) == 0) { + throw std::runtime_error{"Cannot find tree " + ToString(*git_commit_tree_id(raw_commit)) + + " meta on IPFS"}; + } + block->tree_meta_hash = oid_to_meta.at(*git_commit_tree_id(raw_commit)); + unsigned int parents_count = git_commit_parentcount(raw_commit); + for (unsigned int i = 0; i < parents_count; ++i) { + auto* parent_id = git_commit_parent_id(raw_commit, i); + if (oid_to_meta.count(*parent_id) > 0) { + block->parent_hashes.emplace_back(*parent_id, oid_to_meta.at(*parent_id)); + } else { + throw std::runtime_error{ + "Something wrong with push, " + "we cannot find meta object for parent " + + ToString(*parent_id)}; + } + } + return block; +} + +std::unique_ptr GetTreeMetaBlock(const git::Tree& tree, + const HashMapping& oid_to_ipfs) { + git_tree* raw_tree = *tree; + const auto* tree_id = git_tree_id(raw_tree); + auto block = std::make_unique(); + block->hash = {*tree_id, oid_to_ipfs.at(*tree_id)}; + for (size_t i = 0, size = git_tree_entrycount(raw_tree); i < size; ++i) { + const auto& entry_id = *git_tree_entry_id(git_tree_entry_byindex(raw_tree, i)); + block->entries.emplace_back(entry_id, oid_to_ipfs.at(entry_id)); + } + return block; +} + +std::unique_ptr GetMetaBlock(const sourc3::git::RepoAccessor& accessor, + const ObjectInfo& obj, const HashMapping& oid_to_meta, + const HashMapping& oid_to_ipfs) { + if (obj.type == GIT_OBJECT_COMMIT) { + git::Commit commit; + git_commit_lookup(commit.Addr(), *accessor.m_repo, &obj.oid); + return GetCommitMetaBlock(commit, oid_to_meta, oid_to_ipfs); + } else if (obj.type == GIT_OBJECT_TREE) { + git::Tree tree; + git_tree_lookup(tree.Addr(), *accessor.m_repo, &obj.oid); + return GetTreeMetaBlock(tree, oid_to_ipfs); + } + + return nullptr; +} + +bool CheckCommitsLinking(const Metas& metas, const vector& new_refs, + const HashMapping& oid_to_meta) { + std::deque working_hashes; + std::unordered_set used; + for (const auto& ref : new_refs) { + if (oid_to_meta.count(ref.target) == 0) { + return false; + } + string hash = oid_to_meta.at(ref.target); + working_hashes.push_back(hash); + } + + while (!working_hashes.empty()) { + string hash = std::move(working_hashes.back()); + working_hashes.pop_back(); + used.insert(hash); + if (metas.count(hash) == 0) { + return false; + } + + auto* commit = std::get_if(&metas.at(hash)); + if (commit == nullptr) { + return false; + } + + for (const auto& parent_meta : commit->parent_hashes) { + if (used.count(parent_meta.ipfs) == 0) { + working_hashes.push_back(parent_meta.ipfs); + } + } + } + return true; +} + +} // namespace + +IEngine::CommandResult FullIPFSEngine::DoCommand(std::string_view command, + std::vector& args) { + auto it = find_if(begin(commands_), end(commands_), [&](const auto& c) { + return command == c.command; + }); + if (it == end(commands_)) { + cerr << "Unknown command: " << command << endl; + return CommandResult::Failed; + } + return invoke(it->action, this, args); +} + +IEngine::CommandResult FullIPFSEngine::DoList(const vector&) { + auto refs = RequestRefs(); + + for (const auto& r : refs) { + cout << sourc3::ToString(r.target) << " " << r.name << '\n'; + } + if (!refs.empty()) { + cout << "@" << refs.back().name << " HEAD\n"; + } + + return CommandResult::Ok; +} + +IEngine::CommandResult FullIPFSEngine::DoOption(const vector& args) { + static string_view results[] = {"error invalid value", "ok", "unsupported"}; + + auto res = options_.Set(args[1], args[2]); + + cout << results[size_t(res)]; + return CommandResult::Ok; +} + +IEngine::CommandResult FullIPFSEngine::DoFetch(const vector& args) { + using namespace sourc3; + std::set object_hashes; + object_hashes.emplace(args[1].data(), args[1].size()); + size_t depth = 1; + std::set received_objects; + + auto enuque_object = [&](const std::string& oid) { + if (received_objects.find(oid) == received_objects.end()) { + object_hashes.insert(oid); + } + }; + + git::RepoAccessor accessor(client_.GetRepoDir()); + size_t total_objects = 0; + + auto objects = GetUploadedObjects(RequestRefs(), client_, options_.progress); + for (const auto& obj : objects) { + if (git_odb_exists(*accessor.m_odb, &obj.hash) != 0) { + received_objects.insert(ToString(obj.hash)); + ++total_objects; + } + } + auto progress = sourc3::MakeProgress( + "Receiving objects", total_objects - received_objects.size(), options_.progress); + size_t done = 0; + while (!object_hashes.empty()) { + auto it_to_receive = object_hashes.begin(); + const auto& object_to_receive = *it_to_receive; + + git_oid oid; + git_oid_fromstr(&oid, object_to_receive.data()); + + auto it = std::find_if(objects.begin(), objects.end(), [&](auto&& o) { + return o.hash == oid; + }); + if (it == objects.end()) { + received_objects.insert(object_to_receive); // move to received + object_hashes.erase(it_to_receive); + continue; + } + received_objects.insert(object_to_receive); + + auto buf = it->content; + git_oid res_oid; + auto type = it->GetObjectType(); + git_oid r; + git_odb_hash(&r, buf.data(), buf.size(), type); + if (r != oid) { + // invalid hash + return CommandResult::Failed; + } + if (git_odb_write(&res_oid, *accessor.m_odb, buf.data(), buf.size(), type) < 0) { + return CommandResult::Failed; + } + if (type == GIT_OBJECT_TREE) { + git::Tree tree; + git_tree_lookup(tree.Addr(), *accessor.m_repo, &oid); + + auto count = git_tree_entrycount(*tree); + for (size_t i = 0; i < count; ++i) { + auto* entry = git_tree_entry_byindex(*tree, i); + auto s = ToString(*git_tree_entry_id(entry)); + enuque_object(s); + } + } else if (type == GIT_OBJECT_COMMIT) { + git::Commit commit; + git_commit_lookup(commit.Addr(), *accessor.m_repo, &oid); + if (depth < options_.depth || options_.depth == Options::kInfiniteDepth) { + auto count = git_commit_parentcount(*commit); + for (unsigned i = 0; i < count; ++i) { + auto* id = git_commit_parent_id(*commit, i); + auto s = ToString(*id); + enuque_object(s); + } + ++depth; + } + enuque_object(ToString(*git_commit_tree_id(*commit))); + } + if (progress) { + progress->UpdateProgress(++done); + } + + object_hashes.erase(it_to_receive); + } + return CommandResult::Batch; +} +IEngine::CommandResult FullIPFSEngine::DoPush(const vector& args) { + using namespace sourc3; + ObjectCollector collector(client_.GetRepoDir()); + std::vector refs; + std::vector local_refs; + bool is_forced = false; + for (size_t i = 1; i < args.size(); ++i) { + auto& arg = args[i]; + auto p = arg.find(':'); + auto& r = refs.emplace_back(); + is_forced = arg[0] == '+'; + size_t start_index = is_forced ? 1 : 0; + r.localRef = arg.substr(start_index, p - start_index); + r.remoteRef = arg.substr(p + 1); + git::Reference local_ref; + if (git_reference_lookup(local_ref.Addr(), *collector.m_repo, r.localRef.c_str()) < 0) { + cerr << "Local reference \'" << r.localRef << "\' doesn't exist" << endl; + return CommandResult::Failed; + } + auto& lr = local_refs.emplace_back(); + git_oid_cpy(&lr, git_reference_target(*local_ref)); + } + + auto remote_refs = RequestRefs(); + auto [uploaded_objects, metas] = + GetUploadedObjectsWithMetas(remote_refs, client_, options_.progress); + auto uploaded_oids = GetOidsFromObjects(uploaded_objects); + std::vector merge_bases; + for (const auto& remote_ref : remote_refs) { + for (const auto& local_ref : local_refs) { + auto& base = merge_bases.emplace_back(); + git_merge_base(&base, *collector.m_repo, &remote_ref.target, &local_ref); + } + } + + collector.Traverse(refs, merge_bases); + + auto& objs = collector.m_objects; + std::sort(objs.begin(), objs.end(), [](auto&& left, auto&& right) { + return left.oid < right.oid; + }); + { + auto it = std::unique(objs.begin(), objs.end(), [](auto&& left, auto& right) { + return left.oid == right.oid; + }); + objs.erase(it, objs.end()); + } + { + auto non_blob = std::partition(objs.begin(), objs.end(), [](const ObjectInfo& obj) { + return obj.type == GIT_OBJECT_BLOB; + }); + auto commits = std::partition(non_blob, objs.end(), [](const ObjectInfo& obj) { + return obj.type == GIT_OBJECT_TREE; + }); + std::sort(commits, objs.end(), [&collector](const ObjectInfo& lhs, const ObjectInfo& rhs) { + git::Commit rhs_commit; + git_commit_lookup(rhs_commit.Addr(), *collector.m_repo, &rhs.oid); + unsigned int parents_count = git_commit_parentcount(*rhs_commit); + for (unsigned int i = 0; i < parents_count; ++i) { + if (*git_commit_parent_id(*rhs_commit, i) == lhs.oid) { + return true; + } + } + return false; + }); + } + + for (auto& obj : collector.m_objects) { + if (uploaded_oids.find(obj.oid) != uploaded_oids.end()) { + obj.selected = true; + } + } + + { + auto it = std::remove_if(objs.begin(), objs.end(), [](const auto& o) { + return o.selected; + }); + objs.erase(it, objs.end()); + } + + State prev_state; + { + auto state = ParseJsonAndTest(client_.LoadActualState()); + if (!state.is_object()) { + cerr << "Cannot parse object state JSON: \'" << state << "\'" << endl; + return CommandResult::Failed; + } + auto& state_obj = state.as_object(); + if (!state_obj.contains("hash")) { + cerr << "Repo state not consists all objects."; + if (state_obj.contains("error")) { + cerr << " Error: " << state_obj["error"]; + } + cerr << std::endl; + return CommandResult::Failed; + } + auto state_str = ByteBufferToString(FromHex(state_obj["hash"].as_string())); + prev_state.hash = state_str; + } + + HashMapping oid_to_meta; + std::vector prev_commits_parents; + if (!std::all_of(prev_state.hash.begin(), prev_state.hash.end(), [](char c) { + return c == '\0'; + })) { + auto refs_file = GetStringFromIPFS(prev_state.hash, client_); + oid_to_meta = ParseRefHashed(refs_file); + for (const auto& [oid, hash] : oid_to_meta) { + prev_commits_parents.emplace_back(oid, hash); + } + + auto oid_copy = oid_to_meta; + for (const auto& meta_hashes : oid_copy) { + CommitMetaBlock commit(GetStringFromIPFS(meta_hashes.second, client_)); + TreeMetaBlock tree(GetStringFromIPFS(commit.tree_meta_hash, client_)); + oid_to_meta[tree.hash.oid] = commit.tree_meta_hash; + } + } + + for (const auto& [hash, meta] : metas) { + auto oid = std::visit( + [](const auto& value) { + return value.hash.oid; + }, + meta); + oid_to_meta[oid] = hash; + } + + HashMapping oid_to_ipfs; + + for (const auto& obj : uploaded_objects) { + oid_to_ipfs[obj.hash] = obj.ipfs_hash; + } + + uint32_t new_objects = 0; + uint32_t new_metas = 0; + { + auto progress = MakeProgress("Uploading objects to IPFS", collector.m_objects.size(), + options_.progress); + size_t i = 0; + for (auto& obj : collector.m_objects) { + if (obj.type == GIT_OBJECT_BLOB) { + ++new_objects; + } else { + ++new_metas; + } + + auto res = client_.SaveObjectToIPFS(obj.GetData(), obj.GetSize()); + auto r = ParseJsonAndTest(res); + auto hash_str = r.as_object()["result"].as_object()["hash"].as_string(); + obj.ipfsHash = ByteBuffer(hash_str.cbegin(), hash_str.cend()); + oid_to_ipfs[obj.oid] = std::string(hash_str.cbegin(), hash_str.cend()); + auto meta_object = GetMetaBlock(collector, obj, oid_to_meta, oid_to_ipfs); + if (meta_object != nullptr) { + auto meta_buffer = StringToByteBuffer(meta_object->Serialize()); + auto meta_res = ParseJsonAndTest( + client_.SaveObjectToIPFS(meta_buffer.data(), meta_buffer.size())); + std::string hash = + meta_res.as_object()["result"].as_object()["hash"].as_string().c_str(); + oid_to_meta[obj.oid] = hash; + if (obj.type == GIT_OBJECT_COMMIT) { + metas[hash] = *static_cast(meta_object.get()); + } else if (obj.type == GIT_OBJECT_TREE) { + metas[hash] = *static_cast(meta_object.get()); + } + } + if (progress) { + progress->UpdateProgress(++i); + } + } + } + if (!is_forced && !CheckCommitsLinking(metas, collector.m_refs, oid_to_meta)) { + cerr << "Commits linking wrong, looks like you use force push " + "without `--force` flag" + << endl; + return CommandResult::Failed; + } + std::string new_refs_content = CreateRefsFile(collector.m_refs, oid_to_meta); + State new_state; + auto new_refs_buffer = StringToByteBuffer(new_refs_content); + auto new_state_res = + ParseJsonAndTest(client_.SaveObjectToIPFS(new_refs_buffer.data(), new_refs_buffer.size())); + new_state.hash = new_state_res.as_object()["result"].as_object()["hash"].as_string().c_str(); + { + auto progress = MakeProgress("Uploading metadata to blockchain", 1, options_.progress); + ParseJsonAndTest(client_.PushObjects(prev_state, new_state, new_objects, new_metas)); + if (progress) { + progress->AddProgress(1); + } + } + { + auto progress = MakeProgress("Waiting for the transaction completion", + client_.GetTransactionCount(), options_.progress); + + auto res = client_.WaitForCompletion([&](size_t d, const auto& error) { + if (progress) { + if (error.empty()) { + progress->UpdateProgress(d); + } else { + progress->Failed(error); + } + } + }); + cout << (res ? "ok " : "error ") << refs[0].remoteRef << '\n'; + } + + return CommandResult::Batch; +} + +IEngine::CommandResult FullIPFSEngine::DoCapabilities( + [[maybe_unused]] const vector& args) { + for (auto ib = begin(commands_) + 1, ie = end(commands_); ib != ie; ++ib) { + cout << ib->command << '\n'; + } + + return CommandResult::Ok; +} + +std::vector FullIPFSEngine::RequestRefs() { + auto actual_state = ParseJsonAndTest(client_.LoadActualState()); + auto actual_state_str = actual_state.as_object()["hash"].as_string(); + if (std::all_of(actual_state_str.begin(), actual_state_str.end(), [](char c) { + return c == '0'; + })) { + return {}; + } + return ParseRefs( + GetStringFromIPFS(ByteBufferToString(sourc3::FromHex(actual_state_str)), client_)); +} + +IEngine::BaseOptions::SetResult FullIPFSEngine::Options::Set(std::string_view option, + std::string_view value) { + if (option == "progress") { + if (value == "true") { + progress = sourc3::ReporterType::Progress; + } else if (value == "false") { + progress = sourc3::ReporterType::NoOp; + } else { + return SetResult::InvalidValue; + } + return SetResult::Ok; + } /* else if (option == "verbosity") { + char* endPos; + auto v = std::strtol(value.data(), &endPos, 10); + if (endPos == value.data()) { + return SetResult::InvalidValue; + } + verbosity = v; + return SetResult::Ok; + } else if (option == "depth") { + char* endPos; + auto v = std::strtoul(value.data(), &endPos, 10); + if (endPos == value.data()) { + return SetResult::InvalidValue; + } + depth = v; + return SetResult::Ok; + }*/ + + return SetResult::Unsupported; +} diff --git a/remote_helper/engines/ipfs/ipfs_engine.h b/remote_helper/engines/ipfs/ipfs_engine.h new file mode 100644 index 00000000..3b0a3ea5 --- /dev/null +++ b/remote_helper/engines/ipfs/ipfs_engine.h @@ -0,0 +1,61 @@ +// Copyright 2021-2022 SOURC3 Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include +#include +#include "engines/base_engine.h" +#include "git/object_collector.h" + +class FullIPFSEngine final : public IEngine { +public: + explicit FullIPFSEngine(IWalletClient& client) : IEngine(client) { + } + + CommandResult DoCommand(std::string_view command, std::vector& args) final; + +private: + CommandResult DoList([[maybe_unused]] const std::vector& args); + + CommandResult DoOption([[maybe_unused]] const std::vector& args); + + CommandResult DoFetch(const std::vector& args); + + CommandResult DoPush(const std::vector& args); + + CommandResult DoCapabilities([[maybe_unused]] const std::vector& args); + + std::vector RequestRefs(); + + typedef CommandResult (FullIPFSEngine::*Action)(const std::vector& args); + + struct Command { + std::string_view command; + Action action; + }; + + Command commands_[5] = {{"capabilities", &FullIPFSEngine::DoCapabilities}, + {"list", &FullIPFSEngine::DoList}, + {"option", &FullIPFSEngine::DoOption}, + {"fetch", &FullIPFSEngine::DoFetch}, + {"push", &FullIPFSEngine::DoPush}}; + + struct Options final : BaseOptions { + SetResult Set(std::string_view option, std::string_view value) final; + }; + + Options options_; + boost::asio::io_context base_context_; +}; diff --git a/remote_helper/engines/ipfs/types.cpp b/remote_helper/engines/ipfs/types.cpp new file mode 100644 index 00000000..3ffc794b --- /dev/null +++ b/remote_helper/engines/ipfs/types.cpp @@ -0,0 +1,79 @@ +// Copyright 2021-2022 SOURC3 Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "types.h" +#include "git/git_utils.h" + +#include + +namespace sourc3 { +std::string GitIdWithIPFS::ToString() const { + return ipfs + "\n" + sourc3::ToString(oid); +} + +bool GitIdWithIPFS::operator==(const GitIdWithIPFS& other) const { + return (oid == other.oid) && (ipfs == other.ipfs); +} + +CommitMetaBlock::CommitMetaBlock(const std::string& serialized) { + std::istringstream ss(serialized); + std::string hash_oid; + ss >> hash.ipfs; + ss >> hash_oid; + hash.oid = sourc3::FromString(hash_oid); + ss >> tree_meta_hash; + std::string hash_ipfs; + while (ss >> hash_ipfs) { + if (hash_ipfs.empty()) { + break; + } + ss >> hash_oid; + parent_hashes.emplace_back(sourc3::FromString(hash_oid), + std::move(hash_ipfs)); + } +} + +std::string CommitMetaBlock::Serialize() const { + std::string data = hash.ToString() + "\n" + tree_meta_hash + "\n"; + for (const auto& parent : parent_hashes) { + data += parent.ToString() + "\n"; + } + return data; +} + +TreeMetaBlock::TreeMetaBlock(const std::string& serialized) { + std::istringstream ss(serialized); + std::string hash_oid; + ss >> hash.ipfs; + ss >> hash_oid; + hash.oid = sourc3::FromString(hash_oid); + std::string hash_ipfs; + while (ss >> hash_ipfs) { + if (hash_oid.empty()) { + break; + } + ss >> hash_oid; + entries.emplace_back(sourc3::FromString(hash_oid), + std::move(hash_ipfs)); + } +} + +std::string TreeMetaBlock::Serialize() const { + std::string data = hash.ToString() + "\n"; + for (const auto& entry : entries) { + data += entry.ToString() + "\n"; + } + return data; +} +} // namespace sourc3 diff --git a/remote_helper/engines/ipfs/types.h b/remote_helper/engines/ipfs/types.h new file mode 100644 index 00000000..ccc70b76 --- /dev/null +++ b/remote_helper/engines/ipfs/types.h @@ -0,0 +1,66 @@ +// Copyright 2021-2022 SOURC3 Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include "git2.h" +#include +#include +#include + +namespace sourc3 { +struct GitIdWithIPFS { + GitIdWithIPFS() = default; + + GitIdWithIPFS(git_oid oid, std::string ipfs) + : oid(std::move(oid)), ipfs(std::move(ipfs)) { + } + + git_oid oid; + std::string ipfs; + + std::string ToString() const; + + bool operator==(const GitIdWithIPFS& other) const; +}; + +struct MetaBlock { + GitIdWithIPFS hash; + + virtual ~MetaBlock() = default; + + virtual std::string Serialize() const = 0; +}; + +struct CommitMetaBlock final : MetaBlock { + std::string tree_meta_hash; + std::vector parent_hashes; + + CommitMetaBlock() = default; + + explicit CommitMetaBlock(const std::string& serialized); + + std::string Serialize() const final; +}; + +struct TreeMetaBlock final : MetaBlock { + std::vector entries; + + TreeMetaBlock() = default; + + explicit TreeMetaBlock(const std::string& serialized); + + std::string Serialize() const final; +}; +} // namespace sourc3 \ No newline at end of file diff --git a/remote_helper/git_utils.cpp b/remote_helper/git/git_utils.cpp similarity index 96% rename from remote_helper/git_utils.cpp rename to remote_helper/git/git_utils.cpp index 4ca6a07b..fe478b20 100644 --- a/remote_helper/git_utils.cpp +++ b/remote_helper/git/git_utils.cpp @@ -1,63 +1,63 @@ -// Copyright 2021-2022 SOURC3 Team -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "git_utils.h" - -#include - -namespace sourc3::git { -Init::Init() noexcept { - git_libgit2_init(); -} - -Init::~Init() noexcept { - git_libgit2_shutdown(); -} - -///////////////////////////////////////////////////// -RepoAccessor::RepoAccessor(std::string_view dir) { - if (git_repository_open(m_repo.Addr(), dir.data()) < 0) { - throw std::runtime_error("Failed to open repository!"); - } - if (git_repository_odb(m_odb.Addr(), *m_repo) < 0) { - throw std::runtime_error("Failed to open repository database!"); - } -} -} // namespace sourc3::git -namespace sourc3 { -std::string ToString(const git_oid& oid) { - std::string r; - r.resize(GIT_OID_HEXSZ); - git_oid_fmt(r.data(), &oid); - return r; -} - -git_oid FromString(const std::string& str) { - git_oid oid; - git_oid_fromstr(&oid, str.c_str()); - return oid; -} -} // namespace sourc3 - -bool operator<(const git_oid& left, const git_oid& right) noexcept { - return git_oid_cmp(&left, &right) < 0; -} - -bool operator==(const git_oid& left, const git_oid& right) noexcept { - return git_oid_cmp(&left, &right) == 0; -} - -bool operator!=(const git_oid& left, const git_oid& right) noexcept { - return git_oid_cmp(&left, &right) != 0; -} +// Copyright 2021-2022 SOURC3 Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "git_utils.h" + +#include + +namespace sourc3::git { +Init::Init() noexcept { + git_libgit2_init(); +} + +Init::~Init() noexcept { + git_libgit2_shutdown(); +} + +///////////////////////////////////////////////////// +RepoAccessor::RepoAccessor(std::string_view dir) { + if (git_repository_open(m_repo.Addr(), dir.data()) < 0) { + throw std::runtime_error("Failed to open repository!"); + } + if (git_repository_odb(m_odb.Addr(), *m_repo) < 0) { + throw std::runtime_error("Failed to open repository database!"); + } +} +} // namespace sourc3::git +namespace sourc3 { +std::string ToString(const git_oid& oid) { + std::string r; + r.resize(GIT_OID_HEXSZ); + git_oid_fmt(r.data(), &oid); + return r; +} + +git_oid FromString(const std::string& str) { + git_oid oid; + git_oid_fromstr(&oid, str.c_str()); + return oid; +} +} // namespace sourc3 + +bool operator<(const git_oid& left, const git_oid& right) noexcept { + return git_oid_cmp(&left, &right) < 0; +} + +bool operator==(const git_oid& left, const git_oid& right) noexcept { + return git_oid_cmp(&left, &right) == 0; +} + +bool operator!=(const git_oid& left, const git_oid& right) noexcept { + return git_oid_cmp(&left, &right) != 0; +} diff --git a/remote_helper/git_utils.h b/remote_helper/git/git_utils.h similarity index 95% rename from remote_helper/git_utils.h rename to remote_helper/git/git_utils.h index 466c26e2..290fd330 100644 --- a/remote_helper/git_utils.h +++ b/remote_helper/git/git_utils.h @@ -1,91 +1,91 @@ -/* - * Copyright 2021-2022 SOURC3 Team - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include -#include - -// struct git_repository; -// struct git_odb; - -namespace sourc3 { -template -class Holder { -public: - Holder() = default; - - explicit Holder(T* ptr) : obj_(ptr) { - assert(ptr != nullptr); - } - - Holder(const T&) = delete; - - ~Holder() noexcept { - D(obj_); - } - - T** Addr() noexcept { - return &obj_; - } - - explicit operator bool() const noexcept { - return obj_; - } - - T* operator*() const noexcept { - return obj_; - } - -private: - T* obj_ = nullptr; -}; - -namespace git { -using Index = Holder; -using Repository = Holder; -using Tree = Holder; -using Commit = Holder; -using Signature = Holder; -using Config = Holder; -using RevWalk = Holder; -using Object = Holder; -using ObjectDB = Holder; -using Reference = Holder; - -struct Init { - Init() noexcept; - ~Init() noexcept; -}; - -struct RepoAccessor { - explicit RepoAccessor(std::string_view dir); - - Repository m_repo; - ObjectDB m_odb; -}; - -} // namespace git - -std::string ToString(const git_oid& oid); -git_oid FromString(const std::string& str); -} // namespace sourc3 - -bool operator<(const git_oid& left, const git_oid& right) noexcept; -bool operator==(const git_oid& left, const git_oid& right) noexcept; -bool operator!=(const git_oid& left, const git_oid& right) noexcept; +/* + * Copyright 2021-2022 SOURC3 Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "git2.h" +#include +#include +#include + +// struct git_repository; +// struct git_odb; + +namespace sourc3 { +template +class Holder { +public: + Holder() = default; + + explicit Holder(T* ptr) : obj_(ptr) { + assert(ptr != nullptr); + } + + Holder(const T&) = delete; + + ~Holder() noexcept { + D(obj_); + } + + T** Addr() noexcept { + return &obj_; + } + + explicit operator bool() const noexcept { + return obj_; + } + + T* operator*() const noexcept { + return obj_; + } + +private: + T* obj_ = nullptr; +}; + +namespace git { +using Index = Holder; +using Repository = Holder; +using Tree = Holder; +using Commit = Holder; +using Signature = Holder; +using Config = Holder; +using RevWalk = Holder; +using Object = Holder; +using ObjectDB = Holder; +using Reference = Holder; + +struct Init { + Init() noexcept; + ~Init() noexcept; +}; + +struct RepoAccessor { + explicit RepoAccessor(std::string_view dir); + + Repository m_repo; + ObjectDB m_odb; +}; + +} // namespace git + +std::string ToString(const git_oid& oid); +git_oid FromString(const std::string& str); +} // namespace sourc3 + +bool operator<(const git_oid& left, const git_oid& right) noexcept; +bool operator==(const git_oid& left, const git_oid& right) noexcept; +bool operator!=(const git_oid& left, const git_oid& right) noexcept; diff --git a/remote_helper/object_collector.cpp b/remote_helper/git/object_collector.cpp similarity index 96% rename from remote_helper/object_collector.cpp rename to remote_helper/git/object_collector.cpp index dc42eeac..f87a8270 100644 --- a/remote_helper/object_collector.cpp +++ b/remote_helper/git/object_collector.cpp @@ -12,199 +12,199 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "object_collector.h" -#include "utils.h" -#include -#include -#include -namespace sourc3 { -///////////////////////////////////////////////////// - -ObjectInfo::ObjectInfo(const git_oid& o, git_object_t t, git_odb_object* obj) - : oid(o), type(t), object(obj) { -} - -ObjectInfo::ObjectInfo(const ObjectInfo& other) - : oid(other.oid), - type(other.type), - name(other.name), - fullPath(other.fullPath), - selected(other.selected), - ipfsHash(other.ipfsHash) { - git_odb_object_dup(&object, other.object); -} - -ObjectInfo& ObjectInfo::operator=(const ObjectInfo& other) { - if (this != &other) { - oid = other.oid; - type = other.type; - - name = other.name; - fullPath = other.fullPath; - selected = other.selected; - ipfsHash = other.ipfsHash; - - git_odb_object_dup(&object, other.object); - } - return *this; -} - -ObjectInfo::ObjectInfo(ObjectInfo&& other) noexcept - : oid(other.oid), - type(other.type), - object(std::exchange(other.object, nullptr)), - name(std::move(other.name)), - fullPath(std::move(other.fullPath)), - selected(other.selected), - ipfsHash(std::move(other.ipfsHash)) - -{ -} - -ObjectInfo& ObjectInfo::operator=(ObjectInfo&& other) noexcept { - if (this != &other) { - oid = other.oid; - type = other.type; - object = std::exchange(other.object, nullptr); - name = std::move(other.name); - fullPath = std::move(other.fullPath); - selected = other.selected; - ipfsHash = std::move(other.ipfsHash); - } - return *this; -} - -ObjectInfo::~ObjectInfo() noexcept { - git_odb_object_free(object); -} - -std::string ObjectInfo::GetDataString() const { - return ToHex(GetData(), GetSize()); -} - -int8_t ObjectInfo::GetSerializeType() const { - if (IsIPFSObject()) { - return static_cast(type) | 0x80; - } - - return static_cast(type); -} - -const uint8_t* ObjectInfo::GetData() const { - if (IsIPFSObject()) { - return ipfsHash.data(); - } - - return static_cast(git_odb_object_data(object)); -} - -size_t ObjectInfo::GetSize() const { - if (IsIPFSObject()) { - return ipfsHash.size(); - } - - return git_odb_object_size(object); -} - -bool ObjectInfo::IsIPFSObject() const { - return !ipfsHash.empty(); -} - -///////////////////////////////////////////////////// - -void ObjectCollector::Traverse(const std::vector& refs, - const std::vector& hidden) { - using namespace git; - RevWalk walk; - git_revwalk_new(walk.Addr(), *m_repo); - git_revwalk_sorting(*walk, GIT_SORT_TIME); - for (const auto& h : hidden) { - git_revwalk_hide(*walk, &h); - } - for (const auto& ref : refs) { - git_revwalk_push_ref(*walk, ref.localRef.c_str()); - auto& r = m_refs.emplace_back(); - git_reference_name_to_id(&r.target, *m_repo, ref.localRef.c_str()); - r.name = ref.remoteRef; - } - git_oid oid; - while (git_revwalk_next(&oid, *walk) == 0) { - // commits - Object obj; - git_object_lookup(obj.Addr(), *m_repo, &oid, GIT_OBJECT_ANY); - auto p = m_set.emplace(oid); - if (!p.second) { - continue; - } - - git_odb_object* dbobj = nullptr; - git_odb_read(&dbobj, *m_odb, &oid); - m_objects.emplace_back(oid, git_object_type(*obj), dbobj); - - Tree tree; - Commit commit; - git_commit_lookup(commit.Addr(), *m_repo, &oid); - git_commit_tree(tree.Addr(), *commit); - - m_set.emplace(*git_tree_id(*tree)); - CollectObject(*git_tree_id(*tree)); - TraverseTree(*tree); - } -} - -void ObjectCollector::TraverseTree(const git_tree* tree) { - for (size_t i = 0; i < git_tree_entrycount(tree); ++i) { - auto* entry = git_tree_entry_byindex(tree, i); - auto* entry_oid = git_tree_entry_id(entry); - auto p = m_set.emplace(*entry_oid); - if (!p.second) { - continue; // already visited - } - - auto type = git_tree_entry_type(entry); - switch (type) { - case GIT_OBJECT_TREE: { - auto& obj = CollectObject(*entry_oid); - obj.name = git_tree_entry_name(entry); - obj.fullPath = Join(m_path, obj.name); - m_path.push_back(obj.name); - git::Tree sub_tree; - git_tree_lookup(sub_tree.Addr(), *m_repo, entry_oid); - TraverseTree(*sub_tree); - m_path.pop_back(); - } break; - case GIT_OBJECT_BLOB: { - auto& obj = CollectObject(*entry_oid); - obj.name = git_tree_entry_name(entry); - obj.fullPath = Join(m_path, obj.name); - } break; - default: - break; - } - } -} - -std::string ObjectCollector::Join(const std::vector& path, - const std::string& name) { - std::string res; - for (const auto& p : path) { - res.append(p); - res.append("/"); - } - res.append(name); - return res; -} - -ObjectInfo& ObjectCollector::CollectObject(const git_oid& oid) { - git_odb_object* dbobj = nullptr; - git_odb_read(&dbobj, *m_odb, &oid); - - auto obj_size = git_odb_object_size(dbobj); - auto& obj = m_objects.emplace_back(oid, git_odb_object_type(dbobj), dbobj); - git_oid r; - git_odb_hash(&r, git_odb_object_data(dbobj), obj_size, - git_odb_object_type(dbobj)); - - return obj; -} -} // namespace sourc3 +#include "object_collector.h" +#include "utils.h" +#include +#include +#include +namespace sourc3 { +///////////////////////////////////////////////////// + +ObjectInfo::ObjectInfo(const git_oid& o, git_object_t t, git_odb_object* obj) + : oid(o), type(t), object(obj) { +} + +ObjectInfo::ObjectInfo(const ObjectInfo& other) + : oid(other.oid), + type(other.type), + name(other.name), + fullPath(other.fullPath), + selected(other.selected), + ipfsHash(other.ipfsHash) { + git_odb_object_dup(&object, other.object); +} + +ObjectInfo& ObjectInfo::operator=(const ObjectInfo& other) { + if (this != &other) { + oid = other.oid; + type = other.type; + + name = other.name; + fullPath = other.fullPath; + selected = other.selected; + ipfsHash = other.ipfsHash; + + git_odb_object_dup(&object, other.object); + } + return *this; +} + +ObjectInfo::ObjectInfo(ObjectInfo&& other) noexcept + : oid(other.oid), + type(other.type), + object(std::exchange(other.object, nullptr)), + name(std::move(other.name)), + fullPath(std::move(other.fullPath)), + selected(other.selected), + ipfsHash(std::move(other.ipfsHash)) + +{ +} + +ObjectInfo& ObjectInfo::operator=(ObjectInfo&& other) noexcept { + if (this != &other) { + oid = other.oid; + type = other.type; + object = std::exchange(other.object, nullptr); + name = std::move(other.name); + fullPath = std::move(other.fullPath); + selected = other.selected; + ipfsHash = std::move(other.ipfsHash); + } + return *this; +} + +ObjectInfo::~ObjectInfo() noexcept { + git_odb_object_free(object); +} + +std::string ObjectInfo::GetDataString() const { + return ToHex(GetData(), GetSize()); +} + +int8_t ObjectInfo::GetSerializeType() const { + if (IsIPFSObject()) { + return static_cast(type) | 0x80; + } + + return static_cast(type); +} + +const uint8_t* ObjectInfo::GetData() const { + if (IsIPFSObject()) { + return ipfsHash.data(); + } + + return static_cast(git_odb_object_data(object)); +} + +size_t ObjectInfo::GetSize() const { + if (IsIPFSObject()) { + return ipfsHash.size(); + } + + return git_odb_object_size(object); +} + +bool ObjectInfo::IsIPFSObject() const { + return !ipfsHash.empty(); +} + +///////////////////////////////////////////////////// + +void ObjectCollector::Traverse(const std::vector& refs, + const std::vector& hidden) { + using namespace git; + RevWalk walk; + git_revwalk_new(walk.Addr(), *m_repo); + git_revwalk_sorting(*walk, GIT_SORT_TIME); + for (const auto& h : hidden) { + git_revwalk_hide(*walk, &h); + } + for (const auto& ref : refs) { + git_revwalk_push_ref(*walk, ref.localRef.c_str()); + auto& r = m_refs.emplace_back(); + git_reference_name_to_id(&r.target, *m_repo, ref.localRef.c_str()); + r.name = ref.remoteRef; + } + git_oid oid; + while (git_revwalk_next(&oid, *walk) == 0) { + // commits + Object obj; + git_object_lookup(obj.Addr(), *m_repo, &oid, GIT_OBJECT_ANY); + auto p = m_set.emplace(oid); + if (!p.second) { + continue; + } + + git_odb_object* dbobj = nullptr; + git_odb_read(&dbobj, *m_odb, &oid); + m_objects.emplace_back(oid, git_object_type(*obj), dbobj); + + Tree tree; + Commit commit; + git_commit_lookup(commit.Addr(), *m_repo, &oid); + git_commit_tree(tree.Addr(), *commit); + + m_set.emplace(*git_tree_id(*tree)); + CollectObject(*git_tree_id(*tree)); + TraverseTree(*tree); + } +} + +void ObjectCollector::TraverseTree(const git_tree* tree) { + for (size_t i = 0; i < git_tree_entrycount(tree); ++i) { + auto* entry = git_tree_entry_byindex(tree, i); + auto* entry_oid = git_tree_entry_id(entry); + auto p = m_set.emplace(*entry_oid); + if (!p.second) { + continue; // already visited + } + + auto type = git_tree_entry_type(entry); + switch (type) { + case GIT_OBJECT_TREE: { + auto& obj = CollectObject(*entry_oid); + obj.name = git_tree_entry_name(entry); + obj.fullPath = Join(m_path, obj.name); + m_path.push_back(obj.name); + git::Tree sub_tree; + git_tree_lookup(sub_tree.Addr(), *m_repo, entry_oid); + TraverseTree(*sub_tree); + m_path.pop_back(); + } break; + case GIT_OBJECT_BLOB: { + auto& obj = CollectObject(*entry_oid); + obj.name = git_tree_entry_name(entry); + obj.fullPath = Join(m_path, obj.name); + } break; + default: + break; + } + } +} + +std::string ObjectCollector::Join(const std::vector& path, + const std::string& name) { + std::string res; + for (const auto& p : path) { + res.append(p); + res.append("/"); + } + res.append(name); + return res; +} + +ObjectInfo& ObjectCollector::CollectObject(const git_oid& oid) { + git_odb_object* dbobj = nullptr; + git_odb_read(&dbobj, *m_odb, &oid); + + auto obj_size = git_odb_object_size(dbobj); + auto& obj = m_objects.emplace_back(oid, git_odb_object_type(dbobj), dbobj); + git_oid r; + git_odb_hash(&r, git_odb_object_data(dbobj), obj_size, + git_odb_object_type(dbobj)); + + return obj; +} +} // namespace sourc3 diff --git a/remote_helper/object_collector.h b/remote_helper/git/object_collector.h similarity index 96% rename from remote_helper/object_collector.h rename to remote_helper/git/object_collector.h index 0a70c1aa..28e03475 100644 --- a/remote_helper/object_collector.h +++ b/remote_helper/git/object_collector.h @@ -1,171 +1,171 @@ -/* - * Copyright 2021-2022 SOURC3 Team - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "git_utils.h" -#include "utils.h" -#include -#include -#include -#include - -// struct git_odb_object; - -namespace sourc3 { -// structs for serialization -#pragma pack(push, 1) -struct GitObject { - int8_t type; - git_oid hash; - uint32_t data_size; - // followed by data - - bool IsValidObjectType() const { - auto t = type & 0x7f; // clear first bit - return t >= GIT_OBJECT_COMMIT && t <= GIT_OBJECT_TAG; - } - - git_object_t GetObjectType() const { - if (IsValidObjectType()) { - return static_cast(type & 0x7f); - } - - throw std::runtime_error("Invalid object type"); - } - - bool IsIPFSObject() const { - return (type & 0x80) != 0 && IsValidObjectType(); - } -}; - -struct ObjectsInfo { - uint32_t objects_number; - // followed by data -}; -#pragma pack(pop) - -struct ObjectInfo { - git_oid oid; - git_object_t type; - git_odb_object* object; - - std::string name; - std::string fullPath; - bool selected = false; - ByteBuffer ipfsHash; - - ObjectInfo(const git_oid& o, git_object_t t, git_odb_object* obj); - ObjectInfo(const ObjectInfo& other); - ObjectInfo& operator=(const ObjectInfo& other); - ObjectInfo(ObjectInfo&& other) noexcept; - ObjectInfo& operator=(ObjectInfo&& other) noexcept; - ~ObjectInfo() noexcept; - - int8_t GetSerializeType() const; - std::string GetDataString() const; - const uint8_t* GetData() const; - size_t GetSize() const; - bool IsIPFSObject() const; -}; - -struct Refs { - std::string localRef; - std::string remoteRef; -}; - -struct Ref { - std::string name; - std::string ipfs_hash; - git_oid target; -}; - -class ObjectCollector : public git::RepoAccessor { -public: - using git::RepoAccessor::RepoAccessor; - void Traverse(const std::vector& refs, - const std::vector& hidden); - template - void Serialize(Func func) { - // TODO: replace code below with calling serializer - constexpr size_t kSizeThreshold = 100000; - size_t done = 0; - - while (true) { - uint32_t count = 0; - size_t serialized_size = 0; - std::vector indecies; - for (size_t i = 0; i < m_objects.size(); ++i) { - auto& obj = m_objects[i]; - if (obj.selected) { - continue; - } - - auto obj_size = obj.GetSize(); - auto s = sizeof(GitObject) + obj_size; - if (serialized_size + s <= kSizeThreshold) { - serialized_size += s; - ++count; - indecies.emplace_back(i); - obj.selected = true; - } - if (serialized_size == kSizeThreshold) { - break; - } - } - - ByteBuffer buf; - if (count == 0) { - func(buf, done); - break; - } - - // serializing - buf.resize(serialized_size + - sizeof(ObjectsInfo)); // objects count size - auto* p = reinterpret_cast(buf.data()); - p->objects_number = count; - auto* ser_obj = reinterpret_cast(p + 1); - for (size_t i = 0; i < count; ++i) { - const auto& obj = m_objects[indecies[i]]; - ser_obj->data_size = static_cast(obj.GetSize()); - ser_obj->type = obj.GetSerializeType(); - git_oid_cpy(&ser_obj->hash, &obj.oid); - auto* data = reinterpret_cast(ser_obj + 1); - std::copy_n(obj.GetData(), obj.GetSize(), data); - ser_obj = reinterpret_cast(data + obj.GetSize()); - } - - done += count; - if (func(buf, done) == false) { - break; - } - } - } - -private: - void TraverseTree(const git_tree* tree); - std::string Join(const std::vector& path, - const std::string& name); - ObjectInfo& CollectObject(const git_oid& oid); - -public: - std::set m_set; - std::vector m_objects; - std::vector m_refs; - std::vector m_path; -}; -} // namespace sourc3 +/* + * Copyright 2021-2022 SOURC3 Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "git_utils.h" +#include "utils.h" +#include +#include +#include +#include + +// struct git_odb_object; + +namespace sourc3 { +// structs for serialization +#pragma pack(push, 1) +struct GitObject { + int8_t type; + git_oid hash; + uint32_t data_size; + // followed by data + + bool IsValidObjectType() const { + auto t = type & 0x7f; // clear first bit + return t >= GIT_OBJECT_COMMIT && t <= GIT_OBJECT_TAG; + } + + git_object_t GetObjectType() const { + if (IsValidObjectType()) { + return static_cast(type & 0x7f); + } + + throw std::runtime_error("Invalid object type"); + } + + bool IsIPFSObject() const { + return (type & 0x80) != 0 && IsValidObjectType(); + } +}; + +struct ObjectsInfo { + uint32_t objects_number; + // followed by data +}; +#pragma pack(pop) + +struct ObjectInfo { + git_oid oid; + git_object_t type; + git_odb_object* object; + + std::string name; + std::string fullPath; + bool selected = false; + ByteBuffer ipfsHash; + + ObjectInfo(const git_oid& o, git_object_t t, git_odb_object* obj); + ObjectInfo(const ObjectInfo& other); + ObjectInfo& operator=(const ObjectInfo& other); + ObjectInfo(ObjectInfo&& other) noexcept; + ObjectInfo& operator=(ObjectInfo&& other) noexcept; + ~ObjectInfo() noexcept; + + int8_t GetSerializeType() const; + std::string GetDataString() const; + const uint8_t* GetData() const; + size_t GetSize() const; + bool IsIPFSObject() const; +}; + +struct Refs { + std::string localRef; + std::string remoteRef; +}; + +struct Ref { + std::string name; + std::string ipfs_hash; + git_oid target; +}; + +class ObjectCollector : public git::RepoAccessor { +public: + using git::RepoAccessor::RepoAccessor; + void Traverse(const std::vector& refs, + const std::vector& hidden); + template + void Serialize(Func func) { + // TODO: replace code below with calling serializer + constexpr size_t kSizeThreshold = 100000; + size_t done = 0; + + while (true) { + uint32_t count = 0; + size_t serialized_size = 0; + std::vector indecies; + for (size_t i = 0; i < m_objects.size(); ++i) { + auto& obj = m_objects[i]; + if (obj.selected) { + continue; + } + + auto obj_size = obj.GetSize(); + auto s = sizeof(GitObject) + obj_size; + if (serialized_size + s <= kSizeThreshold) { + serialized_size += s; + ++count; + indecies.emplace_back(i); + obj.selected = true; + } + if (serialized_size == kSizeThreshold) { + break; + } + } + + ByteBuffer buf; + if (count == 0) { + func(buf, done); + break; + } + + // serializing + buf.resize(serialized_size + + sizeof(ObjectsInfo)); // objects count size + auto* p = reinterpret_cast(buf.data()); + p->objects_number = count; + auto* ser_obj = reinterpret_cast(p + 1); + for (size_t i = 0; i < count; ++i) { + const auto& obj = m_objects[indecies[i]]; + ser_obj->data_size = static_cast(obj.GetSize()); + ser_obj->type = obj.GetSerializeType(); + git_oid_cpy(&ser_obj->hash, &obj.oid); + auto* data = reinterpret_cast(ser_obj + 1); + std::copy_n(obj.GetData(), obj.GetSize(), data); + ser_obj = reinterpret_cast(data + obj.GetSize()); + } + + done += count; + if (func(buf, done) == false) { + break; + } + } + } + +private: + void TraverseTree(const git_tree* tree); + std::string Join(const std::vector& path, + const std::string& name); + ObjectInfo& CollectObject(const git_oid& oid); + +public: + std::set m_set; + std::vector m_objects; + std::vector m_refs; + std::vector m_path; +}; +} // namespace sourc3 diff --git a/remote_helper/remote_helper.cpp b/remote_helper/remote_helper.cpp index c82e2386..bc3f1bf3 100644 --- a/remote_helper/remote_helper.cpp +++ b/remote_helper/remote_helper.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -31,11 +32,12 @@ #include #include -#include "object_collector.h" +#include "git/object_collector.h" #include "utils.h" -#include "git_utils.h" +#include "git/git_utils.h" #include "version.h" -#include "wallet_client.h" +#include "wallets/client_factory.h" +#include "engines/engine_factory.h" namespace po = boost::program_options; namespace json = boost::json; @@ -45,171 +47,6 @@ using namespace sourc3; #define PROTO_NAME "sourc3" namespace { -class ProgressReporter { -public: - ProgressReporter(std::string_view title, size_t total) - : title_(title), total_(total) { - UpdateProgress(0); - } - - ~ProgressReporter() { - if (failure_reason_.empty()) { - Done(); - } else { - StopProgress(failure_reason_); - } - } - - void UpdateProgress(size_t done) { - done_ = done; - ShowProgress("\r"); - } - - void AddProgress(size_t done) { - done_ += done; - ShowProgress("\r"); - } - - void Done() { - StopProgress("done"); - } - - void Failed(const std::string& failure) { - failure_reason_ = failure; - } - - void StopProgress(std::string_view result) { - std::stringstream ss; - ss << ", " << result << ".\n"; - ShowProgress(ss.str()); - } - -private: - void ShowProgress(std::string_view eol) { - std::stringstream ss; - ss << title_ << ": "; - if (total_ > 0) { - size_t percent = done_ * 100 / total_; - ss << percent << "%" - << " (" << done_ << "/" << total_ << ")"; - } else { - ss << done_; - } - ss << eol; - cerr << ss.str(); - cerr.flush(); - } - -private: - std::string_view title_; - std::string failure_reason_; - size_t done_ = 0; - size_t total_; -}; - -struct ObjectWithContent : public GitObject { - std::string ipfs_hash; - std::string content; -}; - -struct GitIdWithIPFS { - GitIdWithIPFS() = default; - - GitIdWithIPFS(git_oid oid, string ipfs) - : oid(std::move(oid)), ipfs(std::move(ipfs)) { - } - - git_oid oid; - std::string ipfs; - - std::string ToString() const { - return ipfs + "\n" + ::ToString(oid); - } - - bool operator==(const GitIdWithIPFS& other) const { - return (oid == other.oid) && (ipfs == other.ipfs); - } -}; - -struct MetaBlock { - GitIdWithIPFS hash; - - virtual ~MetaBlock() = default; - - virtual std::string Serialize() const = 0; -}; - -struct CommitMetaBlock final : MetaBlock { - std::string tree_meta_hash; - std::vector parent_hashes; - - CommitMetaBlock() = default; - - explicit CommitMetaBlock(const string& serialized) { - istringstream ss(serialized); - string hash_oid; - ss >> hash.ipfs; - ss >> hash_oid; - hash.oid = FromString(hash_oid); - ss >> tree_meta_hash; - string hash_ipfs; - while (ss >> hash_ipfs) { - if (hash_ipfs.empty()) { - break; - } - ss >> hash_oid; - parent_hashes.emplace_back(FromString(hash_oid), - std::move(hash_ipfs)); - } - } - - string Serialize() const final { - std::string data = hash.ToString() + "\n" + tree_meta_hash + "\n"; - for (const auto& parent : parent_hashes) { - data += parent.ToString() + "\n"; - } - return data; - } -}; - -struct TreeMetaBlock final : MetaBlock { - std::vector entries; - - TreeMetaBlock() = default; - - explicit TreeMetaBlock(const string& serialized) { - istringstream ss(serialized); - string hash_oid; - ss >> hash.ipfs; - ss >> hash_oid; - hash.oid = FromString(hash_oid); - string hash_ipfs; - while (ss >> hash_ipfs) { - if (hash_oid.empty()) { - break; - } - ss >> hash_oid; - entries.emplace_back(FromString(hash_oid), std::move(hash_ipfs)); - } - } - - string Serialize() const final { - std::string data = hash.ToString() + "\n"; - for (const auto& entry : entries) { - data += entry.ToString() + "\n"; - } - return data; - } -}; - -template -ByteBuffer FromHex(const String& s) { - ByteBuffer res; - res.reserve(s.size() / 2); - boost::algorithm::unhex(s.begin(), s.end(), std::back_inserter(res)); - return res; -} - vector ParseArgs(std::string_view args_sv) { vector args; while (!args_sv.empty()) { @@ -223,807 +60,15 @@ vector ParseArgs(std::string_view args_sv) { } return args; } - -struct OidHasher { - std::hash hasher; - size_t operator()(const git_oid& oid) const { - return hasher(ToString(oid)); - } -}; - -using HashMapping = std::unordered_map; - -json::value ParseJsonAndTest(json::string_view sv) { - auto r = json::parse(sv); - if (!r.is_object()) { - throw std::runtime_error{sv.to_string() + " isn't an object."}; - } - - if (const auto* error = r.as_object().if_contains("error"); error) { - throw std::runtime_error( - error->as_object().at("message").as_string().c_str()); - } - return r; -} - -std::string CreateRefsFile(const std::vector& refs, - const HashMapping& mapping) { - std::string file; - for (const auto& ref : refs) { - if (mapping.count(ref.target) == 0) { - throw std::runtime_error{"Some refs are not used!"}; - } - file += ref.name + "\t" + mapping.at(ref.target) + "\t" + - ToString(ref.target) + "\n"; - } - return file; -} - -std::vector ParseRefs(const std::string& refs_file) { - std::vector refs; - if (refs_file.empty()) { - return refs; - } - - std::istringstream ss(refs_file); - std::string ref_name; - std::string target_ipfs; - std::string target_oid; - while (ss >> ref_name) { - if (ref_name.empty()) { - break; - } - ss >> target_ipfs; - ss >> target_oid; - refs.push_back( - Ref{std::move(ref_name), target_ipfs, FromString(target_oid)}); - } - return refs; -} - -HashMapping ParseRefHashed(const std::string& refs_file) { - HashMapping mapping; - if (refs_file.empty()) { - return mapping; - } - - std::istringstream ss(refs_file); - std::string ref_name; - std::string target_ipfs; - std::string target_oid; - while (ss >> ref_name) { - if (ref_name.empty()) { - break; - } - ss >> target_ipfs; - ss >> target_oid; - mapping[FromString(target_oid)] = std::move(target_ipfs); - } - return mapping; } -std::unique_ptr GetCommitMetaBlock( - const git::Commit& commit, const HashMapping& oid_to_meta, - const HashMapping& oid_to_ipfs) { - auto block = std::make_unique(); - git_commit* raw_commit = *commit; - const auto* commit_id = git_commit_id(raw_commit); - block->hash.oid = *commit_id; - block->hash.ipfs = oid_to_ipfs.at(*commit_id); - if (oid_to_meta.count(*git_commit_tree_id(raw_commit)) == 0) { - throw std::runtime_error{"Cannot find tree " + - ToString(*git_commit_tree_id(raw_commit)) + - " meta on IPFS"}; - } - block->tree_meta_hash = oid_to_meta.at(*git_commit_tree_id(raw_commit)); - unsigned int parents_count = git_commit_parentcount(raw_commit); - for (unsigned int i = 0; i < parents_count; ++i) { - auto* parent_id = git_commit_parent_id(raw_commit, i); - if (oid_to_meta.count(*parent_id) > 0) { - block->parent_hashes.emplace_back(*parent_id, - oid_to_meta.at(*parent_id)); - } else { - throw std::runtime_error{ - "Something wrong with push, " - "we cannot find meta object for parent " + - ToString(*parent_id)}; - } - } - return block; -} - -std::unique_ptr GetTreeMetaBlock( - const git::Tree& tree, const HashMapping& oid_to_ipfs) { - git_tree* raw_tree = *tree; - const auto* tree_id = git_tree_id(raw_tree); - auto block = std::make_unique(); - block->hash = {*tree_id, oid_to_ipfs.at(*tree_id)}; - for (size_t i = 0, size = git_tree_entrycount(raw_tree); i < size; ++i) { - const auto& entry_id = - *git_tree_entry_id(git_tree_entry_byindex(raw_tree, i)); - block->entries.emplace_back(entry_id, oid_to_ipfs.at(entry_id)); - } - return block; -} - -std::unique_ptr GetMetaBlock( - const sourc3::git::RepoAccessor& accessor, const ObjectInfo& obj, - const HashMapping& oid_to_meta, const HashMapping& oid_to_ipfs) { - if (obj.type == GIT_OBJECT_COMMIT) { - git::Commit commit; - git_commit_lookup(commit.Addr(), *accessor.m_repo, &obj.oid); - return GetCommitMetaBlock(commit, oid_to_meta, oid_to_ipfs); - } else if (obj.type == GIT_OBJECT_TREE) { - git::Tree tree; - git_tree_lookup(tree.Addr(), *accessor.m_repo, &obj.oid); - return GetTreeMetaBlock(tree, oid_to_ipfs); - } - - return nullptr; -} - -std::string GetStringFromIPFS(const std::string& hash, - SimpleWalletClient& wallet_client) { - auto res = ParseJsonAndTest(wallet_client.LoadObjectFromIPFS(hash)); - if (!res.as_object().contains("result")) { - throw std::runtime_error{"No result in resulting JSON, probably error"}; - } - auto d = res.as_object()["result"].as_object()["data"].as_array(); - ByteBuffer buf; - buf.reserve(d.size()); - for (auto&& v : d) { - buf.emplace_back(static_cast(v.get_int64())); - } - return ByteBufferToString(buf); -} - -ObjectWithContent CreateObject(int8_t type, git_oid hash, std::string ipfs_hash, - std::string content) { - ObjectWithContent obj; - obj.hash = std::move(hash); - obj.type = type; - obj.data_size = static_cast(content.size()); - obj.ipfs_hash = std::move(ipfs_hash); - obj.content = std::move(content); - return obj; -} - -bool CheckCommitsLinking( - const unordered_map>& - metas, - const vector& new_refs, - const HashMapping& oid_to_meta) { - deque working_hashes; - for (const auto& ref : new_refs) { - if (oid_to_meta.count(ref.target) == 0) { - return false; - } - string hash = oid_to_meta.at(ref.target); - working_hashes.push_back(hash); - } - - while (!working_hashes.empty()) { - string hash = std::move(working_hashes.back()); - working_hashes.pop_back(); - if (metas.count(hash) == 0) { - return false; - } - - auto* commit = std::get_if(&metas.at(hash)); - if (commit == nullptr) { - return false; - } - - for (const auto& parent_meta : commit->parent_hashes) { - working_hashes.push_back(parent_meta.ipfs); - } - } - return true; -} - -} // namespace - -class RemoteHelper { -public: - enum struct CommandResult { Ok, Failed, Batch }; - explicit RemoteHelper(SimpleWalletClient& wc) : wallet_client_{wc} { - } - - CommandResult DoCommand(string_view command, vector& args) { - auto it = find_if(begin(commands_), end(commands_), [&](const auto& c) { - return command == c.command; - }); - if (it == end(commands_)) { - cerr << "Unknown command: " << command << endl; - return CommandResult::Failed; - } - return std::invoke(it->action, this, args); - } - - CommandResult DoList([[maybe_unused]] const vector& args) { - auto refs = RequestRefs(); - - for (const auto& r : refs) { - cout << ToString(r.target) << " " << r.name << '\n'; - } - if (!refs.empty()) { - cout << "@" << refs.back().name << " HEAD\n"; - } - - return CommandResult::Ok; - } - - CommandResult DoOption([[maybe_unused]] const vector& args) { - static string_view results[] = {"error invalid value", "ok", - "unsupported"}; - - auto res = options_.Set(args[1], args[2]); - - cout << results[size_t(res)]; - return CommandResult::Ok; - } - - CommandResult DoFetch(const vector& args) { - std::set object_hashes; - object_hashes.emplace(args[1].data(), args[1].size()); - size_t depth = 1; - std::set received_objects; - - auto enuque_object = [&](const std::string& oid) { - if (received_objects.find(oid) == received_objects.end()) { - object_hashes.insert(oid); - } - }; - - git::RepoAccessor accessor(wallet_client_.GetRepoDir()); - size_t total_objects = 0; - - auto objects = GetUploadedObjects(RequestRefs()); - for (const auto& obj : objects) { - if (git_odb_exists(*accessor.m_odb, &obj.hash) != 0) { - received_objects.insert(ToString(obj.hash)); - ++total_objects; - } - } - auto progress = MakeProgress("Receiving objects", - total_objects - received_objects.size()); - size_t done = 0; - while (!object_hashes.empty()) { - auto it_to_receive = object_hashes.begin(); - const auto& object_to_receive = *it_to_receive; - - git_oid oid; - git_oid_fromstr(&oid, object_to_receive.data()); - - auto it = - std::find_if(objects.begin(), objects.end(), [&](auto&& o) { - return o.hash == oid; - }); - if (it == objects.end()) { - received_objects.insert(object_to_receive); // move to received - object_hashes.erase(it_to_receive); - continue; - } - received_objects.insert(object_to_receive); - - auto buf = it->content; - git_oid res_oid; - auto type = it->GetObjectType(); - git_oid r; - git_odb_hash(&r, buf.data(), buf.size(), type); - if (r != oid) { - // invalid hash - return CommandResult::Failed; - } - if (git_odb_write(&res_oid, *accessor.m_odb, buf.data(), buf.size(), - type) < 0) { - return CommandResult::Failed; - } - if (type == GIT_OBJECT_TREE) { - git::Tree tree; - git_tree_lookup(tree.Addr(), *accessor.m_repo, &oid); - - auto count = git_tree_entrycount(*tree); - for (size_t i = 0; i < count; ++i) { - auto* entry = git_tree_entry_byindex(*tree, i); - auto s = ToString(*git_tree_entry_id(entry)); - enuque_object(s); - } - } else if (type == GIT_OBJECT_COMMIT) { - git::Commit commit; - git_commit_lookup(commit.Addr(), *accessor.m_repo, &oid); - if (depth < options_.depth || - options_.depth == Options::kInfiniteDepth) { - auto count = git_commit_parentcount(*commit); - for (unsigned i = 0; i < count; ++i) { - auto* id = git_commit_parent_id(*commit, i); - auto s = ToString(*id); - enuque_object(s); - } - ++depth; - } - enuque_object(ToString(*git_commit_tree_id(*commit))); - } - if (progress) { - progress->UpdateProgress(++done); - } - - object_hashes.erase(it_to_receive); - } - return CommandResult::Batch; - } - - CommandResult DoPush(const vector& args) { - ObjectCollector collector(wallet_client_.GetRepoDir()); - std::vector refs; - std::vector local_refs; - bool is_forced = false; - for (size_t i = 1; i < args.size(); ++i) { - auto& arg = args[i]; - auto p = arg.find(':'); - auto& r = refs.emplace_back(); - is_forced = arg[0] == '+'; - size_t start_index = is_forced ? 1 : 0; - r.localRef = arg.substr(start_index, p - start_index); - r.remoteRef = arg.substr(p + 1); - git::Reference local_ref; - if (git_reference_lookup(local_ref.Addr(), *collector.m_repo, - r.localRef.c_str()) < 0) { - cerr << "Local reference \'" << r.localRef << "\' doesn't exist" - << endl; - return CommandResult::Failed; - } - auto& lr = local_refs.emplace_back(); - git_oid_cpy(&lr, git_reference_target(*local_ref)); - } - - auto remote_refs = RequestRefs(); - auto [uploaded_objects, metas] = - GetUploadedObjectsWithMetas(remote_refs); - auto uploaded_oids = GetOidsFromObjects(uploaded_objects); - std::vector merge_bases; - for (const auto& remote_ref : remote_refs) { - for (const auto& local_ref : local_refs) { - auto& base = merge_bases.emplace_back(); - git_merge_base(&base, *collector.m_repo, &remote_ref.target, - &local_ref); - } - } - - collector.Traverse(refs, merge_bases); - - auto& objs = collector.m_objects; - std::sort(objs.begin(), objs.end(), [](auto&& left, auto&& right) { - return left.oid < right.oid; - }); - { - auto it = std::unique(objs.begin(), objs.end(), - [](auto&& left, auto& right) { - return left.oid == right.oid; - }); - objs.erase(it, objs.end()); - } - { - auto non_blob = std::partition( - objs.begin(), objs.end(), [](const ObjectInfo& obj) { - return obj.type == GIT_OBJECT_BLOB; - }); - auto commits = - std::partition(non_blob, objs.end(), [](const ObjectInfo& obj) { - return obj.type == GIT_OBJECT_TREE; - }); - std::sort( - commits, objs.end(), - [&collector](const ObjectInfo& lhs, const ObjectInfo& rhs) { - git::Commit rhs_commit; - git_commit_lookup(rhs_commit.Addr(), *collector.m_repo, - &rhs.oid); - unsigned int parents_count = - git_commit_parentcount(*rhs_commit); - for (unsigned int i = 0; i < parents_count; ++i) { - if (*git_commit_parent_id(*rhs_commit, i) == lhs.oid) { - return true; - } - } - return false; - }); - } - - for (auto& obj : collector.m_objects) { - if (uploaded_oids.find(obj.oid) != uploaded_oids.end()) { - obj.selected = true; - } - } - - { - auto it = - std::remove_if(objs.begin(), objs.end(), [](const auto& o) { - return o.selected; - }); - objs.erase(it, objs.end()); - } - - State prev_state; - { - auto state = ParseJsonAndTest(wallet_client_.LoadActualState()); - if (!state.is_object()) { - cerr << "Cannot parse object state JSON: \'" << state << "\'" - << endl; - return CommandResult::Failed; - } - auto& state_obj = state.as_object(); - if (!state_obj.contains("hash")) { - cerr << "Repo state not consists all objects."; - if (state_obj.contains("error")) { - cerr << " Error: " << state_obj["error"]; - } - cerr << std::endl; - return CommandResult::Failed; - } - auto state_str = - ByteBufferToString(FromHex(state_obj["hash"].as_string())); - prev_state.hash = state_str; - } - - HashMapping oid_to_meta; - std::vector prev_commits_parents; - if (!std::all_of(prev_state.hash.begin(), prev_state.hash.end(), - [](char c) { - return c == '\0'; - })) { - auto refs_file = GetStringFromIPFS(prev_state.hash, wallet_client_); - oid_to_meta = ParseRefHashed(refs_file); - for (const auto& [oid, hash] : oid_to_meta) { - prev_commits_parents.emplace_back(oid, hash); - } - - auto oid_copy = oid_to_meta; - for (const auto& [_, hash] : oid_copy) { - (void)_; - CommitMetaBlock commit(GetStringFromIPFS(hash, wallet_client_)); - TreeMetaBlock tree( - GetStringFromIPFS(commit.tree_meta_hash, wallet_client_)); - oid_to_meta[tree.hash.oid] = commit.tree_meta_hash; - } - } - - for (const auto& [hash, meta] : metas) { - auto oid = std::visit([](const auto& value) { - return value.hash.oid; - }, meta); - oid_to_meta[oid] = hash; - } - - HashMapping oid_to_ipfs; - - for (const auto& obj : uploaded_objects) { - oid_to_ipfs[obj.hash] = obj.ipfs_hash; - } - - uint32_t new_objects = 0; - uint32_t new_metas = 0; - { - auto progress = MakeProgress("Uploading objects to IPFS", - collector.m_objects.size()); - size_t i = 0; - for (auto& obj : collector.m_objects) { - if (obj.type == GIT_OBJECT_BLOB) { - ++new_objects; - } else { - ++new_metas; - } - - auto res = wallet_client_.SaveObjectToIPFS(obj.GetData(), - obj.GetSize()); - auto r = ParseJsonAndTest(res); - auto hash_str = - r.as_object()["result"].as_object()["hash"].as_string(); - obj.ipfsHash = ByteBuffer(hash_str.cbegin(), hash_str.cend()); - oid_to_ipfs[obj.oid] = - std::string(hash_str.cbegin(), hash_str.cend()); - auto meta_object = - GetMetaBlock(collector, obj, oid_to_meta, oid_to_ipfs); - if (meta_object != nullptr) { - auto meta_buffer = - StringToByteBuffer(meta_object->Serialize()); - auto meta_res = - ParseJsonAndTest(wallet_client_.SaveObjectToIPFS( - meta_buffer.data(), meta_buffer.size())); - std::string hash = meta_res.as_object()["result"] - .as_object()["hash"] - .as_string() - .c_str(); - oid_to_meta[obj.oid] = hash; - if (obj.type == GIT_OBJECT_COMMIT) { - metas[hash] = *static_cast(meta_object.get()); - } else if (obj.type == GIT_OBJECT_TREE) { - metas[hash] = *static_cast(meta_object.get()); - } - } - if (progress) { - progress->UpdateProgress(++i); - } - } - } - if (!is_forced && !CheckCommitsLinking(metas, collector.m_refs, - oid_to_meta)) { - cerr << "Commits linking wrong, looks like you use force push " - "without `--force` flag" - << endl; - return CommandResult::Failed; - } - std::string new_refs_content = - CreateRefsFile(collector.m_refs, oid_to_meta); - State new_state; - auto new_refs_buffer = StringToByteBuffer(new_refs_content); - auto new_state_res = ParseJsonAndTest(wallet_client_.SaveObjectToIPFS( - new_refs_buffer.data(), new_refs_buffer.size())); - new_state.hash = new_state_res.as_object()["result"] - .as_object()["hash"] - .as_string() - .c_str(); - { - auto progress = MakeProgress("Uploading metadata to blockchain", 1); - ParseJsonAndTest(wallet_client_.PushObjects( - prev_state, new_state, new_objects, new_metas)); - if (progress) { - progress->AddProgress(1); - } - } - { - auto progress = - MakeProgress("Waiting for the transaction completion", - wallet_client_.GetTransactionCount()); - - auto res = wallet_client_.WaitForCompletion( - [&](size_t d, const auto& error) { - if (progress) { - if (error.empty()) { - progress->UpdateProgress(d); - } else { - progress->Failed(error); - } - } - }); - cout << (res ? "ok " : "error ") << refs[0].remoteRef << '\n'; - } - - return CommandResult::Batch; - } - - CommandResult DoCapabilities( - [[maybe_unused]] const vector& args) { - for (auto ib = begin(commands_) + 1, ie = end(commands_); ib != ie; - ++ib) { - cout << ib->command << '\n'; - } - - return CommandResult::Ok; - } - -private: - std::optional MakeProgress(std::string_view title, - size_t total) { - if (options_.progress) { - return std::optional(std::in_place, title, total); - } - - return {}; - } - - std::vector RequestRefs() { - auto actual_state = ParseJsonAndTest(wallet_client_.LoadActualState()); - auto actual_state_str = actual_state.as_object()["hash"].as_string(); - if (std::all_of(actual_state_str.begin(), actual_state_str.end(), - [](char c) { - return c == '0'; - })) { - return {}; - } - return ParseRefs(GetStringFromIPFS( - ByteBufferToString(FromHex(actual_state_str)), wallet_client_)); - } - - std::vector GetUploadedObjects( - const std::vector& refs) { - std::vector objects; - auto progress = MakeProgress("Enumerate uploaded objects", - wallet_client_.GetUploadedObjectCount()); - for (const auto& ref : refs) { - auto ref_objects = GetAllObjects(ref.ipfs_hash, progress); - std::move(ref_objects.begin(), ref_objects.end(), - std::back_inserter(objects)); - } - if (progress) { - progress->Done(); - } - return objects; - } - - std::pair< - std::vector, - unordered_map>> - GetUploadedObjectsWithMetas(const std::vector& refs) { - std::vector objects; - unordered_map> metas; - auto progress = MakeProgress("Enumerate uploaded objects", - wallet_client_.GetUploadedObjectCount()); - for (const auto& ref : refs) { - auto [ref_objects, ref_metas] = - GetAllObjectsWithMeta(ref.ipfs_hash, progress); - std::move(ref_objects.begin(), ref_objects.end(), - std::back_inserter(objects)); - for (auto&& [key, value] : ref_metas) { - metas[std::move(key)] = std::move(value); - } - } - if (progress) { - progress->Done(); - } - return {std::move(objects), std::move(metas)}; - } - -private: - SimpleWalletClient& wallet_client_; - - typedef CommandResult (RemoteHelper::*Action)( - const vector& args); - - struct Command { - string_view command; - Action action; - }; - - Command commands_[5] = {{"capabilities", &RemoteHelper::DoCapabilities}, - {"list", &RemoteHelper::DoList}, - {"option", &RemoteHelper::DoOption}, - {"fetch", &RemoteHelper::DoFetch}, - {"push", &RemoteHelper::DoPush}}; - - struct Options { - enum struct SetResult { InvalidValue, Ok, Unsupported }; - - static constexpr uint32_t kInfiniteDepth = - (uint32_t)std::numeric_limits::max(); - bool progress = true; - int64_t verbosity = 0; - uint32_t depth = kInfiniteDepth; - - SetResult Set(string_view option, string_view value) { - if (option == "progress") { - if (value == "true") { - progress = true; - } else if (value == "false") { - progress = false; - } else { - return SetResult::InvalidValue; - } - return SetResult::Ok; - } /* else if (option == "verbosity") { - char* endPos; - auto v = std::strtol(value.data(), &endPos, 10); - if (endPos == value.data()) { - return SetResult::InvalidValue; - } - verbosity = v; - return SetResult::Ok; - } else if (option == "depth") { - char* endPos; - auto v = std::strtoul(value.data(), &endPos, 10); - if (endPos == value.data()) { - return SetResult::InvalidValue; - } - depth = v; - return SetResult::Ok; - }*/ - - return SetResult::Unsupported; - } - }; - - Options options_; - - std::vector GetAllObjects( - const std::string& root_ipfs_hash, - std::optional& progress) { - std::vector objects; - CommitMetaBlock commit( - GetStringFromIPFS(root_ipfs_hash, wallet_client_)); - auto commit_content = - GetStringFromIPFS(commit.hash.ipfs, wallet_client_); - objects.push_back(CreateObject( - GIT_OBJECT_COMMIT, std::move(commit.hash.oid), - std::move(commit.hash.ipfs), std::move(commit_content))); - if (progress) { - progress->AddProgress(1); - } - TreeMetaBlock tree( - GetStringFromIPFS(commit.tree_meta_hash, wallet_client_)); - auto tree_objects = GetObjectsFromTreeMeta(tree, progress); - std::move(tree_objects.begin(), tree_objects.end(), - std::back_inserter(objects)); - for (auto&& [_, parent_hash] : commit.parent_hashes) { - (void)_; - auto parent_objects = GetAllObjects(parent_hash, progress); - std::move(parent_objects.begin(), parent_objects.end(), - std::back_inserter(objects)); - } - return objects; - } - - std::pair< - std::vector, - unordered_map>> - GetAllObjectsWithMeta(const std::string& root_ipfs_hash, - std::optional& progress) { - std::vector objects; - unordered_map> metas; - CommitMetaBlock commit( - GetStringFromIPFS(root_ipfs_hash, wallet_client_)); - metas[root_ipfs_hash] = commit; - auto commit_content = - GetStringFromIPFS(commit.hash.ipfs, wallet_client_); - objects.push_back(CreateObject( - GIT_OBJECT_COMMIT, std::move(commit.hash.oid), - std::move(commit.hash.ipfs), std::move(commit_content))); - if (progress) { - progress->AddProgress(1); - } - TreeMetaBlock tree( - GetStringFromIPFS(commit.tree_meta_hash, wallet_client_)); - metas[commit.tree_meta_hash] = tree; - auto tree_objects = GetObjectsFromTreeMeta(tree, progress); - std::move(tree_objects.begin(), tree_objects.end(), - std::back_inserter(objects)); - for (auto&& [_, parent_hash] : commit.parent_hashes) { - (void)_; - auto [parent_objects, parent_metas] = - GetAllObjectsWithMeta(parent_hash, progress); - std::move(parent_objects.begin(), parent_objects.end(), - std::back_inserter(objects)); - for (auto&& [key, value] : parent_metas) { - metas[std::move(key)] = std::move(value); - } - } - return {std::move(objects), std::move(metas)}; - } - - std::vector GetObjectsFromTreeMeta( - const TreeMetaBlock& tree, std::optional& progress) { - std::vector objects; - auto tree_content = GetStringFromIPFS(tree.hash.ipfs, wallet_client_); - objects.push_back( - CreateObject(GIT_OBJECT_TREE, std::move(tree.hash.oid), - std::move(tree.hash.ipfs), std::move(tree_content))); - for (auto&& [oid, hash] : tree.entries) { - auto file_content = GetStringFromIPFS(hash, wallet_client_); - objects.push_back(CreateObject(GIT_OBJECT_BLOB, std::move(oid), - std::move(hash), - std::move(file_content))); - if (progress) { - progress->AddProgress(1); - } - } - return objects; - } - - std::set GetOidsFromObjects( - const std::vector& objects) { - std::set oids; - for (const auto& object : objects) { - oids.insert(object.hash); - } - return oids; - } -}; - int main(int argc, char* argv[]) { if (argc != 3) { cerr << "USAGE: git-remote-sourc3 " << endl; return -1; } try { - SimpleWalletClient::Options options; + IWalletClient::Options options; po::options_description desc("SOURC3 config options"); desc.add_options()("api-host", @@ -1073,14 +118,18 @@ int main(int argc, char* argv[]) { cerr << " Remote: " << argv[1] << "\n URL: " << argv[2] << "\nWorking dir: " << boost::filesystem::current_path() << "\nRepo folder: " << options.repoPath << endl; - SimpleWalletClient wallet_client{options}; - RemoteHelper helper{wallet_client}; + + auto client = CreateClient(PROTO_NAME, options); + if (!client) { + throw std::runtime_error{"Unsupported chain"}; + } + auto engine = CreateEngine(*client); git::Init init; string input; - auto res = RemoteHelper::CommandResult::Ok; + auto res = IEngine::CommandResult::Ok; while (getline(cin, input, '\n')) { if (input.empty()) { - if (res == RemoteHelper::CommandResult::Batch) { + if (res == IEngine::CommandResult::Batch) { cout << endl; continue; } else { @@ -1096,11 +145,11 @@ int main(int argc, char* argv[]) { } cerr << "Command: " << input << endl; - res = helper.DoCommand(args[0], args); + res = engine->DoCommand(args[0], args); - if (res == RemoteHelper::CommandResult::Failed) { + if (res == IEngine::CommandResult::Failed) { return -1; - } else if (res == RemoteHelper::CommandResult::Ok) { + } else if (res == IEngine::CommandResult::Ok) { cout << endl; } } diff --git a/remote_helper/unittests/helper_tests.cpp b/remote_helper/unittests/helper_tests.cpp index 36ec8118..af5bc6d7 100644 --- a/remote_helper/unittests/helper_tests.cpp +++ b/remote_helper/unittests/helper_tests.cpp @@ -16,8 +16,8 @@ #include #include -#include "git_utils.h" -#include "object_collector.h" +#include "git/git_utils.h" +#include "git/object_collector.h" using namespace sourc3; diff --git a/remote_helper/utils.cpp b/remote_helper/utils.cpp index 2bd683c1..79ab0ae2 100644 --- a/remote_helper/utils.cpp +++ b/remote_helper/utils.cpp @@ -14,9 +14,11 @@ #include "utils.h" -#include +#include #include +#include +#include namespace sourc3 { std::string ToHex(const void* p, size_t size) { @@ -36,4 +38,101 @@ ByteBuffer StringToByteBuffer(const std::string& str) { std::string ByteBufferToString(const ByteBuffer& buffer) { return std::string(buffer.begin(), buffer.end()); } + +struct ProgressReporter final : IProgressReporter { + ProgressReporter(std::string_view title, size_t total) : title_(title), total_(total) { + UpdateProgress(0); + } + + ~ProgressReporter() { + if (failure_reason_.empty()) { + Done(); + } else { + StopProgress(failure_reason_); + } + } + + void UpdateProgress(size_t done) final { + done_ = done; + ShowProgress("\r"); + } + + void AddProgress(size_t done) final { + done_ += done; + ShowProgress("\r"); + } + + void Done() final { + StopProgress("done"); + } + + void Failed(const std::string& failure) final { + failure_reason_ = failure; + } + + void StopProgress(std::string_view result) final { + std::stringstream ss; + ss << ", " << result << ".\n"; + ShowProgress(ss.str()); + } + +private: + std::string_view title_; + std::string failure_reason_; + size_t done_ = 0; + size_t total_; + + void ShowProgress(std::string_view eol); +}; + +void ProgressReporter::ShowProgress(std::string_view eol) { + std::stringstream ss; + ss << title_ << ": "; + if (total_ > 0) { + size_t percent = done_ * 100 / total_; + ss << percent << "%" + << " (" << done_ << "/" << total_ << ")"; + } else { + ss << done_; + } + ss << eol; + std::cerr << ss.str(); + std::cerr.flush(); +} + +struct NoOpReporter : IProgressReporter { + void UpdateProgress(size_t) override { + } + void AddProgress(size_t) override { + } + void Done() override { + } + void Failed(const std::string&) override { + } + void StopProgress(std::string_view) override { + } +}; + +std::unique_ptr MakeProgress(std::string_view title, size_t total, + ReporterType type) { + if (type == ReporterType::Progress) { + return std::make_unique(title, total); + } else if (type == ReporterType::NoOp) { + return std::make_unique(); + } + + return {}; +} + +boost::json::value ParseJsonAndTest(boost::json::string_view sv) { + auto r = boost::json::parse(sv); + if (!r.is_object()) { + throw std::runtime_error{sv.to_string() + " isn't an object."}; + } + + if (const auto* error = r.as_object().if_contains("error"); error) { + throw std::runtime_error(error->as_object().at("message").as_string().c_str()); + } + return r; +} } // namespace sourc3 diff --git a/remote_helper/utils.h b/remote_helper/utils.h index 74ae958d..3d92d269 100644 --- a/remote_helper/utils.h +++ b/remote_helper/utils.h @@ -19,6 +19,11 @@ #include #include #include +#include + +#include +#include +#include namespace sourc3 { using ByteBuffer = std::vector; @@ -27,4 +32,33 @@ std::string ToHex(const void* p, size_t size); ByteBuffer StringToByteBuffer(const std::string& str); std::string ByteBufferToString(const ByteBuffer& buffer); +struct IProgressReporter { +public: + virtual ~IProgressReporter() = default; + + virtual void UpdateProgress(size_t done) = 0; + + virtual void AddProgress(size_t done) = 0; + + virtual void Done() = 0; + + virtual void Failed(const std::string& failure) = 0; + + virtual void StopProgress(std::string_view result) = 0; +}; + +enum class ReporterType { NoOp, Progress }; + +std::unique_ptr MakeProgress(std::string_view title, size_t total, + ReporterType type); + +boost::json::value ParseJsonAndTest(boost::json::string_view sv); + +template +ByteBuffer FromHex(const String& s) { + ByteBuffer res; + res.reserve(s.size() / 2); + boost::algorithm::unhex(s.begin(), s.end(), std::back_inserter(res)); + return res; +} } // namespace sourc3 diff --git a/remote_helper/wallets/base_client.h b/remote_helper/wallets/base_client.h new file mode 100644 index 00000000..05f9758c --- /dev/null +++ b/remote_helper/wallets/base_client.h @@ -0,0 +1,67 @@ +// Copyright 2021-2022 SOURC3 Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include +#include +#include + +struct State; + +struct IWalletClient { + struct Options { + std::string apiHost; + std::string apiPort; + std::string apiTarget; + std::string appPath; + std::string repoOwner; + std::string repoName; + std::string repoPath = "."; + bool useIPFS = true; + }; + + explicit IWalletClient(const Options& options) + : options_(options) { + } + + virtual ~IWalletClient() = default; + + virtual std::string PushObjects(const State& expected_state, + const State& desired_state, + uint32_t new_object_count, + uint32_t new_metas_count) = 0; + virtual std::string LoadActualState() = 0; + virtual uint64_t GetUploadedObjectCount() = 0; + + const std::string& GetRepoDir() const { + return options_.repoPath; + } + + virtual std::string LoadObjectFromIPFS(std::string hash) = 0; + virtual std::string SaveObjectToIPFS(const uint8_t* data, size_t size) = 0; + + using WaitFunc = std::function; + + virtual bool WaitForCompletion(WaitFunc&&) = 0; + + virtual size_t GetTransactionCount() const = 0; + + const Options& GetOptions() const { + return options_; + } + +protected: + Options options_; +}; diff --git a/remote_helper/wallet_client.cpp b/remote_helper/wallets/beam_wallet_client.cpp similarity index 65% rename from remote_helper/wallet_client.cpp rename to remote_helper/wallets/beam_wallet_client.cpp index f51f2749..6dc88ed5 100644 --- a/remote_helper/wallet_client.cpp +++ b/remote_helper/wallets/beam_wallet_client.cpp @@ -1,233 +1,217 @@ -// Copyright 2021-2022 SOURC3 Team -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "wallet_client.h" -#include -#include -#include -#include - -namespace sourc3 { -namespace json = boost::json; - -std::string SimpleWalletClient::PushObjects(const State& expected_state, - const State& desired_state, - uint32_t new_object_count, - uint32_t new_metas_count) { - std::stringstream ss; - auto desired_hash = ToHex(desired_state.hash.c_str(), desired_state.hash.size()); - auto expected_hash = ToHex(expected_state.hash.c_str(), expected_state.hash.size()); - ss << "role=user,action=push_state,expected=" << expected_hash - << ",desired=" << desired_hash << ",objects=" << new_object_count - << ",metas=" << new_metas_count; - return InvokeWallet(ss.str()); -} - -std::string SimpleWalletClient::LoadObjectFromIPFS(std::string hash) { - auto msg = - json::value{{kJsonRpcHeader, kJsonRpcVersion}, - {"id", 1}, - {"method", "ipfs_get"}, - {"params", {{"hash", std::move(hash)}, {"timeout", 5000}}}}; - return CallAPI(json::serialize(msg)); -} - -std::string SimpleWalletClient::SaveObjectToIPFS(const uint8_t* data, - size_t size) { - auto msg = json::value{{kJsonRpcHeader, kJsonRpcVersion}, - {"id", 1}, - {"method", "ipfs_add"}, - {"params", - { - {"data", json::array(data, data + size)}, - }}}; - return CallAPI(json::serialize(msg)); -} - -bool SimpleWalletClient::WaitForCompletion(WaitFunc&& func) { - if (transactions_.empty()) { - return true; // ok - } - - SubUnsubEvents(true); - BOOST_SCOPE_EXIT_ALL(&, this) { - SubUnsubEvents(false); - }; - size_t done = 0; - while (!transactions_.empty()) { - auto response = ReadAPI(); - auto r = json::parse(response); - - auto& res = r.as_object()["result"].as_object(); - - for (auto& val : res["txs"].as_array()) { - auto& tx = val.as_object(); - std::string tx_id = tx["txId"].as_string().c_str(); - auto it = transactions_.find(tx_id); - if (it == transactions_.end()) { - continue; - } - - auto status = tx["status"].as_int64(); - if (status == 4) { - func(++done, tx["failure_reason"].as_string().c_str()); - return false; - } else if (status == 2) { - func(++done, "canceled"); - return false; - } else if (status == 3) { - func(++done, ""); - transactions_.erase(tx_id); - } - } - } - return true; -} - -std::string SimpleWalletClient::SubUnsubEvents(bool sub) { - auto msg = json::value{{kJsonRpcHeader, kJsonRpcVersion}, - {"id", 1}, - {"method", "ev_subunsub"}, - {"params", - { - {"ev_txs_changed", sub}, - }}}; - return CallAPI(json::serialize(msg)); -} - -void SimpleWalletClient::EnsureConnected() { - if (connected_) { - return; - } - - auto const results = resolver_.resolve(options_.apiHost, options_.apiPort); - - // Make the connection on the IP address we get from a lookup - stream_.connect(results); - connected_ = true; -} - -std::string SimpleWalletClient::ExtractResult(const std::string& response) { - auto r = json::parse(response); - if (auto* txid = r.as_object()["result"].as_object().if_contains("txid"); - txid) { - if (!std::all_of(txid->as_string().begin(), txid->as_string().end(), - [](auto c) { - return c == '0'; - })) { - transactions_.insert(txid->as_string().c_str()); - } - } - return r.as_object()["result"].as_object()["output"].as_string().c_str(); -} - -std::string SimpleWalletClient::InvokeShader(const std::string& args) { - // std::cerr << "Args: " << args << std::endl; - auto msg = json::value{ - {kJsonRpcHeader, kJsonRpcVersion}, - {"id", 1}, - {"method", "invoke_contract"}, - {"params", {{"contract_file", options_.appPath}, {"args", args}}}}; - - return ExtractResult(CallAPI(json::serialize(msg))); -} - -const std::string& SimpleWalletClient::GetCID() { - if (cid_.empty()) { - auto root = - json::parse(InvokeShader("role=manager,action=view_contracts")); - - assert(root.is_object()); - auto& contracts = root.as_object()["contracts"]; - if (contracts.is_array() && !contracts.as_array().empty()) { - cid_ = - contracts.as_array()[0].as_object()["cid"].as_string().c_str(); - } - } - return cid_; -} - -const std::string& SimpleWalletClient::GetRepoID() { - if (repo_id_.empty()) { - std::string request = "role=user,action=repo_id_by_name,repo_name="; - request.append(options_.repoName) - .append(",repo_owner=") - .append(options_.repoOwner) - .append(",cid=") - .append(GetCID()); - - auto root = json::parse(InvokeShader(request)); - assert(root.is_object()); - if (auto it = root.as_object().find("repo_id"); - it != root.as_object().end()) { - auto& id = *it; - repo_id_ = std::to_string(id.value().to_number()); - } - } - return repo_id_; -} - -std::string SimpleWalletClient::CallAPI(std::string&& request) { - EnsureConnected(); - request.push_back('\n'); - size_t s = request.size(); - size_t transferred = - boost::asio::write(stream_, boost::asio::buffer(request)); - if (s != transferred) { - return ""; - } - return ReadAPI(); -} - -std::string SimpleWalletClient::ReadAPI() { - auto n = boost::asio::read_until(stream_, - boost::asio::dynamic_buffer(data_), '\n'); - auto line = data_.substr(0, n); - data_.erase(0, n); - return line; -} - -void SimpleWalletClient::PrintVersion() { - auto msg = json::value{ - {kJsonRpcHeader, kJsonRpcVersion}, - {"id", 1}, - {"method", "get_version"}, - }; - auto response = CallAPI(json::serialize(msg)); - auto r = json::parse(response); - if (auto* res = r.as_object().if_contains("result"); res) { - std::cerr << "Connected to Beam Wallet API " - << res->as_object()["beam_version"].as_string().c_str() - << " (" - << res->as_object()["beam_branch_name"].as_string().c_str() - << ")" << std::endl; - } -} - -std::string SimpleWalletClient::LoadActualState() { - return InvokeWallet("role=user,action=repo_get_state"); -} - -uint64_t SimpleWalletClient::GetUploadedObjectCount() { - auto res = json::parse(InvokeWallet("role=user,action=repo_get_statistic")); - if (!res.is_object() || !res.as_object().contains("cur_objects") || - !res.as_object().contains("cur_metas")) { - return 0; - } - auto res_obj = res.as_object(); - return res_obj["cur_objects"].as_uint64() + - res_obj["cur_metas"].as_uint64(); -} - -} // namespace sourc3 +// Copyright 2021-2022 SOURC3 Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "beam_wallet_client.h" +#include +#include +#include +#include + +namespace sourc3 { +namespace json = boost::json; + +std::string BeamWalletClient::PushObjects(const State& expected_state, const State& desired_state, + uint32_t new_object_count, uint32_t new_metas_count) { + std::stringstream ss; + auto desired_hash = ToHex(desired_state.hash.c_str(), desired_state.hash.size()); + auto expected_hash = ToHex(expected_state.hash.c_str(), expected_state.hash.size()); + ss << "role=user,action=push_state,expected=" << expected_hash << ",desired=" << desired_hash + << ",objects=" << new_object_count << ",metas=" << new_metas_count; + return InvokeWallet(ss.str()); +} + +std::string BeamWalletClient::LoadObjectFromIPFS(std::string hash) { + auto msg = json::value{{kJsonRpcHeader, kJsonRpcVersion}, + {"id", 1}, + {"method", "ipfs_get"}, + {"params", {{"hash", std::move(hash)}, {"timeout", 5000}}}}; + return CallAPI(json::serialize(msg)); +} + +std::string BeamWalletClient::SaveObjectToIPFS(const uint8_t* data, size_t size) { + auto msg = json::value{{kJsonRpcHeader, kJsonRpcVersion}, + {"id", 1}, + {"method", "ipfs_add"}, + {"params", + { + {"data", json::array(data, data + size)}, + }}}; + return CallAPI(json::serialize(msg)); +} + +bool BeamWalletClient::WaitForCompletion(WaitFunc&& func) { + if (transactions_.empty()) { + return true; // ok + } + + SubUnsubEvents(true); + BOOST_SCOPE_EXIT_ALL(&, this) { + SubUnsubEvents(false); + }; + size_t done = 0; + while (!transactions_.empty()) { + auto response = ReadAPI(); + auto r = json::parse(response); + + auto& res = r.as_object()["result"].as_object(); + + for (auto& val : res["txs"].as_array()) { + auto& tx = val.as_object(); + std::string tx_id = tx["txId"].as_string().c_str(); + auto it = transactions_.find(tx_id); + if (it == transactions_.end()) { + continue; + } + + auto status = tx["status"].as_int64(); + if (status == 4) { + func(++done, tx["failure_reason"].as_string().c_str()); + return false; + } else if (status == 2) { + func(++done, "canceled"); + return false; + } else if (status == 3) { + func(++done, ""); + transactions_.erase(tx_id); + } + } + } + return true; +} + +std::string BeamWalletClient::SubUnsubEvents(bool sub) { + auto msg = json::value{{kJsonRpcHeader, kJsonRpcVersion}, + {"id", 1}, + {"method", "ev_subunsub"}, + {"params", + { + {"ev_txs_changed", sub}, + }}}; + return CallAPI(json::serialize(msg)); +} + +void BeamWalletClient::EnsureConnected() { + if (connected_) { + return; + } + + auto const results = resolver_.resolve(options_.apiHost, options_.apiPort); + + // Make the connection on the IP address we get from a lookup + stream_.connect(results); + connected_ = true; +} + +std::string BeamWalletClient::ExtractResult(const std::string& response) { + auto r = json::parse(response); + if (auto* txid = r.as_object()["result"].as_object().if_contains("txid"); txid) { + if (!std::all_of(txid->as_string().begin(), txid->as_string().end(), [](auto c) { + return c == '0'; + })) { + transactions_.insert(txid->as_string().c_str()); + } + } + return r.as_object()["result"].as_object()["output"].as_string().c_str(); +} + +std::string BeamWalletClient::InvokeShader(const std::string& args) { + // std::cerr << "Args: " << args << std::endl; + auto msg = json::value{{kJsonRpcHeader, kJsonRpcVersion}, + {"id", 1}, + {"method", "invoke_contract"}, + {"params", {{"contract_file", options_.appPath}, {"args", args}}}}; + + return ExtractResult(CallAPI(json::serialize(msg))); +} + +const std::string& BeamWalletClient::GetCID() { + if (cid_.empty()) { + auto root = json::parse(InvokeShader("role=manager,action=view_contracts")); + + assert(root.is_object()); + auto& contracts = root.as_object()["contracts"]; + if (contracts.is_array() && !contracts.as_array().empty()) { + cid_ = contracts.as_array()[0].as_object()["cid"].as_string().c_str(); + } + } + return cid_; +} + +const std::string& BeamWalletClient::GetRepoID() { + if (repo_id_.empty()) { + std::string request = "role=user,action=repo_id_by_name,repo_name="; + request.append(options_.repoName) + .append(",repo_owner=") + .append(options_.repoOwner) + .append(",cid=") + .append(GetCID()); + + auto root = json::parse(InvokeShader(request)); + assert(root.is_object()); + if (auto it = root.as_object().find("repo_id"); it != root.as_object().end()) { + auto& id = *it; + repo_id_ = std::to_string(id.value().to_number()); + } + } + return repo_id_; +} + +std::string BeamWalletClient::CallAPI(std::string&& request) { + EnsureConnected(); + request.push_back('\n'); + size_t s = request.size(); + size_t transferred = boost::asio::write(stream_, boost::asio::buffer(request)); + if (s != transferred) { + return ""; + } + return ReadAPI(); +} + +std::string BeamWalletClient::ReadAPI() { + auto n = boost::asio::read_until(stream_, boost::asio::dynamic_buffer(data_), '\n'); + auto line = data_.substr(0, n); + data_.erase(0, n); + return line; +} + +void BeamWalletClient::PrintVersion() { + auto msg = json::value{ + {kJsonRpcHeader, kJsonRpcVersion}, + {"id", 1}, + {"method", "get_version"}, + }; + auto response = CallAPI(json::serialize(msg)); + auto r = json::parse(response); + if (auto* res = r.as_object().if_contains("result"); res) { + std::cerr << "Connected to Beam Wallet API " + << res->as_object()["beam_version"].as_string().c_str() << " (" + << res->as_object()["beam_branch_name"].as_string().c_str() << ")" << std::endl; + } +} + +std::string BeamWalletClient::LoadActualState() { + return InvokeWallet("role=user,action=repo_get_state"); +} + +uint64_t BeamWalletClient::GetUploadedObjectCount() { + auto res = json::parse(InvokeWallet("role=user,action=repo_get_statistic")); + if (!res.is_object() || !res.as_object().contains("cur_objects") || + !res.as_object().contains("cur_metas")) { + return 0; + } + auto res_obj = res.as_object(); + return res_obj["cur_objects"].as_uint64() + res_obj["cur_metas"].as_uint64(); +} + +} // namespace sourc3 diff --git a/remote_helper/wallet_client.h b/remote_helper/wallets/beam_wallet_client.h similarity index 72% rename from remote_helper/wallet_client.h rename to remote_helper/wallets/beam_wallet_client.h index b4d102ab..e6f69488 100644 --- a/remote_helper/wallet_client.h +++ b/remote_helper/wallets/beam_wallet_client.h @@ -1,120 +1,104 @@ -/* - * Copyright 2021-2022 SOURC3 Team - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include "utils.h" -#include "object_collector.h" -#include "contract_state.hpp" - -namespace sourc3 { -constexpr const char kJsonRpcHeader[] = "jsonrpc"; -constexpr const char kJsonRpcVersion[] = "2.0"; - -namespace beast = boost::beast; // from -namespace http = beast::http; // from -namespace net = boost::asio; // from -using Tcp = net::ip::tcp; // from - -class SimpleWalletClient { -public: - struct Options { - std::string apiHost; - std::string apiPort; - std::string apiTarget; - std::string appPath; - std::string repoOwner; - std::string repoName; - std::string repoPath = "."; - bool useIPFS = true; - }; - - explicit SimpleWalletClient(const Options& options) - : resolver_(ioc_), stream_(ioc_), options_(options) { - PrintVersion(); - } - - ~SimpleWalletClient() { - // Gracefully close the socket - if (connected_) { - beast::error_code ec; - stream_.socket().shutdown(Tcp::socket::shutdown_both, ec); - - if (ec && ec != beast::errc::not_connected) { - // doesn't throw, simply report - std::cerr << "Error: " << beast::system_error{ec}.what() - << std::endl; - } - } - } - - std::string PushObjects(const State& expected_state, - const State& desired_state, - uint32_t new_object_count, - uint32_t new_metas_count); - std::string LoadActualState(); - uint64_t GetUploadedObjectCount(); - - const std::string& GetRepoDir() const { - return options_.repoPath; - } - - std::string LoadObjectFromIPFS(std::string hash); - std::string SaveObjectToIPFS(const uint8_t* data, size_t size); - - using WaitFunc = std::function; - bool WaitForCompletion(WaitFunc&&); - size_t GetTransactionCount() const { - return transactions_.size(); - } - -private: - std::string InvokeWallet(std::string args) { - args.append(",repo_id=") - .append(GetRepoID()) - .append(",cid=") - .append(GetCID()); - return InvokeShader(std::move(args)); - } - std::string SubUnsubEvents(bool sub); - void EnsureConnected(); - std::string ExtractResult(const std::string& response); - std::string InvokeShader(const std::string& args); - const std::string& GetCID(); - const std::string& GetRepoID(); - std::string CallAPI(std::string&& request); - std::string ReadAPI(); - void PrintVersion(); - -private: - net::io_context ioc_; - Tcp::resolver resolver_; - beast::tcp_stream stream_; - bool connected_ = false; - const Options& options_; - std::string repo_id_; - std::string cid_; - std::set transactions_; - std::string data_; -}; -} // namespace sourc3 +/* + * Copyright 2021-2022 SOURC3 Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include "utils.h" +#include "git/object_collector.h" +#include "contract_state.hpp" +#include "base_client.h" + +namespace sourc3 { +constexpr const char kJsonRpcHeader[] = "jsonrpc"; +constexpr const char kJsonRpcVersion[] = "2.0"; + +namespace beast = boost::beast; // from +namespace http = beast::http; // from +namespace net = boost::asio; // from +using Tcp = net::ip::tcp; // from + +class BeamWalletClient final : public IWalletClient { +public: + explicit BeamWalletClient(const Options& options) + : IWalletClient(options), resolver_(ioc_), stream_(ioc_) { + PrintVersion(); + } + + ~BeamWalletClient() final { + // Gracefully close the socket + if (connected_) { + beast::error_code ec; + stream_.socket().shutdown(Tcp::socket::shutdown_both, ec); + + if (ec && ec != beast::errc::not_connected) { + // doesn't throw, simply report + std::cerr << "Error: " << beast::system_error{ec}.what() + << std::endl; + } + } + } + + std::string PushObjects(const State& expected_state, + const State& desired_state, + uint32_t new_object_count, + uint32_t new_metas_count) final; + std::string LoadActualState() final; + uint64_t GetUploadedObjectCount() final; + + std::string LoadObjectFromIPFS(std::string hash) final; + std::string SaveObjectToIPFS(const uint8_t* data, size_t size) final; + + bool WaitForCompletion(WaitFunc&&) final; + size_t GetTransactionCount() const final { + return transactions_.size(); + } + +private: + std::string InvokeWallet(std::string args) { + args.append(",repo_id=") + .append(GetRepoID()) + .append(",cid=") + .append(GetCID()); + return InvokeShader(std::move(args)); + } + std::string SubUnsubEvents(bool sub); + void EnsureConnected(); + std::string ExtractResult(const std::string& response); + std::string InvokeShader(const std::string& args); + const std::string& GetCID(); + const std::string& GetRepoID(); + std::string CallAPI(std::string&& request); + std::string ReadAPI(); + void PrintVersion(); + +private: + net::io_context ioc_; + Tcp::resolver resolver_; + beast::tcp_stream stream_; + bool connected_ = false; + std::string repo_id_; + std::string cid_; + std::set transactions_; + std::string data_; +}; +} // namespace sourc3 diff --git a/remote_helper/wallets/client_factory.cpp b/remote_helper/wallets/client_factory.cpp new file mode 100644 index 00000000..4de11cd0 --- /dev/null +++ b/remote_helper/wallets/client_factory.cpp @@ -0,0 +1,26 @@ +// Copyright 2021-2022 SOURC3 Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "client_factory.h" + +#include "beam_wallet_client.h" + +std::unique_ptr CreateClient(const std::string& prefix, + const IWalletClient::Options& options) { + if (prefix == "sourc3") { + return std::make_unique(options); + } + + return nullptr; +} diff --git a/remote_helper/wallets/client_factory.h b/remote_helper/wallets/client_factory.h new file mode 100644 index 00000000..69cdfce5 --- /dev/null +++ b/remote_helper/wallets/client_factory.h @@ -0,0 +1,22 @@ +// Copyright 2021-2022 SOURC3 Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include + +#include "base_client.h" + +std::unique_ptr CreateClient(const std::string& prefix, + const IWalletClient::Options& options);