A new row, "Downloaded pictures and songs", shows how much space the cached character pictures, covers and downloaded songs take, with a Clear cache button. Clearing deletes them, but keeps the songs the current quiz uses, the settings, config.ini and the saved quiz. The characters are loaded again at once; covers come back when they are shown. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
141 lines
4.4 KiB
GDScript
141 lines
4.4 KiB
GDScript
class_name FileCache
|
|
extends RefCounted
|
|
## Downloaded files kept in a folder next to the settings, with an [code]index.json[/code]
|
|
## that records for each file when the server last changed it and when it was
|
|
## downloaded:
|
|
## [codeblock]
|
|
## {"Crono.png": {"modified": 1730000000, "downloaded": 1730100000}}
|
|
## [/codeblock]
|
|
## Used by [CharacterCache] and [CoverCache], which decide when a file is fresh.
|
|
|
|
const INDEX_FILE_NAME: String = "index.json"
|
|
## The change time of a file when the server can't tell it.
|
|
const UNKNOWN_TIME: int = -1
|
|
|
|
## The folder's name in [method Settings.config_dir].
|
|
var folder_name: String
|
|
|
|
# What index.json holds: key -> {"modified": int, "downloaded": int}
|
|
var _index: Dictionary = {}
|
|
|
|
|
|
func _init(new_folder_name: String) -> void:
|
|
folder_name = new_folder_name
|
|
|
|
|
|
## The folder the files are kept in.
|
|
func folder() -> String:
|
|
return Settings.config_dir().path_join(folder_name)
|
|
|
|
|
|
## Reads [code]index.json[/code]. A missing or broken index starts empty.
|
|
func load_index() -> void:
|
|
_index = {}
|
|
var path: String = folder().path_join(INDEX_FILE_NAME)
|
|
if FileAccess.file_exists(path):
|
|
var parsed: Variant = JSON.parse_string(FileAccess.get_file_as_string(path))
|
|
if parsed is Dictionary:
|
|
_index = parsed
|
|
|
|
|
|
## The keys of the files in the index.
|
|
func keys() -> Array:
|
|
return _index.keys()
|
|
|
|
|
|
## Whether the file for [param key] is in the index and on disk.
|
|
func has_file(key: String) -> bool:
|
|
return _index.has(key) && FileAccess.file_exists(file_path(key))
|
|
|
|
|
|
## When the server last changed the file for [param key], as recorded when it was
|
|
## downloaded, or [constant UNKNOWN_TIME].
|
|
func modified(key: String) -> int:
|
|
return int(_index.get(key, {}).get("modified", UNKNOWN_TIME))
|
|
|
|
|
|
## How many seconds ago the file for [param key] was downloaded.
|
|
func age(key: String) -> float:
|
|
return Time.get_unix_time_from_system() - float(_index.get(key, {}).get("downloaded", 0))
|
|
|
|
|
|
## The file for [param key].
|
|
func read(key: String) -> PackedByteArray:
|
|
return FileAccess.get_file_as_bytes(file_path(key))
|
|
|
|
|
|
## Writes [param bytes] as the file for [param key], last changed on the server at
|
|
## [param modified_time], and saves the index. Returns false if it could not be written.
|
|
func store(key: String, bytes: PackedByteArray, modified_time: int) -> bool:
|
|
DirAccess.make_dir_recursive_absolute(folder())
|
|
var file: FileAccess = FileAccess.open(file_path(key), FileAccess.WRITE)
|
|
if file == null:
|
|
GameLog.warning("Could not cache ", key, " in ", folder_name, ": ",
|
|
error_string(FileAccess.get_open_error()))
|
|
return false
|
|
file.store_buffer(bytes)
|
|
file.close()
|
|
_index[key] = {
|
|
"modified": modified_time,
|
|
"downloaded": int(Time.get_unix_time_from_system()),
|
|
}
|
|
save_index()
|
|
return true
|
|
|
|
|
|
## Deletes the file for [param key] and its entry, and saves the index.
|
|
func remove(key: String) -> void:
|
|
DirAccess.remove_absolute(file_path(key))
|
|
if _index.erase(key):
|
|
save_index()
|
|
|
|
|
|
## Deletes every file whose key is not in [param kept], e.g. files of things the server
|
|
## no longer has.
|
|
func keep_only(kept: Dictionary) -> void:
|
|
var removed: bool = false
|
|
for key: String in _index.keys():
|
|
if !kept.has(key):
|
|
DirAccess.remove_absolute(file_path(key))
|
|
_index.erase(key)
|
|
removed = true
|
|
if removed:
|
|
save_index()
|
|
|
|
|
|
## Deletes every file in the folder, the index too, e.g. to free the space they take.
|
|
func clear() -> void:
|
|
_index = {}
|
|
if !DirAccess.dir_exists_absolute(folder()):
|
|
return
|
|
for file_name: String in DirAccess.get_files_at(folder()):
|
|
DirAccess.remove_absolute(folder().path_join(file_name))
|
|
|
|
|
|
## How many bytes the files in [param dir] take, leaving out the paths in [param skip]
|
|
## and any subfolders. 0 when the folder doesn't exist.
|
|
static func folder_size(dir: String, skip: PackedStringArray = []) -> int:
|
|
if !DirAccess.dir_exists_absolute(dir):
|
|
return 0
|
|
var total: int = 0
|
|
for file_name: String in DirAccess.get_files_at(dir):
|
|
var path: String = dir.path_join(file_name)
|
|
var file: FileAccess = FileAccess.open(path, FileAccess.READ)
|
|
if file != null && !skip.has(path):
|
|
total += file.get_length()
|
|
return total
|
|
|
|
|
|
func save_index() -> void:
|
|
DirAccess.make_dir_recursive_absolute(folder())
|
|
var file: FileAccess = FileAccess.open(folder().path_join(INDEX_FILE_NAME),
|
|
FileAccess.WRITE)
|
|
if file != null:
|
|
file.store_string(JSON.stringify(_index, "\t", true))
|
|
|
|
|
|
## Where the file for [param key] is. Keys come from the server, so they are made safe
|
|
## as file names.
|
|
func file_path(key: String) -> String:
|
|
return folder().path_join(key.validate_filename())
|