Add Clear cache to the settings
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>
This commit is contained in:
1 parent
06421bb63e
commit
c03c0e4efe
10 files changed
+1216
-679
No files matched your search
@@ -34,6 +34,11 @@ static func folder() -> String:
|
|||||||
return Settings.config_dir().path_join(FOLDER_NAME)
|
return Settings.config_dir().path_join(FOLDER_NAME)
|
||||||
|
|
||||||
|
|
||||||
|
## Deletes every cached picture. They are downloaded again when next asked for.
|
||||||
|
func clear() -> void:
|
||||||
|
_files.clear()
|
||||||
|
|
||||||
|
|
||||||
## Fetches the names of the characters and calls [param on_done] with them as an
|
## Fetches the names of the characters and calls [param on_done] with them as an
|
||||||
## [code]Array[String][/code]. Cached pictures of characters no longer on the server are
|
## [code]Array[String][/code]. Cached pictures of characters no longer on the server are
|
||||||
## deleted.
|
## deleted.
|
||||||
|
|||||||
@@ -38,6 +38,11 @@ static func folder() -> String:
|
|||||||
return Settings.config_dir().path_join(FOLDER_NAME)
|
return Settings.config_dir().path_join(FOLDER_NAME)
|
||||||
|
|
||||||
|
|
||||||
|
## Deletes every cached cover. They are downloaded again when next asked for.
|
||||||
|
func clear() -> void:
|
||||||
|
_files.clear()
|
||||||
|
|
||||||
|
|
||||||
## Deletes the covers of soundtracks that are not in [param covers] (UUID -> cover
|
## Deletes the covers of soundtracks that are not in [param covers] (UUID -> cover
|
||||||
## time), and those whose soundtrack no longer has a cover.
|
## time), and those whose soundtrack no longer has a cover.
|
||||||
func keep_only(covers: Dictionary) -> void:
|
func keep_only(covers: Dictionary) -> void:
|
||||||
|
|||||||
@@ -103,6 +103,29 @@ func keep_only(kept: Dictionary) -> void:
|
|||||||
save_index()
|
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:
|
func save_index() -> void:
|
||||||
DirAccess.make_dir_recursive_absolute(folder())
|
DirAccess.make_dir_recursive_absolute(folder())
|
||||||
var file: FileAccess = FileAccess.open(folder().path_join(INDEX_FILE_NAME),
|
var file: FileAccess = FileAccess.open(folder().path_join(INDEX_FILE_NAME),
|
||||||
|
|||||||
@@ -175,6 +175,15 @@ func store_song(song_object: SongObject, mp3: PackedByteArray) -> void:
|
|||||||
|
|
||||||
|
|
||||||
## Deletes the files of the songs downloaded so far, except those in [param keep].
|
## Deletes the files of the songs downloaded so far, except those in [param keep].
|
||||||
|
## The downloaded song files the playlist uses, which must be kept while it does.
|
||||||
|
func song_files_in_use() -> PackedStringArray:
|
||||||
|
var files: PackedStringArray = []
|
||||||
|
for song_object: SongObject in song_object_array:
|
||||||
|
if !song_object.song_path.is_empty():
|
||||||
|
files.append(song_object.song_path)
|
||||||
|
return files
|
||||||
|
|
||||||
|
|
||||||
func clear_song_files(keep: PackedStringArray = []) -> void:
|
func clear_song_files(keep: PackedStringArray = []) -> void:
|
||||||
if !DirAccess.dir_exists_absolute(songs_folder()):
|
if !DirAccess.dir_exists_absolute(songs_folder()):
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ func _ready() -> void:
|
|||||||
top_bar.settings_pressed.connect(settings_popup.open)
|
top_bar.settings_pressed.connect(settings_popup.open)
|
||||||
settings_popup.server_changed.connect(quiz.on_server_changed)
|
settings_popup.server_changed.connect(quiz.on_server_changed)
|
||||||
settings_popup.characters_changed.connect(quiz.character_select_popup.load_characters)
|
settings_popup.characters_changed.connect(quiz.character_select_popup.load_characters)
|
||||||
|
settings_popup.clear_cache_requested.connect(clear_cache)
|
||||||
settings_popup.visibility_changed.connect(_on_settings_popup_visibility_changed)
|
settings_popup.visibility_changed.connect(_on_settings_popup_visibility_changed)
|
||||||
|
|
||||||
sync_popup.sync_started.connect(_on_sync_started)
|
sync_popup.sync_started.connect(_on_sync_started)
|
||||||
@@ -63,6 +64,14 @@ func _ready() -> void:
|
|||||||
show_about_if_new_version()
|
show_about_if_new_version()
|
||||||
|
|
||||||
|
|
||||||
|
## Deletes the cached character pictures and covers. The characters are loaded again at
|
||||||
|
## once, so the character select has its pictures; covers come when they are shown.
|
||||||
|
func clear_cache() -> void:
|
||||||
|
quiz.character_select_popup.character_cache.clear()
|
||||||
|
statistics_view.covers.clear()
|
||||||
|
quiz.character_select_popup.load_characters()
|
||||||
|
|
||||||
|
|
||||||
# The quiz finds a soundtrack's cover by the cover times in the soundtrack list
|
# The quiz finds a soundtrack's cover by the cover times in the soundtrack list
|
||||||
func _remember_cover_times(soundtracks: Array[Soundtrack]) -> void:
|
func _remember_cover_times(soundtracks: Array[Soundtrack]) -> void:
|
||||||
var times: Dictionary = {}
|
var times: Dictionary = {}
|
||||||
|
|||||||
@@ -253,6 +253,27 @@ layout_mode = 2
|
|||||||
focus_mode = 0
|
focus_mode = 0
|
||||||
text = "Open folder"
|
text = "Open folder"
|
||||||
|
|
||||||
|
[node name="ClearCacheRow" type="HBoxContainer" parent="SettingsPanel/VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Title" type="Label" parent="SettingsPanel/VBoxContainer/ClearCacheRow"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
theme_type_variation = &"SettingsRowLabel"
|
||||||
|
text = "Downloaded pictures and songs"
|
||||||
|
|
||||||
|
[node name="CacheSizeLabel" type="Label" parent="SettingsPanel/VBoxContainer/ClearCacheRow"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="ClearCacheButton" type="Button" parent="SettingsPanel/VBoxContainer/ClearCacheRow"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
tooltip_text = "Deletes the downloaded character pictures, covers and songs.
|
||||||
|
They are downloaded again when needed. Songs in the current quiz are kept."
|
||||||
|
focus_mode = 0
|
||||||
|
text = "Clear cache"
|
||||||
|
|
||||||
[node name="FolderDialog" type="FileDialog" parent="."]
|
[node name="FolderDialog" type="FileDialog" parent="."]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
title = "Choose a music folder"
|
title = "Choose a music folder"
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ signal server_changed
|
|||||||
|
|
||||||
## Emitted when a folder of characters is picked or Use server is pressed for them.
|
## Emitted when a folder of characters is picked or Use server is pressed for them.
|
||||||
signal characters_changed
|
signal characters_changed
|
||||||
|
## Emitted when Clear cache is pressed, for the caches to delete their files.
|
||||||
|
signal clear_cache_requested
|
||||||
|
|
||||||
## Space between the rows and the panel's edges.
|
## Space between the rows and the panel's edges.
|
||||||
const PADDING: Vector2 = Vector2(20, 12)
|
const PADDING: Vector2 = Vector2(20, 12)
|
||||||
@@ -46,6 +48,8 @@ const PADDING: Vector2 = Vector2(20, 12)
|
|||||||
@onready var characters_error_label: Label = %CharactersErrorLabel
|
@onready var characters_error_label: Label = %CharactersErrorLabel
|
||||||
@onready var character_folder_dialog: FileDialog = %CharacterFolderDialog
|
@onready var character_folder_dialog: FileDialog = %CharacterFolderDialog
|
||||||
@onready var open_config_folder_button: Button = %OpenConfigFolderButton
|
@onready var open_config_folder_button: Button = %OpenConfigFolderButton
|
||||||
|
@onready var cache_size_label: Label = %CacheSizeLabel
|
||||||
|
@onready var clear_cache_button: Button = %ClearCacheButton
|
||||||
|
|
||||||
## Shows a folder in the system's file manager. The tests replace it, so no file manager
|
## Shows a folder in the system's file manager. The tests replace it, so no file manager
|
||||||
## opens while they run.
|
## opens while they run.
|
||||||
@@ -77,6 +81,7 @@ func _ready() -> void:
|
|||||||
use_server_characters_button.pressed.connect(use_server_characters)
|
use_server_characters_button.pressed.connect(use_server_characters)
|
||||||
open_config_folder_button.pressed.connect(open_config_folder)
|
open_config_folder_button.pressed.connect(open_config_folder)
|
||||||
open_config_folder_button.tooltip_text = Settings.config_dir()
|
open_config_folder_button.tooltip_text = Settings.config_dir()
|
||||||
|
clear_cache_button.pressed.connect(clear_cache)
|
||||||
play_order_button.item_selected.connect(_on_play_order_selected)
|
play_order_button.item_selected.connect(_on_play_order_selected)
|
||||||
lower_cache_button.pressed.connect(lower_cache)
|
lower_cache_button.pressed.connect(lower_cache)
|
||||||
increase_cache_button.pressed.connect(increase_cache)
|
increase_cache_button.pressed.connect(increase_cache)
|
||||||
@@ -136,6 +141,40 @@ func open_config_folder() -> void:
|
|||||||
show_folder.call(dir)
|
show_folder.call(dir)
|
||||||
|
|
||||||
|
|
||||||
|
## Deletes the downloaded character pictures, covers and songs, except the songs the
|
||||||
|
## quiz uses, and shows how much space that freed.
|
||||||
|
func clear_cache() -> void:
|
||||||
|
var freed: int = cache_size()
|
||||||
|
clear_cache_requested.emit()
|
||||||
|
Playlist.clear_song_files(Playlist.song_files_in_use())
|
||||||
|
cache_size_label.text = "Cleared " + size_text(freed)
|
||||||
|
clear_cache_button.disabled = true
|
||||||
|
|
||||||
|
|
||||||
|
## Shows how much space the downloaded files take.
|
||||||
|
func show_cache_size() -> void:
|
||||||
|
var size: int = cache_size()
|
||||||
|
cache_size_label.text = size_text(size)
|
||||||
|
clear_cache_button.disabled = size == 0
|
||||||
|
|
||||||
|
|
||||||
|
## How many bytes the downloaded character pictures, covers and songs take, leaving out
|
||||||
|
## the songs the quiz uses, which clearing keeps.
|
||||||
|
static func cache_size() -> int:
|
||||||
|
return FileCache.folder_size(CharacterCache.folder()) \
|
||||||
|
+ FileCache.folder_size(CoverCache.folder()) \
|
||||||
|
+ FileCache.folder_size(Playlist.songs_folder(), Playlist.song_files_in_use())
|
||||||
|
|
||||||
|
|
||||||
|
## [param bytes] for people, e.g. "350 KB" or "14.2 MB".
|
||||||
|
static func size_text(bytes: int) -> String:
|
||||||
|
if bytes == 0:
|
||||||
|
return "Nothing"
|
||||||
|
if bytes < 1 << 20:
|
||||||
|
return "%d KB" % ceili(bytes / 1024.0)
|
||||||
|
return "%.1f MB" % (bytes / float(1 << 20))
|
||||||
|
|
||||||
|
|
||||||
## Reads the characters from the folder at [param path] instead of the server. A folder
|
## Reads the characters from the folder at [param path] instead of the server. A folder
|
||||||
## without pictures is not used, and the popup says so.
|
## without pictures is not used, and the popup says so.
|
||||||
func use_character_folder(path: String) -> void:
|
func use_character_folder(path: String) -> void:
|
||||||
@@ -189,6 +228,7 @@ func _use_server(new_server: int) -> void:
|
|||||||
func _on_visibility_changed() -> void:
|
func _on_visibility_changed() -> void:
|
||||||
if visible:
|
if visible:
|
||||||
_update_fullscreen_button()
|
_update_fullscreen_button()
|
||||||
|
show_cache_size()
|
||||||
random_mode_button.select(Settings.random_mode)
|
random_mode_button.select(Settings.random_mode)
|
||||||
add_to_database_button.set_pressed_no_signal(Settings.add_to_stats)
|
add_to_database_button.set_pressed_no_signal(Settings.add_to_stats)
|
||||||
else:
|
else:
|
||||||
|
|||||||
+1003
-679
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,100 @@
|
|||||||
|
extends MainWindowTestSuite
|
||||||
|
## Clear cache in the settings: deletes the downloaded character pictures, covers and
|
||||||
|
## songs, but not the songs the quiz uses, the settings or the saved quiz.
|
||||||
|
|
||||||
|
var _popup: SettingsPopup
|
||||||
|
|
||||||
|
|
||||||
|
func before_test() -> void:
|
||||||
|
# The cache is kept next to the settings, which the tests keep in a folder of their own
|
||||||
|
OS.set_environment(Settings.CONFIG_DIR_ENV, create_temp_dir("clear_cache_%d" % randi()))
|
||||||
|
await super()
|
||||||
|
_popup = _window.get_node("SettingsPopup")
|
||||||
|
|
||||||
|
|
||||||
|
func _write(path: String, size: int) -> void:
|
||||||
|
DirAccess.make_dir_recursive_absolute(path.get_base_dir())
|
||||||
|
var file: FileAccess = FileAccess.open(path, FileAccess.WRITE)
|
||||||
|
var bytes: PackedByteArray = PackedByteArray()
|
||||||
|
bytes.resize(size)
|
||||||
|
file.store_buffer(bytes)
|
||||||
|
file.close()
|
||||||
|
|
||||||
|
|
||||||
|
func _files_in(dir: String) -> PackedStringArray:
|
||||||
|
return DirAccess.get_files_at(dir) if DirAccess.dir_exists_absolute(dir) \
|
||||||
|
else PackedStringArray()
|
||||||
|
|
||||||
|
|
||||||
|
func _fill_cache() -> String:
|
||||||
|
_write(CharacterCache.folder().path_join("Crono.png"), 3000)
|
||||||
|
_write(CoverCache.folder().path_join("st-1"), 2000)
|
||||||
|
var old_song: String = Playlist.songs_folder().path_join("old.mp3")
|
||||||
|
_write(old_song, 5000)
|
||||||
|
return old_song
|
||||||
|
|
||||||
|
|
||||||
|
func test_shows_how_much_the_cache_takes() -> void:
|
||||||
|
_fill_cache()
|
||||||
|
|
||||||
|
_popup.open()
|
||||||
|
|
||||||
|
assert_str(_popup.cache_size_label.text).is_equal(
|
||||||
|
SettingsPopup.size_text(SettingsPopup.cache_size()))
|
||||||
|
assert_int(SettingsPopup.cache_size()).is_greater_equal(10000)
|
||||||
|
assert_bool(_popup.clear_cache_button.disabled).is_false()
|
||||||
|
|
||||||
|
|
||||||
|
func test_clear_cache_deletes_pictures_covers_and_old_songs() -> void:
|
||||||
|
var old_song: String = _fill_cache()
|
||||||
|
var playing: PackedStringArray = Playlist.song_files_in_use()
|
||||||
|
assert_array(playing).is_not_empty()
|
||||||
|
var lists_before: int = _server.count("/character/list")
|
||||||
|
_popup.open()
|
||||||
|
|
||||||
|
_popup.clear_cache()
|
||||||
|
|
||||||
|
assert_array(_files_in(CharacterCache.folder())).is_empty()
|
||||||
|
assert_array(_files_in(CoverCache.folder())).is_empty()
|
||||||
|
assert_bool(FileAccess.file_exists(old_song)).is_false()
|
||||||
|
# The quiz's songs stay, so they can still be played
|
||||||
|
for path: String in playing:
|
||||||
|
assert_bool(FileAccess.file_exists(path)).is_true()
|
||||||
|
assert_str(_popup.cache_size_label.text).starts_with("Cleared ")
|
||||||
|
assert_bool(_popup.clear_cache_button.disabled).is_true()
|
||||||
|
# The characters are loaded again at once
|
||||||
|
assert_int(_server.count("/character/list")).is_greater(lists_before)
|
||||||
|
|
||||||
|
|
||||||
|
func test_clear_cache_keeps_the_settings_and_the_saved_quiz() -> void:
|
||||||
|
Settings.save_settings()
|
||||||
|
_quiz.save_soon()
|
||||||
|
await _runner.simulate_frames(1)
|
||||||
|
_popup.open()
|
||||||
|
|
||||||
|
_popup.clear_cache()
|
||||||
|
|
||||||
|
assert_bool(FileAccess.file_exists(Settings.saved_settings_path())).is_true()
|
||||||
|
assert_bool(FileAccess.file_exists(QuizSave.path())).is_true()
|
||||||
|
|
||||||
|
|
||||||
|
func test_nothing_to_clear() -> void:
|
||||||
|
for dir: String in [CharacterCache.folder(), CoverCache.folder()]:
|
||||||
|
for file_name: String in _files_in(dir):
|
||||||
|
DirAccess.remove_absolute(dir.path_join(file_name))
|
||||||
|
Playlist.clear_song_files(Playlist.song_files_in_use())
|
||||||
|
|
||||||
|
_popup.open()
|
||||||
|
|
||||||
|
assert_str(_popup.cache_size_label.text).is_equal("Nothing")
|
||||||
|
assert_bool(_popup.clear_cache_button.disabled).is_true()
|
||||||
|
|
||||||
|
|
||||||
|
@warning_ignore("unused_parameter")
|
||||||
|
func test_size_text(bytes: int, text: String, test_parameters: Array = [
|
||||||
|
[0, "Nothing"],
|
||||||
|
[1, "1 KB"],
|
||||||
|
[350 * 1024, "350 KB"],
|
||||||
|
[int(14.2 * 1024 * 1024), "14.2 MB"],
|
||||||
|
]) -> void:
|
||||||
|
assert_str(SettingsPopup.size_text(bytes)).is_equal(text)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://da5wh1me3wncq
|
||||||
Reference in new issue
Block a user