╭─────────────╮ │scroll to top│ ╰─────────────╯ ╭ Discussion.zig ───────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭ find file ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╮ ╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╮ │ │ │http│ ssh │ │ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╯ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╯
9 description: []const u8, 10 labels: []const u8, // space-separated 11 12 pub const Record = struct { 13 event: Self, 14 removed: bool = false, 15 author_email: ?[]const u8 = null, 16 created_timestamp: u64 = 0, 17 created_order: u64 = 0, 18 updated_order: u64 = 0, 19 }; 20 21 const Self = @This(); 22 23 pub const label_max_len = 64; 24 25 pub const merge_policy: evt.MergePolicy = .target_wins; 26 pub const record_map_key = "event-id->discussion"; 27 pub const all_id_set_key = "discussion-id-set"; 28 pub const active_id_set_key = "active-discussion-id-set"; 29 pub const label_to_id_set_key = "label->discussion-id-set"; 30 const activity_order_key = "discussion-id->activity-order"; 31 32 pub fn labelIterator(labels: []const u8) std.mem.SplitIterator(u8, .scalar) { 33 return std.mem.splitScalar(u8, labels, ' '); 34 } 35 36 pub fn fieldsValid(title: []const u8, labels: []const u8) bool { 37 if (!evt.titleValid(title)) return false; 38 var label_iter = labelIterator(labels); 39 while (label_iter.next()) |label| { 40 if (label.len > label_max_len) return false; 41 } 42 return true; 43 } 44 45 pub fn consume( 46 comptime DB: type, 47 comptime hash_kind: hash.HashKind, 48 haxy_moment: DB.HashMap(.read_write), 49 event_id: *const [evt.event_id_size]u8, 50 record_maybe: ?Record, 51 arena: *std.heap.ArenaAllocator, 52 _: ?[]const u8, 53 ) !void { 54 const discussion_key = hash.hashInt(hash_kind, event_id); 55 const records = try DB.HashMap(.read_write).init(try haxy_moment.putCursor(hash.hashInt(hash_kind, record_map_key))); 56 const active = try DB.SortedSet(.read_write).init(try haxy_moment.putCursor(hash.hashInt(hash_kind, active_id_set_key))); 57 const labels = try DB.SortedMap(.read_write).init(try haxy_moment.putCursor(hash.hashInt(hash_kind, label_to_id_set_key))); 58 var existing_record_maybe: ?Record = null; 59 const existing_cursor_maybe = try records.getCursor(discussion_key); 60 if (existing_cursor_maybe) |existing_cursor| { 61 existing_record_maybe = try evt.read(Record, DB, hash_kind, arena, try DB.HashMap(.read_only).init(existing_cursor)); 62 } 63 64 var record_to_write = record_maybe orelse try evt.removedRecord(Record, DB, hash_kind, haxy_moment.readOnly(), existing_record_maybe); 65 66 if (existing_record_maybe) |existing| { 67 record_to_write.created_order = existing.created_order; 68 record_to_write.author_email = existing.author_email; 69 record_to_write.created_timestamp = existing.created_timestamp; 70 } 71 72 const activity_order = if (existing_record_maybe != null) 73 try activityOrder(DB, hash_kind, haxy_moment.readOnly(), event_id) 74 else 75 record_to_write.created_order; 76 if (existing_record_maybe) |existing| { 77 if (!existing.removed) { 78 const old_key = evt.orderKeyDesc(activity_order, event_id); 79 _ = try active.remove(&old_key); 80 try removeFromLabelSets(DB, labels, existing.event.labels, &old_key); 81 } 82 } 83 84 const record = try DB.HashMap(.read_write).init(try records.putCursor(discussion_key)); 85 try evt.upsert(Record, DB, hash_kind, record, record_to_write); 86 try evt.indexEvent(DB, hash_kind, haxy_moment, event_id, .discuss, existing_record_maybe, record_to_write); 87 88 const order_key = evt.orderKeyDesc(activity_order, event_id); 89 if (existing_cursor_maybe == null) { 90 const activity = try DB.HashMap(.read_write).init(try haxy_moment.putCursor(hash.hashInt(hash_kind, activity_order_key))); 91 try activity.put(discussion_key, .{ .uint = activity_order }); 92 const ids = try DB.SortedSet(.read_write).init(try haxy_moment.putCursor(hash.hashInt(hash_kind, all_id_set_key))); 93 try ids.put(&evt.orderKeyDesc(record_to_write.created_order, event_id)); 94 } 95 96 // the search index is keyed by creation, not activity: ordering is the 97 // list's business, so a comment moving the discussion up leaves it alone 98 const search_key = evt.orderKeyDesc(record_to_write.created_order, event_id); 99 try srch_thrd.update(DB, hash_kind, .discuss, haxy_moment, arena.child_allocator, &search_key, existing_record_maybe, record_to_write); 100 101 if (!record_to_write.removed) { 102 try active.put(&order_key); 103 try addToLabelSets(DB, labels, record_to_write.event.labels, &order_key); 104 } 105 } 106 107 pub fn touch( 108 comptime DB: type, 109 comptime hash_kind: hash.HashKind, 110 haxy_moment: DB.HashMap(.read_write), 111 event_id: *const [evt.event_id_size]u8, 112 order: u64, 113 arena: *std.heap.ArenaAllocator, 114 ) !void { 115 const discussion_key = hash.hashInt(hash_kind, event_id); 116 const records_cursor = try haxy_moment.getCursor(hash.hashInt(hash_kind, record_map_key)) orelse return; 117 const records = try DB.HashMap(.read_only).init(records_cursor); 118 const record_cursor = try records.getCursor(discussion_key) orelse return; 119 const record = try evt.read(Record, DB, hash_kind, arena, try DB.HashMap(.read_only).init(record_cursor)); 120 const current = try activityOrder(DB, hash_kind, haxy_moment.readOnly(), event_id); 121 if (order <= current) return; 122 123 const active = try DB.SortedSet(.read_write).init(try haxy_moment.putCursor(hash.hashInt(hash_kind, active_id_set_key))); 124 const labels = try DB.SortedMap(.read_write).init(try haxy_moment.putCursor(hash.hashInt(hash_kind, label_to_id_set_key))); 125 if (!record.removed) { 126 const old_key = evt.orderKeyDesc(current, event_id); 127 _ = try active.remove(&old_key); 128 try removeFromLabelSets(DB, labels, record.event.labels, &old_key); 129 } 130 131 const activity = try DB.HashMap(.read_write).init(try haxy_moment.putCursor(hash.hashInt(hash_kind, activity_order_key))); 132 try activity.put(discussion_key, .{ .uint = order }); 133 if (!record.removed) { 134 const new_key = evt.orderKeyDesc(order, event_id); 135 try active.put(&new_key); 136 try addToLabelSets(DB, labels, record.event.labels, &new_key); 137 } 138 } 139 140 pub fn activityOrder( 141 comptime DB: type, 142 comptime hash_kind: hash.HashKind, 143 haxy_moment: DB.HashMap(.read_only), 144 event_id: *const [evt.event_id_size]u8, 145 ) !u64 { 146 const map_cursor = try haxy_moment.getCursor(hash.hashInt(hash_kind, activity_order_key)) orelse return error.CursorNotFound; 147 const map = try DB.HashMap(.read_only).init(map_cursor); 148 const order_cursor = try map.getCursor(hash.hashInt(hash_kind, event_id)) orelse return error.CursorNotFound; 149 return try order_cursor.readUint(); 150 } 151 152 pub fn update( 153 host: evt.Host, 154 comptime repo_kind: rp.RepoKind, 155 comptime repo_opts: rp.RepoOpts(repo_kind), 156 io: std.Io, 157 allocator: std.mem.Allocator, 158 repo: *rp.Repo(repo_kind, repo_opts), 159 id: *const [evt.event_id_size]u8, 160 title: []const u8, 161 labels: []const u8, 162 description: []const u8, 163 author: evt.CommitAuthor, 164 ) !void { 165 if (!fieldsValid(title, labels)) return error.InvalidFields; 166 167 var arena = std.heap.ArenaAllocator.init(allocator); 168 defer arena.deinit(); 169 const discussion = (try readById(repo_kind, repo_opts, io, allocator, &arena, repo, id)) orelse return error.NotFound; 170 if (discussion.removed) return error.NotFound; 171 172 try evt.consume(host, .repo, repo_kind, repo_opts, io, allocator, repo, evt.events_ref, &.{.{ 173 .id = std.fmt.bytesToHex(id.*, .lower), 174 .timestamp = @intCast(std.Io.Timestamp.now(io, .real).toSeconds()), 175 .author = author, 176 .event = .{ .discuss = .{ 177 .title = title, 178 .labels = labels, 179 .description = description, 180 } }, 181 }}); 182 } 183 184 fn removeFromLabelSets(comptime DB: type, labels: DB.SortedMap(.read_write), names: []const u8, order_key: []const u8) !void { 185 var label_iter = labelIterator(names); 186 while (label_iter.next()) |label| { 187 if (label.len == 0 or label.len > label_max_len) continue; 188 const cursor = try labels.putCursor(label); 189 const set = try DB.SortedSet(.read_write).init(cursor); 190 _ = try set.remove(order_key); 191 if (0 == try set.count()) _ = try labels.remove(label); 192 } 193 } 194 195 fn addToLabelSets(comptime DB: type, labels: DB.SortedMap(.read_write), names: []const u8, order_key: []const u8) !void { 196 var label_iter = labelIterator(names); 197 while (label_iter.next()) |label| { 198 if (label.len == 0 or label.len > label_max_len) continue; 199 const set = try DB.SortedSet(.read_write).init(try labels.putCursor(label)); 200 try set.put(order_key); 201 } 202 } 203 204 pub fn readById( 205 comptime repo_kind: rp.RepoKind, 206 comptime repo_opts: rp.RepoOpts(repo_kind), 207 io: std.Io, 208 allocator: std.mem.Allocator, 209 arena: *std.heap.ArenaAllocator, 210 repo: *rp.Repo(repo_kind, repo_opts), 211 id: *const [evt.event_id_size]u8, 212 ) !?Record { 213 const DB = evt.EventDB(repo_opts.hash); 214 var event_db_maybe: ?evt.LocalEventDB(repo_opts.hash) = if (repo_kind == .git) try evt.LocalEventDB(repo_opts.hash).openReadOnly(io, allocator, repo.core.repo_dir) else null; 215 defer if (event_db_maybe) |*event_db| event_db.deinit(io, allocator); 216 const moment = (if (event_db_maybe) |*event_db| 217 evt.currentMomentFromDb(repo_opts.hash, event_db.db) 218 else if (repo_kind == .git) 219 return null 220 else 221 evt.currentMoment(repo_opts, repo)) catch return null; 222 const records_cursor = try moment.getCursor(hash.hashInt(repo_opts.hash, record_map_key)) orelse return null; 223 const records = try DB.HashMap(.read_only).init(records_cursor); 224 const record_cursor = try records.getCursor(hash.hashInt(repo_opts.hash, id)) orelse return null; 225 return try evt.read(Record, DB, repo_opts.hash, arena, try DB.HashMap(.read_only).init(record_cursor)); 226 } 227