//! What a save and an event look like on the remote. //! //! Records are JSON so a person can read the bucket, and the layout is the //! same for every backend: //! //! /objects/ the object's compressed bytes, as in the table //! /saves/ the full save record, including its file list //! /events/ one flag, clear or strip event //! /head the ID of the newest save on the line //! //! Only `head` is ever overwritten, and only with a matching version tag. module sync import json import src.events import src.history import src.snapshot import src.storage pub fn object_key(repo string, hash string) string { return '${repo}/objects/${hash}' } pub fn save_key(repo string, id string) string { return '${repo}/saves/${id}' } pub fn event_key(repo string, id string) string { return '${repo}/events/${id}' } pub fn head_key(repo string) string { return '${repo}/head' } pub fn events_prefix(repo string) string { return '${repo}/events' } pub struct FileRecord { pub: path string hash string executable bool } pub struct SaveRecord { pub: id string parent string message string created_at i64 machine string is_checkpoint bool size_bytes i64 agent string session string restores string replayed_from string files []FileRecord } // save_record turns a local save into what travels. The file list is part of // the record, which is why a save needs one round trip, not one per file. pub fn save_record(store &storage.Store, save history.Save) !SaveRecord { files := history.files_of(store, save.id)! mut records := []FileRecord{} for file in files { records << FileRecord{ path: file.path hash: file.hash executable: file.executable } } return SaveRecord{ id: save.id parent: save.parent message: save.message created_at: save.created_at machine: save.machine is_checkpoint: save.is_checkpoint size_bytes: save.size_bytes agent: save.agent session: save.session restores: save.restores replayed_from: save.replayed_from files: records } } pub fn encode_save(record SaveRecord) string { return json.encode(record) } pub fn decode_save(body []u8) !SaveRecord { return json.decode(SaveRecord, body.bytestr())! } pub fn to_save(record SaveRecord) history.Save { return history.Save{ id: record.id parent: record.parent message: record.message created_at: record.created_at machine: record.machine is_checkpoint: record.is_checkpoint size_bytes: record.size_bytes agent: record.agent session: record.session restores: record.restores replayed_from: record.replayed_from } } pub fn to_files(record SaveRecord) []snapshot.FileEntry { mut files := []snapshot.FileEntry{} for file in record.files { files << snapshot.FileEntry{ path: file.path hash: file.hash executable: file.executable } } return files } pub struct EventRecord { pub: id string kind string save_id string path string reason string created_at i64 machine string } pub fn event_record(event events.Event) EventRecord { return EventRecord{ id: event.id kind: event.kind save_id: event.save_id path: event.path reason: event.reason created_at: event.created_at machine: event.machine } } pub fn encode_event(event events.Event) string { return json.encode(event_record(event)) } pub fn decode_event(body []u8) !events.Event { record := json.decode(EventRecord, body.bytestr())! return events.Event{ id: record.id kind: record.kind save_id: record.save_id path: record.path reason: record.reason created_at: record.created_at machine: record.machine } }