╭─────────────╮ │scroll to top│ ╰─────────────╯ ╭ Attachment.zig ───────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭ find file ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╮ ╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╮ │ │ │http│ ssh │ │ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╯ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╯
36 pub fn nameValid(name: []const u8) bool { 37 if (name.len == 0 or name.len > name_max_len) return false; 38 if (std.mem.eql(u8, name, ".") or std.mem.eql(u8, name, "..")) return false; 39 for (name) |ch| { 40 if (ch == '/' or ch == '\\' or std.ascii.isControl(ch)) return false; 41 } 42 return true; 43 } 44 45 fn targetExists( 46 comptime DB: type, 47 comptime hash_kind: hash.HashKind, 48 haxy_moment: anytype, 49 target_id: *const [evt.event_id_size]u8, 50 ) !bool { 51 const kinds_cursor = try haxy_moment.getCursor(hash.hashInt(hash_kind, evt.event_index_key)) orelse return false; 52 const kinds = try DB.HashMap(.read_only).init(kinds_cursor); 53 return try kinds.getCursor(hash.hashInt(hash_kind, target_id)) != null; 54 } 55 56 pub fn consume( 57 comptime DB: type, 58 comptime hash_kind: hash.HashKind, 59 haxy_moment: DB.HashMap(.read_write), 60 event_id: *const [evt.event_id_size]u8, 61 record_maybe: ?Record, 62 arena: *std.heap.ArenaAllocator, 63 _: ?[]const u8, 64 ) !void { 65 const attachment_key = hash.hashInt(hash_kind, event_id); 66 67 const event_id_to_attachment_cursor = try haxy_moment.putCursor(hash.hashInt(hash_kind, record_map_key)); 68 const event_id_to_attachment = try DB.HashMap(.read_write).init(event_id_to_attachment_cursor); 69 70 const target_id_to_attachment_id_set_cursor = try haxy_moment.putCursor(hash.hashInt(hash_kind, target_id_to_attachment_id_set_key)); 71 const target_id_to_attachment_id_set = try DB.HashMap(.read_write).init(target_id_to_attachment_id_set_cursor); 72 73 var existing_record_maybe: ?Record = null; 74 const existing_cursor_maybe = try event_id_to_attachment.getCursor(attachment_key); 75 if (existing_cursor_maybe) |existing_cursor| { 76 const existing_attachment = try DB.HashMap(.read_only).init(existing_cursor); 77 existing_record_maybe = try evt.read(Record, DB, hash_kind, arena, existing_attachment); 78 } 79 80 var record_to_write = record_maybe orelse try evt.removedRecord(Record, DB, hash_kind, haxy_moment.readOnly(), existing_record_maybe); 81 82 const target_id = try evt.parseEventId(&record_to_write.event.target_id); 83 if (!try targetExists(DB, hash_kind, haxy_moment, &target_id)) return error.TargetNotFound; 84 if (!nameValid(record_to_write.name)) return error.InvalidAttachment; 85 86 if (existing_record_maybe) |existing_record| { 87 // the file never changes, so an update preserves everything the original 88 // commit determined 89 record_to_write.created_order = existing_record.created_order; 90 record_to_write.author_email = existing_record.author_email; 91 record_to_write.name = existing_record.name; 92 record_to_write.blob_oid = existing_record.blob_oid; 93 94 // an attachment cannot move to another target 95 if (!std.mem.eql(u8, &existing_record.event.target_id, &record_to_write.event.target_id)) return error.TargetChanged; 96 } 97 98 const attachment_cursor = try event_id_to_attachment.putCursor(attachment_key); 99 const attachment = try DB.HashMap(.read_write).init(attachment_cursor); 100 try evt.upsert(Record, DB, hash_kind, attachment, record_to_write); 101 try evt.indexEvent(DB, hash_kind, haxy_moment, event_id, .attach, existing_record_maybe, record_to_write); 102 103 if (existing_cursor_maybe == null) { 104 const attachment_id_set_cursor = try haxy_moment.putCursor(hash.hashInt(hash_kind, all_id_set_key)); 105 const attachment_id_set = try DB.SortedSet(.read_write).init(attachment_id_set_cursor); 106 try attachment_id_set.put(&evt.orderKeyDesc(record_to_write.created_order, event_id)); 107 } 108 109 // removed attachments stay in the target's set; the views skip them 110 const target_attachments_cursor = try target_id_to_attachment_id_set.putCursor(hash.hashInt(hash_kind, &target_id)); 111 const target_attachments = try DB.SortedSet(.read_write).init(target_attachments_cursor); 112 try target_attachments.put(&evt.orderKey(record_to_write.created_order, event_id)); 113 } 114 115 // attach `blob` to a target event and return the event id. `repo` must be 116 // writable. 117 pub fn create( 118 host: evt.Host, 119 comptime repo_kind: rp.RepoKind, 120 comptime repo_opts: rp.RepoOpts(repo_kind), 121 io: std.Io, 122 allocator: std.mem.Allocator, 123 repo: *rp.Repo(repo_kind, repo_opts), 124 target_kind: evt.EventKind, 125 target_id: *const [evt.event_id_size * 2]u8, 126 blob: evt.Blob, 127 author: evt.CommitAuthor, 128 ) ![evt.event_id_size * 2]u8 { 129 if (!nameValid(blob.name)) return error.InvalidFields; 130 if (blob.size == 0 or blob.size > max_size) return error.InvalidFields; 131 132 const target_id_bytes = evt.parseEventId(target_id) catch return error.InvalidFields; 133 if (!try evt.threadHolds(repo_kind, repo_opts, io, allocator, repo, target_kind, &target_id_bytes, null)) return error.TargetNotFound; 134 135 var id_bytes: [evt.event_id_size]u8 = undefined; 136 io.random(&id_bytes); 137 const event_id = std.fmt.bytesToHex(id_bytes, .lower); 138 try evt.consume(host, .repo, repo_kind, repo_opts, io, allocator, repo, evt.events_ref, &.{.{ 139 .id = event_id, 140 .timestamp = @intCast(std.Io.Timestamp.now(io, .real).toSeconds()), 141 .author = author, 142 .tree_entries = &.{.{ .blob = blob }}, 143 .event = .{ .attach = .{ .target_id = target_id.* } }, 144 }}); 145 return event_id; 146 } 147 148 // read an attachment by event id, or null if the id isn't a known attachment. 149 // field byte slices are allocated in `arena`. 150 pub fn readById( 151 comptime DB: type, 152 comptime hash_kind: hash.HashKind, 153 haxy_moment: DB.HashMap(.read_only), 154 arena: *std.heap.ArenaAllocator, 155 attachment_id: []const u8, 156 ) !?Record { 157 const records_cursor = try haxy_moment.getCursor(hash.hashInt(hash_kind, record_map_key)) orelse return null; 158 const records = try DB.HashMap(.read_only).init(records_cursor); 159 const record_cursor = try records.getCursor(hash.hashInt(hash_kind, attachment_id)) orelse return null; 160 const record_map = try DB.HashMap(.read_only).init(record_cursor); 161 return try evt.read(Record, DB, hash_kind, arena, record_map); 162 } 163