Skip to content

Rewrite backup system #1299

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/Autoload/Global.gd
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ enum HelpMenu {
VIEW_SPLASH_SCREEN,
ONLINE_DOCS,
ISSUE_TRACKER,
RESTORE_BACKUP,
OPEN_EDITOR_DATA_FOLDER,
CHANGELOG,
ABOUT_PIXELORAMA,
Expand Down Expand Up @@ -504,6 +505,13 @@ var dummy_audio_driver := false:
dummy_audio_driver = value
_save_to_override_file()

## Found in Preferences. The maximum limit of recent sessions that can be stored as backup.
var max_backed_sessions := 20.0:
set(value):
if value == max_backed_sessions:
return
max_backed_sessions = value
OpenSave.enforce_backed_sessions_limit()
## Found in Preferences. The time (in minutes) after which backup is created (if enabled).
var autosave_interval := 1.0:
set(value):
Expand Down
66 changes: 52 additions & 14 deletions src/Autoload/OpenSave.gd
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ signal project_saved
signal reference_image_imported
signal shader_copied(file_path: String)

const BACKUPS_DIRECTORY := "user://backups"
const SHADERS_DIRECTORY := "user://shaders"

var current_session_backup := ""
var preview_dialog_tscn := preload("res://src/UI/Dialogs/ImportPreviewDialog.tscn")
var preview_dialogs := [] ## Array of preview dialogs
var last_dialog_option := 0
Expand All @@ -23,6 +25,36 @@ func _ready() -> void:
autosave_timer.timeout.connect(_on_Autosave_timeout)
add_child(autosave_timer)
update_autosave()
# Remove empty sessions
for session_folder in DirAccess.get_directories_at(BACKUPS_DIRECTORY):
if DirAccess.get_files_at(BACKUPS_DIRECTORY.path_join(session_folder)).size() == 0:
DirAccess.remove_absolute(BACKUPS_DIRECTORY.path_join(session_folder))
# Make folder for current session
var date_time: Dictionary = Time.get_datetime_dict_from_system()
var string_dict = {}
for key in date_time.keys():
var value = int(date_time[key])
var value_string = str(value)
if value <= 9:
value_string = str("0", value_string)
string_dict[key] = value_string
current_session_backup = BACKUPS_DIRECTORY.path_join(
str(
string_dict.year,
"_",
string_dict.month,
"_",
string_dict.day,
"_",
string_dict.hour,
"_",
string_dict.minute,
"_",
string_dict.second
)
)
DirAccess.make_dir_recursive_absolute(current_session_backup)
enforce_backed_sessions_limit()


func handle_loading_file(file: String, force_import_dialog_on_images := false) -> void:
Expand Down Expand Up @@ -418,9 +450,9 @@ func save_pxo_file(
var zip_packer := ZIPPacker.new()
var err := zip_packer.open(temp_path)
if err != OK:
Global.popup_error(tr("File failed to save. Error code %s (%s)") % [err, error_string(err)])
if temp_path.is_valid_filename():
return false
Global.popup_error(tr("File failed to save. Error code %s (%s)") % [err, error_string(err)])
if zip_packer: # this would be null if we attempt to save filenames such as "//\\||.pxo"
zip_packer.close()
return false
Expand Down Expand Up @@ -1089,6 +1121,21 @@ func open_ora_file(path: String) -> void:
Global.canvas.camera_zoom()


func enforce_backed_sessions_limit() -> void:
# Enforce session limit
var old_folders = DirAccess.get_directories_at(BACKUPS_DIRECTORY)
if old_folders.size() > Global.max_backed_sessions:
var excess = old_folders.size() - Global.max_backed_sessions
for i in excess:
# Remove oldest folder. The array is sorted alphabetically so the oldest folder
# is the first in array
var oldest = BACKUPS_DIRECTORY.path_join(old_folders[0])
for file in DirAccess.get_files_at(oldest):
DirAccess.remove_absolute(oldest.path_join(file))
DirAccess.remove_absolute(oldest)
old_folders.remove_at(0)


func update_autosave() -> void:
if not is_instance_valid(autosave_timer):
return
Expand All @@ -1102,23 +1149,14 @@ func update_autosave() -> void:
func _on_Autosave_timeout() -> void:
for i in Global.projects.size():
var project := Global.projects[i]
var p_name: String = project.file_name
if project.backup_path.is_empty():
project.backup_path = (
"user://backups/backup-" + str(Time.get_unix_time_from_system()) + "-%s" % i
)
project.backup_path = (current_session_backup.path_join(
"(" + p_name + " backup)-" + str(Time.get_unix_time_from_system()) + "-%s" % i
))
save_pxo_file(project.backup_path, true, false, project)


## Load the backup files
func reload_backup_file() -> void:
var dir := DirAccess.open("user://backups")
var i := 0
for file in dir.get_files():
open_pxo_file("user://backups".path_join(file), true, i == 0)
i += 1
Global.notification_label("Backup reloaded")


func save_project_to_recent_list(path: String) -> void:
var top_menu_container := Global.top_menu_container
if path.get_file().substr(0, 7) == "backup-" or path == "":
Expand Down
7 changes: 0 additions & 7 deletions src/Classes/Project.gd
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,6 @@ func _init(_frames: Array[Frame] = [], _name := tr("untitled"), _size := Vector2


func remove() -> void:
remove_backup_file()
undo_redo.free()
for ri in reference_images:
ri.queue_free()
Expand All @@ -174,12 +173,6 @@ func remove() -> void:
removed.emit()


func remove_backup_file() -> void:
if not backup_path.is_empty():
if FileAccess.file_exists(backup_path):
DirAccess.remove_absolute(backup_path)


func commit_undo() -> void:
if not can_undo:
return
Expand Down
53 changes: 4 additions & 49 deletions src/Main.gd
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ var splash_dialog: AcceptDialog:
return splash_dialog

@onready var main_ui := $MenuAndUI/UI/DockableContainer as DockableContainer
@onready var backup_confirmation: ConfirmationDialog = $Dialogs/BackupConfirmation
## Dialog used to open images and project (.pxo) files.
@onready var open_sprite_dialog := $Dialogs/OpenSprite as FileDialog
## Dialog used to save project (.pxo) files.
Expand Down Expand Up @@ -168,8 +167,8 @@ some useful [SYSTEM OPTIONS] are:

func _init() -> void:
Global.project_switched.connect(_project_switched)
if not DirAccess.dir_exists_absolute("user://backups"):
DirAccess.make_dir_recursive_absolute("user://backups")
if not DirAccess.dir_exists_absolute(OpenSave.BACKUPS_DIRECTORY):
DirAccess.make_dir_recursive_absolute(OpenSave.BACKUPS_DIRECTORY)
Global.shrink = _get_auto_display_scale()
_handle_layout_files()
# Load dither matrix images.
Expand Down Expand Up @@ -197,7 +196,8 @@ func _ready() -> void:
get_tree().root.files_dropped.connect(_on_files_dropped)
if OS.get_name() == "Android":
OS.request_permissions()
_handle_backup()
if Global.open_last_project:
load_last_project()
await get_tree().process_frame
_setup_application_window_size()
_show_splash_screen()
Expand Down Expand Up @@ -333,22 +333,6 @@ func _show_splash_screen() -> void:
modulate = Color(0.5, 0.5, 0.5)


func _handle_backup() -> void:
# If backup file exists, Pixelorama was not closed properly (probably crashed) - reopen backup
backup_confirmation.add_button("Discard All", false, "discard")
var backup_dir := DirAccess.open("user://backups")
if backup_dir.get_files().size() > 0:
# Temporatily stop autosave until user confirms backup
OpenSave.autosave_timer.stop()
backup_confirmation.confirmed.connect(_on_BackupConfirmation_confirmed)
backup_confirmation.custom_action.connect(_on_BackupConfirmation_custom_action)
backup_confirmation.popup_centered()
modulate = Color(0.5, 0.5, 0.5)
else:
if Global.open_last_project:
load_last_project()


func _handle_cmdline_arguments() -> void:
var args := OS.get_cmdline_args()
args.append_array(OS.get_cmdline_user_args())
Expand Down Expand Up @@ -576,38 +560,9 @@ func _quit() -> void:
get_tree().quit()


func _on_BackupConfirmation_confirmed() -> void:
OpenSave.reload_backup_file()


func _on_BackupConfirmation_custom_action(action: String) -> void:
backup_confirmation.hide()
if action != "discard":
return
_clear_backup_files()
# Reopen last project
if Global.open_last_project:
load_last_project()


func _on_backup_confirmation_visibility_changed() -> void:
if backup_confirmation.visible:
return
if Global.enable_autosave:
OpenSave.autosave_timer.start()
Global.dialog_open(false)


func _clear_backup_files() -> void:
for file in DirAccess.get_files_at("user://backups"):
DirAccess.remove_absolute("user://backups".path_join(file))


func _exit_tree() -> void:
for project in Global.projects:
project.remove()
# For some reason, the above is not enough to remove all backup files
_clear_backup_files()
if DisplayServer.get_name() == "headless":
return
Global.config_cache.set_value("window", "layout", Global.layouts.find(main_ui.layout))
Expand Down
7 changes: 0 additions & 7 deletions src/Main.tscn
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,6 @@ dialog_text = "Project %s has unsaved progress. How do you wish to proceed?"
[node name="ErrorDialog" type="AcceptDialog" parent="Dialogs"]
dialog_text = "This is an error message!"

[node name="BackupConfirmation" type="ConfirmationDialog" parent="Dialogs"]
size = Vector2i(320, 200)
dialog_text = "Autosaved project(s) from a crashed session were found.
Do you want to recover the data?"
dialog_autowrap = true

[node name="CelProperties" parent="Dialogs" instance=ExtResource("17_ucs64")]

[node name="FrameProperties" parent="Dialogs" instance=ExtResource("9")]
Expand Down Expand Up @@ -127,6 +121,5 @@ script = ExtResource("17_k1xhp")
[connection signal="custom_action" from="Dialogs/QuitAndSaveDialog" to="." method="_on_QuitAndSaveDialog_custom_action"]
[connection signal="visibility_changed" from="Dialogs/QuitAndSaveDialog" to="." method="_can_draw_true"]
[connection signal="visibility_changed" from="Dialogs/ErrorDialog" to="." method="_can_draw_true"]
[connection signal="visibility_changed" from="Dialogs/BackupConfirmation" to="." method="_on_backup_confirmation_visibility_changed"]
[connection signal="confirmed" from="Dialogs/DownloadImageConfirmationDialog" to="." method="_on_download_image_confirmation_dialog_confirmed"]
[connection signal="request_completed" from="ImageRequest" to="." method="_on_image_request_request_completed"]
3 changes: 3 additions & 0 deletions src/Preferences/PreferencesDialog.gd
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ var preferences: Array[Preference] = [
Preference.new(
"cross_cursor", "Cursors/CursorsContainer/CrossCursorCheckbox", "button_pressed", true
),
Preference.new(
"max_backed_sessions", "Backup/AutosaveContainer/MaxStoredSessions", "value", 20.0
),
Preference.new("autosave_interval", "Backup/AutosaveContainer/AutosaveInterval", "value", 1.0),
Preference.new(
"enable_autosave", "Backup/AutosaveContainer/EnableAutosave", "button_pressed", true
Expand Down
14 changes: 14 additions & 0 deletions src/Preferences/PreferencesDialog.tscn
Original file line number Diff line number Diff line change
Expand Up @@ -1072,6 +1072,20 @@ mouse_default_cursor_shape = 2
button_pressed = true
text = "On"

[node name="MaxStoredSessionsLabel" type="Label" parent="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Backup/AutosaveContainer"]
layout_mode = 2
size_flags_horizontal = 3
text = "Maximum backed sessions:"

[node name="MaxStoredSessions" type="SpinBox" parent="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Backup/AutosaveContainer"]
custom_minimum_size = Vector2(95, 0)
layout_mode = 2
size_flags_horizontal = 3
mouse_default_cursor_shape = 2
min_value = 1.0
value = 20.0
allow_greater = true

[node name="AutosaveIntervalLabel" type="Label" parent="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Backup/AutosaveContainer"]
layout_mode = 2
size_flags_horizontal = 3
Expand Down
Loading