Tidy up turtle API
This commit is contained in:
parent
a44aed2fe3
commit
530535b2a9
@ -13,7 +13,6 @@ import coral.io;
|
||||
|
||||
using coral::closure;
|
||||
using coral::io_error;
|
||||
using coral::path;
|
||||
using coral::slice;
|
||||
using coral::unreachable;
|
||||
using coral::usize;
|
||||
@ -336,126 +335,10 @@ export namespace turtle {
|
||||
};
|
||||
|
||||
/**
|
||||
* Provides unmanaged access to a native directory.
|
||||
* [coral::file_system] wrapper around native file system access to provide a managed and system-agnostic
|
||||
* environment for performing file I/O.
|
||||
*/
|
||||
struct native_directory final : public coral::file_walker {
|
||||
native_directory() = default;
|
||||
|
||||
/**
|
||||
* Attempts to close any currently open directory.
|
||||
*
|
||||
* A [close_result] is returned containing either [close_result::ok] to indicate success or any other value to
|
||||
* indicate an error. See [close_result] for more details.
|
||||
*
|
||||
* *Note*: failing to close should not be treated as a reason to retry the closing operation, and should instead
|
||||
* be used to inform the end-user that the operation failed or that the process should exit.
|
||||
*/
|
||||
close_result close() {
|
||||
if (closedir(this->dir) == 0) return close_result::io_unavailable;
|
||||
|
||||
return close_result::ok;
|
||||
}
|
||||
|
||||
/**
|
||||
* See [coral::file_walker::has_next].
|
||||
*/
|
||||
bool has_next() override {
|
||||
return this->entry != nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if a directory is currently open, otherwise `false`.
|
||||
*/
|
||||
bool is_open() const {
|
||||
return this->dir != nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* See [coral::file_walker::next].
|
||||
*/
|
||||
coral::expected<path, io_error> next() override {
|
||||
usize name_length {0};
|
||||
constexpr usize name_max {sizeof(dirent::d_name) / sizeof(char)};
|
||||
|
||||
while ((name_length < name_max) && (this->entry->d_name[name_length] != 0)) name_length += 1;
|
||||
|
||||
path current_path {path{}.joined(slice{this->entry->d_name, name_length})};
|
||||
|
||||
errno = 0;
|
||||
this->entry = readdir(this->dir);
|
||||
|
||||
if (this->entry == nullptr) switch (errno) {
|
||||
case EBADF: return io_error::unavailable;
|
||||
default: unreachable();
|
||||
}
|
||||
|
||||
return current_path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to open a native directory at `directory_path`.
|
||||
*
|
||||
* An [open_result] is returned containing either [open_result::ok] to indicate success or any other value to
|
||||
* indicate an error. See [open_result] for more details.
|
||||
*
|
||||
* *Note*: the opened directory must be closed using [close] once no longer needed or the process will leak
|
||||
* directory streams.
|
||||
*
|
||||
* *Note*: if a directory is currently open under the native directory, it will attempt to close it before
|
||||
* proceeding with opening the next. This means that [open] is safe to call without first calling [close].
|
||||
*
|
||||
* *Note*: it is recommended to prefer performing file I/O via [sandboxed_fs] unless direct file access is
|
||||
* required.
|
||||
*/
|
||||
open_result open(native_path const & directory_path) {
|
||||
if (this->is_open()) {
|
||||
open_result const result {close_to_open_result(this->close())};
|
||||
|
||||
if (result != open_result::ok) return result;
|
||||
}
|
||||
|
||||
// No room for zero terminator.
|
||||
errno = 0;
|
||||
this->dir = opendir(directory_path.as_slice().as_chars().pointer);
|
||||
|
||||
if (!this->is_open()) switch (errno) {
|
||||
case EACCES: return open_result::access_denied;
|
||||
case EMFILE: case ENFILE: return open_result::too_many;
|
||||
case ENOENT: case ENOTDIR: return open_result::not_found;
|
||||
case ENOMEM: return open_result::out_of_memory;
|
||||
}
|
||||
|
||||
errno = 0;
|
||||
this->entry = readdir(this->dir);
|
||||
|
||||
if (this->entry == nullptr) switch (errno) {
|
||||
case EBADF: {
|
||||
if (this->is_open()) {
|
||||
open_result const result {close_to_open_result(this->close())};
|
||||
|
||||
if (result != open_result::ok) return result;
|
||||
}
|
||||
|
||||
return open_result::io_unavailable;
|
||||
}
|
||||
|
||||
default: unreachable();
|
||||
}
|
||||
|
||||
return open_result::ok;
|
||||
}
|
||||
|
||||
private:
|
||||
DIR * dir {nullptr};
|
||||
|
||||
dirent * entry {nullptr};
|
||||
};
|
||||
|
||||
/**
|
||||
* [coral::fs] wrapper around native file system access to provide a managed and system-agnostic environment for
|
||||
* performing file I/O.
|
||||
*/
|
||||
struct sandboxed_fs : public coral::fs {
|
||||
struct file_sandbox : public coral::file_system {
|
||||
/**
|
||||
* Permission flags that a [sandboxed_fs] may specify for restricting access to it.
|
||||
*/
|
||||
@ -468,16 +351,15 @@ export namespace turtle {
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructs a sandbox located at `sandbox_path` with `sandbox_permissions` as the permissions given to users
|
||||
* of it.
|
||||
* Constructs a sandbox located at `path` with `sandbox_permissions` as the permissions given to users of it.
|
||||
*/
|
||||
sandboxed_fs(native_path const & sandbox_path, permissions const & access_permissions) {
|
||||
this->path = sandbox_path;
|
||||
file_sandbox(native_path const & path, permissions const & access_permissions) {
|
||||
this->path = path;
|
||||
this->access_permissions = access_permissions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reference to a [sandboxed_fs] that provides access to the base directory which, on most systems, is
|
||||
* Returns a reference to a [file_sandbox] that provides access to the base directory which, on most systems, is
|
||||
* the current working directory.
|
||||
*
|
||||
* The base directory may be used to access things loose files created outside of the application, such as user-
|
||||
@ -485,17 +367,17 @@ export namespace turtle {
|
||||
*
|
||||
* *Note*: The base file system does not permit being written to.
|
||||
*/
|
||||
static sandboxed_fs & base() {
|
||||
static sandboxed_fs base_fs {"./", {
|
||||
static file_sandbox & base() {
|
||||
static file_sandbox base_sandbox {"./", {
|
||||
.can_read = true,
|
||||
.can_walk = true
|
||||
}};
|
||||
|
||||
return base_fs;
|
||||
return base_sandbox;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reference to a [sandboxed_fs] that operates as a temporary file store.
|
||||
* Returns a reference to a [file_sandbox] that operates as a temporary file store.
|
||||
*
|
||||
* As the name implies, the existence of files that exist here are not guaranteed beyond the duration of an
|
||||
* application run lifetime. The purpose of the temporary file store is to support transactional I/O
|
||||
@ -503,13 +385,13 @@ export namespace turtle {
|
||||
*
|
||||
* *Note*: The temp file system does not permit being walked.
|
||||
*/
|
||||
static sandboxed_fs & temp() {
|
||||
static sandboxed_fs base_fs {"/tmp", {
|
||||
static file_sandbox & temp() {
|
||||
static file_sandbox temp_sandbox {"/tmp", {
|
||||
.can_read = true,
|
||||
.can_write = false,
|
||||
}};
|
||||
|
||||
return base_fs;
|
||||
return temp_sandbox;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -517,17 +399,16 @@ export namespace turtle {
|
||||
*
|
||||
* *Note*: this function will only work on sandboxes with the [permissions::can_read] flag enabled.
|
||||
*/
|
||||
void read_file(path const & target_path, closure<void(coral::file_reader &)> const & then) override {
|
||||
void read_file(slice<char const> const & path, closure<void(coral::file_reader &)> const & then) override {
|
||||
if (!this->access_permissions.can_read) return;
|
||||
|
||||
this->path.joined(target_path.as_slice()).and_then([&](native_path const & native_file_path) -> void {
|
||||
this->path.joined(path).and_then([&](native_path const & native_file_path) -> void {
|
||||
native_file file;
|
||||
|
||||
if (file.open(native_file_path, native_file::open_mode::read_only) != open_result::ok) return;
|
||||
|
||||
then(file);
|
||||
|
||||
// TODO: Error orphaned file handle!
|
||||
if (file.close() != close_result::ok) return;
|
||||
});
|
||||
}
|
||||
@ -537,18 +418,11 @@ export namespace turtle {
|
||||
*
|
||||
* *Note*: this function will only work on sandboxes with the [permissions::can_walk] flag enabled.
|
||||
*/
|
||||
void walk_files(path const & target_path, closure<void(coral::file_walker &)> const & then) override {
|
||||
void walk_files(slice<char const> const & path, closure<void(coral::file_walker &)> const & then) override {
|
||||
if (!this->access_permissions.can_walk) return;
|
||||
|
||||
this->path.joined(target_path.as_slice()).and_then([&](native_path const & native_directory_path) -> void {
|
||||
native_directory directory;
|
||||
|
||||
if (directory.open(native_directory_path) == open_result::ok) return;
|
||||
|
||||
then(directory);
|
||||
|
||||
// TODO: Error orphaned file handle!
|
||||
if (directory.close() != close_result::ok) return;
|
||||
this->path.joined(path).and_then([&](native_path const & native_directory_path) -> void {
|
||||
// TODO: Implement.
|
||||
});
|
||||
}
|
||||
|
||||
@ -557,17 +431,16 @@ export namespace turtle {
|
||||
*
|
||||
* *Note*: this function will only work on sandboxes with the [permissions::can_write] flag enabled.
|
||||
*/
|
||||
void write_file(path const & target_path, closure<void(coral::file_writer &)> const & then) override {
|
||||
void write_file(slice<char const> const & path, closure<void(coral::file_writer &)> const & then) override {
|
||||
if (!this->access_permissions.can_write) return;
|
||||
|
||||
this->path.joined(target_path.as_slice()).and_then([&](native_path const & native_file_path) -> void {
|
||||
this->path.joined(path).and_then([&](native_path const & native_file_path) -> void {
|
||||
native_file file;
|
||||
|
||||
if (file.open(native_file_path, native_file::open_mode::overwrite) != open_result::ok) return;
|
||||
|
||||
then(file);
|
||||
|
||||
// TODO: Error orphaned file handle!
|
||||
if (file.close() != close_result::ok) return;
|
||||
});
|
||||
}
|
||||
@ -583,12 +456,9 @@ export namespace turtle {
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a reference to the process-wide output device used for writing data out from to the wider system.
|
||||
*
|
||||
* This [coral::writer] is particularly useful for command-line tools which require communicating with another
|
||||
* process via pipes or an end-user via the shell.
|
||||
* Returns a reference to the process-wide error logging [coral::writer] used for recording errors.
|
||||
*/
|
||||
coral::writer & output() {
|
||||
coral::writer & error_log() {
|
||||
static struct : public coral::writer {
|
||||
coral::expected<usize, io_error> write(slice<coral::u8 const> const & data) override {
|
||||
coral::size const data_written {::write(STDOUT_FILENO, data.pointer, sizeof(coral::u8) * data.length)};
|
||||
|
@ -13,26 +13,23 @@ export namespace turtle {
|
||||
};
|
||||
|
||||
struct event_loop {
|
||||
void log(log_level level, coral::slice<char const> const & message) {
|
||||
static_cast<void>(output().write(message.as_chars().as_bytes()));
|
||||
static_cast<void>(output().write(coral::slice{"\n"}.as_bytes()));
|
||||
}
|
||||
|
||||
bool poll() {
|
||||
return false;
|
||||
}
|
||||
|
||||
static int run(coral::path const & title, coral::closure<int(event_loop &)> execute) {
|
||||
static int run(coral::slice<char const> const & title, coral::closure<int(event_loop &)> execute) {
|
||||
event_loop loop{title};
|
||||
|
||||
return execute(loop);
|
||||
}
|
||||
|
||||
private:
|
||||
coral::path title;
|
||||
static constexpr coral::usize title_max = 256;
|
||||
|
||||
event_loop(coral::path const & title) {
|
||||
this->title = title;
|
||||
coral::u8 title_buffer[256];
|
||||
|
||||
event_loop(coral::slice<char const> const & title) {
|
||||
coral::copy(this->title_buffer, title.sliced(0, coral::min(title.length, title_max)).as_bytes());
|
||||
}
|
||||
};
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user