const std = @import("std"); const leveldb = @import("leveldb"); const system = @import("system.zig"); const String = []const u8; const CORE_VERSION = "12.331"; const SYSTEM_NAME = system.SYSTEM_NAME; const SYSTEM_VERSION = system.SYSTEM_VERSION; // Entry must be a tagged union with one tag being // Folder: struct { // name: String, // entries: []const Entry, // }, // and the other tags being the respective Foundry type name pub fn Compendium(base_type: BaseType, Entry: type) type { if (std.meta.activeTag(@typeInfo(Entry)) != .@"union") @compileError("Entry must be a tagged union."); if (@typeInfo(Entry).@"union".tag_type == null) @compileError("Entry must be a tagged union."); return struct { entries: []const Entry = &.{}, pub fn serialize(self: @This(), path: [:0]const u8) !void { try leveldb.destroy(.{ .path = path }); var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer arena.deinit(); var diagnostic: leveldb.Diagnostic = null; const db = leveldb.open(.{ .path = path, .diagnostic = &diagnostic, .options = .{ .create_if_missing = true, .compression = .Snappy } }) catch |err| { std.log.err("leveldb.open failed: {s}", .{ diagnostic.? }); return err; }; defer db.close(); for (self.entries) |entry| { defer _ = arena.reset(.retain_capacity); try serialize_entry(arena.allocator(), db, entry, null); } } fn serialize_entry(allocator: std.mem.Allocator, db: leveldb, entry: Entry, folder: ?String) !void{ switch (entry) { .Folder => |_folder| { const foundry_folder: Folder(base_type) = .{ .name = _folder.name, ._id = &random_id(), .folder = folder, }; const key = try std.fmt.allocPrintZ(allocator, "!folders!{s}", .{ foundry_folder._id.? }); const value = try std.json.stringifyAlloc(allocator, foundry_folder, .{}); try db.put(.{ .key = key, .value = value }); for (_folder.entries) |folder_entry| { try serialize_entry(allocator, db, folder_entry, foundry_folder._id); } }, inline else => |item| { std.debug.assert(item.folder == null); var foundry_item = item; foundry_item.folder = folder; if (foundry_item._id == null) foundry_item._id = &random_id(); const key = try std.fmt.allocPrintZ(allocator, "!{s}!{s}", .{ base_type.to_compendium_type(), foundry_item._id.? }); const value = try std.json.stringifyAlloc(allocator, foundry_item, .{}); try db.put(.{ .key = key, .value = value }); } } } }; } const BaseType = enum { Item, Actor, fn to_compendium_type(self: @This()) String { return switch (self) { .Item => "items", .Actor => "actors", }; } }; fn Folder(base_type: BaseType) type { return struct { _id: ?String = null, name: String, @"type": BaseType = base_type, description: String = "", folder: ?String = null, sorting: enum { a, m } = .a, sort: u64 = 0, color: ?String = null, flags: struct {} = .{}, _stats: DocumentStats = .{}, }; } pub fn Item(comptime typename: String, comptime T: type) type { return struct { const Type: BaseType = .Item; _id: ?String = null, name: String, @"type": String = typename, img: String = "icons/svg/item-bag.svg", system: T, effects: []u0 = &.{}, folder: ?String = null, sort: u64 = 0, ownership: struct { default: u8 = 0, } = .{}, flags: struct {} = .{}, _stats: DocumentStats = .{}, }; } pub const DocumentStats = struct { coreVersion: String = CORE_VERSION, systemId: String = SYSTEM_NAME, systemVersion: String = SYSTEM_VERSION, createdTime: ?u64 = null, modifiedTime: ?u64 = null, lastModifiedBy: ?String = null, compendiumSource: ?String = null, duplicateSource: ?String = null, }; inline fn random_char(alphabet: []const u8) u8 { return alphabet[std.crypto.random.uintLessThan(u8, alphabet.len)]; } var id_set = std.BufSet.init(std.heap.c_allocator); fn random_id() [16]u8 { const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; var result: [16]u8 = undefined; inline for (&result) |*c| c.* = random_char(alphabet); if (id_set.contains(&result)) { return random_id(); } id_set.insert(&result) catch unreachable; return result; }