CORE/src/storage.gd
JeremyStarTM acc305d3db
Update module logging and update many log calls
This commit firstly removes the 'logger' variable in CoreBaseModule, secondly renames 'loggeri' to 'logger' in CoreBaseModule, effectively replacing it, and thirdly it forces using 'stringify_variables' onto all log calls.
2024-04-25 20:20:34 +02:00

183 lines
7.8 KiB
GDScript

# CORE FRAMEWORK SOURCE FILE
# Copyright (c) 2024 The StarOpenSource Project & Contributors
# Licensed under the GNU Affero General Public License v3
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
extends CoreBaseModule
## Easy config management.
##
## Allows you to read and write configuration files with ease, without any headaches.
## Indicates if a storage file is currently open.[br]
## [b]Danger: [i]Don't modify this.[/i][/b]
var is_open: bool = false
# The parsed data inside the storage file.
var storage: Dictionary = {}
# The location of the storage file.
var storage_location: String = ""
# +++ file management +++
## Opens a storage file and loads it into memory.
func open_storage(location: String, create_new: bool = true, sanity_check: bool = true, fail_on_sanity_check: bool = false) -> bool:
if is_open:
logger.error("Failed to open storage: A storage file is already open")
return false
logger.verb(core.stringify_variables("Opening storage file at %location%", { "location": location }))
var file: FileAccess
if !FileAccess.file_exists(location):
if create_new:
file = FileAccess.open(location, FileAccess.WRITE)
if file == null:
await logger.crash(core.stringify_variables("Could not open storage file at %location%: Failed with error %error%", { "location": location, "error": error_string(FileAccess.get_open_error()) }))
return false
file.store_string("{}")
file.close()
else:
logger.error("Failed to open storage: create_new is set to false")
return false
file = FileAccess.open(location, FileAccess.READ)
var storage_temp: Variant = file.get_as_text()
file.close()
storage_temp = JSON.parse_string(storage_temp)
if typeof(storage_temp) != TYPE_DICTIONARY:
logger.error(core.stringify_variables("Failed to open storage: Parsed storage file is of type %type%", { "type": type_string(typeof(storage_temp)) }))
return false
if sanity_check:
var check_result: Array[String] = perform_sanity_check(storage_temp)
if check_result.size() != 0:
if fail_on_sanity_check:
logger.error("Sanity check failed (stopping):")
for error in check_result:
logger.error("-> " + error)
return false
else:
logger.warn("Sanity check failed (continuing anyway):")
for error in check_result:
logger.warn("-> " + error)
storage = storage_temp
storage_location = location
is_open = true
return true
## Closes the active storage file.
func close_storage() -> bool:
if !is_open:
logger.error("Failed to close storage: No storage file is open")
return false
logger.verb("Closing storage file")
storage = {}
is_open = false
return true
## Saves the active storage file to disk.
func save_storage() -> bool:
if !is_open:
logger.error("Failed to save storage: No storage file is open")
return false
var file: FileAccess = FileAccess.open(storage_location, FileAccess.WRITE)
if file == null:
await logger.crash(core.stringify_variables("Could not open storage file at %location%: Failed with error %error%", { "location": storage_location, "error": error_string(FileAccess.get_open_error()) }))
return false
logger.diag("Writing storage file to disk")
file.store_string(JSON.stringify(storage))
file.close()
return true
# +++ config manipulation +++
## Removes all keys from the active storage file. The nuclear option basically.
func nuke_storage(autosave: bool = true) -> bool:
if !is_open:
logger.error("Failed to nuke storage: No storage file is open")
return false
logger.warn("Nuking storage")
storage = {}
if autosave: save_storage()
return true
## Returns a storage key. Can also return a default value if unset.
func get_key(key: String, default: Variant = null) -> Variant:
if !is_open:
logger.error("Failed to get key: No storage file is open")
return NAN
logger.diag(core.stringify_variables("Returning storage key %key% (default=%default%)", { "key": key, "default": default }))
return storage.get(key, default)
## Updates a storage key with the specified value.
func set_key(key: String, value: Variant, overwrite: bool = true, autosave: bool = true) -> bool:
if !is_open:
logger.error("Failed to set key: No storage file is open")
return false
logger.diag(core.stringify_variables("Updating storage key %key% with value %value% (overwrite=%overwrite% autosave=%autosave%)", { "key": key, "value": value, "overwrite": overwrite, "autosave": autosave }))
storage.merge({key: value}, overwrite)
if autosave: save_storage()
return true
## Deletes a storage key.
func del_key(key: String, autosave: bool = true) -> bool:
if !is_open:
logger.errof("storage", "Failed to delete key: No storage file is open")
return false
logger.diag(core.stringify_variables("Deleting storage key %key% (autosave=%autosave%)", { "key": key, "autosave": autosave }))
storage.erase(key)
if autosave: save_storage()
return true
## Returns the [param storage] [class Dictionary], useful for more advanced operations.[br]
## Changes are not reflected onto the [param storage] though. To save changes through this module,
## pass your modified [class Dictionary to [method safe_dict].
func get_dict() -> Dictionary:
if !is_open:
logger.error("Failed to get dictionary: No storage file is open")
return {}
logger.verb("Returning storage dictionary")
return storage
# +++ raw manipulation +++
## Saves a arbitrary dictionary as a [param storage] [class Dictionary] with sanity checking ([code]sanity_check[/code] and [code]fail_on_sanity_check[/code]).
func save_dict(dict: Dictionary, sanity_check: bool = true, fail_on_sanity_check: bool = false, autosave: bool = true) -> bool:
if !is_open:
logger.error("Failed to save dictionary: No storage file is open")
return false
logger.verb("Saving custom dictionary as storage")
if sanity_check:
var check_result: Array[String] = perform_sanity_check(dict)
if check_result.size() != 0:
if fail_on_sanity_check:
logger.error("Sanity check failed (stopping):")
for error in check_result:
logger.error("-> " + error)
return false
else:
logger.warn("Sanity check failed (continuing anyway):")
for error in check_result:
logger.warn("-> " + error)
storage = dict
if autosave: save_storage()
return true
# +++ etc +++
## Performs sanity checks on a [class Dictionary] to determine if it can be saved and loaded using this module.
func perform_sanity_check(storage_check: Dictionary) -> Array[String]:
logger.verb("Performing a sanity check on some storage dictionary")
var errors: Array[String] = []
for key in storage_check:
if typeof(key) != TYPE_STRING:
errors.append(core.stringify_variables("Key %key% is not of type String (is of type %type%)", { "key": key, "type": type_string(typeof(key)) }))
continue
if typeof(storage_check[key]) != TYPE_NIL and typeof(storage_check[key]) != TYPE_STRING and typeof(storage_check[key]) != TYPE_INT and typeof(storage_check[key]) != TYPE_FLOAT and typeof(storage_check[key]) != TYPE_BOOL and typeof(storage_check[key]) != TYPE_ARRAY and typeof(storage_check[key]) != TYPE_DICTIONARY:
errors.append(core.stringify_variables("The value of %key% is not null, a String, an int, a float, boolean, an Array or a Dictionary (is of type %type%)", { "key": key, "type": type_string(typeof(key)) }))
logger.verb(core.stringify_variables("Completed sanity check with %errors% errors", { "errors": errors.size() }))
return errors