ona/src/oar.zig

158 lines
5.0 KiB
Zig
Raw Normal View History

const std = @import("std");
const sys = @import("./sys.zig");
const table = @import("./table.zig");
///
/// Thin file-wrapper and in-memory layout cache of an OAR archive file.
///
pub const Archive = struct {
file_access: sys.FileAccess,
index_cache: IndexCache,
2022-10-11 01:03:02 +01:00
///
/// [OpenError.EntryNotFound] happens when an entry could not be found.
2022-10-11 01:03:02 +01:00
///
pub const FindError = sys.FileAccess.Error || error {
EntryNotFound,
};
2022-10-11 01:03:02 +01:00
///
/// 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 = table.Hashed([]const u8, u64, 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,
.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.name_buffer[0 .. header.name_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));
2022-10-11 01:03:02 +01:00
try archive.file_access.skip(@intCast(i64, skipped));
to_skip -= skipped;
}
}
}
return error.EntryNotFound;
},
.owner = &archive.file_access,
.cursor = 0,
};
2022-10-11 01:03:02 +01:00
}
///
/// 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.
2022-10-11 01:03:02 +01:00
///
/// **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: sys.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: ?*sys.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,
name_buffer: [255]u8 = std.mem.zeroes([255]u8),
name_length: u8 = 0,
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'};
};
};