2023-12-13 12:29:25 +01:00
|
|
|
class_name RendererPersistence
|
|
|
|
## An interface for per-renderer persistent data.
|
|
|
|
##
|
|
|
|
## Allows renderers to store key/value data, split into namespaces (usually the renderer name)
|
|
|
|
## and channels (files on a filesystem). For every namespace, a folder is created,
|
|
|
|
## and populated with JSON files named after the channel name.
|
|
|
|
|
|
|
|
|
|
|
|
const _BASE_PATH := "user://renderer_persistence/"
|
|
|
|
const _FILE_TEMPLATE := "{namespace}/{channel}.json"
|
|
|
|
|
|
|
|
# Dictionary[String -> namespace, Dictionary[String -> channel, Variant]]
|
|
|
|
static var _data: Dictionary
|
|
|
|
|
|
|
|
#region API
|
|
|
|
## Initializes a namespace. Returns [code]false[/code] if the folder for the namespace didn't exist
|
|
|
|
## before, useful for initializing defaults.
|
|
|
|
static func init_namespace(name_space: String) -> bool:
|
|
|
|
var n := name_space.validate_filename()
|
|
|
|
if _data.get(n) != null:
|
|
|
|
return true
|
|
|
|
|
|
|
|
var err := _create_namespace_folder(n)
|
|
|
|
if err != OK:
|
2023-12-14 21:52:22 +01:00
|
|
|
DeckHolder.logger.log_system("Could not create namespace: error %s" % err, Logger.LogType.ERROR)
|
2023-12-13 12:29:25 +01:00
|
|
|
return false
|
|
|
|
_data[name_space] = {}
|
|
|
|
return false
|
|
|
|
|
|
|
|
|
|
|
|
## Returns the corresponding value for the given [param key] in the [param channel] storage.
|
|
|
|
## If the key does not exist, returns [param default], or [code]null[/code] if the parameter is omitted.
|
|
|
|
static func get_value(name_space: String, channel: String, key: String, default: Variant) -> Variant:
|
|
|
|
_lazy_load_channel(name_space, channel)
|
|
|
|
var validated_name := name_space.validate_filename()
|
|
|
|
var validated_channel := channel.validate_filename()
|
|
|
|
|
|
|
|
return (_data[validated_name][validated_channel] as Dictionary).get(key, default)
|
|
|
|
|
|
|
|
|
|
|
|
## Sets the value in the [param channel] storage at [param key] to [param value].
|
|
|
|
static func set_value(name_space: String, channel: String, key: String, value: Variant) -> void:
|
|
|
|
_lazy_load_channel(name_space, channel)
|
|
|
|
var validated_name := name_space.validate_filename()
|
|
|
|
var validated_channel := channel.validate_filename()
|
|
|
|
|
|
|
|
_data[validated_name][validated_channel][key] = value
|
|
|
|
|
|
|
|
|
|
|
|
## Returns the corresponding value for the given [param key] in the [param channel] storage.
|
|
|
|
## If the key does not exist, creates it in the storage, initializes it to [param default]
|
|
|
|
## and returns it.
|
|
|
|
static func get_or_create(name_space: String, channel: String, key: String, default: Variant) -> Variant:
|
|
|
|
_lazy_load_channel(name_space, channel)
|
|
|
|
var validated_name := name_space.validate_filename()
|
|
|
|
var validated_channel := channel.validate_filename()
|
|
|
|
|
|
|
|
var channel_data: Dictionary = _data[validated_name][validated_channel]
|
|
|
|
return _get_or_create(channel_data, key, default)
|
|
|
|
|
|
|
|
|
|
|
|
## Erases an entry in the store by [param key] and returns its previous value,
|
|
|
|
## or [code]null[/code] if it didn't exist.
|
|
|
|
static func erase(name_space: String, channel: String, key: String) -> Variant:
|
|
|
|
_lazy_load_channel(name_space, channel)
|
|
|
|
var validated_name := name_space.validate_filename()
|
|
|
|
var validated_channel := channel.validate_filename()
|
|
|
|
|
|
|
|
var channel_data: Dictionary = _data[validated_name][validated_channel]
|
|
|
|
var ret = channel_data.get(key)
|
|
|
|
channel_data.erase(key)
|
|
|
|
|
|
|
|
return ret
|
|
|
|
|
|
|
|
|
|
|
|
## Commits the [param channel] to the filesystem. If [param channel] is empty,
|
|
|
|
## commits all channels in the [param name_space].
|
|
|
|
static func commit(name_space: String, channel: String = "") -> void:
|
|
|
|
if !channel.is_empty():
|
|
|
|
_commit_channel(name_space, channel)
|
|
|
|
else:
|
|
|
|
for c: String in _data[name_space]:
|
|
|
|
_commit_channel(name_space, c)
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
static func _get_or_create(d: Dictionary, key: String, default: Variant) -> Variant:
|
|
|
|
var r = d.get(key)
|
|
|
|
if r == null:
|
|
|
|
r = default
|
|
|
|
d[key] = r
|
|
|
|
|
|
|
|
return r
|
|
|
|
|
|
|
|
|
|
|
|
static func _create_namespace_folder(name_space: String) -> Error:
|
|
|
|
return DirAccess.make_dir_recursive_absolute(_BASE_PATH.path_join(name_space))
|
|
|
|
|
|
|
|
|
|
|
|
static func _lazy_load_channel(name_space: String, channel: String) -> void:
|
|
|
|
var validated_name := name_space.validate_filename()
|
|
|
|
var validated_channel := channel.validate_filename()
|
|
|
|
if !_data.has(validated_name):
|
2023-12-14 21:52:22 +01:00
|
|
|
DeckHolder.logger.log_system("Namespace %s is not initialized" % name_space, Logger.LogType.ERROR)
|
2023-12-13 12:29:25 +01:00
|
|
|
return
|
|
|
|
|
|
|
|
if (_data[validated_name] as Dictionary).has(validated_channel):
|
|
|
|
return
|
|
|
|
|
|
|
|
var path := _BASE_PATH.path_join(_FILE_TEMPLATE.format(
|
|
|
|
{
|
|
|
|
"namespace": validated_name,
|
|
|
|
"channel": validated_channel
|
|
|
|
}))
|
|
|
|
|
|
|
|
var f := FileAccess.open(path, FileAccess.READ)
|
|
|
|
if f == null:
|
|
|
|
_data[validated_name][validated_channel] = {}
|
|
|
|
return
|
|
|
|
|
|
|
|
var data = JSON.parse_string(f.get_as_text())
|
|
|
|
_data[validated_name][validated_channel] = data
|
|
|
|
|
|
|
|
|
|
|
|
static func _commit_channel(name_space: String, channel: String) -> void:
|
|
|
|
_lazy_load_channel(name_space, channel)
|
|
|
|
var validated_name := name_space.validate_filename()
|
|
|
|
var validated_channel := channel.validate_filename()
|
|
|
|
|
|
|
|
var path := _BASE_PATH.path_join(_FILE_TEMPLATE.format(
|
|
|
|
{
|
|
|
|
"namespace": validated_name,
|
|
|
|
"channel": validated_channel
|
|
|
|
}))
|
|
|
|
var f := FileAccess.open(path, FileAccess.WRITE)
|
|
|
|
f.store_string(JSON.stringify(_data[validated_name][validated_channel], "\t", false))
|