ona/src/oar/main.zig
kayomn 489ece4b7b
Some checks failed
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is failing
Refactor codebase
2022-10-17 10:34:04 +01:00

170 lines
5.1 KiB
Zig

const ona = @import("ona");
const std = @import("std");
///
/// Thin file-wrapper and in-memory layout cache of an OAR archive file.
///
pub const Archive = struct {
file_access: ona.io.FileAccess,
index_cache: IndexCache,
///
/// [OpenError.EntryNotFound] happens when an entry could not be found.
///
pub const FindError = ona.io.FileAccess.Error || error {
EntryNotFound,
};
///
/// See [std.mem.Allocator.Error].
///
pub const InitError = std.mem.Allocator.Error;
///
/// In-memory archive layout cache.
///
/// As the archive is queried via [find], the cache is lazily assembled with the absolute
/// offsets of each queried file.
///
const IndexCache = ona.table.Hashed([]const u8, u64, ona.table.string_context);
///
/// Deinitializes the index cache of `archive`, freeing all associated memory.
///
/// **Note** that this does nothing to the [FileAccess] value that was provided as part of
/// [init].
///
pub fn deint(archive: *Archive) void {
archive.index_cache.deinit();
}
///
/// Finds an entry matching `entry_path` in `archive`.
///
/// The found [Entry] value is returned or a [FindError] if it failed to be found.
///
pub fn find(archive: *Archive, entry_path: []const u8) FindError!Entry {
return Entry{
.header = find_header: {
var header = Entry.Header{
.revision = 0,
.path = Path.empty,
.file_size = 0,
.absolute_offset = 0
};
const header_size = @sizeOf(Entry.Header);
if (archive.index_cache.lookup(entry_path)) |cursor| {
try archive.file_access.seek(cursor);
if ((try archive.file_access.read(std.mem.asBytes(&header))) != header_size) {
std.debug.assert(archive.index_cache.remove(entry_path) != null);
return error.EntryNotFound;
}
break: find_header header;
} else {
const mem = std.mem;
// Start from beginning of archive.
try archive.file_access.seek(0);
// Read first entry.
while ((try archive.file_access.read(mem.asBytes(&header))) == header_size) {
if (mem.eql(u8, entry_path, header.path.buffer[0 .. header.path.length])) {
// If caching fails... oh well...
archive.index_cache.insert(entry_path, header.absolute_offset) catch {};
break: find_header header;
}
// Move over file data following the entry.
var to_skip = header.file_size;
while (to_skip != 0) {
const math = std.math;
const skipped = math.min(to_skip, math.maxInt(i64));
try archive.file_access.skip(@intCast(i64, skipped));
to_skip -= skipped;
}
}
}
return error.EntryNotFound;
},
.owner = &archive.file_access,
.cursor = 0,
};
}
///
/// Attempts to initialize a new [Archive] with `cache_allocator` as the allocator for managing
/// the in-memory archive layout caches and `archive_file_access` as the currently open archive
/// file.
///
/// **Note** that `archive_file_access` does nothing to manage the lifetime of the open file.
///
pub fn init(cache_allocator: std.mem.Allocator,
archive_file_access: ona.io.FileAccess) InitError!Archive {
return Archive{
.index_cache = try IndexCache.init(cache_allocator),
.file_access = archive_file_access,
};
}
};
///
/// Handles the state of an opened archive entry.
///
pub const Entry = struct {
owner: ?*ona.io.FileAccess,
cursor: u64,
header: Header,
///
/// An entry block of an Oar archive file.
///
/// Typically, following the block in memory is the file data it holds the meta-information for.
///
pub const Header = extern struct {
signature: [signature_magic.len]u8 = signature_magic,
revision: u8,
path: Path,
file_size: u64,
absolute_offset: u64,
padding: [232]u8 = std.mem.zeroes([232]u8),
comptime {
const entry_size = @sizeOf(Header);
if (entry_size != 512)
@compileError("Entry is " ++
std.fmt.comptimePrint("{d}", .{entry_size}) ++ " bytes");
}
///
/// Magic identifier used to validate [Entry] data.
///
pub const signature_magic = [3]u8{'o', 'a', 'r'};
};
};
///
///
///
pub const Path = extern struct {
buffer: [255]u8,
length: u8,
///
///
///
pub const empty = std.mem.zeroes(Path);
};