Preserve data across the Straywild transition
This commit is contained in:
parent
737d1cf2e0
commit
98a27e2f53
28 changed files with 577 additions and 94 deletions
|
|
@ -10,11 +10,41 @@ const BOOTSTRAP_VERSION := 1
|
|||
const MANIFEST_VERSION := 1
|
||||
const LAYOUT_VERSION := 1
|
||||
const MANIFEST_FILENAME := "straywild_data.json"
|
||||
const LEGACY_MANIFEST_FILENAME := "netfishing_data.json"
|
||||
const README_FILENAME := "README.txt"
|
||||
const ENVIRONMENT_VARIABLE := "straywild_DATA_DIR"
|
||||
const CREATE_ENVIRONMENT_VARIABLE := "straywild_CREATE_DATA_DIR"
|
||||
const LEGACY_ENVIRONMENT_VARIABLE := "NETFISHING_DATA_DIR"
|
||||
const LEGACY_CREATE_ENVIRONMENT_VARIABLE := "NETFISHING_CREATE_DATA_DIR"
|
||||
const APPLICATION_ID := "straywild"
|
||||
const LEGACY_APPLICATION_ID := "netfishing"
|
||||
const APP_DATA_PORTABLE_PATH := "user://portable-data"
|
||||
const LEGACY_USER_DIRECTORY_NAME := "NETFISHING"
|
||||
const LEGACY_USER_FILES := [
|
||||
"data_root_bootstrap.json",
|
||||
"data_root_bootstrap.json.backup",
|
||||
"network_discovery.cfg",
|
||||
"player_settings.json",
|
||||
"player_settings.json.backup",
|
||||
"controller_mappings.json",
|
||||
"controller_mappings.json.backup",
|
||||
"keyboard_mouse_bindings.json",
|
||||
"keyboard_mouse_bindings.json.backup",
|
||||
"player_save.json",
|
||||
"network_profile.json",
|
||||
"player_appearance.json",
|
||||
"saved_servers.json",
|
||||
"known_players.json",
|
||||
"player_relationships.json",
|
||||
"server_trust.json",
|
||||
"host_bans.json",
|
||||
"player_identity.key",
|
||||
"player_identity.pub",
|
||||
"player_identity.json",
|
||||
"host_identity.key",
|
||||
"host_identity.pub",
|
||||
"host_identity.json",
|
||||
]
|
||||
const PATH_ALLOWED: StringName = &"allowed"
|
||||
const PATH_SOURCE_PROJECT: StringName = &"source_project"
|
||||
const PATH_INSTALLATION: StringName = &"installation"
|
||||
|
|
@ -37,23 +67,29 @@ var override_active := false
|
|||
|
||||
|
||||
func resolve() -> bool:
|
||||
prepare_legacy_user_data()
|
||||
_load_bootstrap_identity()
|
||||
var command_line: String = _command_line_override()
|
||||
if not command_line.is_empty():
|
||||
override_active = true
|
||||
mode = Mode.COMMAND_LINE_OVERRIDE
|
||||
return _activate_existing(command_line, "", false)
|
||||
if OS.has_environment(ENVIRONMENT_VARIABLE):
|
||||
var environment_variable: String = _available_environment_variable(
|
||||
ENVIRONMENT_VARIABLE,
|
||||
LEGACY_ENVIRONMENT_VARIABLE,
|
||||
)
|
||||
if not environment_variable.is_empty():
|
||||
override_active = true
|
||||
mode = Mode.ENVIRONMENT_OVERRIDE
|
||||
var environment_path: String = OS.get_environment(ENVIRONMENT_VARIABLE)
|
||||
var environment_path: String = OS.get_environment(environment_variable)
|
||||
if not environment_path.is_absolute_path():
|
||||
return _fail("straywild_DATA_DIR must be an absolute path.")
|
||||
if (
|
||||
OS.get_environment(CREATE_ENVIRONMENT_VARIABLE) == "1"
|
||||
and not FileAccess.file_exists(
|
||||
environment_path.path_join(MANIFEST_FILENAME)
|
||||
_environment_flag(
|
||||
CREATE_ENVIRONMENT_VARIABLE,
|
||||
LEGACY_CREATE_ENVIRONMENT_VARIABLE,
|
||||
)
|
||||
and not has_data_manifest(environment_path)
|
||||
):
|
||||
var created: Dictionary = create_unbound_root(environment_path)
|
||||
if not bool(created.get("ok", false)):
|
||||
|
|
@ -80,6 +116,30 @@ func resolve() -> bool:
|
|||
return false
|
||||
|
||||
|
||||
func prepare_legacy_user_data() -> void:
|
||||
var current_root: String = _normalize(
|
||||
ProjectSettings.globalize_path("user://")
|
||||
)
|
||||
if current_root.is_empty():
|
||||
return
|
||||
var legacy_root: String = _normalize(
|
||||
current_root.get_base_dir().path_join(LEGACY_USER_DIRECTORY_NAME)
|
||||
)
|
||||
if legacy_root == current_root or not DirAccess.dir_exists_absolute(legacy_root):
|
||||
return
|
||||
for filename: String in LEGACY_USER_FILES:
|
||||
var destination: String = current_root.path_join(filename)
|
||||
if FileAccess.file_exists(destination):
|
||||
continue
|
||||
var source: String = legacy_root.path_join(filename)
|
||||
if FileAccess.file_exists(source):
|
||||
_copy_legacy_user_file(source, destination)
|
||||
|
||||
|
||||
func has_data_manifest(path: String) -> bool:
|
||||
return not _existing_manifest_path(_normalize(path)).is_empty()
|
||||
|
||||
|
||||
func default_visible_path() -> String:
|
||||
var documents: String = OS.get_system_dir(OS.SYSTEM_DIR_DOCUMENTS)
|
||||
if documents.is_empty():
|
||||
|
|
@ -105,12 +165,14 @@ func select_new_root(path: String, app_data: bool = false) -> bool:
|
|||
normalized = ProjectSettings.globalize_path(APP_DATA_PORTABLE_PATH)
|
||||
if not _validate_candidate(normalized, true):
|
||||
return false
|
||||
var manifest_path: String = normalized.path_join(MANIFEST_FILENAME)
|
||||
if FileAccess.file_exists(manifest_path):
|
||||
var manifest_path: String = _existing_manifest_path(normalized)
|
||||
if not manifest_path.is_empty():
|
||||
var manifest: Dictionary = _read_json(manifest_path)
|
||||
if not _valid_manifest(manifest):
|
||||
return _fail("The selected folder has a malformed straywild manifest.")
|
||||
root_id = str(manifest["root_id"])
|
||||
if not _upgrade_legacy_manifest(normalized, manifest, manifest_path):
|
||||
return _fail("The legacy data-folder manifest could not be upgraded.")
|
||||
else:
|
||||
if not _directory_is_empty(normalized) and not app_data:
|
||||
return _fail(
|
||||
|
|
@ -145,14 +207,14 @@ func create_app_data_layout_for_migration(path: String) -> Dictionary:
|
|||
var normalized: String = _normalize(path)
|
||||
if not _validate_candidate(normalized, false):
|
||||
return {"ok": false, "message": error_message}
|
||||
var manifest_path: String = normalized.path_join(MANIFEST_FILENAME)
|
||||
if FileAccess.file_exists(manifest_path):
|
||||
var manifest_path: String = _existing_manifest_path(normalized)
|
||||
if not manifest_path.is_empty():
|
||||
var manifest: Dictionary = _read_json(manifest_path)
|
||||
return (
|
||||
{"ok": true, "root_id": str(manifest.get("root_id", ""))}
|
||||
if _valid_manifest(manifest)
|
||||
else {"ok": false, "message": "The app-data manifest is malformed."}
|
||||
)
|
||||
if not _valid_manifest(manifest):
|
||||
return {"ok": false, "message": "The app-data manifest is malformed."}
|
||||
if not _upgrade_legacy_manifest(normalized, manifest, manifest_path):
|
||||
return {"ok": false, "message": "The app-data manifest could not be upgraded."}
|
||||
return {"ok": true, "root_id": str(manifest.get("root_id", ""))}
|
||||
var id: String = Crypto.new().generate_random_bytes(16).hex_encode()
|
||||
if not _create_layout(normalized, id):
|
||||
return {"ok": false, "message": "The app-data layout could not be created."}
|
||||
|
|
@ -236,12 +298,14 @@ func _activate_existing(path: String, expected_id: String, permit_creation: bool
|
|||
var normalized: String = _normalize(path)
|
||||
if not _validate_candidate(normalized, permit_creation):
|
||||
return false
|
||||
var manifest_path: String = normalized.path_join(MANIFEST_FILENAME)
|
||||
if not FileAccess.file_exists(manifest_path):
|
||||
var manifest_path: String = _existing_manifest_path(normalized)
|
||||
if manifest_path.is_empty():
|
||||
return _fail("The selected folder is not a straywild data folder.")
|
||||
var manifest: Dictionary = _read_json(manifest_path)
|
||||
if not _valid_manifest(manifest):
|
||||
return _fail("The straywild data-folder manifest is malformed.")
|
||||
if not _upgrade_legacy_manifest(normalized, manifest, manifest_path):
|
||||
return _fail("The legacy data-folder manifest could not be upgraded.")
|
||||
var found_id: String = str(manifest["root_id"])
|
||||
if not expected_id.is_empty() and expected_id != found_id:
|
||||
return _fail("The selected data folder does not match this device pointer.")
|
||||
|
|
@ -422,12 +486,71 @@ func _valid_manifest(data: Dictionary) -> bool:
|
|||
return (
|
||||
data.get("format_version") == MANIFEST_VERSION
|
||||
and data.get("layout_version") == LAYOUT_VERSION
|
||||
and data.get("application") == APPLICATION_ID
|
||||
and data.get("application") in [APPLICATION_ID, LEGACY_APPLICATION_ID]
|
||||
and typeof(data.get("root_id")) == TYPE_STRING
|
||||
and str(data["root_id"]).length() == 32
|
||||
)
|
||||
|
||||
|
||||
func _existing_manifest_path(path: String) -> String:
|
||||
var current: String = path.path_join(MANIFEST_FILENAME)
|
||||
if FileAccess.file_exists(current):
|
||||
return current
|
||||
var legacy: String = path.path_join(LEGACY_MANIFEST_FILENAME)
|
||||
return legacy if FileAccess.file_exists(legacy) else ""
|
||||
|
||||
|
||||
func _upgrade_legacy_manifest(
|
||||
path: String,
|
||||
manifest: Dictionary,
|
||||
manifest_path: String,
|
||||
) -> bool:
|
||||
if (
|
||||
manifest_path.get_file() == MANIFEST_FILENAME
|
||||
and manifest.get("application") == APPLICATION_ID
|
||||
):
|
||||
return true
|
||||
var upgraded: Dictionary = manifest.duplicate(true)
|
||||
upgraded["application"] = APPLICATION_ID
|
||||
upgraded["last_opened_at_unix"] = int(Time.get_unix_time_from_system())
|
||||
return _write_text_atomic(
|
||||
path.path_join(MANIFEST_FILENAME),
|
||||
JSON.stringify(upgraded, "\t"),
|
||||
)
|
||||
|
||||
|
||||
func _available_environment_variable(primary: String, legacy: String) -> String:
|
||||
if OS.has_environment(primary):
|
||||
return primary
|
||||
return legacy if OS.has_environment(legacy) else ""
|
||||
|
||||
|
||||
func _environment_flag(primary: String, legacy: String) -> bool:
|
||||
var variable: String = _available_environment_variable(primary, legacy)
|
||||
return not variable.is_empty() and OS.get_environment(variable) == "1"
|
||||
|
||||
|
||||
func _copy_legacy_user_file(source: String, destination: String) -> bool:
|
||||
var bytes: PackedByteArray = PortableFileGuard.read_bytes(
|
||||
source,
|
||||
16 * 1024 * 1024,
|
||||
)
|
||||
if bytes.is_empty() and FileAccess.get_open_error() != OK:
|
||||
return false
|
||||
if DirAccess.make_dir_recursive_absolute(destination.get_base_dir()) != OK:
|
||||
return false
|
||||
var file: FileAccess = FileAccess.open(destination, FileAccess.WRITE)
|
||||
if file == null:
|
||||
return false
|
||||
file.store_buffer(bytes)
|
||||
file.flush()
|
||||
var copied: bool = file.get_error() == OK
|
||||
file.close()
|
||||
if copied and source.ends_with(".key"):
|
||||
FileAccess.set_unix_permissions(destination, 384)
|
||||
return copied
|
||||
|
||||
|
||||
func _directory_is_empty(path: String) -> bool:
|
||||
var access: DirAccess = DirAccess.open(path)
|
||||
if access == null:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue