export module oar.files; import coral; import coral.files; import oar; using coral::closure; using coral::expected; using coral::file_reader; using coral::file_system; using coral::file_walker; using coral::io_error; using coral::slice; using coral::u64; enum class [[nodiscard]] find_result { ok, not_found, io_unavailable, archive_invalid, }; struct archive_reader : public file_reader { archive_reader(file_reader & archive_file_reader) : archive_file_reader{archive_file_reader} {} find_result find(oar::entry_path const & path) { this->data_offset = 0; this->data_length = 0; this->data_cursor = 0; if (!this->archive_file_reader.seek(0).is_ok()) return find_result::io_unavailable; oar::header_block archive_header_block; if (!archive_header_block.read(this->archive_file_reader).ok().or_value(false)) return find_result::archive_invalid; // Read file table. u64 head {0}; u64 tail {archive_header_block.layout.entry_count - 1}; oar::entry_block archive_entry_block {}; while (head <= tail) { u64 const midpoint {head + ((tail - head) / 2)}; if (!archive_entry_block.read(this->archive_file_reader).ok().or_value(false)) return find_result::archive_invalid; coral::size const comparison {path.compare(archive_entry_block.layout.path)}; if (comparison == 0) { this->data_offset = archive_entry_block.layout.data_offset; this->data_length = archive_entry_block.layout.data_length; this->data_cursor = archive_entry_block.layout.data_offset; return find_result::ok; } if (comparison > 0) { head = (midpoint + 1); } else { tail = (midpoint - 1); } } return find_result::not_found; } /** * Attempts to read `data.length` bytes from the file and fill `data` with it, returning the * number of bytes actually read or a [io_error] value to indicate an error occured. */ expected read(slice const & data) override { if (this->data_offset < sizeof(oar::header_block)) return io_error::unavailable; coral::usize const data_tail {this->data_offset + this->data_length}; if (!this->archive_file_reader.seek(coral::clamp(this->data_offset + this->data_cursor, this->data_offset, data_tail)).is_ok()) return io_error::unavailable; expected const data_read {this->archive_file_reader.read( data.sliced(0, coral::min(data.length, data_tail - this->data_cursor)))}; if (data_read.is_ok()) this->data_cursor += *data_read.ok(); return data_read; } /** * Attempts to seek to `offset` absolute position in the file, returning the new absolute * cursor or a [io_error] value to indicate an error occured. */ expected seek(u64 offset) override { if (this->data_offset < sizeof(oar::header_block)) return io_error::unavailable; this->data_cursor = offset; return io_error::unavailable; } /** * Attempts to read to read the absolute file cursor position, returning it or a [io_error] * value to indicate an error occured. */ expected tell() override { if (this->data_offset < sizeof(oar::header_block)) return io_error::unavailable; return this->data_cursor; } private: file_reader & archive_file_reader; u64 data_offset {0}; u64 data_length {0}; u64 data_cursor {0}; }; struct archive_walker : public file_walker { archive_walker(file_reader & archive_file_reader) : archive_file_reader{archive_file_reader} {} bool enumerate() override { // TODO: implement. return false; } find_result find(oar::entry_path const & path) { // TODO: implement. return find_result::not_found; } expected, io_error> value() override { // TODO: implement. return io_error::unavailable; } private: file_reader & archive_file_reader; u64 files_enumerated {0}; }; export namespace oar { struct archive_file : public file_system { static void read(file_system & base_file_system, slice const & archive_path, closure const & then) { if (base_file_system.query_file(archive_path).is_error()) return; archive_file file {base_file_system, archive_path}; then(file); } void read_file(slice const & path, closure const & then) override { this->base_file_system.read_file(this->archive_path, [&](file_reader & archive_file_reader) { expected const parsed_entry_path {entry_path::parse(path)}; if (parsed_entry_path.is_error()) return; archive_reader reader {archive_file_reader}; if (reader.find(*parsed_entry_path.ok()) != find_result::ok) return; then(reader); }); } void walk_files(slice const & path, closure const & then) override { this->base_file_system.read_file(this->archive_path, [&](file_reader & archive_file_reader) { expected const parsed_entry_path {entry_path::parse(path)}; if (parsed_entry_path.is_error()) return; archive_walker walker {archive_file_reader}; if (walker.find(*parsed_entry_path.ok()) != find_result::ok) return; then(walker); }); } private: file_system & base_file_system; slice archive_path; archive_file(file_system & base_file_system, slice const & archive_path) : base_file_system{base_file_system} { this->archive_path = archive_path; } }; }