SaveStore prüft jeden Schreibvorgang
Ein Speicherstand ersetzt den vorigen nur noch, wenn er vollständig geschrieben und zurückgelesen wurde. Ein nicht gespeicherter Spielerschlüssel wird gemeldet, und set_aside_save meldet, wenn nichts verschoben wurde. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -25,7 +25,8 @@ func player_key() -> PackedByteArray:
|
|||||||
var key := FileAccess.get_file_as_bytes(path)
|
var key := FileAccess.get_file_as_bytes(path)
|
||||||
if key.size() != KEY_BYTES:
|
if key.size() != KEY_BYTES:
|
||||||
key = Crypto.new().generate_random_bytes(KEY_BYTES)
|
key = Crypto.new().generate_random_bytes(KEY_BYTES)
|
||||||
_write(path, key)
|
if not _write(path, key):
|
||||||
|
push_error("player key could not be stored at %s" % path)
|
||||||
return key
|
return key
|
||||||
|
|
||||||
|
|
||||||
@@ -37,24 +38,28 @@ func read_save() -> PackedByteArray:
|
|||||||
return FileAccess.get_file_as_bytes(base_dir.path_join(SAVE_FILE))
|
return FileAccess.get_file_as_bytes(base_dir.path_join(SAVE_FILE))
|
||||||
|
|
||||||
|
|
||||||
## Writes the save through a temporary file, so that a crash while writing
|
## Writes the save through a temporary file, so that a crash or a failed
|
||||||
## never destroys the previous save. Returns false if nothing was written.
|
## write never destroys the previous save. Returns false if the new save was
|
||||||
|
## not stored completely; the previous save then stays as it was.
|
||||||
func write_save(bytes: PackedByteArray) -> bool:
|
func write_save(bytes: PackedByteArray) -> bool:
|
||||||
if bytes.is_empty():
|
if bytes.is_empty():
|
||||||
return false
|
return false
|
||||||
var path := base_dir.path_join(SAVE_FILE)
|
var path := base_dir.path_join(SAVE_FILE)
|
||||||
var temporary := path + ".tmp"
|
var temporary := path + ".tmp"
|
||||||
if not _write(temporary, bytes):
|
if not _write(temporary, bytes) or FileAccess.get_file_as_bytes(temporary) != bytes:
|
||||||
|
DirAccess.remove_absolute(temporary)
|
||||||
return false
|
return false
|
||||||
return DirAccess.rename_absolute(temporary, path) == OK
|
return DirAccess.rename_absolute(temporary, path) == OK
|
||||||
|
|
||||||
|
|
||||||
## Moves an unusable save out of the way, so that the next save does not
|
## Moves an unusable save out of the way, so that the next save does not
|
||||||
## overwrite it. An older set-aside save is replaced. Returns the new path.
|
## overwrite it. An older set-aside save is replaced. Returns the new path,
|
||||||
|
## or an empty text if there was no save or it could not be moved.
|
||||||
func set_aside_save() -> String:
|
func set_aside_save() -> String:
|
||||||
var path := base_dir.path_join(SAVE_FILE)
|
var path := base_dir.path_join(SAVE_FILE)
|
||||||
var aside := path + ".broken"
|
var aside := path + ".broken"
|
||||||
DirAccess.rename_absolute(path, aside)
|
if DirAccess.rename_absolute(path, aside) != OK:
|
||||||
|
return ""
|
||||||
return aside
|
return aside
|
||||||
|
|
||||||
|
|
||||||
@@ -88,6 +93,6 @@ func _write(path: String, bytes: PackedByteArray) -> bool:
|
|||||||
var file := FileAccess.open(path, FileAccess.WRITE)
|
var file := FileAccess.open(path, FileAccess.WRITE)
|
||||||
if file == null:
|
if file == null:
|
||||||
return false
|
return false
|
||||||
file.store_buffer(bytes)
|
var stored := file.store_buffer(bytes)
|
||||||
file.close()
|
file.close()
|
||||||
return true
|
return stored
|
||||||
|
|||||||
@@ -30,6 +30,25 @@ func test_an_empty_save_is_not_written() -> void:
|
|||||||
TestFiles.remove_dir(directory)
|
TestFiles.remove_dir(directory)
|
||||||
|
|
||||||
|
|
||||||
|
func test_a_save_that_cannot_be_written_keeps_the_previous_one() -> void:
|
||||||
|
var directory := TestFiles.temporary_dir("blocked")
|
||||||
|
var store := SaveStore.new(directory)
|
||||||
|
store.write_save(PackedByteArray([1, 2, 3]))
|
||||||
|
# A folder in place of the temporary file makes the write fail.
|
||||||
|
DirAccess.make_dir_absolute(directory.path_join(SaveStore.SAVE_FILE + ".tmp"))
|
||||||
|
check(not store.write_save(PackedByteArray([4, 5])), "the failed save is reported")
|
||||||
|
check_eq(store.read_save(), PackedByteArray([1, 2, 3]), "the previous save is untouched")
|
||||||
|
check(not store.write_save(PackedByteArray()), "an empty save is refused")
|
||||||
|
check_eq(store.read_save(), PackedByteArray([1, 2, 3]), "still untouched")
|
||||||
|
TestFiles.remove_dir(directory)
|
||||||
|
|
||||||
|
|
||||||
|
func test_setting_aside_without_a_save_reports_nothing_moved() -> void:
|
||||||
|
var directory := TestFiles.temporary_dir("nothing")
|
||||||
|
check_eq(SaveStore.new(directory).set_aside_save(), "", "no save, nothing moved")
|
||||||
|
TestFiles.remove_dir(directory)
|
||||||
|
|
||||||
|
|
||||||
func test_an_unusable_save_can_be_set_aside() -> void:
|
func test_an_unusable_save_can_be_set_aside() -> void:
|
||||||
var directory := TestFiles.temporary_dir("aside")
|
var directory := TestFiles.temporary_dir("aside")
|
||||||
var store := SaveStore.new(directory)
|
var store := SaveStore.new(directory)
|
||||||
|
|||||||
Reference in New Issue
Block a user