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::closure;
|
||||||
using coral::io_error;
|
using coral::io_error;
|
||||||
using coral::path;
|
|
||||||
using coral::slice;
|
using coral::slice;
|
||||||
using coral::unreachable;
|
using coral::unreachable;
|
||||||
using coral::usize;
|
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 {
|
struct file_sandbox : public coral::file_system {
|
||||||
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 {
|
|
||||||
/**
|
/**
|
||||||
* Permission flags that a [sandboxed_fs] may specify for restricting access to it.
|
* 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
|
* Constructs a sandbox located at `path` with `sandbox_permissions` as the permissions given to users of it.
|
||||||
* of it.
|
|
||||||
*/
|
*/
|
||||||
sandboxed_fs(native_path const & sandbox_path, permissions const & access_permissions) {
|
file_sandbox(native_path const & path, permissions const & access_permissions) {
|
||||||
this->path = sandbox_path;
|
this->path = path;
|
||||||
this->access_permissions = access_permissions;
|
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 current working directory.
|
||||||
*
|
*
|
||||||
* The base directory may be used to access things loose files created outside of the application, such as user-
|
* 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.
|
* *Note*: The base file system does not permit being written to.
|
||||||
*/
|
*/
|
||||||
static sandboxed_fs & base() {
|
static file_sandbox & base() {
|
||||||
static sandboxed_fs base_fs {"./", {
|
static file_sandbox base_sandbox {"./", {
|
||||||
.can_read = true,
|
.can_read = true,
|
||||||
.can_walk = 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
|
* 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
|
* 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.
|
* *Note*: The temp file system does not permit being walked.
|
||||||
*/
|
*/
|
||||||
static sandboxed_fs & temp() {
|
static file_sandbox & temp() {
|
||||||
static sandboxed_fs base_fs {"/tmp", {
|
static file_sandbox temp_sandbox {"/tmp", {
|
||||||
.can_read = true,
|
.can_read = true,
|
||||||
.can_write = false,
|
.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.
|
* *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;
|
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;
|
native_file file;
|
||||||
|
|
||||||
if (file.open(native_file_path, native_file::open_mode::read_only) != open_result::ok) return;
|
if (file.open(native_file_path, native_file::open_mode::read_only) != open_result::ok) return;
|
||||||
|
|
||||||
then(file);
|
then(file);
|
||||||
|
|
||||||
// TODO: Error orphaned file handle!
|
|
||||||
if (file.close() != close_result::ok) return;
|
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.
|
* *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;
|
if (!this->access_permissions.can_walk) return;
|
||||||
|
|
||||||
this->path.joined(target_path.as_slice()).and_then([&](native_path const & native_directory_path) -> void {
|
this->path.joined(path).and_then([&](native_path const & native_directory_path) -> void {
|
||||||
native_directory directory;
|
// TODO: Implement.
|
||||||
|
|
||||||
if (directory.open(native_directory_path) == open_result::ok) return;
|
|
||||||
|
|
||||||
then(directory);
|
|
||||||
|
|
||||||
// TODO: Error orphaned file handle!
|
|
||||||
if (directory.close() != close_result::ok) return;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -557,17 +431,16 @@ export namespace turtle {
|
|||||||
*
|
*
|
||||||
* *Note*: this function will only work on sandboxes with the [permissions::can_write] flag enabled.
|
* *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;
|
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;
|
native_file file;
|
||||||
|
|
||||||
if (file.open(native_file_path, native_file::open_mode::overwrite) != open_result::ok) return;
|
if (file.open(native_file_path, native_file::open_mode::overwrite) != open_result::ok) return;
|
||||||
|
|
||||||
then(file);
|
then(file);
|
||||||
|
|
||||||
// TODO: Error orphaned file handle!
|
|
||||||
if (file.close() != close_result::ok) return;
|
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.
|
* Returns a reference to the process-wide error logging [coral::writer] used for recording errors.
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
*/
|
||||||
coral::writer & output() {
|
coral::writer & error_log() {
|
||||||
static struct : public coral::writer {
|
static struct : public coral::writer {
|
||||||
coral::expected<usize, io_error> write(slice<coral::u8 const> const & data) override {
|
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)};
|
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 {
|
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() {
|
bool poll() {
|
||||||
return false;
|
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};
|
event_loop loop{title};
|
||||||
|
|
||||||
return execute(loop);
|
return execute(loop);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
coral::path title;
|
static constexpr coral::usize title_max = 256;
|
||||||
|
|
||||||
event_loop(coral::path const & title) {
|
coral::u8 title_buffer[256];
|
||||||
this->title = title;
|
|
||||||
|
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