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}434445pub fn events_prefix(repo string) string {46 return '${repo}/events'47}4849pub struct FileRecord {50pub:51 path string52 hash string53 executable bool54}5556pub struct SaveRecord {57pub:58 id string59 parent string60 message string61 created_at i6462 machine string63 is_checkpoint bool64 size_bytes i6465 agent string66 session string67 restores string68 replayed_from string69pub mut:70 files []FileRecord71}72737475pub fn save_record(store &storage.Store, save history.Save) !SaveRecord {76 files := history.files_of(store, save.id)!77 mut records := []FileRecord{}78 for file in files {79 records << FileRecord{80 path: file.path81 hash: file.hash82 executable: file.executable83 }84 }85 return SaveRecord{86 id: save.id87 parent: save.parent88 message: save.message89 created_at: save.created_at90 machine: save.machine91 is_checkpoint: save.is_checkpoint92 size_bytes: save.size_bytes93 agent: save.agent94 session: save.session95 restores: save.restores96 replayed_from: save.replayed_from97 files: records98 }99}100101pub fn encode_save(record SaveRecord) string {102 return json2.encode(record)103}104105106107pub fn decode_save(body []u8) !SaveRecord {108 return json2.decode[SaveRecord](lone_surrogates_as_bytes(body).bytestr())!109}110111112113fn ignored_strip(event events.Event) bool {114 return event.kind == events.kind_strip && !snapshot.legal_path(event.path)115}116117118119120fn lone_surrogates_as_bytes(body []u8) []u8 {121 mut out := []u8{cap: body.len}122 mut i := 0123 for i < body.len {124 if body[i] != `\\` || i + 1 >= body.len {125 out << body[i]126 i++127 continue128 }129 unit := escaped_surrogate(body, i)130 if unit >= 0xd800 && unit <= 0xdbff && escaped_surrogate(body, i + 6) >= 0xdc00 {131 out << body[i..i + 12]132 i += 12133 } else if unit >= 0 {134 out << [u8(0xe0 | (unit >> 12)), u8(0x80 | ((unit >> 6) & 0x3f)), u8(0x80 | (unit & 0x3f))]135 i += 6136 } else {137 138 out << body[i..i + 2]139 i += 2140 }141 }142 return out143}144145146fn escaped_surrogate(body []u8, at int) int {147 if at + 6 > body.len || body[at] != `\\` || body[at + 1] != `u` {148 return -1149 }150 unit := strconv.parse_int(body[at + 2..at + 6].bytestr(), 16, 32) or { return -1 }151 return if unit >= 0xd800 && unit <= 0xdfff { int(unit) } else { -1 }152}153154155pub fn drop_unsafe_paths(mut record SaveRecord) []string {156 mut kept := []FileRecord{}157 mut dropped := []string{}158 for file in record.files {159 if snapshot.legal_path(file.path) {160 kept << file161 } else {162 dropped << snapshot.escape_path(file.path)163 }164 }165 record.files = kept166 dropped.sort()167 return dropped168}169170pub fn to_save(record SaveRecord) history.Save {171 return history.Save{172 id: record.id173 parent: record.parent174 message: record.message175 created_at: record.created_at176 machine: record.machine177 is_checkpoint: record.is_checkpoint178 size_bytes: record.size_bytes179 agent: record.agent180 session: record.session181 restores: record.restores182 replayed_from: record.replayed_from183 }184}185186pub fn to_files(record SaveRecord) []snapshot.FileEntry {187 mut files := []snapshot.FileEntry{}188 for file in record.files {189 files << snapshot.FileEntry{190 path: file.path191 hash: file.hash192 executable: file.executable193 }194 }195 return files196}197198pub struct EventRecord {199pub:200 id string201 kind string202 save_id string203 path string204 reason string205 created_at i64206 machine string207}208209pub fn event_record(event events.Event) EventRecord {210 return EventRecord{211 id: event.id212 kind: event.kind213 save_id: event.save_id214 path: event.path215 reason: event.reason216 created_at: event.created_at217 machine: event.machine218 }219}220221pub fn encode_event(event events.Event) string {222 return json2.encode(event_record(event))223}224225226pub fn decode_event(body []u8) !events.Event {227 record := json2.decode[EventRecord](lone_surrogates_as_bytes(body).bytestr())!228 return events.Event{229 id: record.id230 kind: record.kind231 save_id: record.save_id232 path: record.path233 reason: record.reason234 created_at: record.created_at235 machine: record.machine236 }237}