1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
|
// 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<Application.Storage.KeyType, Application.Storage.ValueType> {
var stored = store.getValue(META_KEY);
if (stored == null) {
var fresh = {"schema" => CURRENT, "ids" => [], "pending" => []} as Dictionary<Application.Storage.KeyType, Application.Storage.ValueType>;
store.setValue(META_KEY, fresh);
return fresh;
}
if (!(stored instanceof Dictionary)) {
throw new VaultError(VaultError.DAMAGED, "metadata damaged");
}
var meta = stored as Dictionary<Application.Storage.KeyType, Application.Storage.ValueType>;
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<Application.Storage.KeyType, Application.Storage.ValueType>) as Dictionary<Application.Storage.KeyType, Application.Storage.ValueType> {
var order = meta["order"];
return {
"schema" => 1,
"ids" => order instanceof Array ? order : [],
"pending" => []
} as Dictionary<Application.Storage.KeyType, Application.Storage.ValueType>;
}
// True when "ids" and "pending" are arrays of distinct valid IDs.
function isValidV1(meta as Dictionary<Application.Storage.KeyType, Application.Storage.ValueType>) as Boolean {
var seen = {} as Dictionary<String, Boolean>;
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;
}
}
|