Skip to content

Commit

Permalink
GD-194: Fix load/restart errors on GdUnit update (#219)
Browse files Browse the repository at this point in the history
# Why
The update process was buggy and the Godot editor was not restarted
automatically after the update.

# What
- rework `GdMarkDownReader`
  - fix image handling, save always as PNG
  - fix render header 1 and 2 to show a line below
  - code cleanup 
- rework on GdUnit upgrade tool
- improve the upgrade dialog
- move download part to upgrade tool


![image](https://github.com/MikeSchulze/gdUnit4/assets/347037/84d4c905-fe8d-4181-9cc3-6fae2b8c18cf)


![image](https://github.com/MikeSchulze/gdUnit4/assets/347037/efea4ee3-a3f4-46df-960d-9ca17a7bce17)


![image](https://github.com/MikeSchulze/gdUnit4/assets/347037/002db6cd-a142-4e97-8413-f8a5e0a45fe2)
  • Loading branch information
MikeSchulze authored Jun 19, 2023
1 parent dcd6c40 commit 3439939
Show file tree
Hide file tree
Showing 20 changed files with 351 additions and 190 deletions.
3 changes: 3 additions & 0 deletions addons/gdUnit4/plugin.gd
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ func _enter_tree():
add_child(_server_node)
_fixup_node_inspector()
prints("Loading GdUnit4 Plugin success")
if GdUnitSettings.is_update_notification_enabled():
var update_tool = load("res://addons/gdUnit4/src/update/GdUnitUpdateNotify.tscn").instantiate()
Engine.get_main_loop().root.call_deferred("add_child", update_tool)


func _exit_tree():
Expand Down
3 changes: 0 additions & 3 deletions addons/gdUnit4/src/ui/GdUnitInspector.gd
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,6 @@ func _ready():
if Engine.is_editor_hint():
var plugin :EditorPlugin = Engine.get_meta("GdUnitEditorPlugin")
_getEditorThemes(plugin.get_editor_interface())
if GdUnitSettings.is_update_notification_enabled():
var update_tool = load("res://addons/gdUnit4/src/update/GdUnitUpdateNotify.tscn").instantiate()
add_child(update_tool)
GdUnitCommandHandler.instance().gdunit_runner_start.connect(func():
var tab_container :TabContainer = get_parent_control()
for tab_index in tab_container.get_tab_count():
Expand Down
73 changes: 48 additions & 25 deletions addons/gdUnit4/src/update/GdMarkDownReader.gd
Original file line number Diff line number Diff line change
@@ -1,30 +1,48 @@
extends RefCounted

const FONT_H1 := 32
const FONT_H2 := 28
const FONT_H3 := 24
const FONT_H4 := 20
const FONT_H5 := 16
const FONT_H6 := 12

const HORIZONTAL_RULE := "[img=4000x2]res://addons/gdUnit4/src/update/assets/horizontal-line2.png[/img]\n"
const HEADER_RULE := "[font_size=%d]$1[/font_size]\n"
const HEADER_CENTERED_RULE := "[font_size=%d][center]$1[/center][/font_size]\n"

const image_download_folder := "res://addons/gdUnit4/tmp-update/"

const exclude_font_size := "\b(?!(?:(font_size))\b)"

var md_replace_patterns := [
# horizontal rules
[regex("(?m)^ {0,3}---$"), "[img=4000x2]res://addons/gdUnit4/src/update/assets/horizontal-line2.png[/img]"],
[regex("(?m)^[ ]{0,3}___$"), "[img=4000x2]res://addons/gdUnit4/src/update/assets/horizontal-line2.png[/img]"],
[regex("(?m)^[ ]{0,3}\\*\\*\\*$"), "[img=4000x2]res://addons/gdUnit4/src/update/assets/horizontal-line2.png[/img]"],
[regex("(?m)^[ ]{0,3}---$"), HORIZONTAL_RULE],
[regex("(?m)^[ ]{0,3}___$"), HORIZONTAL_RULE],
[regex("(?m)^[ ]{0,3}\\*\\*\\*$"), HORIZONTAL_RULE],

# headers
[regex("(?m)^##### (.*)"), "[font_size=8]$1[/font_size]"],
[regex("(?m)^#### (.*)"), "[font_size=12]$1[/font_size]"],
[regex("(?m)^### (.*)"), "[font_size=16]$1[/font_size]"],
[regex("(?m)^## (.*)"), "[font_size=20]$1[/font_size]"],
[regex("(?m)^# (.*)"), "[font_size=24]$1[/font_size]"],
[regex("(?m)^(.+)=={2,}$"), "[font_size=20]$1[/font_size]"],
[regex("(?m)^(.+)--{2,}$"), "[font_size=24]$1[/font_size]"],
[regex("(?m)^###### (.*)"), HEADER_RULE % FONT_H6],
[regex("(?m)^##### (.*)"), HEADER_RULE % FONT_H5],
[regex("(?m)^#### (.*)"), HEADER_RULE % FONT_H4],
[regex("(?m)^### (.*)"), HEADER_RULE % FONT_H3],
[regex("(?m)^## (.*)"), (HEADER_RULE + HORIZONTAL_RULE) % FONT_H2],
[regex("(?m)^# (.*)"), (HEADER_RULE + HORIZONTAL_RULE) % FONT_H1],
[regex("(?m)^(.+)=={2,}$"), HEADER_RULE % FONT_H1],
[regex("(?m)^(.+)--{2,}$"), HEADER_RULE % FONT_H2],
# html headers
[regex("<h1>((.*?\\R?)+)<\\/h1>"), "[font_size=24]$1[/font_size]"],
[regex("<h1[ ]*align[ ]*=[ ]*\"center\">((.*?\\R?)+)<\\/h1>"), "[font_size=24][center]$1[/center][/font_size]"],
[regex("<h2>((.*?\\R?)+)<\\/h2>"), "[font_size=20]$1[/font_size]"],
[regex("<h2[ ]*align[ ]*=[ ]*\"center\">((.*?\\R?)+)<\\/h2>"), "[font_size=20][center]$1[/center][/font_size]"],
[regex("<h3>((.*?\\R?)+)<\\/h3>"), "[font_size=16]$1[/font_size]"],
[regex("<h3[ ]*align[ ]*=[ ]*\"center\">((.*?\\R?)+)<\\/h3>"), "[font_size=16][center]$1[/center][/font_size]"],
[regex("<h4>((.*?\\R?)+)<\\/h4>"), "[font_size=12]$1[/font_size]"],
[regex("<h4[ ]*align[ ]*=[ ]*\"center\">((.*?\\R?)+)<\\/h4>"), "[font_size=12][center]$1[/center][/font_size]"],
[regex("<h1>((.*?\\R?)+)<\\/h1>"), (HEADER_RULE + HORIZONTAL_RULE) % FONT_H1],
[regex("<h1[ ]*align[ ]*=[ ]*\"center\">((.*?\\R?)+)<\\/h1>"), (HEADER_CENTERED_RULE + HORIZONTAL_RULE) % FONT_H1],
[regex("<h2>((.*?\\R?)+)<\\/h2>"), (HEADER_RULE + HORIZONTAL_RULE) % FONT_H2],
[regex("<h2[ ]*align[ ]*=[ ]*\"center\">((.*?\\R?)+)<\\/h2>"), (HEADER_CENTERED_RULE + HORIZONTAL_RULE) % FONT_H1],
[regex("<h3>((.*?\\R?)+)<\\/h3>"), HEADER_RULE % FONT_H3],
[regex("<h3[ ]*align[ ]*=[ ]*\"center\">((.*?\\R?)+)<\\/h3>"), HEADER_CENTERED_RULE % FONT_H3],
[regex("<h4>((.*?\\R?)+)<\\/h4>"), HEADER_RULE % FONT_H4],
[regex("<h4[ ]*align[ ]*=[ ]*\"center\">((.*?\\R?)+)<\\/h4>"), HEADER_CENTERED_RULE % FONT_H4],
[regex("<h5>((.*?\\R?)+)<\\/h5>"), HEADER_RULE % FONT_H5],
[regex("<h5[ ]*align[ ]*=[ ]*\"center\">((.*?\\R?)+)<\\/h5>"), HEADER_CENTERED_RULE % FONT_H5],
[regex("<h6>((.*?\\R?)+)<\\/h6>"), HEADER_RULE % FONT_H6],
[regex("<h6[ ]*align[ ]*=[ ]*\"center\">((.*?\\R?)+)<\\/h6>"), HEADER_CENTERED_RULE % FONT_H6],

# asterics
#[regex("(\\*)"), "xxx$1xxx"],
Expand Down Expand Up @@ -68,13 +86,13 @@ var md_replace_patterns := [
[regex("(?m)^[ ]{8,9}[*\\-+] (.*)$"), list_replace(4)],

# code blocks, code blocks looks not like code blocks in richtext
[regex("```(javascript|python|)([\\s\\S]*?\n)```"), code_block("$2", true)],
[regex("```(javascript|python|shell|gdscript)([\\s\\S]*?\n)```"), code_block("$2", true)],
[regex("``([\\s\\S]*?)``"), code_block("$1")],
[regex("`([\\s\\S]*?)`{1,2}"), code_block("$1")],
]

var _img_replace_regex := RegEx.new()
var _image_urls := Array()
var _image_urls := PackedStringArray()
var _on_table_tag := false
var _client

Expand Down Expand Up @@ -132,7 +150,7 @@ func to_bbcode(input :String) -> String:
input = await bb_replace.call(regex_, input)
else:
input = regex_.sub(input, bb_replace, true)
return input
return input + "\n"


func process_tables(input :String) -> String:
Expand Down Expand Up @@ -292,23 +310,28 @@ func process_image(p_regex :RegEx, p_input :String) -> String:


func _process_external_image_resources(input :String) -> String:
DirAccess.make_dir_recursive_absolute(image_download_folder)
# scan all img for external resources and download it
for value in _img_replace_regex.search_all(input):
if value.get_group_count() >= 1:
var image_url :String = value.get_string(1)
# if not a local resource we need to download it
if image_url.begins_with("http"):
prints("download immage:", image_url)
if OS.is_stdout_verbose():
prints("download image:", image_url)
var response = await _client.request_image(image_url)
if response.code() == 200:
var image = Image.new()
var error = image.load_png_from_buffer(response.body())
if error != OK:
prints("Error creating image from response", error)
var new_url := "res://addons/gdUnit4/src/update/%s" % image_url.get_file()
# replace characters where format characters
new_url = new_url.replace("_", "-")
image.save_png(new_url)
var new_url := image_download_folder + image_url.get_file().replace("_", "-")
if new_url.get_extension() != 'png':
new_url = new_url + '.png'
var err := image.save_png(new_url)
if err:
push_error("Can't save image to '%s'. Error: %s" % [new_url, error_string(err)])
_image_urls.append(new_url)
input = input.replace(image_url, new_url)
return input
94 changes: 63 additions & 31 deletions addons/gdUnit4/src/update/GdUnitUpdate.gd
Original file line number Diff line number Diff line change
@@ -1,36 +1,65 @@
@tool
extends ConfirmationDialog

const GdUnitUpdateClient = preload("res://addons/gdUnit4/src/update/GdUnitUpdateClient.gd")
const spinner_icon := "res://addons/gdUnit4/src/ui/assets/spinner.tres"

#@onready var _progress_panel :Control =$UpdateProgress
@onready var _progress_content :Label = $UpdateProgress/Progress/label
@onready var _progress_bar :ProgressBar = $UpdateProgress/Progress/bar

@onready var _progress_content :RichTextLabel = %message
@onready var _progress_bar :TextureProgressBar = %progress


var _debug_mode := false
var _editor_interface :EditorInterface
var _update_client :GdUnitUpdateClient
var _download_url :String


func _ready():
message_h4("Press 'Update' to start!", Color.GREEN)
init_progress(5)


func _process(_delta):
if _progress_content != null and _progress_content.is_visible_in_tree():
_progress_content.queue_redraw()


func init_progress(max_value : int) -> void:
_progress_bar.max_value = max_value
_progress_bar.value = 0
_progress_bar.value = 1


func setup(editor_interface :EditorInterface, update_client :GdUnitUpdateClient, download_url :String) -> void:
_editor_interface = editor_interface
_update_client = update_client
_download_url = download_url


func update_progress(message :String) -> void:
prints("..", message)
_progress_content.text = message
message_h4(message, Color.GREEN)
_progress_bar.value += 1
if _debug_mode:
await get_tree().create_timer(3).timeout
else:
await get_tree().process_frame
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) -> void:
_progress_content.clear()
_progress_content.append_text("[font_size=16]%s[/font_size]" % _colored(message, color))


func run_update() -> void:
get_cancel_button().disabled = true
get_ok_button().disabled = true
init_progress(4)

await update_progress("Extract update ..")
await update_progress("Download Release ... [img=24x24]%s[/img]" % spinner_icon)
await download_release()
await update_progress("Extract update ... [img=24x24]%s[/img]" % spinner_icon)
var zip_file := temp_dir() + "/update.zip"
var tmp_path := create_temp_dir("update")
var result :Variant = extract_zip(zip_file, tmp_path)
Expand All @@ -40,44 +69,31 @@ func run_update() -> void:
queue_free()
return

await update_progress("Deinstall GdUnit4 ..")
await update_progress("Uninstall GdUnit4 ... [img=24x24]%s[/img]" % spinner_icon)
disable_gdUnit()
if not _debug_mode:
delete_directory("res://addons/gdUnit4/")
# give editor time to react on deleted files
await get_tree().create_timer(3).timeout
await get_tree().create_timer(1).timeout

await update_progress("Install new GdUnit4 version ..")
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("New GdUnit version successfully installed, Restarting Godot ...")
enable_gdUnit()
await get_tree().create_timer(3).timeout
enable_gdUnit()
hide()
delete_directory("res://addons/.gdunit_update")
restart_godot()


func rescan() -> void:
await get_tree().process_frame
prints("Rescan Project")
var plugin :EditorPlugin = Engine.get_meta("GdUnitEditorPlugin")
var fs := plugin.get_editor_interface().get_resource_filesystem()
fs.scan_sources()
fs.update_script_classes()
fs.scan()
while fs.is_scanning():
prints("Rescan Project ... scan ...")
await get_tree().create_timer(.2).timeout


func restart_godot() -> void:
prints("Force restart Godot")
var plugin :EditorPlugin = Engine.get_meta("GdUnitEditorPlugin")
plugin.get_editor_interface().restart_editor(true)
if _editor_interface:
_editor_interface.restart_editor(true)


func enable_gdUnit() -> void:
Expand All @@ -91,8 +107,8 @@ func enable_gdUnit() -> void:


func disable_gdUnit() -> void:
var plugin :EditorPlugin = Engine.get_meta("GdUnitEditorPlugin")
plugin.get_editor_interface().set_plugin_enabled("gdUnit4", false)
if _editor_interface:
_editor_interface.set_plugin_enabled("gdUnit4", false)


const GDUNIT_TEMP := "user://tmp"
Expand Down Expand Up @@ -192,5 +208,21 @@ func extract_zip(zip_package :String, dest_path :String) -> Variant:
return dest_path


func download_release() -> void:
var zip_file := GdUnitTools.temp_dir() + "/update.zip"
var response :GdUnitUpdateClient.HttpResponse
if _debug_mode:
response = GdUnitUpdateClient.HttpResponse.new(200, PackedByteArray())
zip_file = "res://update.zip"
else:
response = await _update_client.request_zip_package(_download_url, zip_file)
_update_client.queue_free()
if response.code() != 200:
push_warning("Update information cannot be retrieved from GitHub! \n Error code: %d : %s" % [response.code(), response.response()])
await message_h4("Update failed! Try it later again.", Color.RED)
await get_tree().create_timer(3).timeout
return


func _on_confirmed():
await run_update()
Loading

0 comments on commit 3439939

Please sign in to comment.