╭ Patch.zig ────────────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╮ │http│ ssh │ │ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╯
1 const std = @import("std"); 2 const evt = @import("../event.zig"); 3 const xit = @import("xit"); 4 const hash = xit.hash; 5 6 title: []const u8, 7 description: []const u8, 8 tags: []const u8, // space-separated 9 target_patch_id: ?[evt.event_id_size * 2]u8 = null, 10 patchrev_id: ?[evt.event_id_size * 2]u8 = null, 11 12 pub const Record = struct { 13 event: Self, 14 removed: bool = false, 15 author_email: ?[]const u8 = null, 16 created_order: u64 = 0, 17 }; 18 19 const Self = @This(); 20 21 pub const tag_max_len = 64; 22 pub const merge_policy: evt.MergePolicy = .field_conflicts; 23 pub const record_map_key = "event-id->patch"; 24 pub const id_set_key = "patch-id-set"; 25 pub const conflicts_key = "conflicted-patch-id->conflict"; 26 pub const id_to_field_to_oid_key = "patch-id->field->oid"; 27 pub const target_patch_id_to_patch_id_set_key = "target-patch-id->patch-id-set"; 28 29 pub fn fieldsValid(title: []const u8, tags: []const u8) bool { 30 if (title.len == 0) return false; 31 var tag_iter = std.mem.splitScalar(u8, tags, ' '); 32 while (tag_iter.next()) |tag| { 33 if (tag.len > tag_max_len) return false; 34 } 35 return true; 36 } 37 38 pub fn consume( 39 comptime DB: type, 40 comptime hash_kind: hash.HashKind, 41 haxy_moment: DB.HashMap(.read_write), 42 event_id: *const [evt.event_id_size]u8, 43 record_maybe: ?Record, 44 arena: *std.heap.ArenaAllocator, 45 event_oid: ?[]const u8, 46 ) !void { 47 const record_key = hash.hashInt(hash_kind, event_id); 48 const records = try DB.HashMap(.read_write).init(try haxy_moment.putCursor(hash.hashInt(hash_kind, record_map_key))); 49 const conflicts = try DB.SortedMap(.read_write).init(try haxy_moment.putCursor(hash.hashInt(hash_kind, conflicts_key))); 50 const field_oid_map = try DB.HashMap(.read_write).init(try haxy_moment.putCursor(hash.hashInt(hash_kind, id_to_field_to_oid_key))); 51 52 var existing_maybe: ?Record = null; 53 const existing_cursor_maybe = try records.getCursor(record_key); 54 if (existing_cursor_maybe) |cursor| { 55 existing_maybe = try evt.read(Record, DB, hash_kind, arena, try DB.HashMap(.read_only).init(cursor)); 56 } 57 58 var record = if (record_maybe) |value| 59 value 60 else blk: { 61 var value = existing_maybe orelse return error.EventNotFound; 62 value.removed = true; 63 break :blk value; 64 }; 65 66 if (!fieldsValid(record.event.title, record.event.tags)) return error.InvalidPatch; 67 if (record.event.patchrev_id) |*id| _ = try evt.parseEventId(id); 68 const target_patch_id = if (record.event.target_patch_id) |*id| try evt.parseEventId(id) else null; 69 if (target_patch_id) |*id| { 70 if (std.mem.eql(u8, id, event_id)) return error.InvalidPatch; 71 } 72 73 if (existing_maybe) |existing| { 74 record.created_order = existing.created_order; 75 record.author_email = existing.author_email; 76 if (event_oid != null) { 77 if (!std.meta.eql(existing.event.target_patch_id, record.event.target_patch_id)) { 78 return error.TargetPatchChanged; 79 } 80 } 81 if (event_oid != null or record.removed) { 82 _ = try conflicts.remove(&evt.orderKeyDesc(existing.created_order, event_id)); 83 } 84 } 85 86 try evt.writeOid(Self, DB, hash_kind, field_oid_map, record_key, if (existing_maybe) |existing| existing.event else null, record.event, event_oid); 87 88 const cursor = try records.putCursor(record_key); 89 try evt.upsert(Record, DB, hash_kind, try DB.HashMap(.read_write).init(cursor), record); 90 try evt.indexEvent(DB, hash_kind, haxy_moment, event_id, .patch, record.created_order, record.removed); 91 92 if (existing_cursor_maybe == null) { 93 const ids = try DB.SortedSet(.read_write).init(try haxy_moment.putCursor(hash.hashInt(hash_kind, id_set_key))); 94 try ids.put(&evt.orderKeyDesc(record.created_order, event_id)); 95 if (target_patch_id) |*target_id| { 96 const targets = try DB.HashMap(.read_write).init(try haxy_moment.putCursor(hash.hashInt(hash_kind, target_patch_id_to_patch_id_set_key))); 97 const children = try DB.SortedSet(.read_write).init(try targets.putCursor(hash.hashInt(hash_kind, target_id))); 98 try children.put(&evt.orderKeyDesc(record.created_order, event_id)); 99 } 100 } 101 } 102 103 pub fn readById( 104 comptime DB: type, 105 comptime hash_kind: hash.HashKind, 106 haxy_moment: DB.HashMap(.read_only), 107 arena: *std.heap.ArenaAllocator, 108 id: *const [evt.event_id_size]u8, 109 ) !?Record { 110 const records_cursor = try haxy_moment.getCursor(hash.hashInt(hash_kind, record_map_key)) orelse return null; 111 const records = try DB.HashMap(.read_only).init(records_cursor); 112 const record_cursor = try records.getCursor(hash.hashInt(hash_kind, id)) orelse return null; 113 return try evt.read(Record, DB, hash_kind, arena, try DB.HashMap(.read_only).init(record_cursor)); 114 } 115