Files
clice/src/Async/FileSystem.cpp
2025-09-03 22:21:49 +08:00

233 lines
5.5 KiB
C++

#include "Async/FileSystem.h"
#include "Support/Logger.h"
namespace clice::async::awaiter {}
namespace clice::async::fs {
namespace awaiter {
template <typename Derived, typename Ret = void>
struct fs : async::awaiter::uv<fs<Derived, Ret>, uv_fs_t, Ret> {
int start(auto callback) {
return static_cast<Derived*>(this)->start(callback);
}
void cleanup() {
if(this->request.result < 0) {
this->error = this->request.result;
}
uv_fs_req_cleanup(&this->request);
}
auto result() {
return static_cast<Derived*>(this)->result();
}
};
struct open : fs<open, handle> {
const char* path;
int flags;
int start(uv_fs_cb cb) {
/// `uv_fs_open` will copy the path, so we don't need to worry about the
/// lifetime of the path.
return uv_fs_open(async::loop, &request, path, flags, 0666, cb);
}
auto result() {
return handle{static_cast<uv_file>(request.result)};
}
};
struct read : fs<read, ssize_t> {
uv_file file;
uv_buf_t bufs[1];
int start(uv_fs_cb cb) {
return uv_fs_read(async::loop, &request, file, bufs, 1, -1, cb);
}
auto result() {
return request.result;
}
};
struct write : fs<write> {
uv_file file;
uv_buf_t bufs[1];
int start(uv_fs_cb cb) {
return uv_fs_write(async::loop, &request, file, bufs, 1, 0, cb);
}
};
struct stat : fs<stat, Stats> {
const char* path;
int start(uv_fs_cb cb) {
return uv_fs_stat(async::loop, &request, path, cb);
}
auto result() {
Stats stats;
stats.mtime = std::chrono::milliseconds(request.statbuf.st_mtim.tv_sec * 1000);
stats.size = request.statbuf.st_size;
return stats;
}
};
} // namespace awaiter
static int transformFlags(Mode mode) {
int flags = 0;
if(mode & Mode::Read) {
flags |= O_RDONLY;
}
if(mode & Mode::Write) {
flags |= O_WRONLY;
}
if(mode & Mode::ReadWrite) {
flags |= O_RDWR;
}
if(mode & Mode::Create) {
flags |= O_CREAT;
}
if(mode & Mode::Append) {
flags |= O_APPEND;
}
if(mode & Mode::Truncate) {
flags |= O_TRUNC;
}
if(mode & Mode::Exclusive) {
flags |= O_EXCL;
}
return flags;
}
handle::~handle() {
if(file == -1) {
return;
}
uv_fs_t request;
int error = uv_fs_close(async::loop, &request, file, nullptr);
if(error < 0) {
log::warn("Failed to close file: {}", uv_strerror(error));
}
uv_fs_req_cleanup(&request);
}
Result<handle> open(std::string path, Mode mode) {
co_return co_await awaiter::open{
.path = path.c_str(),
.flags = transformFlags(mode),
};
}
// MSVC seems to have a bug that it will try to optimize the function with tail call
// but it leads to "C4737: Unable to perform required tail call. Performance may be degraded."
// Discovered in MSVC 14.44.35207
// Adding /EHa won't help.
// Related:
// https://developercommunity.visualstudio.com/t/coroutine-compilation-resulting-in-error-c4737-una/1510427
#ifdef _MSC_VER
#pragma optimize("g", off)
#endif
Result<ssize_t> read(const handle& handle, char* buffer, std::size_t size) {
co_return co_await awaiter::read{
.file = handle.value(),
.bufs = {uv_buf_init(buffer, size)},
};
}
#ifdef _MSC_VER
#pragma optimize("g", on)
#endif
Result<std::string> read(std::string path, Mode mode) {
/// First stat the file to check if it exists, and to get the size
auto stats = co_await stat(path);
if(!stats) {
co_return std::unexpected(stats.error());
}
/// Open the file.
auto file = co_await open(path, mode);
if(!file) {
co_return std::unexpected(file.error());
}
/// Read the file content.
std::string content;
content.reserve(stats->size + 1);
char buffer[4096];
while(true) {
auto result = co_await read(*file, buffer, sizeof(buffer));
if(!result) {
co_return std::unexpected(result.error());
}
/// FIXME: Move this to awaiter.
if(*result < 0) {
co_return std::unexpected(std::error_code(*result, async::category()));
}
if(*result == 0) {
break;
}
content.append(buffer, *result);
}
co_return content;
}
// MSVC seems to have a bug that it will try to optimize the function with tail call
// but it leads to "C4737: Unable to perform required tail call. Performance may be degraded."
// Discovered in MSVC 14.44.35207
// Adding /EHa won't help.
// Related:
// https://developercommunity.visualstudio.com/t/coroutine-compilation-resulting-in-error-c4737-una/1510427
#ifdef _MSC_VER
#pragma optimize("g", off)
#endif
Result<void> write(const handle& handle, char* buffer, std::size_t size) {
co_return co_await awaiter::write{
.file = handle.value(),
.bufs = {uv_buf_init(buffer, size)},
};
}
#ifdef _MSC_VER
#pragma optimize("g", on)
#endif
Result<void> write(std::string path, char* buffer, std::size_t size, Mode mode) {
auto file = co_await open(path, mode);
if(!file) {
co_return std::unexpected(file.error());
}
if(auto result = co_await write(*file, buffer, size); !result) {
co_return std::unexpected(result.error());
}
co_return std::expected<void, std::error_code>();
}
Result<Stats> stat(std::string path) {
co_return co_await awaiter::stat{.path = path.c_str()};
}
} // namespace clice::async::fs