1234567891011121314module exchange1516import json217import src.events18import src.history19import src.snapshot20import src.storage21import strconv222324pub fn format_key(repo string) string {25 return '${repo}/format'26}2728pub fn object_key(repo string, hash string) string {29 return '${repo}/objects/${hash}'30}3132pub fn save_key(repo string, id string) string {33 return '${repo}/saves/${id}'34}3536pub fn event_key(repo string, id string) string {37 return '${repo}/events/${id}'38}3940pub fn head_key(repo string) string {41 return '${repo}/head'42}4344pub fn bundle_key(repo string) string {45 return '${repo}/bundle'46}4748pub fn events_prefix(repo string) string {49 return '${repo}/events'50}5152pub struct FileRecord {53pub:54 path string55 hash string56 executable bool57}5859pub struct SaveRecord {60pub:61 id string62 parent string63 message string64 created_at i6465 machine string66 is_checkpoint bool67 size_bytes i6468 agent string69 session string70 restores string71 replayed_from string72pub mut:73 files []FileRecord74}75767778pub fn save_record(store &storage.Store, save history.Save) !SaveRecord {79 files := history.files_of(store, save.id)!80 mut records := []FileRecord{}81 for file in files {82 records << FileRecord{83 path: file.path84 hash: file.hash85 executable: file.executable86 }87 }88 return SaveRecord{89 id: save.id90 parent: save.parent91 message: save.message92 created_at: save.created_at93 machine: save.machine94 is_checkpoint: save.is_checkpoint95 size_bytes: save.size_bytes96 agent: save.agent97 session: save.session98 restores: save.restores99 replayed_from: save.replayed_from100 files: records101 }102}103104pub fn encode_save(record SaveRecord) string {105 return json2.encode(record)106}107108109110pub fn decode_save(body []u8) !SaveRecord {111 return json2.decode[SaveRecord](lone_surrogates_as_bytes(body).bytestr())!112}113114115116fn ignored_strip(event events.Event) bool {117 return event.kind == events.kind_strip && !snapshot.legal_path(event.path)118}119120121122123fn lone_surrogates_as_bytes(body []u8) []u8 {124 mut out := []u8{cap: body.len}125 mut i := 0126 for i < body.len {127 if body[i] != `\\` || i + 1 >= body.len {128 out << body[i]129 i++130 continue131 }132 unit := escaped_surrogate(body, i)133 if unit >= 0xd800 && unit <= 0xdbff && escaped_surrogate(body, i + 6) >= 0xdc00 {134 out << body[i..i + 12]135 i += 12136 } else if unit >= 0 {137 out << [u8(0xe0 | (unit >> 12)), u8(0x80 | ((unit >> 6) & 0x3f)), u8(0x80 | (unit & 0x3f))]138 i += 6139 } else {140 141 out << body[i..i + 2]142 i += 2143 }144 }145 return out146}147148149fn escaped_surrogate(body []u8, at int) int {150 if at + 6 > body.len || body[at] != `\\` || body[at + 1] != `u` {151 return -1152 }153 unit := strconv.parse_int(body[at + 2..at + 6].bytestr(), 16, 32) or { return -1 }154 return if unit >= 0xd800 && unit <= 0xdfff { int(unit) } else { -1 }155}156157158pub fn drop_unsafe_paths(mut record SaveRecord) []string {159 mut kept := []FileRecord{}160 mut dropped := []string{}161 for file in record.files {162 if snapshot.legal_path(file.path) {163 kept << file164 } else {165 dropped << snapshot.escape_path(file.path)166 }167 }168 record.files = kept169 dropped.sort()170 return dropped171}172173pub fn to_save(record SaveRecord) history.Save {174 return history.Save{175 id: record.id176 parent: record.parent177 message: record.message178 created_at: record.created_at179 machine: record.machine180 is_checkpoint: record.is_checkpoint181 size_bytes: record.size_bytes182 agent: record.agent183 session: record.session184 restores: record.restores185 replayed_from: record.replayed_from186 }187}188189pub fn to_files(record SaveRecord) []snapshot.FileEntry {190 mut files := []snapshot.FileEntry{}191 for file in record.files {192 files << snapshot.FileEntry{193 path: file.path194 hash: file.hash195 executable: file.executable196 }197 }198 return files199}200201pub struct EventRecord {202pub:203 id string204 kind string205 save_id string206 path string207 reason string208 created_at i64209 machine string210}211212pub fn event_record(event events.Event) EventRecord {213 return EventRecord{214 id: event.id215 kind: event.kind216 save_id: event.save_id217 path: event.path218 reason: event.reason219 created_at: event.created_at220 machine: event.machine221 }222}223224pub fn encode_event(event events.Event) string {225 return json2.encode(event_record(event))226}227228229pub fn decode_event(body []u8) !events.Event {230 record := json2.decode[EventRecord](lone_surrogates_as_bytes(body).bytestr())!231 return events.Event{232 id: record.id233 kind: record.kind234 save_id: record.save_id235 path: record.path236 reason: record.reason237 created_at: record.created_at238 machine: record.machine239 }240}