records.v

4.4 KB · raw

1//! What a save and an event look like on the remote.2//!3//! Records are JSON so a person can read the bucket, and the layout is the4//! same for every backend:5//!6//   <repo>/format          the remote layout, `koh-remote 1`7//   <repo>/objects/<hash>   the object's compressed bytes, as in the table8//   <repo>/saves/<id>       the full save record, including its file list9//   <repo>/events/<id>      one flag, clear or strip event10//   <repo>/head             the ID of the newest save on the line11//12// Only `head` is ever overwritten, and only with a matching version tag.1314module exchange1516import json217import src.events18import src.history19import src.snapshot20import src.storage2122pub fn format_key(repo string) string {23	return '${repo}/format'24}2526pub fn object_key(repo string, hash string) string {27	return '${repo}/objects/${hash}'28}2930pub fn save_key(repo string, id string) string {31	return '${repo}/saves/${id}'32}3334pub fn event_key(repo string, id string) string {35	return '${repo}/events/${id}'36}3738pub fn head_key(repo string) string {39	return '${repo}/head'40}4142pub fn events_prefix(repo string) string {43	return '${repo}/events'44}4546pub struct FileRecord {47pub:48	path       string49	hash       string50	executable bool51}5253pub struct SaveRecord {54pub:55	id            string56	parent        string57	message       string58	created_at    i6459	machine       string60	is_checkpoint bool61	size_bytes    i6462	agent         string63	session       string64	restores      string65	replayed_from string66pub mut:67	files []FileRecord68}6970// save_record turns a local save into what travels. The file list is part of71// the record, which is why a save needs one round trip, not one per file.72pub fn save_record(store &storage.Store, save history.Save) !SaveRecord {73	files := history.files_of(store, save.id)!74	mut records := []FileRecord{}75	for file in files {76		records << FileRecord{77			path:       file.path78			hash:       file.hash79			executable: file.executable80		}81	}82	return SaveRecord{83		id:            save.id84		parent:        save.parent85		message:       save.message86		created_at:    save.created_at87		machine:       save.machine88		is_checkpoint: save.is_checkpoint89		size_bytes:    save.size_bytes90		agent:         save.agent91		session:       save.session92		restores:      save.restores93		replayed_from: save.replayed_from94		files:         records95	}96}9798pub fn encode_save(record SaveRecord) string {99	return json2.encode(record)100}101102pub fn decode_save(body []u8) !SaveRecord {103	return json2.decode[SaveRecord](body.bytestr())!104}105106// drop_unsafe_paths applies the shared path contract before an arriving record is used.107pub fn drop_unsafe_paths(mut record SaveRecord) []string {108	mut kept := []FileRecord{}109	mut dropped := []string{}110	for file in record.files {111		if snapshot.legal_path(file.path) {112			kept << file113		} else {114			dropped << snapshot.escape_path(file.path)115		}116	}117	record.files = kept118	dropped.sort()119	return dropped120}121122pub fn to_save(record SaveRecord) history.Save {123	return history.Save{124		id:            record.id125		parent:        record.parent126		message:       record.message127		created_at:    record.created_at128		machine:       record.machine129		is_checkpoint: record.is_checkpoint130		size_bytes:    record.size_bytes131		agent:         record.agent132		session:       record.session133		restores:      record.restores134		replayed_from: record.replayed_from135	}136}137138pub fn to_files(record SaveRecord) []snapshot.FileEntry {139	mut files := []snapshot.FileEntry{}140	for file in record.files {141		files << snapshot.FileEntry{142			path:       file.path143			hash:       file.hash144			executable: file.executable145		}146	}147	return files148}149150pub struct EventRecord {151pub:152	id         string153	kind       string154	save_id    string155	path       string156	reason     string157	created_at i64158	machine    string159}160161pub fn event_record(event events.Event) EventRecord {162	return EventRecord{163		id:         event.id164		kind:       event.kind165		save_id:    event.save_id166		path:       event.path167		reason:     event.reason168		created_at: event.created_at169		machine:    event.machine170	}171}172173pub fn encode_event(event events.Event) string {174	return json2.encode(event_record(event))175}176177pub fn decode_event(body []u8) !events.Event {178	record := json2.decode[EventRecord](body.bytestr())!179	return events.Event{180		id:         record.id181		kind:       record.kind182		save_id:    record.save_id183		path:       record.path184		reason:     record.reason185		created_at: record.created_at186		machine:    record.machine187	}188}