2022-10-09 23:10:13 +01:00
|
|
|
const std = @import("std");
|
|
|
|
const sys = @import("./sys.zig");
|
2022-10-15 13:14:02 +01:00
|
|
|
const table = @import("./table.zig");
|
2022-10-09 23:10:13 +01:00
|
|
|
|
|
|
|
///
|
|
|
|
///
|
|
|
|
///
|
2022-10-15 13:14:02 +01:00
|
|
|
pub const Archive = struct {
|
|
|
|
file_access: sys.FileAccess,
|
|
|
|
index_cache: IndexCache,
|
2022-10-11 01:03:02 +01:00
|
|
|
|
|
|
|
///
|
2022-10-15 13:14:02 +01:00
|
|
|
/// [OpenError.EntryNotFound] happens when an entry could not be found.
|
2022-10-11 01:03:02 +01:00
|
|
|
///
|
2022-10-13 14:37:38 +01:00
|
|
|
pub const FindError = sys.FileAccess.Error || error {
|
|
|
|
EntryNotFound,
|
|
|
|
};
|
2022-10-11 01:03:02 +01:00
|
|
|
|
2022-10-13 14:37:38 +01:00
|
|
|
///
|
|
|
|
///
|
|
|
|
///
|
2022-10-15 13:14:02 +01:00
|
|
|
pub const InitError = std.mem.Allocator.Error;
|
|
|
|
|
|
|
|
///
|
|
|
|
///
|
|
|
|
///
|
2022-10-15 21:11:15 +01:00
|
|
|
const IndexCache = table.Hashed([]const u8, u64, table.string_context);
|
2022-10-15 13:14:02 +01:00
|
|
|
|
|
|
|
///
|
|
|
|
/// 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: {
|
2022-10-15 21:11:15 +01:00
|
|
|
var header = Entry.Header{
|
2022-10-15 13:14:02 +01:00
|
|
|
.revision = 0,
|
|
|
|
.file_size = 0,
|
2022-10-15 21:11:15 +01:00
|
|
|
.absolute_offset = 0
|
2022-10-15 13:14:02 +01:00
|
|
|
};
|
|
|
|
|
2022-10-15 21:11:15 +01:00
|
|
|
const header_size = @sizeOf(Entry.Header);
|
2022-10-15 13:14:02 +01:00
|
|
|
|
2022-10-15 21:11:15 +01:00
|
|
|
if (archive.index_cache.lookup(entry_path)) |cursor| {
|
|
|
|
try archive.file_access.seek(cursor);
|
2022-10-15 13:14:02 +01:00
|
|
|
|
2022-10-15 21:11:15 +01:00
|
|
|
if ((try archive.file_access.read(std.mem.asBytes(&header))) != header_size) {
|
|
|
|
std.debug.assert(archive.index_cache.remove(entry_path) != null);
|
2022-10-15 13:14:02 +01:00
|
|
|
|
2022-10-15 21:11:15 +01:00
|
|
|
return error.EntryNotFound;
|
2022-10-15 13:14:02 +01:00
|
|
|
}
|
|
|
|
|
2022-10-15 21:11:15 +01:00
|
|
|
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;
|
2022-10-15 13:14:02 +01:00
|
|
|
|
2022-10-15 21:11:15 +01:00
|
|
|
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
|
|
|
|
2022-10-15 21:11:15 +01:00
|
|
|
try archive.file_access.skip(@intCast(i64, skipped));
|
2022-10-15 13:14:02 +01:00
|
|
|
|
2022-10-15 21:11:15 +01:00
|
|
|
to_skip -= skipped;
|
|
|
|
}
|
2022-10-15 13:14:02 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return error.EntryNotFound;
|
|
|
|
},
|
|
|
|
|
|
|
|
.owner = &archive.file_access,
|
|
|
|
.cursor = 0,
|
|
|
|
};
|
2022-10-11 01:03:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
///
|
|
|
|
///
|
2022-10-15 13:14:02 +01:00
|
|
|
///
|
|
|
|
pub fn init(allocator: std.mem.Allocator, archive_file_access: sys.FileAccess) InitError!Archive {
|
|
|
|
return Archive{
|
|
|
|
.index_cache = try IndexCache.init(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,
|
2022-10-15 21:11:15 +01:00
|
|
|
absolute_offset: u64,
|
2022-10-15 13:14:02 +01:00
|
|
|
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'};
|
|
|
|
};
|
2022-10-09 23:10:13 +01:00
|
|
|
};
|