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 events_prefix(repo string) string {45 return '${repo}/events'46}4748pub struct FileRecord {49pub:50 path string51 hash string52 executable bool53}5455pub struct SaveRecord {56pub:57 id string58 parent string59 message string60 created_at i6461 machine string62 is_checkpoint bool63 size_bytes i6464 agent string65 session string66 restores string67 replayed_from string68pub mut:69 files []FileRecord70}71727374pub fn save_record(store &storage.Store, save history.Save) !SaveRecord {75 files := history.files_of(store, save.id)!76 mut records := []FileRecord{}77 for file in files {78 records << FileRecord{79 path: file.path80 hash: file.hash81 executable: file.executable82 }83 }84 return SaveRecord{85 id: save.id86 parent: save.parent87 message: save.message88 created_at: save.created_at89 machine: save.machine90 is_checkpoint: save.is_checkpoint91 size_bytes: save.size_bytes92 agent: save.agent93 session: save.session94 restores: save.restores95 replayed_from: save.replayed_from96 files: records97 }98}99100pub fn encode_save(record SaveRecord) string {101 return json2.encode(record)102}103104105106pub fn decode_save(body []u8) !SaveRecord {107 return json2.decode[SaveRecord](lone_surrogates_as_bytes(body).bytestr())!108}109110111112fn ignored_strip(event events.Event) bool {113 return event.kind == events.kind_strip && !snapshot.legal_path(event.path)114}115116117118119fn lone_surrogates_as_bytes(body []u8) []u8 {120 mut out := []u8{cap: body.len}121 mut i := 0122 for i < body.len {123 if body[i] != `\\` || i + 1 >= body.len {124 out << body[i]125 i++126 continue127 }128 unit := escaped_surrogate(body, i)129 if unit >= 0xd800 && unit <= 0xdbff && escaped_surrogate(body, i + 6) >= 0xdc00 {130 out << body[i..i + 12]131 i += 12132 } else if unit >= 0 {133 out << [u8(0xe0 | (unit >> 12)), u8(0x80 | ((unit >> 6) & 0x3f)), u8(0x80 | (unit & 0x3f))]134 i += 6135 } else {136 137 out << body[i..i + 2]138 i += 2139 }140 }141 return out142}143144145fn escaped_surrogate(body []u8, at int) int {146 if at + 6 > body.len || body[at] != `\\` || body[at + 1] != `u` {147 return -1148 }149 unit := strconv.parse_int(body[at + 2..at + 6].bytestr(), 16, 32) or { return -1 }150 return if unit >= 0xd800 && unit <= 0xdfff { int(unit) } else { -1 }151}152153154pub fn drop_unsafe_paths(mut record SaveRecord) []string {155 mut kept := []FileRecord{}156 mut dropped := []string{}157 for file in record.files {158 if snapshot.legal_path(file.path) {159 kept << file160 } else {161 dropped << snapshot.escape_path(file.path)162 }163 }164 record.files = kept165 dropped.sort()166 return dropped167}168169pub fn to_save(record SaveRecord) history.Save {170 return history.Save{171 id: record.id172 parent: record.parent173 message: record.message174 created_at: record.created_at175 machine: record.machine176 is_checkpoint: record.is_checkpoint177 size_bytes: record.size_bytes178 agent: record.agent179 session: record.session180 restores: record.restores181 replayed_from: record.replayed_from182 }183}184185pub fn to_files(record SaveRecord) []snapshot.FileEntry {186 mut files := []snapshot.FileEntry{}187 for file in record.files {188 files << snapshot.FileEntry{189 path: file.path190 hash: file.hash191 executable: file.executable192 }193 }194 return files195}196197pub struct EventRecord {198pub:199 id string200 kind string201 save_id string202 path string203 reason string204 created_at i64205 machine string206}207208pub fn event_record(event events.Event) EventRecord {209 return EventRecord{210 id: event.id211 kind: event.kind212 save_id: event.save_id213 path: event.path214 reason: event.reason215 created_at: event.created_at216 machine: event.machine217 }218}219220pub fn encode_event(event events.Event) string {221 return json2.encode(event_record(event))222}223224225pub fn decode_event(body []u8) !events.Event {226 record := json2.decode[EventRecord](lone_surrogates_as_bytes(body).bytestr())!227 return events.Event{228 id: record.id229 kind: record.kind230 save_id: record.save_id231 path: record.path232 reason: record.reason233 created_at: record.created_at234 machine: record.machine235 }236}