Merge branch 'v2.1.0-rc-develop' of https://git.fifthgrid.com/blockstorage/repertory into v2.1.0-rc-develop

This commit is contained in:
2025-09-28 06:07:31 -05:00
14 changed files with 340 additions and 124 deletions

View File

@@ -22,6 +22,8 @@
#ifndef REPERTORY_INCLUDE_COMM_PACKET_COMMON_HPP_
#define REPERTORY_INCLUDE_COMM_PACKET_COMMON_HPP_
#include "events/event_system.hpp"
#include "events/types/packet_client_timeout.hpp"
#include "utils/common.hpp"
namespace repertory::comm {
@@ -46,6 +48,14 @@ private:
boost::asio::ip::tcp::socket &sock;
};
template <class op_t>
inline void run_with_deadline(boost::asio::io_context &io_ctx,
boost::asio::ip::tcp::socket &sock,
op_t operation,
std::chrono::milliseconds deadline,
const std::string &event_name,
const std::string &function_name);
void apply_common_socket_properties(boost::asio::ip::tcp::socket &sock);
[[nodiscard]] auto is_socket_still_alive(boost::asio::ip::tcp::socket &sock)
@@ -66,6 +76,59 @@ void write_all_with_deadline(boost::asio::io_context &io_ctx,
boost::asio::ip::tcp::socket &sock,
boost::asio::mutable_buffer buf,
std::chrono::milliseconds deadline);
template <class op_t>
inline void run_with_deadline(boost::asio::io_context &io_ctx,
boost::asio::ip::tcp::socket &sock,
op_t operation,
std::chrono::milliseconds deadline,
const std::string &event_name,
const std::string &function_name) {
deadline = std::max(deadline, std::chrono::milliseconds{250});
struct request_state final {
std::atomic<bool> done{false};
std::atomic<bool> timed_out{false};
boost::system::error_code err;
};
auto state = std::make_shared<request_state>();
boost::asio::steady_timer timer{io_ctx};
timer.expires_after(deadline);
timer.async_wait([state, &sock](auto &&err) {
if (not err && not state->done) {
state->timed_out = true;
boost::system::error_code ignored_ec;
[[maybe_unused]] auto res = sock.cancel(ignored_ec);
}
});
operation([state](auto &&err) {
state->err = err;
state->done = true;
});
io_ctx.restart();
while (not state->done && not state->timed_out) {
io_ctx.run_one();
}
timer.cancel();
io_ctx.poll();
if (state->timed_out) {
repertory::event_system::instance().raise<repertory::packet_client_timeout>(
std::string(event_name), std::string(function_name));
throw std::runtime_error(event_name + " timed-out");
}
if (state->err) {
throw std::runtime_error(event_name + " failed|err|" +
state->err.message());
}
}
} // namespace repertory::comm
#endif // REPERTORY_INCLUDE_COMM_PACKET_COMMON_HPP_

View File

@@ -90,8 +90,7 @@ public:
remote::file_handle handle)
-> packet::error_type override;
[[nodiscard]] auto fuse_ftruncate(const char *path,
remote::file_offset size,
[[nodiscard]] auto fuse_ftruncate(const char *path, remote::file_offset size,
remote::file_handle handle)
-> packet::error_type override;
@@ -119,17 +118,17 @@ public:
[[nodiscard]] auto fuse_mkdir(const char *path, remote::file_mode mode)
-> packet::error_type override;
[[nodiscard]] auto fuse_opendir(const char *path, remote::file_handle handle)
[[nodiscard]] auto fuse_opendir(const char *path, remote::file_handle &handle)
-> packet::error_type override;
[[nodiscard]] auto
fuse_create(const char *path, remote::file_mode mode,
const remote::open_flags &flags, remote::file_handle handle)
[[nodiscard]] auto fuse_create(const char *path, remote::file_mode mode,
const remote::open_flags &flags,
remote::file_handle &handle)
-> packet::error_type override;
[[nodiscard]] auto fuse_open(const char *path,
const remote::open_flags &flags,
remote::file_handle handle)
remote::file_handle &handle)
-> packet::error_type override;
[[nodiscard]] auto
@@ -140,10 +139,9 @@ public:
[[nodiscard]] auto fuse_rename(const char *from, const char *to)
-> packet::error_type override;
[[nodiscard]] auto fuse_write(const char *path, const char *buffer,
remote::file_size write_size,
remote::file_offset write_offset,
remote::file_handle handle)
[[nodiscard]] auto
fuse_write(const char *path, const char *buffer, remote::file_size write_size,
remote::file_offset write_offset, remote::file_handle handle)
-> packet::error_type override;
[[nodiscard]] auto fuse_write_base64(const char *path, const char *buffer,
@@ -152,9 +150,9 @@ public:
remote::file_handle handle)
-> packet::error_type override;
[[nodiscard]] auto
fuse_readdir(const char *path, remote::file_offset offset,
remote::file_handle handle, std::string &item_path)
[[nodiscard]] auto fuse_readdir(const char *path, remote::file_offset offset,
remote::file_handle handle,
std::string &item_path)
-> packet::error_type override;
[[nodiscard]] auto fuse_release(const char *path, remote::file_handle handle)
@@ -181,8 +179,7 @@ public:
remote::file_time chgtime)
-> packet::error_type override;
[[nodiscard]] auto fuse_setcrtime(const char *path,
remote::file_time crtime)
[[nodiscard]] auto fuse_setcrtime(const char *path, remote::file_time crtime)
-> packet::error_type override;
[[nodiscard]] auto fuse_setvolname(const char *volname)
@@ -204,8 +201,7 @@ public:
remote::statfs_x &r_stat)
-> packet::error_type override;
[[nodiscard]] auto fuse_truncate(const char *path,
remote::file_offset size)
[[nodiscard]] auto fuse_truncate(const char *path, remote::file_offset size)
-> packet::error_type override;
[[nodiscard]] auto fuse_unlink(const char *path)
@@ -215,8 +211,8 @@ public:
std::uint64_t op0, std::uint64_t op1)
-> packet::error_type override;
void set_fuse_uid_gid(remote::user_id /* uid */,
remote::group_id /* gid */) override {}
void set_fuse_uid_gid(remote::user_id /* uid */,
remote::group_id /* gid */) override {}
// JSON Layer
[[nodiscard]] auto json_create_directory_snapshot(const std::string &path,

View File

@@ -21,9 +21,6 @@
*/
#include "comm/packet/common.hpp"
#include "events/event_system.hpp"
#include "events/types/packet_client_timeout.hpp"
namespace repertory::comm {
non_blocking_guard::non_blocking_guard(boost::asio::ip::tcp::socket &sock_)
: non_blocking(sock_.non_blocking()), sock(sock_) {
@@ -83,50 +80,6 @@ void apply_common_socket_properties(boost::asio::ip::tcp::socket &sock) {
sock.set_option(boost::asio::socket_base::keep_alive(true));
}
template <class op_t>
void run_with_deadline(boost::asio::io_context &io_ctx,
boost::asio::ip::tcp::socket &sock, op_t &&operation,
std::chrono::milliseconds deadline,
std::string_view event_name,
std::string_view function_name) {
deadline = std::max(deadline, std::chrono::milliseconds{250});
bool done = false;
bool timed_out = false;
boost::asio::steady_timer timer{io_ctx};
timer.expires_after(deadline);
timer.async_wait([&done, &sock, &timed_out](auto &&err_) {
if (not err_ && not done) {
timed_out = true;
sock.cancel();
}
});
boost::system::error_code err{};
operation([&done, &err](auto &&err_) {
err = err_;
done = true;
});
io_ctx.restart();
while (not done) {
io_ctx.run_one();
}
timer.cancel();
if (timed_out) {
repertory::event_system::instance().raise<repertory::packet_client_timeout>(
std::string(event_name), std::string(function_name));
throw std::runtime_error(std::string(event_name) + " timed-out");
}
if (err) {
throw std::runtime_error(std::string(event_name) + " failed|err|" +
err.message());
}
}
void connect_with_deadline(
boost::asio::io_context &io_ctx, boost::asio::ip::tcp::socket &sock,
boost::asio::ip::basic_resolver<boost::asio::ip::tcp>::results_type
@@ -140,7 +93,7 @@ void connect_with_deadline(
boost::asio::async_connect(
sock, endpoints, [handler](auto &&err, auto &&) { handler(err); });
},
deadline, "connect", function_name);
deadline, "connect", std::string{function_name});
}
void read_exact_with_deadline(boost::asio::io_context &io_ctx,
@@ -167,7 +120,7 @@ void read_exact_with_deadline(boost::asio::io_context &io_ctx,
handler(err);
});
},
deadline, "read", function_name);
deadline, "read", std::string{function_name});
if (bytes_read == 0U) {
throw std::runtime_error("0 bytes read");
@@ -200,7 +153,7 @@ void write_all_with_deadline(boost::asio::io_context &io_ctx,
handler(err);
});
},
deadline, "write", function_name);
deadline, "write", std::string{function_name});
if (bytes_written == 0U) {
throw std::runtime_error("0 bytes written");

View File

@@ -58,10 +58,11 @@ packet_client::~packet_client() {
void packet_client::close(client &cli) noexcept {
boost::system::error_code err1;
auto res = cli.socket.shutdown(boost::asio::socket_base::shutdown_both, err1);
[[maybe_unused]] auto res =
cli.socket.shutdown(boost::asio::socket_base::shutdown_both, err1);
boost::system::error_code err2;
res = cli.socket.close(err2);
[[maybe_unused]] auto res2 = cli.socket.close(err2);
}
void packet_client::close_all() {
@@ -244,8 +245,12 @@ auto packet_client::read_packet(client &cli, boost::asio::io_context &ctx,
std::memcpy(&to_read, buffer.data(), sizeof(to_read));
boost::endian::big_to_native_inplace(to_read);
if (to_read == 0U || to_read > comm::max_packet_bytes) {
return utils::from_api_error(api_error::comm_error);
if (to_read > comm::max_packet_bytes) {
throw std::runtime_error(fmt::format("packet too large|size|{}", to_read));
}
if (to_read < utils::encryption::encryption_header_size) {
throw std::runtime_error(fmt::format("packet too small|size|{}", to_read));
}
buffer.resize(to_read);

View File

@@ -289,6 +289,11 @@ void packet_server::read_packet(std::shared_ptr<connection> conn,
fmt::format("packet too large|size|{}", data_size));
}
if (data_size < utils::encryption::encryption_header_size) {
throw std::runtime_error(
fmt::format("packet too small|size|{}", data_size));
}
auto should_send_response = true;
auto response = std::make_shared<packet>();
conn->buffer.resize(data_size);

View File

@@ -131,8 +131,7 @@ auto remote_server::fuse_chflags(const char *path, std::uint32_t /*flags*/)
return ret;
}
auto remote_server::fuse_chmod(const char *path,
remote::file_mode /*mode*/)
auto remote_server::fuse_chmod(const char *path, remote::file_mode /*mode*/)
-> packet::error_type {
REPERTORY_USES_FUNCTION_NAME();
@@ -142,10 +141,8 @@ auto remote_server::fuse_chmod(const char *path,
return ret;
}
auto remote_server::fuse_chown(const char *path,
remote::user_id /*uid*/,
remote::group_id /*gid*/)
-> packet::error_type {
auto remote_server::fuse_chown(const char *path, remote::user_id /*uid*/,
remote::group_id /*gid*/) -> packet::error_type {
REPERTORY_USES_FUNCTION_NAME();
auto file_path = construct_path(path);
@@ -211,8 +208,7 @@ auto remote_server::fuse_fsetattr_x(const char *path,
return ret;
}
auto remote_server::fuse_fsync(const char *path,
std::int32_t /*datasync*/,
auto remote_server::fuse_fsync(const char *path, std::int32_t /*datasync*/,
remote::file_handle handle)
-> packet::error_type {
REPERTORY_USES_FUNCTION_NAME();
@@ -241,8 +237,7 @@ auto remote_server::fuse_fsync(const char *path,
return ret;
}
auto remote_server::fuse_ftruncate(const char *path,
remote::file_offset size,
auto remote_server::fuse_ftruncate(const char *path, remote::file_offset size,
remote::file_handle handle)
-> packet::error_type {
REPERTORY_USES_FUNCTION_NAME();
@@ -336,8 +331,7 @@ construct_path(path); auto ret = STATUS_NOT_IMPLEMENTED;
return ret;
}*/
auto remote_server::fuse_mkdir(const char *path,
remote::file_mode /*mode*/)
auto remote_server::fuse_mkdir(const char *path, remote::file_mode /*mode*/)
-> packet::error_type {
REPERTORY_USES_FUNCTION_NAME();
@@ -350,7 +344,7 @@ auto remote_server::fuse_mkdir(const char *path,
return ret;
}
auto remote_server::fuse_opendir(const char *path, remote::file_handle handle)
auto remote_server::fuse_opendir(const char *path, remote::file_handle &handle)
-> packet::error_type {
REPERTORY_USES_FUNCTION_NAME();
@@ -377,7 +371,7 @@ auto remote_server::fuse_opendir(const char *path, remote::file_handle handle)
auto remote_server::fuse_create(const char *path, remote::file_mode mode,
const remote::open_flags &flags,
remote::file_handle handle)
remote::file_handle &handle)
-> packet::error_type {
REPERTORY_USES_FUNCTION_NAME();
@@ -414,7 +408,7 @@ auto remote_server::fuse_create(const char *path, remote::file_mode mode,
}
auto remote_server::fuse_open(const char *path, const remote::open_flags &flags,
remote::file_handle handle)
remote::file_handle &handle)
-> packet::error_type {
REPERTORY_USES_FUNCTION_NAME();
@@ -531,17 +525,17 @@ auto remote_server::fuse_write(const char *path, const char *buffer,
return ret;
}
auto remote_server::fuse_write_base64(
const char * /*path*/, const char * /*buffer*/,
remote::file_size /*write_size*/,
remote::file_offset /*write_offset*/,
remote::file_handle /*handle*/) -> packet::error_type {
auto remote_server::fuse_write_base64(const char * /*path*/,
const char * /*buffer*/,
remote::file_size /*write_size*/,
remote::file_offset /*write_offset*/,
remote::file_handle /*handle*/)
-> packet::error_type {
// DOES NOTHING
return 0;
}
auto remote_server::fuse_readdir(const char *path,
remote::file_offset offset,
auto remote_server::fuse_readdir(const char *path, remote::file_offset offset,
remote::file_handle handle,
std::string &item_path) -> packet::error_type {
REPERTORY_USES_FUNCTION_NAME();
@@ -623,7 +617,7 @@ auto remote_server::fuse_setattr_x(const char *path,
}
auto remote_server::fuse_setbkuptime(const char *path,
remote::file_time /*bkuptime*/)
remote::file_time /*bkuptime*/)
-> packet::error_type {
REPERTORY_USES_FUNCTION_NAME();
@@ -634,7 +628,7 @@ auto remote_server::fuse_setbkuptime(const char *path,
}
auto remote_server::fuse_setchgtime(const char *path,
remote::file_time /*chgtime*/)
remote::file_time /*chgtime*/)
-> packet::error_type {
REPERTORY_USES_FUNCTION_NAME();
@@ -645,7 +639,7 @@ auto remote_server::fuse_setchgtime(const char *path,
}
auto remote_server::fuse_setcrtime(const char *path,
remote::file_time /*crtime*/)
remote::file_time /*crtime*/)
-> packet::error_type {
REPERTORY_USES_FUNCTION_NAME();
@@ -725,8 +719,7 @@ auto remote_server::fuse_statfs_x(const char *path, std::uint64_t bsize,
return 0;
}
auto remote_server::fuse_truncate(const char *path,
remote::file_offset size)
auto remote_server::fuse_truncate(const char *path, remote::file_offset size)
-> packet::error_type {
REPERTORY_USES_FUNCTION_NAME();

View File

@@ -330,7 +330,7 @@ auto winfsp_drive::Create(PWSTR file_name, UINT32 create_options,
auto now = utils::time::get_time_now();
auto meta = create_meta_attributes(
now, attributes, now, now, (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0U,
0U, "", 0U, now, 0U, 0U, 0U,
0U, "", 0U, now, 0U, 0U,
(attributes & FILE_ATTRIBUTE_DIRECTORY) != 0U
? ""
: utils::path::combine(config_.get_cache_directory(),

View File

@@ -450,25 +450,8 @@ void ui_server::handle_put_mount_location(const httplib::Request &req,
void ui_server::handle_get_available_locations(httplib::Response &res) {
#if defined(_WIN32)
constexpr std::array<std::string_view, 26U> letters{
"a:", "b:", "c:", "d:", "e:", "f:", "g:", "h:", "i:",
"j:", "k:", "l:", "m:", "n:", "o:", "p:", "q:", "r:",
"s:", "t:", "u:", "v:", "w:", "x:", "y:", "z:",
};
auto available = std::accumulate(
letters.begin(), letters.end(), std::vector<std::string_view>(),
[](auto &&vec, auto &&letter) -> std::vector<std::string_view> {
if (utils::file::directory{utils::path::combine(letter, {"\\"})}
.exists()) {
return vec;
}
vec.emplace_back(letter);
return vec;
});
res.set_content(nlohmann::json(available).dump(), "application/json");
res.set_content(nlohmann::json(utils::get_available_drive_letters()).dump(),
"application/json");
#else // !defined(_WIN32)
res.set_content(nlohmann::json(std::vector<std::string_view>()).dump(),
"application/json");

View File

@@ -332,14 +332,21 @@ protected:
mount_location2 = mount_location;
#if defined(_WIN32)
mount_location = utils::string::to_lower(std::string{"V:"});
auto letter = utils::get_available_drive_letter('d');
ASSERT_TRUE(letter.has_value());
mount_location = utils::string::to_lower(std::string{letter.value()});
#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");
#if defined(_WIN32)
auto config2 =
std::make_unique<app_config>(provider_type::remote, cfg_dir2);
#else // !defined(_WIN32)
config2 = std::make_unique<app_config>(provider_type::remote, cfg_dir2);
#endif // defined(_WIN32)
config2->set_enable_drive_events(true);
config2->set_event_level(event_level::trace);
#if !defined(_WIN32)

View File

@@ -138,6 +138,89 @@ TYPED_TEST(fuse_test, directory_rmdir_on_non_empty_directory_should_fail) {
this->rmdir_and_test(dir);
this->rmdir_and_test(dir2);
}
TYPED_TEST(fuse_test,
directory_rmdir_open_directory_handle_then_readdir_and_closedir) {
std::string dname{"rm_opendir"};
auto dir = this->create_directory_and_test(dname);
auto *dir_ptr = ::opendir(dir.c_str());
ASSERT_NE(dir_ptr, nullptr);
errno = 0;
auto res = ::rmdir(dir.c_str());
if (res == -1 && errno == EBUSY) {
::closedir(dir_ptr);
GTEST_SKIP();
}
ASSERT_EQ(0, res);
struct stat u_stat{};
EXPECT_EQ(-1, ::stat(dir.c_str(), &u_stat));
EXPECT_EQ(ENOENT, errno);
::rewinddir(dir_ptr);
errno = 0;
auto *dir_entry = ::readdir(dir_ptr);
EXPECT_EQ(nullptr, dir_entry);
::closedir(dir_ptr);
}
TYPED_TEST(fuse_test,
directory_rmdir_open_directory_handle_non_empty_ENOTEMPTY) {
std::string dname{"rm_opendir_ne"};
auto dir = this->create_directory_and_test(dname);
std::string child{dname + "/child"};
auto child_path = this->create_file_and_test(child, 0644);
this->overwrite_text(child_path, "x");
auto *dir_ptr = ::opendir(dir.c_str());
ASSERT_NE(dir_ptr, nullptr);
errno = 0;
EXPECT_EQ(-1, ::rmdir(dir.c_str()));
EXPECT_EQ(ENOTEMPTY, errno);
::rewinddir(dir_ptr);
[[maybe_unused]] auto *dir_ptr2 = ::readdir(dir_ptr);
::closedir(dir_ptr);
this->unlink_file_and_test(child_path);
this->rmdir_and_test(dir);
}
// TODO implement POSIX-compliant rmdir when handle is open
// TYPED_TEST(fuse_test, directory_rmdir_open_directory_handle_fstat_dirfd_ok) {
// std::string dname{"rm_opendir_fstat"};
// auto dir = this->create_directory_and_test(dname);
//
// auto *dir_ptr = ::opendir(dir.c_str());
// ASSERT_NE(dir_ptr, nullptr);
// auto dfd = ::dirfd(dir_ptr);
// ASSERT_NE(dfd, -1);
//
// struct stat before{};
// ASSERT_EQ(0, ::fstat(dfd, &before));
//
// errno = 0;
// auto res = ::rmdir(dir.c_str());
// if (res == -1 && errno == EBUSY) {
// ::closedir(dir_ptr);
// GTEST_SKIP();
// }
// ASSERT_EQ(0, res);
//
// struct stat after{};
// ASSERT_EQ(0, ::fstat(dfd, &after));
//
// ::closedir(dir_ptr);
//
// struct stat u_stat{};
// EXPECT_EQ(-1, ::stat(dir.c_str(), &u_stat));
// EXPECT_EQ(ENOENT, errno);
// }
} // namespace repertory
#endif // !defined(_WIN32)

View File

@@ -0,0 +1,74 @@
/*
Copyright <2018-2025> <scott.e.graves@protonmail.com>
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.
*/
#include "test_common.hpp"
#include "comm/packet/common.hpp"
using namespace repertory;
using namespace repertory::comm;
using boost::asio::ip::tcp;
TEST(packet_comm_common_test, operation_completes_prior_to_timeout) {
boost::asio::io_context io_ctx;
tcp::socket sock(io_ctx);
std::atomic<bool> completed{false};
auto operation = [&](auto &&handler) {
boost::asio::post(io_ctx, [&completed, handler]() {
completed = true;
handler(boost::system::error_code{});
});
};
EXPECT_NO_THROW(run_with_deadline(io_ctx, sock, operation,
std::chrono::milliseconds(300), "read",
"packet_deadline_test"));
EXPECT_TRUE(completed);
io_ctx.poll();
}
TEST(packet_comm_common_test, timeout_completes_prior_to_operation) {
boost::asio::io_context io_ctx;
tcp::socket sock(io_ctx);
std::atomic<bool> completed{false};
auto operation = [&](auto &&handler) {
auto delayed = std::make_shared<boost::asio::steady_timer>(io_ctx);
delayed->expires_after(std::chrono::milliseconds(500));
delayed->async_wait([&completed, delayed, handler](auto &&) {
completed = true;
handler(boost::system::error_code{});
});
};
EXPECT_THROW(run_with_deadline(io_ctx, sock, operation,
std::chrono::milliseconds(300), "read",
"packet_deadline_test"),
std::runtime_error);
for (std::uint8_t idx = 0; idx < 80U && not completed; ++idx) {
std::this_thread::sleep_for(std::chrono::milliseconds(10));
io_ctx.poll();
}
EXPECT_TRUE(completed);
}

View File

@@ -30,6 +30,12 @@ void create_console();
void free_console();
[[nodiscard]] auto get_available_drive_letter(char first = 'a')
-> std::optional<std::string_view>;
[[nodiscard]] auto get_available_drive_letters(char first = 'a')
-> std::vector<std::string_view>;
[[nodiscard]] auto get_local_app_data_directory() -> const std::string &;
[[nodiscard]] auto get_last_error_code() -> DWORD;

View File

@@ -210,8 +210,7 @@ auto get_times(std::string_view path) -> std::optional<file_times> {
ret.accessed =
utils::time::windows_file_time_to_unix_time(times.at(1U));
ret.changed = utils::time::windows_file_time_to_unix_time(times.at(2U));
ret.created = =
utils::time::windows_file_time_to_unix_time(times.at(0U));
ret.created = utils::time::windows_file_time_to_unix_time(times.at(0U));
ret.modified =
utils::time::windows_file_time_to_unix_time(times.at(2U));
ret.written = utils::time::windows_file_time_to_unix_time(times.at(2U));

View File

@@ -29,6 +29,14 @@
#include "utils/path.hpp"
#include "utils/string.hpp"
namespace {
constexpr std::array<std::string_view, 26U> drive_letters{
"a:", "b:", "c:", "d:", "e:", "f:", "g:", "h:", "i:",
"j:", "k:", "l:", "m:", "n:", "o:", "p:", "q:", "r:",
"s:", "t:", "u:", "v:", "w:", "x:", "y:", "z:",
};
}
namespace repertory::utils {
void create_console() {
if (::AllocConsole() == 0) {
@@ -61,6 +69,47 @@ void create_console() {
void free_console() { ::FreeConsole(); }
auto get_available_drive_letter(char first) -> std::optional<std::string_view> {
const auto *begin = std::ranges::find_if(
drive_letters, [first](auto &&val) { return val.at(0U) == first; });
if (begin == drive_letters.end()) {
begin = drive_letters.begin();
}
auto available =
std::ranges::find_if(begin, drive_letters.end(), [](auto &&val) -> bool {
return not utils::file::directory{utils::path::combine(val, {"\\"})}
.exists();
});
if (available == drive_letters.end()) {
return std::nullopt;
}
return *available;
}
auto get_available_drive_letters(char first) -> std::vector<std::string_view> {
const auto *begin =
std::ranges::find_if(drive_letters, [first](auto &&val) -> bool {
return val.at(0U) == first;
});
if (begin == drive_letters.end()) {
begin = drive_letters.begin();
}
return std::accumulate(
begin, drive_letters.end(), std::vector<std::string_view>(),
[](auto &&vec, auto &&letter) -> auto {
if (utils::file::directory{utils::path::combine(letter, {"\\"})}
.exists()) {
return vec;
}
vec.emplace_back(letter);
return vec;
});
}
auto get_last_error_code() -> DWORD { return ::GetLastError(); }
auto get_local_app_data_directory() -> const std::string & {