init
This commit is contained in:
@@ -0,0 +1,162 @@
|
||||
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
|
||||
Reference in New Issue
Block a user