mirror of
https://github.com/tonytins/CozyPixelStudio.git
synced 2025-06-25 15:34:43 -04:00
Add autosave feature with backup of unsaved new projects (#221)
* Add autosave feature with backup of unsaved new projects. * Fix wrong indentation on line 205. * Store backup for every opened file in user://. Some other improvements. * Remove unnecessary variable. * Update Translations.pot Co-authored-by: Manolis Papadeas <35376950+OverloadedOrama@users.noreply.github.com>
This commit is contained in:
parent
c82c54d096
commit
82fe186b65
6 changed files with 343 additions and 110 deletions
|
@ -41,6 +41,9 @@ func _ready() -> void:
|
|||
# Disable input until the shortcut selector is displayed
|
||||
set_process_input(false)
|
||||
|
||||
# Replace OK with Close since preference changes are being applied immediately, not after OK confirmation
|
||||
get_ok().text = tr("Close")
|
||||
|
||||
for child in languages.get_children():
|
||||
if child is Button:
|
||||
child.connect("pressed", self, "_on_Language_pressed", [child])
|
||||
|
@ -76,6 +79,16 @@ func _ready() -> void:
|
|||
Global.show_right_tool_icon = Global.config_cache.get_value("preferences", "show_right_tool_icon")
|
||||
right_tool_icon.pressed = Global.show_right_tool_icon
|
||||
|
||||
# Get autosave settings
|
||||
if Global.config_cache.has_section_key("preferences", "autosave_interval"):
|
||||
var autosave_interval = Global.config_cache.get_value("preferences", "autosave_interval")
|
||||
OpenSave.set_autosave_interval(autosave_interval)
|
||||
general.get_node("AutosaveInterval/AutosaveInterval").value = autosave_interval
|
||||
if Global.config_cache.has_section_key("preferences", "enable_autosave"):
|
||||
var enable_autosave = Global.config_cache.get_value("preferences", "enable_autosave")
|
||||
OpenSave.toggle_autosave(enable_autosave)
|
||||
general.get_node("EnableAutosave").pressed = enable_autosave
|
||||
|
||||
# Set default values for Canvas options
|
||||
if Global.config_cache.has_section_key("preferences", "grid_size"):
|
||||
var grid_size = Global.config_cache.get_value("preferences", "grid_size")
|
||||
|
@ -527,3 +540,15 @@ func _on_OpenLastProject_pressed():
|
|||
Global.open_last_project = !Global.open_last_project
|
||||
Global.config_cache.set_value("preferences", "open_last_project", Global.open_last_project)
|
||||
Global.config_cache.save("user://cache.ini")
|
||||
|
||||
|
||||
func _on_EnableAutosave_toggled(button_pressed : bool) -> void:
|
||||
OpenSave.toggle_autosave(button_pressed)
|
||||
Global.config_cache.set_value("preferences", "enable_autosave", button_pressed)
|
||||
Global.config_cache.save("user://cache.ini")
|
||||
|
||||
|
||||
func _on_AutosaveInterval_value_changed(value : float) -> void:
|
||||
OpenSave.set_autosave_interval(value)
|
||||
Global.config_cache.set_value("preferences", "autosave_interval", value)
|
||||
Global.config_cache.save("user://cache.ini")
|
||||
|
|
|
@ -193,8 +193,28 @@ func _ready() -> void:
|
|||
Global.config_cache.set_value("preferences", "open_last_project", true)
|
||||
if Global.config_cache.get_value("preferences", "open_last_project"):
|
||||
Global.open_last_project = Global.config_cache.get_value("preferences", "open_last_project")
|
||||
|
||||
# If backup file exists then Pixelorama was not closed properly (probably crashed) - reopen backup
|
||||
$BackupConfirmation.get_cancel().text = tr("Delete")
|
||||
if Global.config_cache.has_section("backups"):
|
||||
var project_paths = Global.config_cache.get_section_keys("backups")
|
||||
if project_paths.size() > 0:
|
||||
# Get backup path
|
||||
var backup_path = Global.config_cache.get_value("backups", project_paths[0])
|
||||
# Temporatily stop autosave until user confirms backup
|
||||
OpenSave.autosave_timer.stop()
|
||||
Global.can_draw = false
|
||||
# For it's only possible to reload the first found backup
|
||||
$BackupConfirmation.dialog_text = $BackupConfirmation.dialog_text % project_paths[0]
|
||||
$BackupConfirmation.connect("confirmed", self, "_on_BackupConfirmation_confirmed", [project_paths[0], backup_path])
|
||||
$BackupConfirmation.get_cancel().connect("pressed", self, "_on_BackupConfirmation_delete", [project_paths[0], backup_path])
|
||||
$BackupConfirmation.popup_centered()
|
||||
else:
|
||||
load_last_project()
|
||||
else:
|
||||
load_last_project()
|
||||
|
||||
|
||||
func _input(event : InputEvent) -> void:
|
||||
Global.left_cursor.position = get_global_mouse_position() + Vector2(-32, 32)
|
||||
Global.left_cursor.texture = Global.left_cursor_tool_texture
|
||||
|
@ -420,16 +440,17 @@ func help_menu_id_pressed(id : int) -> void:
|
|||
Global.can_draw = false
|
||||
|
||||
func load_last_project():
|
||||
# Check if any project was saved or opened last time
|
||||
if Global.config_cache.has_section_key("preferences", "last_project_path"):
|
||||
# Check if file still exists on disk
|
||||
var file_path = Global.config_cache.get_value("preferences", "last_project_path")
|
||||
var file_check := File.new()
|
||||
if file_check.file_exists(file_path): # If yes then load the file
|
||||
_on_OpenSprite_file_selected(file_path)
|
||||
else:
|
||||
# If file doesn't exist on disk then warn user about this
|
||||
$OpenLastProjectAlertDialog.popup_centered()
|
||||
if Global.open_last_project:
|
||||
# Check if any project was saved or opened last time
|
||||
if Global.config_cache.has_section_key("preferences", "last_project_path"):
|
||||
# Check if file still exists on disk
|
||||
var file_path = Global.config_cache.get_value("preferences", "last_project_path")
|
||||
var file_check := File.new()
|
||||
if file_check.file_exists(file_path): # If yes then load the file
|
||||
_on_OpenSprite_file_selected(file_path)
|
||||
else:
|
||||
# If file doesn't exist on disk then warn user about this
|
||||
$OpenLastProjectAlertDialog.popup_centered()
|
||||
|
||||
|
||||
func _on_UnsavedCanvasDialog_confirmed() -> void:
|
||||
|
@ -451,7 +472,7 @@ func _on_OpenSprite_file_selected(path : String) -> void:
|
|||
|
||||
|
||||
func _on_SaveSprite_file_selected(path : String) -> void:
|
||||
OpenSave.save_pxo_file(path)
|
||||
OpenSave.save_pxo_file(path, false)
|
||||
|
||||
# Set last opened project path and save
|
||||
Global.config_cache.set_value("preferences", "last_project_path", path)
|
||||
|
@ -782,16 +803,34 @@ func _on_QuitAndSaveDialog_custom_action(action : String) -> void:
|
|||
$SaveSprite.popup_centered()
|
||||
$QuitDialog.hide()
|
||||
Global.can_draw = false
|
||||
OpenSave.remove_backup()
|
||||
|
||||
|
||||
func _on_QuitDialog_confirmed() -> void:
|
||||
# Darken the UI to denote that the application is currently exiting
|
||||
# (it won't respond to user input in this state).
|
||||
modulate = Color(0.5, 0.5, 0.5)
|
||||
|
||||
OpenSave.remove_backup()
|
||||
get_tree().quit()
|
||||
|
||||
|
||||
func _on_BackupConfirmation_confirmed(project_path : String, backup_path : String) -> void:
|
||||
OpenSave.reload_backup_file(project_path, backup_path)
|
||||
OpenSave.autosave_timer.start()
|
||||
$ExportDialog.file_name = OpenSave.current_save_path.get_file().trim_suffix(".pxo")
|
||||
$ExportDialog.directory_path = OpenSave.current_save_path.get_base_dir()
|
||||
$ExportDialog.was_exported = false
|
||||
file_menu.set_item_text(3, tr("Save") + " %s" % OpenSave.current_save_path.get_file())
|
||||
file_menu.set_item_text(6, tr("Export"))
|
||||
|
||||
|
||||
func _on_BackupConfirmation_delete(project_path : String, backup_path : String) -> void:
|
||||
OpenSave.remove_backup_by_path(project_path, backup_path)
|
||||
OpenSave.autosave_timer.start()
|
||||
# Reopen last project
|
||||
load_last_project()
|
||||
|
||||
|
||||
func _on_LeftPixelPerfectMode_toggled(button_pressed) -> void:
|
||||
Global.left_pixel_perfect = button_pressed
|
||||
|
||||
|
|
|
@ -1,17 +1,31 @@
|
|||
extends Node
|
||||
|
||||
var current_save_path := ""
|
||||
# Stores a filename of a backup file in user:// until user saves manually
|
||||
var backup_save_path = ""
|
||||
|
||||
onready var autosave_timer : Timer
|
||||
var default_autosave_interval := 5 # Minutes
|
||||
|
||||
func _ready():
|
||||
autosave_timer = Timer.new()
|
||||
autosave_timer.one_shot = false
|
||||
autosave_timer.process_mode = Timer.TIMER_PROCESS_IDLE
|
||||
autosave_timer.connect("timeout", self, "_on_Autosave_timeout")
|
||||
add_child(autosave_timer)
|
||||
set_autosave_interval(default_autosave_interval)
|
||||
toggle_autosave(false) # Gets started from preferences dialog
|
||||
|
||||
|
||||
func open_pxo_file(path : String) -> void:
|
||||
func open_pxo_file(path : String, untitled_backup : bool = false) -> void:
|
||||
var file := File.new()
|
||||
var err := file.open_compressed(path, File.READ, File.COMPRESSION_ZSTD)
|
||||
if err == ERR_FILE_UNRECOGNIZED:
|
||||
err = file.open(path, File.READ) # If the file is not compressed open it raw (pre-v0.7)
|
||||
|
||||
if err != OK:
|
||||
Global.notification_label("File failed to open")
|
||||
file.close()
|
||||
OS.alert("Can't load file")
|
||||
return
|
||||
|
||||
var file_version := file.get_line() # Example, "v0.6"
|
||||
|
@ -133,13 +147,13 @@ func open_pxo_file(path : String) -> void:
|
|||
|
||||
file.close()
|
||||
|
||||
current_save_path = path
|
||||
Global.window_title = path.get_file() + " - Pixelorama"
|
||||
if not untitled_backup:
|
||||
# Untitled backup should not change window title and save path
|
||||
current_save_path = path
|
||||
Global.window_title = path.get_file() + " - Pixelorama"
|
||||
|
||||
|
||||
func save_pxo_file(path : String) -> void:
|
||||
current_save_path = path
|
||||
|
||||
func save_pxo_file(path : String, autosave : bool) -> void:
|
||||
var file := File.new()
|
||||
var err := file.open_compressed(path, File.WRITE, File.COMPRESSION_ZSTD)
|
||||
if err == OK:
|
||||
|
@ -209,10 +223,89 @@ func save_pxo_file(path : String) -> void:
|
|||
file.store_8(tag[3]) # Tag "to", the last frame
|
||||
file.store_line("END_FRAME_TAGS")
|
||||
|
||||
if !Global.saved:
|
||||
file.close()
|
||||
|
||||
if !Global.saved and not autosave:
|
||||
Global.saved = true
|
||||
|
||||
Global.window_title = current_save_path.get_file() + " - Pixelorama"
|
||||
Global.notification_label("File saved")
|
||||
file.close()
|
||||
if autosave:
|
||||
Global.notification_label("File autosaved")
|
||||
else:
|
||||
# First remove backup then set current save path
|
||||
remove_backup()
|
||||
current_save_path = path
|
||||
Global.notification_label("File saved")
|
||||
|
||||
if backup_save_path == "":
|
||||
Global.window_title = path.get_file() + " - Pixelorama"
|
||||
|
||||
else:
|
||||
Global.notification_label("File failed to save")
|
||||
|
||||
|
||||
func toggle_autosave(enable : bool) -> void:
|
||||
if enable:
|
||||
autosave_timer.start()
|
||||
else:
|
||||
autosave_timer.stop()
|
||||
|
||||
|
||||
func set_autosave_interval(interval : float) -> void:
|
||||
autosave_timer.wait_time = interval * 60 # Interval parameter is in minutes, wait_time is seconds
|
||||
autosave_timer.start()
|
||||
|
||||
|
||||
func _on_Autosave_timeout() -> void:
|
||||
if backup_save_path == "":
|
||||
# Create a new backup file if it doesn't exist yet
|
||||
backup_save_path = "user://backup-" + String(OS.get_unix_time())
|
||||
|
||||
store_backup_path()
|
||||
save_pxo_file(backup_save_path, true)
|
||||
|
||||
|
||||
# Backup paths are stored in two ways:
|
||||
# 1) User already manually saved and defined a save path -> {current_save_path, backup_save_path}
|
||||
# 2) User didn't manually saved, "untitled" backup is stored -> {backup_save_path, backup_save_path}
|
||||
func store_backup_path() -> void:
|
||||
if current_save_path != "":
|
||||
# Remove "untitled" backup if it existed on this project instance
|
||||
if Global.config_cache.has_section_key("backups", backup_save_path):
|
||||
Global.config_cache.erase_section_key("backups", backup_save_path)
|
||||
|
||||
Global.config_cache.set_value("backups", current_save_path, backup_save_path)
|
||||
else:
|
||||
Global.config_cache.set_value("backups", backup_save_path, backup_save_path)
|
||||
|
||||
Global.config_cache.save("user://cache.ini")
|
||||
|
||||
|
||||
func remove_backup() -> void:
|
||||
# Remove backup file
|
||||
if backup_save_path != "":
|
||||
if current_save_path != "":
|
||||
remove_backup_by_path(current_save_path, backup_save_path)
|
||||
else:
|
||||
# If manual save was not yet done - remove "untitled" backup
|
||||
remove_backup_by_path(backup_save_path, backup_save_path)
|
||||
backup_save_path = ""
|
||||
|
||||
|
||||
func remove_backup_by_path(project_path : String, backup_path : String) -> void:
|
||||
Directory.new().remove(backup_path)
|
||||
Global.config_cache.erase_section_key("backups", project_path)
|
||||
Global.config_cache.save("user://cache.ini")
|
||||
|
||||
|
||||
func reload_backup_file(project_path : String, backup_path : String) -> void:
|
||||
# If project path is the same as backup save path -> the backup was untitled
|
||||
open_pxo_file(backup_path, project_path == backup_path)
|
||||
backup_save_path = backup_path
|
||||
|
||||
if project_path != backup_path:
|
||||
current_save_path = project_path
|
||||
Global.window_title = project_path.get_file() + " - Pixelorama(*)"
|
||||
Global.saved = false
|
||||
|
||||
Global.notification_label("Backup reloaded")
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue