1234567891011121314module exchange1516import json217import src.events18import src.history19import src.snapshot20import src.storage21import strconv2223pub fn format_key(repo string) string {24 return '${repo}/format'25}2627pub fn object_key(repo string, hash string) string {28 return '${repo}/objects/${hash}'29}3031pub fn save_key(repo string, id string) string {32 return '${repo}/saves/${id}'33}3435pub fn event_key(repo string, id string) string {36 return '${repo}/events/${id}'37}3839pub fn head_key(repo string) string {40 return '${repo}/head'41}4243pub fn events_prefix(repo string) string {44 return '${repo}/events'45}4647pub struct FileRecord {48pub:49 path string50 hash string51 executable bool52}5354pub struct SaveRecord {55pub:56 id string57 parent string58 message string59 created_at i6460 machine string61 is_checkpoint bool62 size_bytes i6463 agent string64 session string65 restores string66 replayed_from string67pub mut:68 files []FileRecord69}70717273pub fn save_record(store &storage.Store, save history.Save) !SaveRecord {74 files := history.files_of(store, save.id)!75 mut records := []FileRecord{}76 for file in files {77 records << FileRecord{78 path: file.path79 hash: file.hash80 executable: file.executable81 }82 }83 return SaveRecord{84 id: save.id85 parent: save.parent86 message: save.message87 created_at: save.created_at88 machine: save.machine89 is_checkpoint: save.is_checkpoint90 size_bytes: save.size_bytes91 agent: save.agent92 session: save.session93 restores: save.restores94 replayed_from: save.replayed_from95 files: records96 }97}9899pub fn encode_save(record SaveRecord) string {100 return json2.encode(record)101}102103pub fn decode_save(body []u8) !SaveRecord {104 return json2.decode[SaveRecord](lone_surrogates_as_bytes(body).bytestr())!105}106107108109fn ignored_strip(event events.Event) bool {110 return event.kind == events.kind_strip && !snapshot.legal_path(event.path)111}112113114115116fn lone_surrogates_as_bytes(body []u8) []u8 {117 mut out := []u8{cap: body.len}118 mut i := 0119 for i < body.len {120 if body[i] != `\\` || i + 1 >= body.len {121 out << body[i]122 i++123 continue124 }125 unit := escaped_surrogate(body, i)126 if unit >= 0xd800 && unit <= 0xdbff && escaped_surrogate(body, i + 6) >= 0xdc00 {127 out << body[i..i + 12]128 i += 12129 } else if unit >= 0 {130 out << [u8(0xe0 | (unit >> 12)), u8(0x80 | ((unit >> 6) & 0x3f)), u8(0x80 | (unit & 0x3f))]131 i += 6132 } else {133 134 out << body[i..i + 2]135 i += 2136 }137 }138 return out139}140141142fn escaped_surrogate(body []u8, at int) int {143 if at + 6 > body.len || body[at] != `\\` || body[at + 1] != `u` {144 return -1145 }146 unit := strconv.parse_int(body[at + 2..at + 6].bytestr(), 16, 32) or { return -1 }147 return if unit >= 0xd800 && unit <= 0xdfff { int(unit) } else { -1 }148}149150151pub fn drop_unsafe_paths(mut record SaveRecord) []string {152 mut kept := []FileRecord{}153 mut dropped := []string{}154 for file in record.files {155 if snapshot.legal_path(file.path) {156 kept << file157 } else {158 dropped << snapshot.escape_path(file.path)159 }160 }161 record.files = kept162 dropped.sort()163 return dropped164}165166pub fn to_save(record SaveRecord) history.Save {167 return history.Save{168 id: record.id169 parent: record.parent170 message: record.message171 created_at: record.created_at172 machine: record.machine173 is_checkpoint: record.is_checkpoint174 size_bytes: record.size_bytes175 agent: record.agent176 session: record.session177 restores: record.restores178 replayed_from: record.replayed_from179 }180}181182pub fn to_files(record SaveRecord) []snapshot.FileEntry {183 mut files := []snapshot.FileEntry{}184 for file in record.files {185 files << snapshot.FileEntry{186 path: file.path187 hash: file.hash188 executable: file.executable189 }190 }191 return files192}193194pub struct EventRecord {195pub:196 id string197 kind string198 save_id string199 path string200 reason string201 created_at i64202 machine string203}204205pub fn event_record(event events.Event) EventRecord {206 return EventRecord{207 id: event.id208 kind: event.kind209 save_id: event.save_id210 path: event.path211 reason: event.reason212 created_at: event.created_at213 machine: event.machine214 }215}216217pub fn encode_event(event events.Event) string {218 return json2.encode(event_record(event))219}220221pub fn decode_event(body []u8) !events.Event {222 record := json2.decode[EventRecord](lone_surrogates_as_bytes(body).bytestr())!223 return events.Event{224 id: record.id225 kind: record.kind226 save_id: record.save_id227 path: record.path228 reason: record.reason229 created_at: record.created_at230 machine: record.machine231 }232}