Backlog/lib/packer/src/packer.zig

238 lines
8.3 KiB
Zig

const std = @import("std");
const p2 = @import("p2");
const PackedFileEntry = @import("PackedFileEntry.zig");
const packerfs = @import("packerfs.zig");
pub const PackerFS = packerfs.PackerFS;
pub const PackerBytesMapping = packerfs.PackerBytesMapping;
const constants = @import("constants.zig");
pub const PackerMagic = constants.PackerMagic;
pub const littleEndian = constants.littleEndian;
// this is invalidated if a PackedArhive is modified
pub const PackerBytesRef = struct {
typeTag: []const u8,
raw_bytes: []const u8,
};
const PackedArchiveContents = std.ArrayListAlignedUnmanaged(u8, 8);
// only responsible for reading and writing archives from disk
// not intended to be the end API
pub const PackedArchive = struct {
allocator: std.mem.Allocator,
contents: *PackedArchiveContents,
// file by convention is little-endian for u32s and u64s
headerEntries: std.ArrayListUnmanaged(PackedFileEntry),
entriesByString: std.StringHashMapUnmanaged(usize), // does not get serialized.
contentBuffer: []const u8 = undefined,
finished: bool = false,
pub fn initEmpty(allocator: std.mem.Allocator) !@This() {
const self: @This() = .{
.contents = try allocator.create(PackedArchiveContents),
.allocator = allocator,
.headerEntries = .{},
.entriesByString = .{},
};
self.contents.* = .{};
return self;
}
pub fn appendFileByBytes(self: *@This(), fileType: []const u8, fileName: []const u8, bytes: []const u8) !void {
try p2.assert(self.finished != true);
const writer = self.contents.writer(self.allocator);
const fileOffset = self.contents.items.len;
try writer.writeAll(bytes);
const bytesWritten = bytes.len;
// if bytes written + fileOffset results in a non-64-bit aligned offset, then we shall pad with zeroes until we hit a 32 bit aligned offset
const padding = 8 - ((bytesWritten + fileOffset) % 8);
try writer.writeByteNTimes(0x0, padding);
const newHeaderEntry = try PackedFileEntry.init(fileType, fileName, fileOffset, @intCast(bytes.len));
try self.addHeader(newHeaderEntry);
}
// finish building, set externalContentData if this contents does NOT contain
// the actual content data, such as in the case where the content is
// sent over a network or if the content is embedded with @embedFile
pub fn finishBuilding(self: *@This(), externalContentData: bool) !void {
if (!externalContentData) {
self.contentBuffer = self.contents.items;
}
for (self.headerEntries.items, 0..) |*entry, i| {
try self.entriesByString.put(self.allocator, entry.getFileName(), i);
}
self.finished = true;
}
fn loadHeadersFromReader(self: *@This(), reader: anytype) !usize {
var iterator = try PackedFileEntry.ReaderIterator.init(reader);
while (try iterator.next(reader)) |entry| {
try self.headerEntries.append(self.allocator, entry);
}
return iterator.bytesRead;
}
fn loadContentFromReader(self: *@This(), reader: anytype) !usize {
const writer = self.contents.writer(self.allocator);
var totalRead: usize = 0;
var readBuffer: [1024]u8 = undefined;
var bytesRead = try reader.read(&readBuffer);
totalRead += bytesRead;
_ = try writer.write((&readBuffer)[0..bytesRead]);
while (bytesRead > 0) {
bytesRead = try reader.read(&readBuffer);
_ = try writer.write((&readBuffer)[0..bytesRead]);
totalRead += bytesRead;
}
return totalRead;
}
fn setContentRefToOffset(self: *@This(), bytes: []const u8, offset: usize) void {
self.contentBuffer = bytes[offset..];
}
pub fn loadFromBytes(self: *@This(), bytes: []const u8) !void {
try p2.assert(self.finished != true);
var reader = std.io.fixedBufferStream(bytes);
const bytesRead = try self.loadHeadersFromReader(&reader);
self.setContentRefToOffset(bytes, bytesRead);
try self.finishBuilding(true);
}
pub fn loadFromFile(self: *@This(), filePath: []const u8) !void {
try p2.assert(self.finished != true);
// open file for reading
const file = try std.fs.cwd().openFile(filePath, .{});
defer file.close();
const reader = file.reader();
_ = try self.loadHeadersFromReader(reader);
const contentBytes = try self.loadContentFromReader(reader);
try self.finishBuilding(false);
std.debug.print("total content bytes {d}\n", .{contentBytes});
}
pub fn writeToFile(self: @This(), filePath: []const u8) !void {
// create file
const file = try p2.createFileWithPath(filePath);
defer file.close();
const writer = file.writer();
// write out pack magic and number of header elements to indicate start of file.
try writer.writeAll(&@as([4]u8, @bitCast(PackerMagic)));
try writer.writeAll(&@as([4]u8, @bitCast(
@as(
u32,
@intCast(self.headerEntries.items.len),
),
)));
var headerSize: usize = 0;
for (self.headerEntries.items) |entry| {
headerSize += try entry.writeHeader(writer, @intCast(self.headerEntries.items.len));
}
std.debug.print("total header size = {d}\n", .{headerSize});
// write out contents buffer,
try writer.writeAll(self.contents.items);
}
pub fn addHeader(self: *@This(), fileEntry: PackedFileEntry) !void {
try self.headerEntries.append(self.allocator, fileEntry);
}
pub fn updateHeaderOffset(self: @This(), offset: u64) void {
const p = @as(*u64, @ptrCast(@alignCast(self.contents.items.ptr)));
p.* = offset;
}
pub fn debugPrintAllFiles(self: @This()) void {
for (self.headerEntries.items) |entry| {
std.debug.print("> {s}({s}, size = {d} bytes)\n", .{
entry.getFileName(),
entry.getTypeName(),
entry.fileLen,
});
}
std.debug.print("totalContentSize = {d}\n", .{
self.contents.items.len - 8,
});
}
// only used for debugging or while generating a PackerFileSystem.
//
// using a PackerFileSystem is the better method of getting file contents
pub fn getFileByName(self: *@This(), fileName: []const u8) ?PackerBytesRef {
if (!self.finished) {
return null;
}
if (self.entriesByString.get(fileName)) |entryOffset| {
const header = &self.headerEntries.items[entryOffset];
var bytes: []const u8 = undefined;
bytes.ptr = @ptrCast(&self.contentBuffer[header.fileOffset]);
bytes.len = header.fileLen;
return .{
.typeTag = header.getTypeName(),
.raw_bytes = bytes,
};
}
return null;
}
pub fn deinit(self: *@This()) void {
self.contents.deinit(self.allocator);
self.headerEntries.deinit(self.allocator);
if (self.finished) {
self.entriesByString.deinit(self.allocator);
}
self.allocator.destroy(self.contents);
}
};
fn loadHeadersFromReader(self: *@This(), reader: anytype) !usize {
var bytesRead: usize = 0;
// read and verify magic
var magic: [4]u8 align(4) = undefined;
try p2.assert(try reader.read(&magic) == 4);
try p2.assert(p2.arrayTo_u32(magic) == PackerMagic);
std.debug.print("magic = {s}\n", .{magic});
bytesRead += 4;
// read header entries count
var entriesCount_read: [4]u8 align(4) = undefined;
try p2.assert(try reader.read(&entriesCount_read) == 4);
const entriesCount = p2.arrayTo_u32(entriesCount_read);
std.debug.print("entriesCount = {d}\n", .{entriesCount});
bytesRead += 4;
for (0..entriesCount) |_| {
const newEntry = try self.headerEntries.addOne(self.allocator);
try newEntry.loadFromReader(reader);
bytesRead += PackedFileEntry.calculateHeaderLen();
}
return bytesRead;
}