ludic.save save_write_tree: a tree straight to disk through Json.write_file, the backup and read-back kept; ludic.telemetry writes its id file the same way
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
edb33400b3
commit
045ab0cdf6
4 changed files with 46 additions and 3 deletions
|
|
@ -17,7 +17,7 @@ f.is_one = fn looks_like_a_trip # an object with a day in it
|
||||||
let r = save_open(path, backup, f) # read, check, carry forward, or the backup
|
let r = save_open(path, backup, f) # read, check, carry forward, or the backup
|
||||||
if r.status == SAVE_OK { apply(r.value) }
|
if r.status == SAVE_OK { apply(r.value) }
|
||||||
...
|
...
|
||||||
save_write(path, backup, Json.encode(v)) # false unless it is on disk
|
save_write_tree(path, backup, v) # false unless it is on disk; keeps nothing
|
||||||
```
|
```
|
||||||
|
|
||||||
## The contract
|
## The contract
|
||||||
|
|
@ -31,7 +31,8 @@ save_write(path, backup, Json.encode(v)) # false unless it is on disk
|
||||||
string closed). A best-effort JSON parser hands back half a file as a shorter, plausible one.
|
string closed). A best-effort JSON parser hands back half a file as a shorter, plausible one.
|
||||||
- **A backup, and only of a whole file**: `save_write` copies the old file aside when it is intact,
|
- **A backup, and only of a whole file**: `save_write` copies the old file aside when it is intact,
|
||||||
writes, and reads the text straight back. A caller about to delete something on the strength of
|
writes, and reads the text straight back. A caller about to delete something on the strength of
|
||||||
the answer needs to know it is on disk.
|
the answer needs to know it is on disk. `save_write_tree` does the same for a tree through
|
||||||
|
`Json.write_file`, so a save holds no text of its own afterwards.
|
||||||
- **A newer build's file is refused and read-only.** It is never replaced by its backup (that is from
|
- **A newer build's file is refused and read-only.** It is never replaced by its backup (that is from
|
||||||
the newer build too), and `save_write_to` refuses a read that says `readonly`. Both copies damaged
|
the newer build too), and `save_write_to` refuses a read that says `readonly`. Both copies damaged
|
||||||
is read-only as well. Never write over a file you could not read.
|
is read-only as well. Never write over a file you could not read.
|
||||||
|
|
|
||||||
|
|
@ -91,6 +91,25 @@ program SaveTest {
|
||||||
expect(Fs.read_text(bak()) == "{\"body\": \"one\", \"version\": 3}")
|
expect(Fs.read_text(bak()) == "{\"body\": \"one\", \"version\": 3}")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "a tree is written straight to the file, and the old file backed up only when whole" {
|
||||||
|
fresh()
|
||||||
|
let v = value_object()
|
||||||
|
value_put(v, "body", value_str("one"))
|
||||||
|
value_put(v, "version", value_int(3))
|
||||||
|
expect(save_write_tree(path(), bak(), v))
|
||||||
|
expect(not Fs.exists(bak()))
|
||||||
|
expect(Fs.read_text(path()) == Json.encode(v))
|
||||||
|
value_put(v, "body", value_str("two"))
|
||||||
|
expect(save_write_tree(path(), bak(), v))
|
||||||
|
expect(save_file_intact(bak()))
|
||||||
|
expect(save_open(path(), bak(), fmt()).status == SAVE_OK)
|
||||||
|
Fs.write_text(path(), "{\"body\": \"thr")
|
||||||
|
expect(save_write_tree(path(), bak(), v))
|
||||||
|
expect(save_int(Json.parse(Fs.read_text(bak())), "version", 0) == 3)
|
||||||
|
let r = save_open(path(), bak(), fmt())
|
||||||
|
r.readonly = true
|
||||||
|
expect(not save_write_tree_to(r, path(), bak(), v))
|
||||||
|
}
|
||||||
test "the backup stands in for a damaged file, and both damaged is read-only" {
|
test "the backup stands in for a damaged file, and both damaged is read-only" {
|
||||||
fresh()
|
fresh()
|
||||||
Fs.write_text(path(), "{\"body\": \"cut")
|
Fs.write_text(path(), "{\"body\": \"cut")
|
||||||
|
|
|
||||||
|
|
@ -14,3 +14,25 @@ export function save_write_to(r: SaveRead, path: string, backup: string, text: s
|
||||||
if r != null and r.readonly { return false }
|
if r != null and r.readonly { return false }
|
||||||
return save_write(path, backup, text)
|
return save_write(path, backup, text)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# save_write for a tree: the same backup, then Json.write_file (the runtime's kept buffer, .tmp and
|
||||||
|
# rename), so a game that saves often holds nothing more for it. Read back whole, as a save is read
|
||||||
|
export function save_write_tree(path: string, backup: string, v: Val) -> bool {
|
||||||
|
if len(backup) > 0 and save_file_intact(path) { Fs.copy(path, backup) }
|
||||||
|
if not Json.write_file(v, path) { return false }
|
||||||
|
return save_file_intact(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function save_write_tree_to(r: SaveRead, path: string, backup: string, v: Val) -> bool {
|
||||||
|
if r != null and r.readonly { return false }
|
||||||
|
return save_write_tree(path, backup, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
# whether the file on disk is a whole one; the text read to tell is given back
|
||||||
|
export function save_file_intact(path: string) -> bool {
|
||||||
|
let s = Fs.read_text(path)
|
||||||
|
if s == null { return false }
|
||||||
|
let whole = save_text_intact(s)
|
||||||
|
unsafe { free(s) }
|
||||||
|
return whole
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,8 @@ export function telemetry_set_id(telemetry_st: mut TelemetryState, id: string) -
|
||||||
var v = telemetry__read_obj(path)
|
var v = telemetry__read_obj(path)
|
||||||
if v == null { v = Value.object() }
|
if v == null { v = Value.object() }
|
||||||
Value.put(v, "id", Value.str(id))
|
Value.put(v, "id", Value.str(id))
|
||||||
Fs.write_text(path, Json.encode(v))
|
Json.write_file(v, path)
|
||||||
|
Json.free(v)
|
||||||
}
|
}
|
||||||
telemetry__fact(telemetry_st, TELEMETRY_ID, 0, 0, 0)
|
telemetry__fact(telemetry_st, TELEMETRY_ID, 0, 0, 0)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue