Add way to bundle files in a coral::fs into an Ona archive
Some checks failed
continuous-integration/drone/push Build is failing
Some checks failed
continuous-integration/drone/push Build is failing
This commit is contained in:
parent
98fb389ff0
commit
13fffacd98
174
source/coral.cpp
174
source/coral.cpp
@ -40,6 +40,8 @@ export namespace coral {
|
|||||||
|
|
||||||
using u32 = uint32_t;
|
using u32 = uint32_t;
|
||||||
|
|
||||||
|
usize const u32_max = 0xffffffff;
|
||||||
|
|
||||||
using i32 = int32_t;
|
using i32 = int32_t;
|
||||||
|
|
||||||
usize const i32_max = 0xffffffff;
|
usize const i32_max = 0xffffffff;
|
||||||
@ -351,83 +353,160 @@ export namespace coral {
|
|||||||
result(* dispatch)(void *, arguments...);
|
result(* dispatch)(void *, arguments...);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Monadic container for a single-`element` value or nothing.
|
||||||
|
*/
|
||||||
|
template<typename element> struct [[nodiscard]] optional {
|
||||||
|
/**
|
||||||
|
* Constructs an empty [optional].
|
||||||
|
*/
|
||||||
|
constexpr optional() = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an [optional] that contains `value`.
|
||||||
|
*/
|
||||||
|
constexpr optional(element const & value) {
|
||||||
|
(*reinterpret_cast<element *>(this->buffer)) = value;
|
||||||
|
this->buffer[sizeof(element)] = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an [optional] from `that`, copying over its data.
|
||||||
|
*/
|
||||||
|
constexpr optional(optional const & that) {
|
||||||
|
if (that.has_value()) {
|
||||||
|
(*reinterpret_cast<element *>(this->buffer)) = *that;
|
||||||
|
this->buffer[sizeof(element)] = 1;
|
||||||
|
} else {
|
||||||
|
this->buffer[sizeof(element)] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns `true` if the optional contains a value, otherwise `false`.
|
||||||
|
*/
|
||||||
|
bool has_value() const {
|
||||||
|
return this->buffer[sizeof(element)] == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Monadically maps `apply` to the value if it exists, otherwise doing nothing.
|
||||||
|
*/
|
||||||
|
template<typename result> optional<result> map(closure<result(element const &)> const & apply) const {
|
||||||
|
if (this->has_value()) return apply(this->value());
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the contained value or `fallback` if the optional is empty.
|
||||||
|
*/
|
||||||
|
element const & or_value(element const & fallback) const {
|
||||||
|
return this->has_value() ? *reinterpret_cast<element const *>(this->buffer) : fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a reference to the contained value.
|
||||||
|
*
|
||||||
|
* *Note*: attempting to access the value of an empty optional will trigger safety-checked behavior.
|
||||||
|
*/
|
||||||
|
element & operator *() {
|
||||||
|
if (!this->has_value()) unreachable();
|
||||||
|
|
||||||
|
return *reinterpret_cast<element *>(this->buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a const reference to the contained value.
|
||||||
|
*
|
||||||
|
* *Note*: attempting to access the value of an empty optional will trigger safety-checked behavior.
|
||||||
|
*/
|
||||||
|
element const & operator *() const {
|
||||||
|
if (!this->has_value()) unreachable();
|
||||||
|
|
||||||
|
return *reinterpret_cast<element const *>(this->buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
u8 buffer[sizeof(element) + 1] {0};
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Monadic container for a descriminating union of either `expects` or `errors`.
|
* Monadic container for a descriminating union of either `expects` or `errors`.
|
||||||
*/
|
*/
|
||||||
template<typename expects, typename errors> struct [[nodiscard]] expected {
|
template<typename expects, typename errors> struct [[nodiscard]] expected {
|
||||||
expected(expects const & value) : buffer{0} {
|
template<typename value> using rebound = expected<value, errors>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs from `value`, creating an [expected] that contains the expected type.
|
||||||
|
*/
|
||||||
|
expected(expects const & value) {
|
||||||
(*reinterpret_cast<expects *>(this->buffer)) = value;
|
(*reinterpret_cast<expects *>(this->buffer)) = value;
|
||||||
this->buffer[buffer_size] = 1;
|
this->buffer[buffer_size] = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
expected(errors const & error) : buffer{0} {
|
/**
|
||||||
|
* Constructs from `error`, creating an [expected] that does not contain the expected type.
|
||||||
|
*/
|
||||||
|
expected(errors const & error) {
|
||||||
(*reinterpret_cast<errors *>(this->buffer)) = error;
|
(*reinterpret_cast<errors *>(this->buffer)) = error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the contained error as an [optional].
|
||||||
|
*/
|
||||||
|
optional<errors> error() const {
|
||||||
|
if (this->is_error()) return *reinterpret_cast<errors const *>(this->buffer);
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns `true` if the optional holds an error, otherwise `false` if it is ok.
|
||||||
|
*/
|
||||||
|
bool is_error() const {
|
||||||
|
return this->buffer[buffer_size] == 0;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns `true` if the optional contains the expected value, otherwise `false` if it holds an error.
|
* Returns `true` if the optional contains the expected value, otherwise `false` if it holds an error.
|
||||||
*/
|
*/
|
||||||
bool is_ok() const {
|
bool is_ok() const {
|
||||||
return this->buffer[buffer_size];
|
return this->buffer[buffer_size] == 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a reference to the contained value.
|
* Monadically maps `apply` to the value if it exists, otherwise doing nothing.
|
||||||
*
|
|
||||||
* *Note*: attempting to access the value of an erroneous expected will trigger safety-checked behavior.
|
|
||||||
*/
|
*/
|
||||||
expects & value() {
|
template<typename result> rebound<result> map(closure<result(expects const &)> const & apply) const {
|
||||||
if (!this->is_ok()) unreachable();
|
if (this->is_ok()) return apply(*this->ok());
|
||||||
|
|
||||||
return *reinterpret_cast<expects *>(this->buffer);
|
return *this->error();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the contained value.
|
* Returns the contained ok value as an [optional].
|
||||||
*
|
|
||||||
* *Note*: attempting to access the value of an erroneous expected will trigger safety-checked behavior.
|
|
||||||
*/
|
*/
|
||||||
expects const & value() const {
|
optional<expects> ok() const {
|
||||||
if (!this->is_ok()) unreachable();
|
if (this->is_ok()) return *reinterpret_cast<expects const *>(this->buffer);
|
||||||
|
|
||||||
return *reinterpret_cast<expects const *>(this->buffer);
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a reference to the contained error.
|
* Returns the contained value or `value` if it is not ok.
|
||||||
*
|
|
||||||
* *Note*: attempting to access the error of a non-erroneous expected will trigger safety-checked behavior.
|
|
||||||
*/
|
*/
|
||||||
errors & error() {
|
expects ok_or(expects value) const {
|
||||||
if (this->is_ok()) unreachable();
|
if (this->is_ok()) return *this->ok();
|
||||||
|
|
||||||
return *reinterpret_cast<errors *>(this->buffer);
|
return value;
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the contained error.
|
|
||||||
*
|
|
||||||
* *Note*: attempting to access the error of a non-erroneous expected will trigger safety-checked behavior.
|
|
||||||
*/
|
|
||||||
errors const & error() const {
|
|
||||||
if (this->is_ok()) unreachable();
|
|
||||||
|
|
||||||
return *reinterpret_cast<errors const *>(this->buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
template<typename result> expected<result, errors> map(closure<result(expects const &)> const & apply) const {
|
|
||||||
if (this->is_ok()) return apply(this->value());
|
|
||||||
|
|
||||||
return this->error();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static constexpr usize buffer_size = max(sizeof(expects), sizeof(errors));
|
static constexpr usize buffer_size = max(sizeof(expects), sizeof(errors));
|
||||||
|
|
||||||
u8 buffer[buffer_size + 1];
|
u8 buffer[buffer_size + 1] {0};
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -514,11 +593,20 @@ export namespace coral {
|
|||||||
constexpr bool equals(slice<u8 const> const & a, slice<u8 const> const & b) {
|
constexpr bool equals(slice<u8 const> const & a, slice<u8 const> const & b) {
|
||||||
if (a.length != b.length) return false;
|
if (a.length != b.length) return false;
|
||||||
|
|
||||||
for (size_t i = 0; i < a.length; i += 1) if (a[i] != b[i]) return false;
|
for (usize i = 0; i < a.length; i += 1) if (a[i] != b[i]) return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs a linear search from the back of
|
||||||
|
*/
|
||||||
|
constexpr optional<usize> find_last(slice<u8 const> const & bytes, u8 byte) {
|
||||||
|
for (usize i = bytes.length; i >= 0; i -= 1) if (bytes[i] == byte) return i;
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a hash code generated from the values in `bytes`.
|
* Returns a hash code generated from the values in `bytes`.
|
||||||
*
|
*
|
||||||
|
@ -153,16 +153,19 @@ export namespace coral {
|
|||||||
bool can_write;
|
bool can_write;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class [[nodiscard]] walk_result {
|
||||||
|
ok,
|
||||||
|
not_implemented,
|
||||||
|
access_denied,
|
||||||
|
not_found,
|
||||||
|
io_error,
|
||||||
|
};
|
||||||
|
|
||||||
virtual ~fs() {};
|
virtual ~fs() {};
|
||||||
|
|
||||||
/**
|
virtual walk_result walk_files(path const & target_path, closure<bool(path const &)> const & apply) {
|
||||||
* Attempts to read the files in `directory_path`, calling `apply` for each of the files encountered with the
|
return walk_result::not_implemented;
|
||||||
* fully-qualified file path. If either no files are found or the file-system does not support the operation,
|
}
|
||||||
* `apply` is never caled.
|
|
||||||
*
|
|
||||||
* `false` may be returned inside of `apply` to halt the enumeration.
|
|
||||||
*/
|
|
||||||
virtual void enumerate_directory(path const & directory_path, closure<bool(path const &)> const & apply) {}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Queries the file-system for its global [access_rules], returning them.
|
* Queries the file-system for its global [access_rules], returning them.
|
||||||
@ -172,12 +175,12 @@ export namespace coral {
|
|||||||
/**
|
/**
|
||||||
* Attempts to read the file in `file_path`, calling `then` if it was successfully opened for reading.
|
* Attempts to read the file in `file_path`, calling `then` if it was successfully opened for reading.
|
||||||
*/
|
*/
|
||||||
virtual void read_file(path const & file_path, closure<void(file_reader &)> const & then) {}
|
virtual void read_file(path const & target_path, closure<void(file_reader &)> const & then) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempts to write the file in the file system located at `file_path`, calling `then` if it was successfully
|
* Attempts to write the file in the file system located at `file_path`, calling `then` if it was successfully
|
||||||
* opened for writing.
|
* opened for writing.
|
||||||
*/
|
*/
|
||||||
virtual void write_file(path const & file_path, closure<void(file_writer &)> const & then) {}
|
virtual void write_file(path const & target_path, closure<void(file_writer &)> const & then) {}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -123,25 +123,25 @@ export namespace coral {
|
|||||||
* *Note*: if `buffer` has a length of `0`, no data will be streamed as there is nowhere to temporarily place data
|
* *Note*: if `buffer` has a length of `0`, no data will be streamed as there is nowhere to temporarily place data
|
||||||
* during streaming.
|
* during streaming.
|
||||||
*/
|
*/
|
||||||
expected<usize, io_error> stream(writer & output, reader & input, slice<u8> const & buffer) {
|
expected<u64, io_error> stream(writer & output, reader & input, slice<u8> const & buffer) {
|
||||||
usize total_bytes_written = 0;
|
u64 total_bytes_written {0};
|
||||||
expected bytes_read = input.read(buffer);
|
expected bytes_read {input.read(buffer)};
|
||||||
|
|
||||||
if (!bytes_read.is_ok()) return bytes_read.error();
|
if (bytes_read.is_error()) return *bytes_read.error();
|
||||||
|
|
||||||
usize read = bytes_read.value();
|
usize read {*bytes_read.ok()};
|
||||||
|
|
||||||
while (read != 0) {
|
while (read != 0) {
|
||||||
expected const bytes_written = output.write(buffer.sliced(0, read));
|
expected const bytes_written = output.write(buffer.sliced(0, read));
|
||||||
|
|
||||||
if (!bytes_written.is_ok()) return bytes_read.error();
|
if (bytes_written.is_error()) return *bytes_read.error();
|
||||||
|
|
||||||
total_bytes_written += bytes_written.value();
|
total_bytes_written += *bytes_written.ok();
|
||||||
bytes_read = input.read(buffer);
|
bytes_read = input.read(buffer);
|
||||||
|
|
||||||
if (!bytes_read.is_ok()) return bytes_read.error();
|
if (bytes_read.is_error()) return *bytes_read.error();
|
||||||
|
|
||||||
read = bytes_read.value();
|
read = *bytes_read.ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
return total_bytes_written;
|
return total_bytes_written;
|
||||||
|
@ -26,12 +26,31 @@ export namespace coral {
|
|||||||
virtual ~stack() {};
|
virtual ~stack() {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a read-only [slice] of the current range values.
|
* Returns `true` if there are no elements in the stack, otherwise `false`.
|
||||||
*
|
|
||||||
* *Note*: the behavior of retaining the returned value past the scope of the source [stack] or any subsequent
|
|
||||||
* modifications to it is implementation-defined.
|
|
||||||
*/
|
*/
|
||||||
virtual slice<element const> as_slice() const = 0;
|
virtual bool is_empty() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invokes `apply` on each element in the stack and evaluating a user-defined condition which will return a
|
||||||
|
* `bool` at the end of each evaluation. After all elements have been evaluated, `true` is returned if every
|
||||||
|
* element elavuated `true`. Otherwise, `false` is returned to indicate that one of more elements failed
|
||||||
|
* evaluation.
|
||||||
|
*
|
||||||
|
* *Note*: This function uses short-circuit evaluation, so the enumeration will terminate upon the first failure
|
||||||
|
* case. This may be leveraged to create conditional looping behavior.
|
||||||
|
*/
|
||||||
|
virtual bool every(closure<bool(element &)> apply) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invokes `apply` on each element in the stack and evaluating a user-defined condition which will return a
|
||||||
|
* `bool` at the end of each evaluation. After all elements have been evaluated, `true` is returned if every
|
||||||
|
* element elavuated `true`. Otherwise, `false` is returned to indicate that one of more elements failed
|
||||||
|
* evaluation.
|
||||||
|
*
|
||||||
|
* *Note*: This function uses short-circuit evaluation, so the enumeration will terminate upon the first failure
|
||||||
|
* case. This may be leveraged to create conditional looping behavior.
|
||||||
|
*/
|
||||||
|
virtual bool every(closure<bool(element const &)> apply) const = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempts to append `source_elements` to the stack.
|
* Attempts to append `source_elements` to the stack.
|
||||||
@ -55,28 +74,60 @@ export namespace coral {
|
|||||||
* *Note*: the [allocator] referenced in the stack must remain valid for the duration of the stack lifetime.
|
* *Note*: the [allocator] referenced in the stack must remain valid for the duration of the stack lifetime.
|
||||||
*/
|
*/
|
||||||
template<typename element, usize init_capacity = 1> struct small_stack : public stack<element> {
|
template<typename element, usize init_capacity = 1> struct small_stack : public stack<element> {
|
||||||
small_stack(allocator * dynamic_allocator) {
|
small_stack(allocator & dynamic_allocator) : dynamic_allocator{dynamic_allocator} {}
|
||||||
this->dynamic_allocator = dynamic_allocator;
|
|
||||||
}
|
|
||||||
|
|
||||||
~small_stack() override {
|
~small_stack() override {
|
||||||
if (this->is_dynamic()) {
|
if (this->is_dynamic()) {
|
||||||
for (element & e : this->elements) e.~element();
|
for (element & e : this->elements) e.~element();
|
||||||
|
|
||||||
this->dynamic_allocator->deallocate(this->elements.pointer);
|
this->dynamic_allocator.deallocate(this->elements.pointer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a read-only [slice] of the current stack values.
|
* Returns a const [slice] of the current stack values.
|
||||||
*
|
*
|
||||||
* *Note*: the returned slice should be considered invalid if any mutable operation is performed on the source
|
* *Note*: the returned slice should be considered invalid if any mutable operation is performed on the source
|
||||||
* [stack] or it is no longer in scope.
|
* [stack] or it is no longer in scope.
|
||||||
*/
|
*/
|
||||||
slice<element const> as_slice() const override {
|
slice<element const> as_slice() const {
|
||||||
return this->elements.sliced(0, this->filled);
|
return this->elements.sliced(0, this->filled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invokes `apply` on each element in the stack and evaluating a user-defined condition which will return a
|
||||||
|
* `bool` at the end of each evaluation. After all elements have been evaluated, `true` is returned if every
|
||||||
|
* element elavuated `true`. Otherwise, `false` is returned to indicate that one of more elements failed
|
||||||
|
* evaluation.
|
||||||
|
*
|
||||||
|
* *Note*: This function uses short-circuit evaluation, so the enumeration will terminate upon the first failure
|
||||||
|
* case. This may be leveraged to create conditional looping behavior.
|
||||||
|
*/
|
||||||
|
bool every(closure<bool(element &)> apply) override {
|
||||||
|
for (usize index = 0; index < this->filled; index += 1) {
|
||||||
|
if (!apply(this->elements[index])) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invokes `apply` on each element in the stack and evaluating a user-defined condition which will return a
|
||||||
|
* `bool` at the end of each evaluation. After all elements have been evaluated, `true` is returned if every
|
||||||
|
* element elavuated `true`. Otherwise, `false` is returned to indicate that one of more elements failed
|
||||||
|
* evaluation.
|
||||||
|
*
|
||||||
|
* *Note*: This function uses short-circuit evaluation, so the enumeration will terminate upon the first failure
|
||||||
|
* case. This may be leveraged to create conditional looping behavior.
|
||||||
|
*/
|
||||||
|
bool every(closure<bool(element const &)> apply) const override {
|
||||||
|
for (usize index = 0; index < this->filled; index += 1) {
|
||||||
|
if (!apply(this->elements[index])) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns `true` if the stack is backed by dynamic memory, otherwise `false`.
|
* Returns `true` if the stack is backed by dynamic memory, otherwise `false`.
|
||||||
*/
|
*/
|
||||||
@ -84,6 +135,13 @@ export namespace coral {
|
|||||||
return this->elements.pointer != reinterpret_cast<element const *>(this->local_buffer);
|
return this->elements.pointer != reinterpret_cast<element const *>(this->local_buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns `true` if there are no elements in the stack, otherwise `false`.
|
||||||
|
*/
|
||||||
|
bool is_empty() const override {
|
||||||
|
return this->filled == 0;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempts to append `source_element` to the top of the stack.
|
* Attempts to append `source_element` to the top of the stack.
|
||||||
*
|
*
|
||||||
@ -149,7 +207,7 @@ export namespace coral {
|
|||||||
usize const requested_capacity = this->filled + capacity;
|
usize const requested_capacity = this->filled + capacity;
|
||||||
|
|
||||||
if (this->is_dynamic()) {
|
if (this->is_dynamic()) {
|
||||||
u8 * const buffer = this->dynamic_allocator->reallocate(
|
u8 * const buffer = this->dynamic_allocator.reallocate(
|
||||||
reinterpret_cast<u8 *>(this->elements.pointer),
|
reinterpret_cast<u8 *>(this->elements.pointer),
|
||||||
sizeof(element) * requested_capacity);
|
sizeof(element) * requested_capacity);
|
||||||
|
|
||||||
@ -162,7 +220,7 @@ export namespace coral {
|
|||||||
this->elements = {reinterpret_cast<element *>(buffer), requested_capacity};
|
this->elements = {reinterpret_cast<element *>(buffer), requested_capacity};
|
||||||
} else {
|
} else {
|
||||||
usize const buffer_size = sizeof(element) * requested_capacity;
|
usize const buffer_size = sizeof(element) * requested_capacity;
|
||||||
u8 * const buffer = this->dynamic_allocator->reallocate(nullptr, buffer_size);
|
u8 * const buffer = this->dynamic_allocator.reallocate(nullptr, buffer_size);
|
||||||
|
|
||||||
if (buffer == nullptr) {
|
if (buffer == nullptr) {
|
||||||
this->elements = {};
|
this->elements = {};
|
||||||
@ -179,7 +237,7 @@ export namespace coral {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
allocator * dynamic_allocator{nullptr};
|
allocator & dynamic_allocator;
|
||||||
|
|
||||||
usize filled{0};
|
usize filled{0};
|
||||||
|
|
||||||
@ -206,14 +264,18 @@ export namespace coral {
|
|||||||
* Reads the data from the target stack into `buffer`, returning the number bytes read.
|
* Reads the data from the target stack into `buffer`, returning the number bytes read.
|
||||||
*/
|
*/
|
||||||
expected<usize, io_error> read(slice<u8> const & buffer) override {
|
expected<usize, io_error> read(slice<u8> const & buffer) override {
|
||||||
slice const stack_elements {this->stack->as_slice()};
|
usize data_written = 0;
|
||||||
usize const read {min(buffer.length, stack_elements.length - this->cursor)};
|
|
||||||
|
|
||||||
copy(buffer, stack_elements.sliced(cursor, read));
|
this->stack->every([&](u8 byte) -> bool {
|
||||||
|
buffer[data_written] = byte;
|
||||||
|
data_written += 1;
|
||||||
|
|
||||||
this->cursor += read;
|
return data_written < buffer.length;
|
||||||
|
});
|
||||||
|
|
||||||
return read;
|
this->cursor += data_written;
|
||||||
|
|
||||||
|
return data_written;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
384
source/oar.cpp
384
source/oar.cpp
@ -2,137 +2,151 @@ export module oar;
|
|||||||
|
|
||||||
import coral;
|
import coral;
|
||||||
import coral.files;
|
import coral.files;
|
||||||
|
import coral.functional;
|
||||||
|
import coral.io;
|
||||||
|
import coral.stack;
|
||||||
|
|
||||||
|
using coral::closure;
|
||||||
|
using coral::expected;
|
||||||
|
using coral::file_reader;
|
||||||
|
using coral::fs;
|
||||||
|
using coral::io_error;
|
||||||
|
using coral::path;
|
||||||
|
using coral::u8;
|
||||||
|
using coral::u64;
|
||||||
|
using coral::usize;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Length of the full magic signature at the beginning of an Oar file.
|
* Length of the full magic signature at the beginning of an Oar file.
|
||||||
*/
|
*/
|
||||||
constexpr coral::usize signature_length {4};
|
constexpr static usize signature_length {4};
|
||||||
|
|
||||||
/**
|
|
||||||
* Length of the magic signature at the beginning of an Oar file without the version indicator
|
|
||||||
* byte.
|
|
||||||
*/
|
|
||||||
constexpr coral::usize signature_identifier_length {signature_length - 1};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hardcoded signature magic value that this implementation of Oar expects when reading archives.
|
* Hardcoded signature magic value that this implementation of Oar expects when reading archives.
|
||||||
*/
|
*/
|
||||||
constexpr coral::u8 signature_magic[signature_length] {'o', 'a', 'r', 0};
|
constexpr static u8 signature_magic[signature_length] {'o', 'a', 'r', 1};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Oar file header format.
|
* Oar file header format.
|
||||||
*/
|
*/
|
||||||
struct header {
|
union header {
|
||||||
coral::u8 signature_magic[signature_length];
|
struct {
|
||||||
|
u8 signature[signature_length];
|
||||||
|
|
||||||
coral::u32 entry_count;
|
coral::u32 entry_count;
|
||||||
|
} layout;
|
||||||
|
|
||||||
coral::u8 padding[504];
|
u8 bytes[512];
|
||||||
|
|
||||||
|
static constexpr bool is_sizeof(usize value) {
|
||||||
|
return value == sizeof(header);
|
||||||
|
}
|
||||||
|
|
||||||
|
expected<bool, io_error> read(coral::reader & archive_reader) {
|
||||||
|
return archive_reader.read(this->bytes).map<bool>(is_sizeof).map<bool>([&](bool is_valid) -> bool {
|
||||||
|
return is_valid && coral::equals(this->layout.signature, signature_magic);
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
static_assert(sizeof(header) == 512);
|
static_assert(header::is_sizeof(512));
|
||||||
|
|
||||||
/**
|
enum class entry_kind {
|
||||||
* Oar file header format.
|
file,
|
||||||
*/
|
directory,
|
||||||
struct entry {
|
|
||||||
coral::path path;
|
|
||||||
|
|
||||||
coral::u64 data_offset;
|
|
||||||
|
|
||||||
coral::u64 data_length;
|
|
||||||
|
|
||||||
coral::u8 padding[240];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static_assert(sizeof(entry) == 512);
|
/**
|
||||||
|
* Oar entry block format.
|
||||||
|
*/
|
||||||
|
union block {
|
||||||
|
struct {
|
||||||
|
path path;
|
||||||
|
|
||||||
|
u64 data_offset;
|
||||||
|
|
||||||
|
u64 data_length;
|
||||||
|
|
||||||
|
entry_kind kind;
|
||||||
|
} layout;
|
||||||
|
|
||||||
|
u8 bytes[512];
|
||||||
|
|
||||||
|
static constexpr bool is_sizeof(usize value) {
|
||||||
|
return value == sizeof(block);
|
||||||
|
}
|
||||||
|
|
||||||
|
expected<bool, io_error> read(coral::reader & archive_reader) {
|
||||||
|
return archive_reader.read(this->bytes).map<bool>(is_sizeof);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static_assert(block::is_sizeof(512));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Archive file access interface.
|
* Archive entry access interface.
|
||||||
*/
|
*/
|
||||||
struct archive_file : public coral::file_reader {
|
struct entry : public file_reader {
|
||||||
/**
|
/**
|
||||||
* Results of a find operation performed on an [archive_file].
|
* Results of a find operation performed on an [archive_file].
|
||||||
*
|
*
|
||||||
* [find_result::ok] means that the find operation was successful.
|
* [find_result::ok] means that the find operation was successful.
|
||||||
*
|
*
|
||||||
* [find_result::io_unavailable] signals a failure to communicate with the underlying
|
* [find_result::io_unavailable] signals a failure to communicate with the underlying [file_reader] for whatever
|
||||||
* [coral::file_reader] for whatever reason.
|
* reason.
|
||||||
*
|
*
|
||||||
* [find_result::archive_invalid] signals that data was read but it does not match the format
|
* [find_result::archive_invalid] signals that data was read but it does not match the format of an Oar archive.
|
||||||
* of an Oar archive. This is typically because the underlying [coral::file_reader] is not
|
* This is typically because the underlying [file_reader] is not reading from an Oar archive file or the archive does
|
||||||
* reading from an Oar archive file.
|
* not match the supported version.
|
||||||
*
|
*
|
||||||
* [find_result::archive_unsupported] signals that data was read and was formatted as expected
|
* [find_result::not_found] indicates that no entry in the archive could be found that matches the given query.
|
||||||
* for an Oar archive, however, it is from an unsupported version of the archive format.
|
|
||||||
*
|
|
||||||
* [find_result::not_found] indicates that no entry in the archive could be found that matches
|
|
||||||
* the given query.
|
|
||||||
*/
|
*/
|
||||||
enum class [[nodiscard]] find_result {
|
enum class [[nodiscard]] find_result {
|
||||||
ok,
|
ok,
|
||||||
io_unavailable,
|
io_unavailable,
|
||||||
archive_invalid,
|
archive_invalid,
|
||||||
archive_unsupported,
|
|
||||||
not_found,
|
not_found,
|
||||||
};
|
};
|
||||||
|
|
||||||
archive_file(coral::file_reader * archive_reader) {
|
entry(file_reader * archive_reader) {
|
||||||
this->archive_reader = archive_reader;
|
this->archive_reader = archive_reader;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Performs a lookup for a file entry matching the path `file_path` in the archive, returning
|
* Performs a lookup for a file entry matching the path `file_path` in the archive, returning [find_result] to
|
||||||
* [find_result] to indicate the result of the operation.
|
* indicate the result of the operation.
|
||||||
*/
|
*/
|
||||||
find_result find(coral::path const & file_path) {
|
find_result find(entry_kind kind, path const & entry_path) {
|
||||||
this->data_offset = 0;
|
this->data_offset = 0;
|
||||||
this->data_length = 0;
|
this->data_length = 0;
|
||||||
this->data_cursor = 0;
|
this->data_cursor = 0;
|
||||||
|
|
||||||
if (!this->archive_reader->seek(0).is_ok()) return find_result::io_unavailable;
|
if (!this->archive_reader->seek(0).is_ok()) return find_result::io_unavailable;
|
||||||
|
|
||||||
constexpr coral::usize header_size {sizeof(header)};
|
header archive_header {};
|
||||||
coral::u8 archive_header_buffer[header_size] {0};
|
|
||||||
|
|
||||||
if (!this->archive_reader->read(archive_header_buffer).and_test(
|
if (!archive_header.read(*this->archive_reader).map<bool>(coral::equality_predicate(true)).is_ok())
|
||||||
[](coral::usize value) -> bool { return value == header_size; }))
|
return find_result::archive_invalid;
|
||||||
return find_result::archive_invalid;
|
|
||||||
|
|
||||||
header const * const archive_header {
|
|
||||||
reinterpret_cast<header const *>(archive_header_buffer)};
|
|
||||||
|
|
||||||
if (!coral::equals({archive_header->signature_magic, signature_identifier_length},
|
|
||||||
{signature_magic, signature_identifier_length})) return find_result::archive_invalid;
|
|
||||||
|
|
||||||
if (archive_header->signature_magic[signature_identifier_length] !=
|
|
||||||
signature_magic[signature_identifier_length]) return find_result::archive_unsupported;
|
|
||||||
|
|
||||||
// Read file table.
|
// Read file table.
|
||||||
coral::u64 head {0};
|
u64 head {0};
|
||||||
coral::u64 tail {archive_header->entry_count - 1};
|
u64 tail {archive_header.layout.entry_count - 1};
|
||||||
constexpr coral::usize entry_size {sizeof(entry)};
|
block archive_block {};
|
||||||
coral::u8 archive_entry_buffer[entry_size] {0};
|
|
||||||
|
|
||||||
while (head <= tail) {
|
while (head <= tail) {
|
||||||
coral::u64 const midpoint {head + ((tail - head) / 2)};
|
u64 const midpoint {head + ((tail - head) / 2)};
|
||||||
|
|
||||||
if (!this->archive_reader->seek(header_size + (entry_size * midpoint)).is_ok())
|
if (!archive_block.read(*this->archive_reader).map<bool>(coral::equality_predicate(true)).is_ok())
|
||||||
return find_result::archive_invalid;
|
return find_result::archive_invalid;
|
||||||
|
|
||||||
if (!this->archive_reader->read(archive_entry_buffer).and_test(
|
if (archive_block.layout.kind == kind) return find_result::not_found;
|
||||||
[](coral::usize value) -> bool { return value == entry_size; }))
|
|
||||||
return find_result::archive_invalid;
|
|
||||||
|
|
||||||
entry const * const archive_entry {
|
coral::size const comparison {entry_path.compare(archive_block.layout.path)};
|
||||||
reinterpret_cast<entry const *>(archive_entry_buffer)};
|
|
||||||
|
|
||||||
coral::size const comparison {file_path.compare(archive_entry->path)};
|
|
||||||
|
|
||||||
if (comparison == 0) {
|
if (comparison == 0) {
|
||||||
this->data_offset = archive_entry->data_offset;
|
this->data_offset = archive_block.layout.data_offset;
|
||||||
this->data_length = archive_entry->data_length;
|
this->data_length = archive_block.layout.data_length;
|
||||||
this->data_cursor = archive_entry->data_offset;
|
this->data_cursor = archive_block.layout.data_offset;
|
||||||
|
|
||||||
return find_result::ok;
|
return find_result::ok;
|
||||||
}
|
}
|
||||||
@ -149,65 +163,115 @@ struct archive_file : public coral::file_reader {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempts to read `data.length` bytes from the file and fill `data` with it, returning the
|
* Attempts to read `data.length` bytes from the file and fill `data` with it, returning the
|
||||||
* number of bytes actually read or a [coral::io_error] value to indicate an error occured.
|
* number of bytes actually read or a [io_error] value to indicate an error occured.
|
||||||
*/
|
*/
|
||||||
coral::expected<coral::usize, coral::io_error> read(coral::slice<coral::u8> const & data) override {
|
expected<usize, io_error> read(coral::slice<u8> const & data) override {
|
||||||
if (this->data_offset < sizeof(header)) return coral::io_error::unavailable;
|
if (this->data_offset < sizeof(header)) return io_error::unavailable;
|
||||||
|
|
||||||
coral::usize const data_tail {this->data_offset + this->data_length};
|
usize const data_tail {this->data_offset + this->data_length};
|
||||||
|
|
||||||
if (!this->archive_reader->seek(coral::clamp(this->data_offset + this->data_cursor,
|
if (!this->archive_reader->seek(coral::clamp(this->data_offset + this->data_cursor,
|
||||||
this->data_offset, data_tail)).is_ok()) return coral::io_error::unavailable;
|
this->data_offset, data_tail)).is_ok()) return io_error::unavailable;
|
||||||
|
|
||||||
coral::expected const data_read {this->archive_reader->read(
|
expected const data_read {this->archive_reader->read(
|
||||||
data.sliced(0, coral::min(data.length, data_tail - this->data_cursor)))};
|
data.sliced(0, coral::min(data.length, data_tail - this->data_cursor)))};
|
||||||
|
|
||||||
if (data_read.is_ok()) this->data_cursor += data_read.value();
|
if (data_read.is_ok()) this->data_cursor += *data_read.ok();
|
||||||
|
|
||||||
return data_read;
|
return data_read;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempts to seek to `offset` absolute position in the file, returning the new absolute
|
* Attempts to seek to `offset` absolute position in the file, returning the new absolute
|
||||||
* cursor or a [coral::io_error] value to indicate an error occured.
|
* cursor or a [io_error] value to indicate an error occured.
|
||||||
*/
|
*/
|
||||||
coral::expected<coral::u64, coral::io_error> seek(coral::u64 offset) override {
|
expected<u64, io_error> seek(u64 offset) override {
|
||||||
if (this->data_offset < sizeof(header)) return coral::io_error::unavailable;
|
if (this->data_offset < sizeof(header)) return io_error::unavailable;
|
||||||
|
|
||||||
this->data_cursor = offset;
|
this->data_cursor = offset;
|
||||||
|
|
||||||
return coral::io_error::unavailable;
|
return io_error::unavailable;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempts to read to read the absolute file cursor position, returning it or a
|
* Attempts to read to read the absolute file cursor position, returning it or a [io_error]
|
||||||
* [coral::io_error] value to indicate an error occured.
|
* value to indicate an error occured.
|
||||||
*/
|
*/
|
||||||
coral::expected<coral::u64, coral::io_error> tell() override {
|
expected<u64, io_error> tell() override {
|
||||||
if (this->data_offset < sizeof(header)) return coral::io_error::unavailable;
|
if (this->data_offset < sizeof(header)) return io_error::unavailable;
|
||||||
|
|
||||||
return this->data_cursor;
|
return this->data_cursor;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
coral::file_reader * archive_reader {nullptr};
|
file_reader * archive_reader {nullptr};
|
||||||
|
|
||||||
coral::u64 data_offset {0};
|
u64 data_offset {0};
|
||||||
|
|
||||||
coral::u64 data_length {0};
|
u64 data_length {0};
|
||||||
|
|
||||||
coral::u64 data_cursor {0};
|
u64 data_cursor {0};
|
||||||
};
|
};
|
||||||
|
|
||||||
export namespace oar {
|
export namespace oar {
|
||||||
struct archive : public coral::fs {
|
struct archive : public fs {
|
||||||
archive(coral::fs * backing_fs, coral::path const & archive_path) {
|
archive(fs * backing_fs, path const & archive_path) {
|
||||||
this->backing_fs = backing_fs;
|
this->backing_fs = backing_fs;
|
||||||
this->archive_path = archive_path;
|
this->archive_path = archive_path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
walk_result walk_files(path const & target_path, closure<bool(path const &)> const & apply) override {
|
||||||
|
bool not_found {false};
|
||||||
|
bool has_io_error {false};
|
||||||
|
|
||||||
|
this->backing_fs->read_file(this->archive_path, [&](file_reader & archive_reader) {
|
||||||
|
entry archive_entry{&archive_reader};
|
||||||
|
|
||||||
|
if (archive_entry.find(entry_kind::directory, target_path) != entry::find_result::ok) {
|
||||||
|
not_found = true;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
constexpr usize path_size {sizeof(path)};
|
||||||
|
u8 path_buffer[path_size] {0};
|
||||||
|
expected const data_read {archive_entry.read(path_buffer)};
|
||||||
|
|
||||||
|
if (data_read.is_error()) {
|
||||||
|
has_io_error = true;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (usize const data_read_value {*data_read.ok()}; data_read_value != path_size) {
|
||||||
|
if (data_read_value != 0) has_io_error = true;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify existence of zero terminator in path.
|
||||||
|
if (!coral::find_last(path_buffer, 0).has_value()) {
|
||||||
|
has_io_error = true;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (archive_entry.read(path_buffer).map<bool>(coral::equality_predicate(path_size)).ok_or(false))
|
||||||
|
|
||||||
|
if (!apply(*reinterpret_cast<path const *>(path_buffer))) return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (not_found) return walk_result::not_found;
|
||||||
|
|
||||||
|
if (has_io_error) return walk_result::io_error;
|
||||||
|
|
||||||
|
return walk_result::ok;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Queries the archive for the [coral::fs::access_rules] and returns them.
|
* Queries the archive for the [fs::access_rules] and returns them.
|
||||||
*/
|
*/
|
||||||
access_rules query_access() override {
|
access_rules query_access() override {
|
||||||
return {
|
return {
|
||||||
@ -217,37 +281,119 @@ export namespace oar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempts to open a readable context for reading from the archive file identified by
|
* Attempts to open a readable context for reading from the archive file identified by `file_path`, doing
|
||||||
* `file_path`, doing nothing if the requested file could not be found.
|
* nothing if the requested file could not be found.
|
||||||
*/
|
*/
|
||||||
void read_file(coral::path const & file_path,
|
void read_file(path const & file_path, closure<void(file_reader &)> const & then) override {
|
||||||
coral::closure<void(coral::file_reader &)> const & then) override {
|
if ((this->backing_fs == nullptr) || (this->archive_path.byte_size() == 0)) return;
|
||||||
|
|
||||||
if ((this->backing_fs == nullptr) || (this->archive_path.byte_size() == 0)) return;
|
this->backing_fs->read_file(this->archive_path, [&](file_reader & archive_reader) {
|
||||||
|
entry archive_entry {&archive_reader};
|
||||||
|
|
||||||
this->backing_fs->read_file(this->archive_path,
|
if (archive_entry.find(entry_kind::file, file_path) != entry::find_result::ok) return;
|
||||||
[&](coral::file_reader & archive_reader) {
|
|
||||||
archive_file file{&archive_reader};
|
|
||||||
|
|
||||||
if (file.find(file_path) != archive_file::find_result::ok) return;
|
then(archive_entry);
|
||||||
|
});
|
||||||
then(file);
|
}
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attempts to open a writable context for reading from the archive file identified by
|
|
||||||
* `file_path`, however this will always do nothing as archive file-systems are read-only.
|
|
||||||
*/
|
|
||||||
void write_file(coral::path const & file_path,
|
|
||||||
coral::closure<void(coral::file_writer &)> const & then) override {
|
|
||||||
|
|
||||||
// Read-only file system.
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
coral::fs * backing_fs;
|
fs * backing_fs;
|
||||||
|
|
||||||
coral::path archive_path;
|
path archive_path;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class [[nodiscard]] bundle_result {
|
||||||
|
ok,
|
||||||
|
out_of_memory,
|
||||||
|
too_many_files,
|
||||||
|
io_error,
|
||||||
|
};
|
||||||
|
|
||||||
|
bundle_result bundle(coral::allocator & allocator, fs & output_fs,
|
||||||
|
path const & output_path, fs & input_fs, path const & input_path) {
|
||||||
|
|
||||||
|
coral::small_stack<block, 64> archive_blocks {allocator};
|
||||||
|
u64 file_count {0};
|
||||||
|
|
||||||
|
// Walk input dir to create blocks for all files needed.
|
||||||
|
{
|
||||||
|
bool has_memory {true};
|
||||||
|
|
||||||
|
if (input_fs.walk_files(input_path, [&](path const & entry_path) -> bool {
|
||||||
|
has_memory = archive_blocks.push({.layout = {.path = entry_path}}) == coral::push_result::ok;
|
||||||
|
|
||||||
|
return !has_memory;
|
||||||
|
|
||||||
|
file_count += 1;
|
||||||
|
}) != fs::walk_result::ok) return bundle_result::io_error;
|
||||||
|
|
||||||
|
if (!has_memory) return bundle_result::out_of_memory;
|
||||||
|
|
||||||
|
if (file_count > coral::u32_max) return bundle_result::too_many_files;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write header, file data, and blocks to archive.
|
||||||
|
{
|
||||||
|
bool has_io_error {false};
|
||||||
|
|
||||||
|
output_fs.write_file(output_path, [&](coral::file_writer & archive_writer) {
|
||||||
|
header archive_header {};
|
||||||
|
|
||||||
|
coral::copy(archive_header.layout.signature, signature_magic);
|
||||||
|
|
||||||
|
archive_header.layout.entry_count = static_cast<coral::u32>(file_count);
|
||||||
|
|
||||||
|
if (!archive_writer.write(archive_header.bytes).map<bool>(header::is_sizeof).ok_or(false)) {
|
||||||
|
has_io_error = true;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!archive_blocks.every([&](block & archive_block) -> bool {
|
||||||
|
bool file_read {false};
|
||||||
|
|
||||||
|
input_fs.read_file(archive_block.layout.path, [&](coral::file_reader & entry_reader) {
|
||||||
|
expected const data_position {entry_reader.tell()};
|
||||||
|
|
||||||
|
if (data_position.is_error()) {
|
||||||
|
has_io_error = true;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
archive_block.layout.data_offset = *data_position.ok();
|
||||||
|
|
||||||
|
{
|
||||||
|
u8 stream_buffer[4096] {0};
|
||||||
|
expected const data_written {coral::stream(archive_writer, entry_reader, stream_buffer)};
|
||||||
|
|
||||||
|
if (data_written.is_error()) {
|
||||||
|
has_io_error = true;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
archive_block.layout.data_length = *data_written.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
file_read = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
return file_read && (!has_io_error);
|
||||||
|
})) return;
|
||||||
|
|
||||||
|
if (!archive_blocks.every([&](block const & archive_block) -> bool {
|
||||||
|
if (!archive_writer.write(archive_block.bytes).map<bool>(block::is_sizeof).ok_or(false)) {
|
||||||
|
has_io_error = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return !has_io_error;
|
||||||
|
})) return;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (has_io_error) return bundle_result::io_error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return bundle_result::ok;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user