Files
tekton/launcher/scripts/update_applier.gd
T
2025-12-10 23:11:59 +08:00

163 lines
4.7 KiB
GDScript

extends Node
class_name UpdateApplier
## Handles applying downloaded updates: backup, replace, rollback
signal update_started
signal update_progress(step: String, percentage: float)
signal update_completed
signal update_failed(error: String)
signal rollback_completed
const BACKUP_COUNT := 2 # Keep this many backup versions
func apply_update(downloaded_pck_path: String, version: String) -> void:
emit_signal("update_started")
var game_dir := Config.GAME_DIRECTORY
var pck_name := Config.get_game_pck()
var target_pck := game_dir + pck_name
var backup_dir := Config.BACKUP_DIRECTORY
# Step 1: Ensure game directory exists
emit_signal("update_progress", "Preparing directories...", 10.0)
if not _ensure_directory(game_dir):
emit_signal("update_failed", "Failed to create game directory")
return
if not _ensure_directory(backup_dir):
emit_signal("update_failed", "Failed to create backup directory")
return
# Step 2: Backup existing PCK if it exists
emit_signal("update_progress", "Backing up current version...", 30.0)
if FileAccess.file_exists(target_pck):
var backup_name := pck_name.get_basename() + "_backup_" + Time.get_datetime_string_from_system().replace(":", "-") + ".pck"
var backup_path := backup_dir + backup_name
if not _copy_file(target_pck, backup_path):
emit_signal("update_failed", "Failed to backup current version")
return
# Clean up old backups
_cleanup_old_backups(backup_dir, BACKUP_COUNT)
# Step 3: Move downloaded file to game directory
emit_signal("update_progress", "Installing update...", 60.0)
# Delete old PCK first
if FileAccess.file_exists(target_pck):
var dir := DirAccess.open(game_dir)
if dir:
var err := dir.remove(pck_name)
if err != OK:
emit_signal("update_failed", "Failed to remove old game file")
return
# Copy new PCK
if not _copy_file(downloaded_pck_path, target_pck):
emit_signal("update_failed", "Failed to install update")
return
# Step 4: Copy executable if needed (first install)
emit_signal("update_progress", "Finalizing...", 80.0)
# Clean up temp file
var temp_dir := DirAccess.open("user://temp/")
if temp_dir:
temp_dir.remove(downloaded_pck_path.get_file())
emit_signal("update_progress", "Update complete!", 100.0)
emit_signal("update_completed")
func _ensure_directory(path: String) -> bool:
var dir := DirAccess.open("user://")
if dir:
var relative_path := path.replace("user://", "")
if not dir.dir_exists(relative_path):
return dir.make_dir_recursive(relative_path) == OK
return true
return false
func _copy_file(from: String, to: String) -> bool:
var source := FileAccess.open(from, FileAccess.READ)
if not source:
push_error("[UpdateApplier] Cannot open source file: " + from)
return false
var dest := FileAccess.open(to, FileAccess.WRITE)
if not dest:
source.close()
push_error("[UpdateApplier] Cannot open destination file: " + to)
return false
# Copy in chunks for large files
const CHUNK_SIZE := 1024 * 1024 # 1MB chunks
while source.get_position() < source.get_length():
var chunk := source.get_buffer(CHUNK_SIZE)
dest.store_buffer(chunk)
source.close()
dest.close()
print("[UpdateApplier] Copied: ", from, " -> ", to)
return true
func _cleanup_old_backups(backup_dir: String, keep_count: int) -> void:
var dir := DirAccess.open(backup_dir)
if not dir:
return
var backups: Array[String] = []
dir.list_dir_begin()
var file := dir.get_next()
while file != "":
if file.ends_with(".pck") and "backup" in file:
backups.append(file)
file = dir.get_next()
dir.list_dir_end()
# Sort by name (which includes timestamp)
backups.sort()
# Remove oldest backups if we have too many
while backups.size() > keep_count:
var old_backup = backups.pop_front()
dir.remove(old_backup)
print("[UpdateApplier] Removed old backup: ", old_backup)
func rollback_to_backup() -> bool:
## Rollback to the most recent backup
var backup_dir := Config.BACKUP_DIRECTORY
var game_dir := Config.GAME_DIRECTORY
var pck_name := Config.get_game_pck()
var dir := DirAccess.open(backup_dir)
if not dir:
push_error("[UpdateApplier] Cannot access backup directory")
return false
# Find the most recent backup
var backups: Array[String] = []
dir.list_dir_begin()
var file := dir.get_next()
while file != "":
if file.ends_with(".pck") and "backup" in file:
backups.append(file)
file = dir.get_next()
dir.list_dir_end()
if backups.is_empty():
push_error("[UpdateApplier] No backups found")
return false
backups.sort()
var latest_backup = backups.back()
# Restore the backup
if _copy_file(backup_dir + latest_backup, game_dir + pck_name):
emit_signal("rollback_completed")
print("[UpdateApplier] Rolled back to: ", latest_backup)
return true
return false