Compare commits
3 Commits
3a79cee2f9
...
57b007759e
Author | SHA1 | Date | |
---|---|---|---|
57b007759e | |||
8692541e7f | |||
f5b827a039 |
@ -90,8 +90,8 @@ auto meta_db::get_api_path_list() -> std::vector<std::string> {
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto meta_db::get_item_meta(const std::string &api_path, api_meta_map &meta)
|
auto meta_db::get_item_meta(const std::string &api_path,
|
||||||
-> api_error {
|
api_meta_map &meta) -> api_error {
|
||||||
REPERTORY_USES_FUNCTION_NAME();
|
REPERTORY_USES_FUNCTION_NAME();
|
||||||
|
|
||||||
auto result = utils::db::sqlite::db_select{*db_, table_name}
|
auto result = utils::db::sqlite::db_select{*db_, table_name}
|
||||||
@ -274,8 +274,8 @@ auto meta_db::set_item_meta(const std::string &api_path,
|
|||||||
return update_item_meta(api_path, existing_meta);
|
return update_item_meta(api_path, existing_meta);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto meta_db::update_item_meta(const std::string &api_path, api_meta_map meta)
|
auto meta_db::update_item_meta(const std::string &api_path,
|
||||||
-> api_error {
|
api_meta_map meta) -> api_error {
|
||||||
REPERTORY_USES_FUNCTION_NAME();
|
REPERTORY_USES_FUNCTION_NAME();
|
||||||
|
|
||||||
auto directory = utils::string::to_bool(meta[META_DIRECTORY]);
|
auto directory = utils::string::to_bool(meta[META_DIRECTORY]);
|
||||||
|
@ -30,6 +30,7 @@
|
|||||||
#include "drives/fuse/fuse_drive.hpp"
|
#include "drives/fuse/fuse_drive.hpp"
|
||||||
#include "platform/platform.hpp"
|
#include "platform/platform.hpp"
|
||||||
#include "providers/encrypt/encrypt_provider.hpp"
|
#include "providers/encrypt/encrypt_provider.hpp"
|
||||||
|
#include "providers/meta_db.hpp"
|
||||||
#include "providers/s3/s3_provider.hpp"
|
#include "providers/s3/s3_provider.hpp"
|
||||||
#include "providers/sia/sia_provider.hpp"
|
#include "providers/sia/sia_provider.hpp"
|
||||||
#include "types/repertory.hpp"
|
#include "types/repertory.hpp"
|
||||||
@ -51,12 +52,12 @@ namespace repertory {
|
|||||||
template <typename provider_t> class fuse_test : public ::testing::Test {
|
template <typename provider_t> class fuse_test : public ::testing::Test {
|
||||||
public:
|
public:
|
||||||
static std::string cfg_directory;
|
static std::string cfg_directory;
|
||||||
static std::unique_ptr<curl_comm> comm;
|
|
||||||
static std::unique_ptr<app_config> config;
|
static std::unique_ptr<app_config> config;
|
||||||
static std::filesystem::path current_directory;
|
static std::filesystem::path current_directory;
|
||||||
static std::unique_ptr<fuse_drive> drive;
|
static std::unique_ptr<fuse_drive> drive;
|
||||||
|
static std::vector<std::string> drive_args;
|
||||||
|
static std::unique_ptr<meta_db> meta;
|
||||||
static std::string mount_location;
|
static std::string mount_location;
|
||||||
static std::unique_ptr<i_provider> provider;
|
|
||||||
static std::string test_directory;
|
static std::string test_directory;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
@ -80,24 +81,22 @@ protected:
|
|||||||
|
|
||||||
config = std::make_unique<app_config>(provider_t::type, cfg_directory);
|
config = std::make_unique<app_config>(provider_t::type, cfg_directory);
|
||||||
|
|
||||||
std::vector<std::string> drive_args{};
|
|
||||||
switch (provider_t::type) {
|
switch (provider_t::type) {
|
||||||
case provider_type::s3: {
|
case provider_type::s3: {
|
||||||
{
|
{
|
||||||
app_config src_cfg{
|
app_config src_cfg{
|
||||||
provider_type::s3,
|
provider_type::s3,
|
||||||
utils::path::combine(test::get_test_config_dir(), {"storj"}),
|
utils::path::combine(test::get_test_config_dir(), {"repertory_s3"}),
|
||||||
};
|
};
|
||||||
config->set_enable_drive_events(true);
|
config->set_enable_drive_events(true);
|
||||||
config->set_event_level(event_level::trace);
|
config->set_event_level(event_level::trace);
|
||||||
config->set_s3_config(src_cfg.get_s3_config());
|
config->set_s3_config(src_cfg.get_s3_config());
|
||||||
}
|
}
|
||||||
|
|
||||||
comm = std::make_unique<curl_comm>(config->get_s3_config());
|
|
||||||
drive_args = std::vector<std::string>({
|
drive_args = std::vector<std::string>({
|
||||||
"-s3",
|
"-s3",
|
||||||
"-na",
|
"-na",
|
||||||
"storj",
|
"repertory_s3",
|
||||||
});
|
});
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
@ -112,7 +111,11 @@ protected:
|
|||||||
config->set_host_config(src_cfg.get_host_config());
|
config->set_host_config(src_cfg.get_host_config());
|
||||||
}
|
}
|
||||||
|
|
||||||
comm = std::make_unique<curl_comm>(config->get_host_config());
|
drive_args = std::vector<std::string>({
|
||||||
|
"-sia",
|
||||||
|
"-na",
|
||||||
|
"sia",
|
||||||
|
});
|
||||||
} break;
|
} break;
|
||||||
// case 0U: {
|
// case 0U: {
|
||||||
// config =
|
// config =
|
||||||
@ -126,10 +129,6 @@ protected:
|
|||||||
// config->set_event_level(event_level::trace);
|
// config->set_event_level(event_level::trace);
|
||||||
// config->set_s3_config(src_cfg.get_s3_config());
|
// config->set_s3_config(src_cfg.get_s3_config());
|
||||||
// }
|
// }
|
||||||
//
|
|
||||||
// comm = std::make_unique<curl_comm>(config->get_s3_config());
|
|
||||||
// provider = std::make_unique<s3_provider>(*config, *comm);
|
|
||||||
// drive_args = std::vector<std::string>({"-en"});
|
|
||||||
// } break;
|
// } break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@ -137,9 +136,10 @@ protected:
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
provider = std::make_unique<provider_t>(*config, *comm);
|
meta = std::make_unique<meta_db>(*config);
|
||||||
|
|
||||||
drive_args.push_back(mount_location);
|
drive_args.push_back(mount_location);
|
||||||
execute_mount(drive_args);
|
execute_mount();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void TearDownTestCase() {
|
static void TearDownTestCase() {
|
||||||
@ -164,7 +164,9 @@ public:
|
|||||||
|
|
||||||
auto opt_size = utils::file::file{file_path}.size();
|
auto opt_size = utils::file::file{file_path}.size();
|
||||||
EXPECT_TRUE(opt_size.has_value());
|
EXPECT_TRUE(opt_size.has_value());
|
||||||
|
if (opt_size.has_value()) {
|
||||||
EXPECT_EQ(0U, opt_size.value());
|
EXPECT_EQ(0U, opt_size.value());
|
||||||
|
}
|
||||||
|
|
||||||
EXPECT_EQ(0, close(fd));
|
EXPECT_EQ(0, close(fd));
|
||||||
std::this_thread::sleep_for(SLEEP_SECONDS);
|
std::this_thread::sleep_for(SLEEP_SECONDS);
|
||||||
@ -176,7 +178,7 @@ public:
|
|||||||
auto file_path = create_file_and_test(file_name);
|
auto file_path = create_file_and_test(file_name);
|
||||||
auto api_path = utils::path::create_api_path(file_name);
|
auto api_path = utils::path::create_api_path(file_name);
|
||||||
|
|
||||||
provider->set_item_meta(api_path, {
|
meta->set_item_meta(api_path, {
|
||||||
{META_UID, "0"},
|
{META_UID, "0"},
|
||||||
{META_GID, "0"},
|
{META_GID, "0"},
|
||||||
});
|
});
|
||||||
@ -184,7 +186,7 @@ public:
|
|||||||
return file_path;
|
return file_path;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void execute_mount(auto &&drive_args) {
|
static void execute_mount() {
|
||||||
auto mount_cmd = "./repertory -dd \"" + config->get_data_directory() +
|
auto mount_cmd = "./repertory -dd \"" + config->get_data_directory() +
|
||||||
"\"" + " " + utils::string::join(drive_args, ' ');
|
"\"" + " " + utils::string::join(drive_args, ' ');
|
||||||
std::cout << "mount command: " << mount_cmd << std::endl;
|
std::cout << "mount command: " << mount_cmd << std::endl;
|
||||||
@ -196,12 +198,15 @@ public:
|
|||||||
static void execute_unmount() {
|
static void execute_unmount() {
|
||||||
auto unmounted{false};
|
auto unmounted{false};
|
||||||
|
|
||||||
auto unmount_cmd =
|
auto unmount_cmd = "./repertory -dd \"" + config->get_data_directory() +
|
||||||
"./repertory -dd \"" + config->get_data_directory() + "\" -unmount";
|
"\"" + " " + utils::string::join(drive_args, ' ') +
|
||||||
|
" -unmount";
|
||||||
|
|
||||||
for (int i = 0; not unmounted && (i < 50); i++) {
|
for (int i = 0; not unmounted && (i < 50); i++) {
|
||||||
std::cout << "unmount command: " << unmount_cmd << std::endl;
|
std::cout << "unmount command: " << unmount_cmd << std::endl;
|
||||||
ASSERT_EQ(0, system(unmount_cmd.c_str()));
|
auto res = system(unmount_cmd.c_str());
|
||||||
unmounted = not utils::file::directory{mount_location}.exists();
|
unmounted = res == 0;
|
||||||
|
ASSERT_EQ(0, res);
|
||||||
if (not unmounted) {
|
if (not unmounted) {
|
||||||
std::this_thread::sleep_for(5s);
|
std::this_thread::sleep_for(5s);
|
||||||
}
|
}
|
||||||
@ -221,7 +226,7 @@ public:
|
|||||||
auto api_path = utils::path::create_api_path(
|
auto api_path = utils::path::create_api_path(
|
||||||
utils::path::strip_to_file_name(file_path));
|
utils::path::strip_to_file_name(file_path));
|
||||||
|
|
||||||
provider->set_item_meta(api_path, {
|
meta->set_item_meta(api_path, {
|
||||||
{META_UID, std::to_string(getuid())},
|
{META_UID, std::to_string(getuid())},
|
||||||
{META_GID, std::to_string(getgid())},
|
{META_GID, std::to_string(getgid())},
|
||||||
});
|
});
|
||||||
@ -233,9 +238,6 @@ public:
|
|||||||
template <typename provider_t>
|
template <typename provider_t>
|
||||||
std::string fuse_test<provider_t>::cfg_directory{};
|
std::string fuse_test<provider_t>::cfg_directory{};
|
||||||
|
|
||||||
template <typename provider_t>
|
|
||||||
std::unique_ptr<curl_comm> fuse_test<provider_t>::comm{};
|
|
||||||
|
|
||||||
template <typename provider_t>
|
template <typename provider_t>
|
||||||
std::unique_ptr<app_config> fuse_test<provider_t>::config{};
|
std::unique_ptr<app_config> fuse_test<provider_t>::config{};
|
||||||
|
|
||||||
@ -245,16 +247,20 @@ std::filesystem::path fuse_test<provider_t>::current_directory{};
|
|||||||
template <typename provider_t>
|
template <typename provider_t>
|
||||||
std::unique_ptr<fuse_drive> fuse_test<provider_t>::drive{};
|
std::unique_ptr<fuse_drive> fuse_test<provider_t>::drive{};
|
||||||
|
|
||||||
|
template <typename provider_t>
|
||||||
|
std::vector<std::string> fuse_test<provider_t>::drive_args;
|
||||||
|
|
||||||
template <typename provider_t>
|
template <typename provider_t>
|
||||||
std::string fuse_test<provider_t>::mount_location{};
|
std::string fuse_test<provider_t>::mount_location{};
|
||||||
|
|
||||||
template <typename provider_t>
|
template <typename provider_t>
|
||||||
std::unique_ptr<i_provider> fuse_test<provider_t>::provider{};
|
std::unique_ptr<meta_db> fuse_test<provider_t>::meta{};
|
||||||
|
|
||||||
template <typename provider_t>
|
template <typename provider_t>
|
||||||
std::string fuse_test<provider_t>::test_directory;
|
std::string fuse_test<provider_t>::test_directory;
|
||||||
|
|
||||||
using fuse_provider_types = ::testing::Types<s3_provider, sia_provider>;
|
// using fuse_provider_types = ::testing::Types<s3_provider, sia_provider>;
|
||||||
|
using fuse_provider_types = ::testing::Types<s3_provider>;
|
||||||
} // namespace repertory
|
} // namespace repertory
|
||||||
|
|
||||||
#endif // !defined(_WIN32)
|
#endif // !defined(_WIN32)
|
||||||
|
@ -21,7 +21,6 @@
|
|||||||
*/
|
*/
|
||||||
#ifndef REPERTORY_TEST_INCLUDE_FIXTURES_WINFSP_FIXTURE_HPP
|
#ifndef REPERTORY_TEST_INCLUDE_FIXTURES_WINFSP_FIXTURE_HPP
|
||||||
#define REPERTORY_TEST_INCLUDE_FIXTURES_WINFSP_FIXTURE_HPP
|
#define REPERTORY_TEST_INCLUDE_FIXTURES_WINFSP_FIXTURE_HPP
|
||||||
// #if 0
|
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32)
|
||||||
|
|
||||||
#include "test_common.hpp"
|
#include "test_common.hpp"
|
||||||
@ -78,7 +77,7 @@ protected:
|
|||||||
{
|
{
|
||||||
app_config src_cfg{
|
app_config src_cfg{
|
||||||
provider_type::s3,
|
provider_type::s3,
|
||||||
utils::path::combine(test::get_test_config_dir(), {"storj"}),
|
utils::path::combine(test::get_test_config_dir(), {"repertory_s3"}),
|
||||||
};
|
};
|
||||||
config->set_enable_drive_events(true);
|
config->set_enable_drive_events(true);
|
||||||
config->set_event_level(event_level::trace);
|
config->set_event_level(event_level::trace);
|
||||||
@ -89,7 +88,7 @@ protected:
|
|||||||
drive_args = std::vector<std::string>({
|
drive_args = std::vector<std::string>({
|
||||||
"-s3",
|
"-s3",
|
||||||
"-na",
|
"-na",
|
||||||
"storj",
|
"repertory_s3",
|
||||||
});
|
});
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
@ -141,8 +140,8 @@ protected:
|
|||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
[[nodiscard]] static auto create_directory_and_test(std::string &dir_name)
|
[[nodiscard]] static auto
|
||||||
-> std::string {
|
create_directory_and_test(std::string &dir_name) -> std::string {
|
||||||
dir_name += std::to_string(++idx);
|
dir_name += std::to_string(++idx);
|
||||||
auto api_path = utils::path::create_api_path(dir_name);
|
auto api_path = utils::path::create_api_path(dir_name);
|
||||||
auto dir_path = utils::path::combine(mount_location, {dir_name});
|
auto dir_path = utils::path::combine(mount_location, {dir_name});
|
||||||
@ -153,8 +152,8 @@ public:
|
|||||||
return dir_path;
|
return dir_path;
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] static auto create_file_and_test(std::string &file_name)
|
[[nodiscard]] static auto
|
||||||
-> std::string {
|
create_file_and_test(std::string &file_name) -> std::string {
|
||||||
file_name += std::to_string(++idx);
|
file_name += std::to_string(++idx);
|
||||||
auto api_path = utils::path::create_api_path(file_name);
|
auto api_path = utils::path::create_api_path(file_name);
|
||||||
auto file_path = utils::path::combine(mount_location, {file_name});
|
auto file_path = utils::path::combine(mount_location, {file_name});
|
||||||
@ -247,5 +246,4 @@ using winfsp_provider_types = ::testing::Types<s3_provider, sia_provider>;
|
|||||||
} // namespace repertory
|
} // namespace repertory
|
||||||
|
|
||||||
#endif // defined(_WIN32)
|
#endif // defined(_WIN32)
|
||||||
// #endif // 0
|
|
||||||
#endif // REPERTORY_TEST_INCLUDE_FIXTURES_WINFSP_FIXTURE_HPP
|
#endif // REPERTORY_TEST_INCLUDE_FIXTURES_WINFSP_FIXTURE_HPP
|
||||||
|
@ -20,7 +20,6 @@
|
|||||||
SOFTWARE.
|
SOFTWARE.
|
||||||
*/
|
*/
|
||||||
#if !defined(_WIN32)
|
#if !defined(_WIN32)
|
||||||
#if 0
|
|
||||||
|
|
||||||
#include "fixtures/fuse_fixture.hpp"
|
#include "fixtures/fuse_fixture.hpp"
|
||||||
|
|
||||||
@ -536,6 +535,7 @@ TYPED_TEST(fuse_test, can_chmod_if_owner) {
|
|||||||
TYPED_TEST(fuse_test, can_not_chmod_if_not_owner) {
|
TYPED_TEST(fuse_test, can_not_chmod_if_not_owner) {
|
||||||
std::string file_name{"chmod_test"};
|
std::string file_name{"chmod_test"};
|
||||||
auto file_path = this->create_root_file(file_name);
|
auto file_path = this->create_root_file(file_name);
|
||||||
|
std::cout << file_path << std::endl;
|
||||||
|
|
||||||
EXPECT_EQ(-1, chmod(file_path.c_str(), S_IRUSR | S_IWUSR));
|
EXPECT_EQ(-1, chmod(file_path.c_str(), S_IRUSR | S_IWUSR));
|
||||||
EXPECT_EQ(EPERM, errno);
|
EXPECT_EQ(EPERM, errno);
|
||||||
@ -581,6 +581,7 @@ TYPED_TEST(fuse_test, can_chown_group_if_owner_and_a_member_of_the_group) {
|
|||||||
EXPECT_EQ(0, stat64(file_path.c_str(), &unix_st));
|
EXPECT_EQ(0, stat64(file_path.c_str(), &unix_st));
|
||||||
|
|
||||||
EXPECT_EQ(0, chown(file_path.c_str(), static_cast<uid_t>(-1), getgid()));
|
EXPECT_EQ(0, chown(file_path.c_str(), static_cast<uid_t>(-1), getgid()));
|
||||||
|
std::cout << errno << std::endl;
|
||||||
std::this_thread::sleep_for(SLEEP_SECONDS);
|
std::this_thread::sleep_for(SLEEP_SECONDS);
|
||||||
|
|
||||||
struct stat64 unix_st2 {};
|
struct stat64 unix_st2 {};
|
||||||
@ -647,5 +648,4 @@ TYPED_TEST(fuse_test, can_not_chown_user_if_not_root) {
|
|||||||
}
|
}
|
||||||
} // namespace repertory
|
} // namespace repertory
|
||||||
|
|
||||||
#endif // 0
|
|
||||||
#endif // !defined(_WIN32)
|
#endif // !defined(_WIN32)
|
||||||
|
@ -26,9 +26,9 @@
|
|||||||
|
|
||||||
namespace repertory::utils {
|
namespace repertory::utils {
|
||||||
struct result final {
|
struct result final {
|
||||||
std::string_view function_name;
|
std::string function_name;
|
||||||
bool ok{false};
|
bool ok{true};
|
||||||
std::string reason{};
|
std::string reason{"success"};
|
||||||
|
|
||||||
[[nodiscard]] operator bool() const { return ok; }
|
[[nodiscard]] operator bool() const { return ok; }
|
||||||
};
|
};
|
||||||
|
@ -41,8 +41,7 @@ convert_to_uint64(const thread_t *thread_ptr) -> std::uint64_t;
|
|||||||
|
|
||||||
[[nodiscard]] auto get_thread_id() -> std::uint64_t;
|
[[nodiscard]] auto get_thread_id() -> std::uint64_t;
|
||||||
|
|
||||||
[[nodiscard]] auto is_uid_member_of_group(const uid_t &uid,
|
[[nodiscard]] auto is_uid_member_of_group(uid_t uid, gid_t gid) -> bool;
|
||||||
const gid_t &gid) -> bool;
|
|
||||||
|
|
||||||
void set_last_error_code(int error_code);
|
void set_last_error_code(int error_code);
|
||||||
|
|
||||||
|
@ -64,7 +64,7 @@ namespace {
|
|||||||
home = repertory::utils::path::combine("/home", {pw->pw_name});
|
home = repertory::utils::path::combine("/home", {pw->pw_name});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (res) {
|
if (not res) {
|
||||||
throw repertory::utils::error::create_exception(function_name,
|
throw repertory::utils::error::create_exception(function_name,
|
||||||
{
|
{
|
||||||
"failed to getpwuid",
|
"failed to getpwuid",
|
||||||
|
@ -19,11 +19,10 @@
|
|||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
SOFTWARE.
|
SOFTWARE.
|
||||||
*/
|
*/
|
||||||
#include <utils/config.hpp>
|
|
||||||
#if !defined(_WIN32)
|
#if !defined(_WIN32)
|
||||||
|
|
||||||
#include "utils/collection.hpp"
|
|
||||||
#include "utils/unix.hpp"
|
#include "utils/unix.hpp"
|
||||||
|
#include "utils/collection.hpp"
|
||||||
|
|
||||||
namespace repertory::utils {
|
namespace repertory::utils {
|
||||||
#if !defined(__APPLE__)
|
#if !defined(__APPLE__)
|
||||||
@ -38,7 +37,7 @@ auto get_thread_id() -> std::uint64_t {
|
|||||||
return convert_to_uint64(pthread_self());
|
return convert_to_uint64(pthread_self());
|
||||||
}
|
}
|
||||||
|
|
||||||
auto is_uid_member_of_group(const uid_t &uid, const gid_t &gid) -> bool {
|
auto is_uid_member_of_group(uid_t uid, gid_t gid) -> bool {
|
||||||
std::vector<gid_t> groups{};
|
std::vector<gid_t> groups{};
|
||||||
auto res = use_getpwuid(uid, [&groups](struct passwd *pass) {
|
auto res = use_getpwuid(uid, [&groups](struct passwd *pass) {
|
||||||
int group_count{};
|
int group_count{};
|
||||||
@ -53,7 +52,7 @@ auto is_uid_member_of_group(const uid_t &uid, const gid_t &gid) -> bool {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (not res.ok) {
|
if (not res) {
|
||||||
throw utils::error::create_exception(res.function_name,
|
throw utils::error::create_exception(res.function_name,
|
||||||
{"use_getpwuid failed", res.reason});
|
{"use_getpwuid failed", res.reason});
|
||||||
}
|
}
|
||||||
@ -69,11 +68,15 @@ auto use_getpwuid(uid_t uid, passwd_callback_t callback) -> result {
|
|||||||
|
|
||||||
auto *temp_pw = getpwuid(uid);
|
auto *temp_pw = getpwuid(uid);
|
||||||
if (temp_pw == nullptr) {
|
if (temp_pw == nullptr) {
|
||||||
return {function_name, false, "'getpwuid' returned nullptr"};
|
return {
|
||||||
|
std::string{function_name},
|
||||||
|
false,
|
||||||
|
"'getpwuid' returned nullptr",
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
callback(temp_pw);
|
callback(temp_pw);
|
||||||
return {function_name};
|
return {std::string{function_name}};
|
||||||
}
|
}
|
||||||
} // namespace repertory::utils
|
} // namespace repertory::utils
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user