diff --git a/repertory/repertory_test/include/fixtures/drive_fixture.hpp b/repertory/repertory_test/include/fixtures/drive_fixture.hpp new file mode 100644 index 00000000..9fd72885 --- /dev/null +++ b/repertory/repertory_test/include/fixtures/drive_fixture.hpp @@ -0,0 +1,531 @@ +/* + Copyright <2018-2025> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#ifndef REPERTORY_TEST_INCLUDE_FIXTURES_PLATFORM_FIXTURE_HPP +#define REPERTORY_TEST_INCLUDE_FIXTURES_PLATFORM_FIXTURE_HPP + +#include "test_common.hpp" + +#include "app_config.hpp" +#include "platform/platform.hpp" +#include "types/repertory.hpp" +#include "utils/file_utils.hpp" +#include "utils/path.hpp" +#include "utils/utils.hpp" + +#if defined(_WIN32) +#include "drives/winfsp/remotewinfsp/remote_winfsp_drive.hpp" +#include "drives/winfsp/winfsp_drive.hpp" +#include "providers/i_provider.hpp" +#include "providers/s3/s3_provider.hpp" +#include "providers/sia/sia_provider.hpp" +#else +#include "comm/curl/curl_comm.hpp" +#include "db/i_meta_db.hpp" +#include "db/meta_db.hpp" +#include "drives/fuse/fuse_drive.hpp" +#include "providers/encrypt/encrypt_provider.hpp" +#include "providers/s3/s3_provider.hpp" +#include "providers/sia/sia_provider.hpp" +#if !defined(ACCESSPERMS) +#define ACCESSPERMS (S_IRWXU | S_IRWXG | S_IRWXO) /* 0777 */ +#endif +#endif + +namespace { +std::atomic provider_idx{0U}; +constexpr auto SLEEP_SECONDS{1.5s}; +} // namespace + +namespace repertory { +struct local_s3_no_encryption final { + static constexpr provider_type type{provider_type::s3}; + static constexpr provider_type type2{provider_type::s3}; + static constexpr std::uint16_t remote_port{41000U}; + static constexpr bool force_legacy_encryption{false}; + static constexpr std::string_view encryption_token{""}; +}; +struct local_s3_encryption final { + static constexpr provider_type type{provider_type::s3}; + static constexpr provider_type type2{provider_type::s3}; + static constexpr std::uint16_t remote_port{41000U}; + static constexpr bool force_legacy_encryption{false}; + static constexpr std::string_view encryption_token{"encryption_token"}; +}; +struct local_s3_legacy_encryption final { + static constexpr provider_type type{provider_type::s3}; + static constexpr provider_type type2{provider_type::s3}; + static constexpr std::uint16_t remote_port{41000U}; + static constexpr bool force_legacy_encryption{true}; + static constexpr std::string_view encryption_token{"encryption_token"}; +}; +struct remote_s3_no_encryption final { + static constexpr provider_type type{provider_type::remote}; + static constexpr provider_type type2{provider_type::s3}; + static constexpr std::uint16_t remote_port{41000U}; + static constexpr bool force_legacy_encryption{false}; + static constexpr std::string_view encryption_token{""}; +}; +struct remote_s3_encryption final { + static constexpr provider_type type{provider_type::remote}; + static constexpr provider_type type2{provider_type::s3}; + static constexpr std::uint16_t remote_port{41000U}; + static constexpr bool force_legacy_encryption{false}; + static constexpr std::string_view encryption_token{"encryption_token"}; +}; +struct remote_s3_legacy_encryption final { + static constexpr provider_type type{provider_type::remote}; + static constexpr provider_type type2{provider_type::s3}; + static constexpr std::uint16_t remote_port{41000U}; + static constexpr bool force_legacy_encryption{true}; + static constexpr std::string_view encryption_token{"encryption_token"}; +}; +struct local_sia final { + static constexpr provider_type type{provider_type::sia}; + static constexpr provider_type type2{provider_type::sia}; + static constexpr std::uint16_t remote_port{41001U}; + static constexpr bool force_legacy_encryption{false}; + static constexpr std::string_view encryption_token{""}; +}; +struct remote_sia final { + static constexpr provider_type type{provider_type::remote}; + static constexpr provider_type type2{provider_type::sia}; + static constexpr std::uint16_t remote_port{41001U}; + static constexpr bool force_legacy_encryption{false}; + static constexpr std::string_view encryption_token{""}; +}; +struct remote_winfsp_to_linux final { + static constexpr provider_type type{provider_type::remote}; + static constexpr provider_type type2{provider_type::unknown}; + static constexpr std::uint16_t remote_port{41002U}; + static constexpr bool force_legacy_encryption{false}; + static constexpr std::string_view encryption_token{""}; +}; +struct remote_linux_to_winfsp final { + static constexpr provider_type type{provider_type::remote}; + static constexpr provider_type type2{provider_type::unknown}; + static constexpr std::uint16_t remote_port{41002U}; + static constexpr bool force_legacy_encryption{false}; + static constexpr std::string_view encryption_token{""}; +}; + +struct platform_ops { + static void ensure_process_cwd() { +#if !defined(_WIN32) + EXPECT_TRUE(utils::file::change_to_process_directory()); +#endif // !defined(_WIN32) + } + + static std::string build_cmd(const std::string &args_joined, bool is_mount) { +#if defined(_WIN32) + if (is_mount) { + return "start .\\repertory.exe -f " + args_joined; + } + return ".\\repertory.exe " + args_joined; +#else // !defined(_WIN32) +#if defined(__APPLE__) + constexpr const char *kBin = "./repertory.app/Contents/MacOS/repertory "; +#else // !defined(__APPLE__) + constexpr const char *kBin = "./repertory "; +#endif // defined(__APPLE__) + return std::string(kBin) + args_joined; +#endif // defined(_WIN32) + } + + static void execute_mount(std::vector args_without_location, + const std::string &location) { + ensure_process_cwd(); + args_without_location.emplace_back(location); + + const auto cmd = build_cmd(utils::string::join(args_without_location, ' '), + /*is_mount*/ true); + std::cout << "mount command: " << cmd << std::endl; + + ASSERT_EQ(0, system(cmd.c_str())); + std::this_thread::sleep_for(5s); + ASSERT_TRUE(utils::file::directory{location}.exists()); + } + + static void execute_unmount(std::vector args_without_unmount, + const std::string &location) { + ensure_process_cwd(); + args_without_unmount.emplace_back("-unmount"); + + const auto cmd = build_cmd(utils::string::join(args_without_unmount, ' '), + /*is_mount*/ false); + std::cout << "unmount command: " << cmd << std::endl; + +#if defined(_WIN32) + std::this_thread::sleep_for(10s); + bool unmounted = false; + for (int i = 0; !unmounted && i < 6; ++i) { + (void)system(cmd.c_str()); + unmounted = !utils::file::directory{location}.exists(); + if (!unmounted) + std::this_thread::sleep_for(5s); + } + ASSERT_TRUE(unmounted); +#else // !defined(_WIN32) + auto res = system(cmd.c_str()); + EXPECT_EQ(0, res); +#endif // defined(_WIN32) + } +}; + +template class drive_fixture : public ::testing::Test { +public: +#if !defined(_WIN32) + static std::unique_ptr config; + static std::unique_ptr meta; +#endif // !defined(_WIN32) + static std::filesystem::path current_directory; + static provider_type current_provider; + static std::vector drive_args; + static std::vector drive_args2; + static std::string mount_location; + static std::string mount_location2; + +protected: + static void SetUpTestCase() { + current_directory = std::filesystem::current_path(); + + const auto make_test_dir = [](std::string_view suite, + std::string_view sub) { + return utils::path::combine(test::get_test_output_dir(), + {std::string(suite), std::string(sub)}); + }; + + const auto make_cfg_dir = [](const std::string &root, + std::string_view name) { + auto cfg = utils::path::combine(root, {std::string(name)}); + ASSERT_TRUE(utils::file::directory(cfg).create_directory()); + return cfg; + }; + + const auto configure_s3 = [](app_config &cfg_obj) { + app_config src_cfg{ + provider_type::s3, + utils::path::combine(test::get_test_config_dir(), {"s3"})}; + auto cfg = src_cfg.get_s3_config(); + cfg.force_legacy_encryption = provider_t::force_legacy_encryption; + cfg.encryption_token = provider_t::encryption_token; + + cfg_obj.set_enable_drive_events(true); + cfg_obj.set_event_level(event_level::trace); + cfg_obj.set_s3_config(cfg); + + auto r = cfg_obj.get_remote_mount(); + r.enable = true; + r.api_port = provider_t::remote_port; + cfg_obj.set_remote_mount(r); + }; + + const auto configure_sia = [](app_config &cfg_obj) { + app_config src_cfg{ + provider_type::sia, + utils::path::combine(test::get_test_config_dir(), {"sia"})}; + + cfg_obj.set_enable_drive_events(true); + cfg_obj.set_event_level(event_level::trace); + cfg_obj.set_host_config(src_cfg.get_host_config()); + cfg_obj.set_sia_config(src_cfg.get_sia_config()); + + auto r = cfg_obj.get_remote_mount(); + r.enable = true; + r.api_port = provider_t::remote_port; + cfg_obj.set_remote_mount(r); + }; + + const auto mount_local_s3 = [&](bool as_remote) { + const auto test_dir = make_test_dir( +#if defined(_WIN32) + "winfsp_test", +#else // !defined(_WIN32) + "fuse_test", +#endif // defined(_WIN32) + fmt::format("{}_{}", app_config::get_provider_name(current_provider), + as_remote)); +#if defined(_WIN32) + mount_location = utils::string::to_lower(std::string{"U:"}); +#else // !defined(_WIN32) + mount_location = utils::path::combine(test_dir, {"mount"}); + ASSERT_TRUE(utils::file::directory(mount_location).create_directory()); +#endif // defined(_WIN32) + + auto cfg_dir = make_cfg_dir(test_dir, "cfg"); + auto cfg = std::make_unique(provider_type::s3, cfg_dir); + configure_s3(*cfg); + + drive_args = {"-dd", cfg->get_data_directory(), "-s3", "-na", "s3"}; + +#if !defined(_WIN32) + cfg->set_database_type(database_type::sqlite); + config = std::move(cfg); + meta = create_meta_db(*config); +#endif // !defined(_WIN32) + + platform_ops::execute_mount(drive_args, mount_location); + }; + + const auto mount_local_sia = [&](bool as_remote) { + const auto test_dir = make_test_dir( +#if defined(_WIN32) + "winfsp_test", +#else // !defined(_WIN32) + "fuse_test", +#endif // defined(_WIN32) + fmt::format("{}_{}", app_config::get_provider_name(current_provider), + as_remote)); +#if defined(_WIN32) + mount_location = utils::string::to_lower(std::string{"U:"}); +#else // !defined(_WIN32) + mount_location = utils::path::combine(test_dir, {"mount"}); + ASSERT_TRUE(utils::file::directory(mount_location).create_directory()); +#endif // defined(_WIN32) + + auto cfg_dir = make_cfg_dir(test_dir, "cfg"); + auto cfg = std::make_unique(provider_type::sia, cfg_dir); + configure_sia(*cfg); + + drive_args = {"-dd", cfg->get_data_directory(), "-na", "sia"}; + +#if !defined(_WIN32) + cfg->set_database_type(database_type::sqlite); + config = std::move(cfg); + meta = create_meta_db(*config); +#endif // !defined(_WIN32) + + platform_ops::execute_mount(drive_args, mount_location); + }; + + const auto mount_remote = [&] { + const auto test_dir = make_test_dir( +#if defined(_WIN32) + "winfsp_test", +#else // !defined(_WIN32) + "fuse_test", +#endif // defined(_WIN32) + fmt::format("{}_{}_{}", + app_config::get_provider_name(provider_t::type), + app_config::get_provider_name(provider_t::type2), + provider_t::remote_port)); + + mount_location2 = mount_location; + +#if defined(_WIN32) + mount_location = utils::string::to_lower(std::string{"V:"}); +#else // !defined(_WIN32) + mount_location = utils::path::combine(test_dir, {"mount"}); + ASSERT_TRUE(utils::file::directory(mount_location).create_directory()); +#endif // defined(_WIN32) + + auto cfg_dir2 = make_cfg_dir(test_dir, "cfg2"); + auto cfg2 = std::make_unique(provider_type::remote, cfg_dir2); + cfg2->set_enable_drive_events(true); + cfg2->set_event_level(event_level::trace); +#if !defined(_WIN32) + cfg2->set_database_type(database_type::sqlite); +#endif // !defined(_WIN32) + + drive_args2 = {"-dd", cfg2->get_data_directory(), "-rm", + fmt::format("localhost:{}", provider_t::remote_port)}; + + platform_ops::execute_mount(drive_args2, mount_location); + }; + + switch (provider_t::type) { + case provider_type::s3: { + mount_local_s3(false); + } break; + case provider_type::sia: { + mount_local_sia(false); + } break; + case provider_type::remote: { + switch (provider_t::type2) { + case provider_type::s3: + mount_local_s3(true); + break; + case provider_type::sia: + mount_local_sia(true); + break; + case provider_type::unknown: + mount_remote(); + return; + default: + throw std::runtime_error("remote provider type is not implemented"); + } + mount_remote(); + } break; + default: + throw std::runtime_error("provider type is not implemented"); + } + } + + static void TearDownTestCase() { + if (provider_t::type == provider_type::remote) { + platform_ops::execute_unmount(drive_args2, mount_location); + if (provider_t::type2 != provider_type::unknown) { + platform_ops::execute_unmount(drive_args, mount_location2); + } + } else { + platform_ops::execute_unmount(drive_args, mount_location); + } + +#if !defined(_WIN32) + meta.reset(); + config.reset(); +#endif // !defined(_WIN32) + std::filesystem::current_path(current_directory); + } + +#if !defined(_WIN32) +public: + static auto create_file_path(std::string &file_name) { + file_name += std::to_string(++provider_idx); + return utils::path::combine(mount_location, {file_name}); + } + + static auto create_file_and_test(std::string &file_name, mode_t perms) + -> std::string { + file_name += std::to_string(++provider_idx); + auto file_path = utils::path::combine(mount_location, {file_name}); + + auto handle = open(file_path.c_str(), O_CREAT | O_EXCL | O_RDWR, perms); + EXPECT_LE(1, handle); + + auto opt_size = utils::file::file{file_path}.size(); + EXPECT_TRUE(opt_size.has_value()); + if (opt_size.has_value()) { + EXPECT_EQ(0U, opt_size.value()); + } + + EXPECT_EQ(0, close(handle)); + + EXPECT_TRUE(utils::file::file(file_path).exists()); + EXPECT_FALSE(utils::file::directory(file_path).exists()); + + struct stat64 u_stat{}; + EXPECT_EQ(0, stat64(file_path.c_str(), &u_stat)); + EXPECT_EQ(getgid(), u_stat.st_gid); + EXPECT_EQ(getuid(), u_stat.st_uid); + + return file_path; + } + + static auto create_file_and_test(std::string &file_name) -> std::string { + return create_file_and_test(file_name, ACCESSPERMS); + } + + static auto create_directory_and_test(std::string &dir_name, mode_t perms) + -> std::string { + dir_name += std::to_string(++provider_idx); + + auto dir_path = utils::path::combine(mount_location, {dir_name}); + mkdir(dir_path.c_str(), perms); + + EXPECT_TRUE(utils::file::directory(dir_path).exists()); + EXPECT_EQ(0U, utils::file::directory(dir_path).count(false)); + EXPECT_EQ(0U, utils::file::directory(dir_path).count(true)); + EXPECT_FALSE(utils::file::file(dir_path).exists()); + + struct stat64 u_stat{}; + EXPECT_EQ(0, stat64(dir_path.c_str(), &u_stat)); + EXPECT_EQ(getgid(), u_stat.st_gid); + EXPECT_EQ(getuid(), u_stat.st_uid); + + return dir_path; + } + + static auto create_directory_and_test(std::string &dir_name) -> std::string { + return create_directory_and_test(dir_name, ACCESSPERMS); + } + + static auto create_root_file(std::string &file_name) -> std::string { + auto file_path = create_file_and_test(file_name); + auto api_path = utils::path::create_api_path(file_name); + + [[maybe_unused]] auto res = + meta->set_item_meta(api_path, {{META_UID, "0"}, {META_GID, "0"}}); + std::this_thread::sleep_for(SLEEP_SECONDS); + + return file_path; + } + + static void rmdir_and_test(std::string_view dir_path) { + EXPECT_TRUE(utils::file::directory(dir_path).remove()); + EXPECT_FALSE(utils::file::directory(dir_path).exists()); + EXPECT_FALSE(utils::file::file(dir_path).exists()); + } + + static void unlink_file_and_test(std::string_view file_path) { + EXPECT_TRUE(utils::file::file(file_path).remove()); + EXPECT_FALSE(utils::file::file(file_path).exists()); + EXPECT_FALSE(utils::file::directory(file_path).exists()); + } + + static void unlink_root_file(const std::string &file_path) { + auto api_path = utils::path::create_api_path( + utils::path::strip_to_file_name(file_path)); + + [[maybe_unused]] auto res = + meta->set_item_meta(api_path, {{META_UID, std::to_string(getuid())}, + { META_GID, + std::to_string(getgid()) }}); + std::this_thread::sleep_for(SLEEP_SECONDS); + + unlink_file_and_test(file_path); + } +#endif // !defined(_WIN32) +}; + +#if !defined(_WIN32) +template +std::unique_ptr drive_fixture::config; +template +std::unique_ptr drive_fixture::meta{}; +#endif // !defined(_WIN32) + +template +std::filesystem::path drive_fixture::current_directory; +template +provider_type drive_fixture::current_provider{provider_t::type2}; +template +std::vector drive_fixture::drive_args; +template +std::vector drive_fixture::drive_args2; +template +std::string drive_fixture::mount_location; +template +std::string drive_fixture::mount_location2; + +using platform_provider_types = + ::testing::Types; +#if defined(_WIN32) +using winfsp_test = drive_fixture; +#else +using fuse_test = drive_fixture; +#endif +} // namespace repertory + +#endif // REPERTORY_TEST_INCLUDE_FIXTURES_PLATFORM_FIXTURE_HPP diff --git a/web/repertory/lib/screens/auth_screen.dart b/web/repertory/lib/screens/auth_screen.dart index daa8edb5..7a66ff6c 100644 --- a/web/repertory/lib/screens/auth_screen.dart +++ b/web/repertory/lib/screens/auth_screen.dart @@ -60,12 +60,7 @@ class _AuthScreenState extends State { return; } - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Invalid username or password'), - behavior: SnackBarBehavior.floating, - ), - ); + displayErrorMessage(context, 'Invalid username or password', clear: true); } return Scaffold( diff --git a/web/repertory/lib/widgets/app_dropdown.dart b/web/repertory/lib/widgets/app_dropdown.dart index 966f510a..729cfad7 100644 --- a/web/repertory/lib/widgets/app_dropdown.dart +++ b/web/repertory/lib/widgets/app_dropdown.dart @@ -116,6 +116,7 @@ class AppDropdownFormField extends StatelessWidget { decoration: createCommonDecoration( scheme, labelText ?? "", + filled: true, icon: prefixIcon, ), dropdownColor: dropdownColor ?? effectiveFill,