// Author: cirakg & claude ai // Summary: Storage schema versions: create the metadata on first start, migrate old schemas, refuse newer ones. // // The metadata "m" is a dictionary: // "schema" data version (CURRENT) // "ids" entry IDs in display order // "pending" IDs of pending locked entries (README 7.7) // // Rules (README 6.3): // * Each migration is one small, idempotent function from version k to // k + 1, run in order at start-up. // * A migration that rewrites records writes each new record under a // temporary key and switches the index to it before deleting the old // one, so a crash or power loss mid-migration loses nothing. The only // migration so far (0 to 1) changes just the metadata, in one write. // * Data from a newer schema (after a downgrade) is never modified. // // Version 0 never shipped: it is a fixture layout ("m" = {"order" => ids}) // that exists to test the migration machinery before a real migration is // needed. import Toybox.Application; import Toybox.Lang; module Migration { const CURRENT = 1; const META_KEY = "m"; // The metadata at the CURRENT schema, migrating or creating it as needed. // Throws VaultError NEWER_SCHEMA or DAMAGED. function run(store as Persist.Backend) as Dictionary { var stored = store.getValue(META_KEY); if (stored == null) { var fresh = {"schema" => CURRENT, "ids" => [], "pending" => []} as Dictionary; store.setValue(META_KEY, fresh); return fresh; } if (!(stored instanceof Dictionary)) { throw new VaultError(VaultError.DAMAGED, "metadata damaged"); } var meta = stored as Dictionary; var schema = meta.hasKey("schema") ? meta["schema"] : 0; if (!(schema instanceof Number) || schema < 0) { throw new VaultError(VaultError.DAMAGED, "metadata damaged"); } if (schema > CURRENT) { throw new VaultError(VaultError.NEWER_SCHEMA, "data from a newer version"); } if (schema == 0) { meta = v0ToV1(meta); store.setValue(META_KEY, meta); } if (!isValidV1(meta)) { throw new VaultError(VaultError.DAMAGED, "metadata damaged"); } return meta; } // Version 0 to 1: "order" becomes "ids", and "pending" is added. function v0ToV1(meta as Dictionary) as Dictionary { var order = meta["order"]; return { "schema" => 1, "ids" => order instanceof Array ? order : [], "pending" => [] } as Dictionary; } // True when "ids" and "pending" are arrays of distinct valid IDs. function isValidV1(meta as Dictionary) as Boolean { var seen = {} as Dictionary; var lists = [meta["ids"], meta["pending"]]; for (var l = 0; l < lists.size(); l++) { var list = lists[l]; if (!(list instanceof Array)) { return false; } for (var i = 0; i < list.size(); i++) { var id = list[i]; if (!(id instanceof String) || !Entry.isValidId(id) || seen.hasKey(id)) { return false; } seen[id] = true; } } return true; } }