CORE/src/Storage.gd
2024-03-21 15:47:39 +01:00

159 lines
6.5 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
var is_open: bool = false
var storage: Dictionary = {}
var storage_location: String = ""
func open_storage(location: String, create_new: bool = true, sanity_check: bool = true, fail_on_sanity_check: bool = false) -> bool:
if is_open:
logger.errorf("Storage", "Failed to open storage: A storage file is already open")
return false
logger.verbf("Storage", "Opening storage file at \"" + location + "\"")
var file: FileAccess
if !FileAccess.file_exists(location):
if create_new:
file = FileAccess.open(location, FileAccess.WRITE)
if file == null:
await logger.crashf("Storage", "Could not open storage file at \"" + location + "\": Failed with code " + str(FileAccess.get_open_error()))
return false
file.store_string("{}")
file.close()
else:
logger.errorf("Storage", "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.errorf("Storage", "Failed to open storage: Parsed storage file is of type " + str(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.errorf("Storage", "Sanity check failed (stopping):")
for error in check_result:
logger.errorf("Storage", "-> " + error)
return false
else:
logger.warnf("Storage", "Sanity check failed (continuing anyway):")
for error in check_result:
logger.warnf("Storage", "-> " + error)
storage = storage_temp
storage_location = location
is_open = true
return true
func close_storage() -> bool:
if !is_open:
logger.errorf("Storage", "Failed to close storage: No storage file is open")
return false
logger.verbf("Storage", "Closing storage file")
storage = {}
is_open = false
return true
func save_storage() -> bool:
if !is_open:
logger.errorf("Storage", "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.crashf("Storage", "Could not open storage file at \"" + storage_location + "\": Failed with code " + str(FileAccess.get_open_error()))
return false
logger.diagf("Storage", "Writing storage file to disk")
file.store_string(JSON.stringify(storage))
file.close()
return true
func nuke_storage(autosave: bool = true) -> bool:
if !is_open:
logger.errorf("Storage", "Failed to nuke storage: No storage file is open")
return false
logger.warnf("Storage", "Nuking storage")
storage = {}
if autosave: save_storage()
return true
func get_key(key: String, default: Variant) -> Variant:
if !is_open:
logger.errorf("Storage", "Failed to get key: No storage file is open")
return NAN
logger.diagf("Storage", "Returning storage key \"" + key + "\" (default='" + default + "')")
return storage.get(key, default)
func set_key(key: String, value: Variant, overwrite: bool = true, autosave: bool = true) -> bool:
if !is_open:
logger.errorf("Storage", "Failed to set key: No storage file is open")
return false
logger.diagf("Storage", "Updating storage key \"" + key + "\" with value '" + str(value) + "' (overwrite='" + str(overwrite) + "' autosave='" + str(autosave) + "'")
storage.merge({key: value}, overwrite)
if autosave: save_storage()
return true
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.diagf("Storage", "Deleting storage key \"" + key + "\" (autosave='" + str(autosave) + "')")
storage.erase(key)
if autosave: save_storage()
return true
func get_dict() -> Dictionary:
if !is_open:
logger.errorf("Storage", "Failed to get dictionary: No storage file is open")
return {}
logger.verbf("Storage", "Returning storage dictionary")
return storage
func save_dict(dict: Dictionary, sanity_check: bool = true, fail_on_sanity_check: bool = false, autosave: bool = true) -> bool:
if !is_open:
logger.errorf("Storage", "Failed to save dictionary: No storage file is open")
return false
logger.verbf("Storage", "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.errorf("Storage", "Sanity check failed (stopping):")
for error in check_result:
logger.errorf("Storage", "-> " + error)
return false
else:
logger.warnf("Storage", "Sanity check failed (continuing anyway):")
for error in check_result:
logger.warnf("Storage", "-> " + error)
storage = dict
if autosave: save_storage()
return true
func perform_sanity_check(storage_check: Dictionary) -> Array[String]:
logger.verbf("Storage", "Performing a sanity check on some storage dictionary")
var errors: Array[String] = []
for key in storage_check:
if typeof(key) != TYPE_STRING:
errors.append("Key \"" + str(key) + "\" is not of type String (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("The value of \"" + key + "\" is not null, a string, an integer, a float, boolean, array or dictionary (type '" + type_string(typeof(key)) + "')")
logger.verbf("Storage", "Completed sanity check with " + str(errors.size()) + " errors")
return errors