summaryrefslogtreecommitdiff
path: root/addons/gdUnit4/src/update/GdUnitUpdate.gd
diff options
context:
space:
mode:
authorradiohotline <radoihotline@disroot.rg>2026-08-25 16:33:56 +0300
committerradiohotline <radoihotline@disroot.rg>2026-08-25 16:33:56 +0300
commita89e744ed0e8c759caf041a580112365bbfe4303 (patch)
treedd61e320904176995727e86a857d00c90d4933ba /addons/gdUnit4/src/update/GdUnitUpdate.gd
parent6d53d8cac78250dc888aa49d5f9d4175c6652feb (diff)
escoria updateHEADmain
Diffstat (limited to 'addons/gdUnit4/src/update/GdUnitUpdate.gd')
-rw-r--r--addons/gdUnit4/src/update/GdUnitUpdate.gd305
1 files changed, 305 insertions, 0 deletions
diff --git a/addons/gdUnit4/src/update/GdUnitUpdate.gd b/addons/gdUnit4/src/update/GdUnitUpdate.gd
new file mode 100644
index 0000000..97b7fdd
--- /dev/null
+++ b/addons/gdUnit4/src/update/GdUnitUpdate.gd
@@ -0,0 +1,305 @@
+@tool
+extends Container
+
+const GdUnitTools := preload("res://addons/gdUnit4/src/core/GdUnitTools.gd")
+const GdUnitUpdateClient := preload("res://addons/gdUnit4/src/update/GdUnitUpdateClient.gd")
+const GDUNIT_TEMP := "user://tmp"
+
+@onready var _progress_content: RichTextLabel = %message
+@onready var _progress_bar: TextureProgressBar = %progress
+@onready var _cancel_btn: Button = %cancel
+@onready var _update_btn: Button = %update
+@onready var _spinner_img := GdUnitUiTools.get_spinner()
+
+
+var _debug_mode := false
+var _update_client :GdUnitUpdateClient
+var _download_url :String
+
+
+func _ready() -> void:
+ init_progress(6)
+
+
+func _process(_delta :float) -> void:
+ if _progress_content != null and _progress_content.is_visible_in_tree():
+ _progress_content.queue_redraw()
+
+
+func init_progress(max_value: int) -> void:
+ _cancel_btn.disabled = false
+ _update_btn.disabled = false
+ _progress_bar.max_value = max_value
+ _progress_bar.value = 1
+ message_h4("Press [Update] to start.", Color.GREEN, false)
+
+
+func setup(update_client: GdUnitUpdateClient, download_url: String) -> void:
+ _update_client = update_client
+ _download_url = download_url
+
+
+func update_progress(message: String, color := Color.GREEN) -> void:
+ message_h4(message, color)
+ _progress_bar.value += 1
+ if _debug_mode:
+ await get_tree().create_timer(3).timeout
+ await get_tree().create_timer(.2).timeout
+
+
+func _colored(message: String, color: Color) -> String:
+ return "[color=#%s]%s[/color]" % [color.to_html(), message]
+
+
+func message_h4(message: String, color: Color, show_spinner := true) -> void:
+ _progress_content.clear()
+ if show_spinner:
+ _progress_content.add_image(_spinner_img)
+ _progress_content.append_text(" [font_size=16]%s[/font_size]" % _colored(message, color))
+ if _debug_mode:
+ prints(message)
+
+
+@warning_ignore("return_value_discarded")
+func run_update() -> void:
+ _cancel_btn.disabled = true
+ _update_btn.disabled = true
+
+ await update_progress("Downloading the update.")
+ await download_release()
+ await update_progress("Extracting")
+ var zip_file := temp_dir() + "/update.zip"
+ var tmp_path := create_temp_dir("update")
+ var result :Variant = extract_zip(zip_file, tmp_path)
+ if result == null:
+ await update_progress("Update failed! .. Rollback.", Color.INDIAN_RED)
+ await get_tree().create_timer(3).timeout
+ _cancel_btn.disabled = false
+ _update_btn.disabled = false
+ init_progress(5)
+ hide()
+ return
+
+ await update_progress("Uninstall GdUnit4.")
+ disable_gdUnit()
+ if not _debug_mode:
+ GdUnitFileAccess.delete_directory("res://addons/gdUnit4/")
+ # give editor time to react on deleted files
+ await get_tree().create_timer(1).timeout
+
+ await update_progress("Install new GdUnit4 version.")
+ if _debug_mode:
+ copy_directory(tmp_path, "res://debug")
+ else:
+ copy_directory(tmp_path, "res://")
+
+ await update_progress("Patch invalid UID's")
+ await patch_uids()
+
+ await rebuild_project()
+
+ await update_progress("New GdUnit version successfully installed, Restarting Godot please wait.")
+ await get_tree().create_timer(3).timeout
+ enable_gdUnit()
+ hide()
+ GdUnitFileAccess.delete_directory("res://addons/.gdunit_update")
+ restart_godot()
+
+
+func patch_uids(path := "res://addons/gdUnit4/src/") -> void:
+ var to_reimport: PackedStringArray
+ for file in DirAccess.get_files_at(path):
+ var file_path := path.path_join(file)
+ var ext := file.get_extension()
+
+ if ext == "tscn" or ext == "scn" or ext == "tres" or ext == "res":
+ message_h4("Patch GdUnit4 scene: '%s'" % file, Color.WEB_GREEN)
+ remove_uids_from_file(file_path)
+ elif FileAccess.file_exists(file_path + ".import"):
+ to_reimport.append(file_path)
+
+ if not to_reimport.is_empty():
+ message_h4("Reimport resources '%s'" % ", ".join(to_reimport), Color.WEB_GREEN)
+ if Engine.is_editor_hint():
+ EditorInterface.get_resource_filesystem().reimport_files(to_reimport)
+
+ for dir in DirAccess.get_directories_at(path):
+ if not dir.begins_with("."):
+ patch_uids(path.path_join(dir))
+ await get_tree().process_frame
+
+
+func remove_uids_from_file(file_path: String) -> bool:
+ var file := FileAccess.open(file_path, FileAccess.READ)
+ if file == null:
+ print("Failed to open file: ", file_path)
+ return false
+
+ var original_content := file.get_as_text()
+ file.close()
+
+ # Remove UIDs using regex
+ var regex := RegEx.new()
+ regex.compile("(\\[ext_resource[^\\]]*?)\\s+uid=\"uid://[^\"]*\"")
+
+ var modified_content := regex.sub(original_content, "$1", true)
+
+ # Check if any changes were made
+ if original_content != modified_content:
+ prints("Patched invalid uid's out in '%s'" % file_path)
+ # Write the modified content back
+ file = FileAccess.open(file_path, FileAccess.WRITE)
+ if file == null:
+ print("Failed to write to file: ", file_path)
+ return false
+
+ file.store_string(modified_content)
+ file.close()
+ return true
+
+ return false
+
+
+func restart_godot() -> void:
+ prints("Force restart Godot")
+ EditorInterface.restart_editor(true)
+
+
+@warning_ignore("return_value_discarded")
+func enable_gdUnit() -> void:
+ var enabled_plugins := PackedStringArray()
+ if ProjectSettings.has_setting("editor_plugins/enabled"):
+ enabled_plugins = ProjectSettings.get_setting("editor_plugins/enabled")
+ if not enabled_plugins.has("res://addons/gdUnit4/plugin.cfg"):
+ enabled_plugins.append("res://addons/gdUnit4/plugin.cfg")
+ ProjectSettings.set_setting("editor_plugins/enabled", enabled_plugins)
+ ProjectSettings.save()
+
+
+func disable_gdUnit() -> void:
+ EditorInterface.set_plugin_enabled("gdUnit4", false)
+
+
+func temp_dir() -> String:
+ if not DirAccess.dir_exists_absolute(GDUNIT_TEMP):
+ @warning_ignore("return_value_discarded")
+ DirAccess.make_dir_recursive_absolute(GDUNIT_TEMP)
+ return GDUNIT_TEMP
+
+
+func create_temp_dir(folder_name :String) -> String:
+ var new_folder := temp_dir() + "/" + folder_name
+ GdUnitFileAccess.delete_directory(new_folder)
+ if not DirAccess.dir_exists_absolute(new_folder):
+ @warning_ignore("return_value_discarded")
+ DirAccess.make_dir_recursive_absolute(new_folder)
+ return new_folder
+
+
+func copy_directory(from_dir: String, to_dir: String) -> bool:
+ if not DirAccess.dir_exists_absolute(from_dir):
+ printerr("Source directory not found '%s'" % from_dir)
+ return false
+ # check if destination exists
+ if not DirAccess.dir_exists_absolute(to_dir):
+ # create it
+ var err := DirAccess.make_dir_recursive_absolute(to_dir)
+ if err != OK:
+ printerr("Can't create directory '%s'. Error: %s" % [to_dir, error_string(err)])
+ return false
+ var source_dir := DirAccess.open(from_dir)
+ var dest_dir := DirAccess.open(to_dir)
+ if source_dir != null:
+ @warning_ignore("return_value_discarded")
+ source_dir.list_dir_begin()
+ var next := "."
+
+ while next != "":
+ next = source_dir.get_next()
+ if next == "" or next == "." or next == "..":
+ continue
+ var source := source_dir.get_current_dir() + "/" + next
+ var dest := dest_dir.get_current_dir() + "/" + next
+ if source_dir.current_is_dir():
+ @warning_ignore("return_value_discarded")
+ copy_directory(source + "/", dest)
+ continue
+ var err := source_dir.copy(source, dest)
+ if err != OK:
+ printerr("Error checked copy file '%s' to '%s'" % [source, dest])
+ return false
+ return true
+ else:
+ printerr("Directory not found: " + from_dir)
+ return false
+
+
+func extract_zip(zip_package: String, dest_path: String) -> Variant:
+ var zip: ZIPReader = ZIPReader.new()
+ var err := zip.open(zip_package)
+ if err != OK:
+ printerr("Extracting `%s` failed! Please collect the error log and report this. Error Code: %s" % [zip_package, err])
+ return null
+ var zip_entries: PackedStringArray = zip.get_files()
+ # Get base path and step over archive folder
+ var archive_path := zip_entries[0]
+ zip_entries.remove_at(0)
+
+ for zip_entry in zip_entries:
+ var new_file_path: String = dest_path + "/" + zip_entry.replace(archive_path, "")
+ if zip_entry.ends_with("/"):
+ @warning_ignore("return_value_discarded")
+ DirAccess.make_dir_recursive_absolute(new_file_path)
+ continue
+ var file: FileAccess = FileAccess.open(new_file_path, FileAccess.WRITE)
+ file.store_buffer(zip.read_file(zip_entry))
+ @warning_ignore("return_value_discarded")
+ zip.close()
+ return dest_path
+
+
+func download_release() -> void:
+ var zip_file := GdUnitFileAccess.temp_dir() + "/update.zip"
+ var response :GdUnitUpdateClient.HttpResponse
+ if _debug_mode:
+ response = GdUnitUpdateClient.HttpResponse.new(200, PackedByteArray())
+ zip_file = "res://update.zip"
+ return
+
+ response = await _update_client.request_zip_package(_download_url, zip_file)
+ if response.status() != 200:
+ push_warning("Update information cannot be retrieved from GitHub! \n Error code: %d : %s" % [response.status(), response.response()])
+ message_h4("Download the update failed! Try it later again.", Color.INDIAN_RED)
+ await get_tree().create_timer(3).timeout
+
+
+func rebuild_project() -> void:
+ # Check if this is a Godot .NET runtime instance
+ if not ClassDB.class_exists("CSharpScript"):
+ return
+
+ update_progress("Rebuild the project ...")
+ await get_tree().process_frame
+
+ var output := []
+ var exit_code := OS.execute("dotnet", ["build"], output)
+ if exit_code == -1:
+ message_h4("Rebuild the project failed, check your project dependencies.", Color.INDIAN_RED)
+ await get_tree().create_timer(3).timeout
+ return
+
+ for out: String in output:
+ print_rich("[color=DEEP_SKY_BLUE] %s" % out.strip_edges())
+ await get_tree().process_frame
+
+
+func _on_confirmed() -> void:
+ await run_update()
+
+
+func _on_cancel_pressed() -> void:
+ hide()
+
+
+func _on_update_pressed() -> void:
+ await run_update()