# 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 . 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