reinstalling GDUnit from assetlib
Some checks failed
Create tag and build when new code gets to main / Export (push) Failing after 6m41s
Some checks failed
Create tag and build when new code gets to main / Export (push) Failing after 6m41s
This commit is contained in:
@@ -0,0 +1,405 @@
|
||||
@tool
|
||||
extends RefCounted
|
||||
|
||||
const GdUnitUpdateClient = preload("res://addons/gdUnit4/src/update/GdUnitUpdateClient.gd")
|
||||
|
||||
const FONT_H1 := 22
|
||||
const FONT_H2 := 20
|
||||
const FONT_H3 := 18
|
||||
const FONT_H4 := 16
|
||||
const FONT_H5 := 14
|
||||
const FONT_H6 := 12
|
||||
|
||||
const HORIZONTAL_RULE := "[img=4000x2]res://addons/gdUnit4/src/update/assets/horizontal-line2.png[/img]"
|
||||
const HEADER_RULE := "[font_size=%d]$1[/font_size]"
|
||||
const HEADER_CENTERED_RULE := "[font_size=%d][center]$1[/center][/font_size]"
|
||||
|
||||
const image_download_folder := "res://addons/gdUnit4/tmp-update/"
|
||||
|
||||
const exclude_font_size := "\b(?!(?:(font_size))\b)"
|
||||
|
||||
var md_replace_patterns := [
|
||||
# comments
|
||||
[regex("(?m)^\\n?\\s*<!--[\\s\\S]*?-->\\s*\\n?"), ""],
|
||||
|
||||
# horizontal rules
|
||||
[regex("(?m)^[ ]{0,3}---$"), HORIZONTAL_RULE],
|
||||
[regex("(?m)^[ ]{0,3}___$"), HORIZONTAL_RULE],
|
||||
[regex("(?m)^[ ]{0,3}\\*\\*\\*$"), HORIZONTAL_RULE],
|
||||
|
||||
# headers
|
||||
[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>"), (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"],
|
||||
|
||||
# extract/compile image references
|
||||
[regex("!\\[(.*?)\\]\\[(.*?)\\]"), process_image_references],
|
||||
# extract images with path and optional tool tip
|
||||
[regex("!\\[(.*?)\\]\\((.*?)(( )+(.*?))?\\)"), process_image],
|
||||
|
||||
# links
|
||||
[regex("([!]|)\\[(.+)\\]\\(([^ ]+?)\\)"), "[url={\"url\":\"$3\"}]$2[/url]"],
|
||||
# links with tool tip
|
||||
[regex("([!]|)\\[(.+)\\]\\(([^ ]+?)( \"(.+)\")?\\)"), "[url={\"url\":\"$3\", \"tool_tip\":\"$5\"}]$2[/url]"],
|
||||
# links to github, as shorted link
|
||||
[regex("(https://github.*/?/(\\S+))"), '[url={"url":"$1", "tool_tip":"$1"}]#$2[/url]'],
|
||||
|
||||
# embeded text
|
||||
[regex("(?m)^[ ]{0,3}>(.*?)$"), "[img=50x14]res://addons/gdUnit4/src/update/assets/embedded.png[/img][i]$1[/i]"],
|
||||
|
||||
# italic + bold font
|
||||
[regex("[_]{3}(.*?)[_]{3}"), "[i][b]$1[/b][/i]"],
|
||||
[regex("[\\*]{3}(.*?)[\\*]{3}"), "[i][b]$1[/b][/i]"],
|
||||
# bold font
|
||||
[regex("<b>(.*?)<\\/b>"), "[b]$1[/b]"],
|
||||
[regex("[_]{2}(.*?)[_]{2}"), "[b]$1[/b]"],
|
||||
[regex("[\\*]{2}(.*?)[\\*]{2}"), "[b]$1[/b]"],
|
||||
# italic font
|
||||
[regex("<i>(.*?)<\\/i>"), "[i]$1[/i]"],
|
||||
[regex(exclude_font_size+"_(.*?)_"), "[i]$1[/i]"],
|
||||
[regex("\\*(.*?)\\*"), "[i]$1[/i]"],
|
||||
|
||||
# strikethrough font
|
||||
[regex("<s>(.*?)</s>"), "[s]$1[/s]"],
|
||||
[regex("~~(.*?)~~"), "[s]$1[/s]"],
|
||||
[regex("~(.*?)~"), "[s]$1[/s]"],
|
||||
|
||||
# handling lists
|
||||
# using an image for dots
|
||||
[regex("(?m)^[ ]{0,1}[*\\-+] (.*)$"), list_replace(0)],
|
||||
[regex("(?m)^[ ]{2,3}[*\\-+] (.*)$"), list_replace(1)],
|
||||
[regex("(?m)^[ ]{4,5}[*\\-+] (.*)$"), list_replace(2)],
|
||||
[regex("(?m)^[ ]{6,7}[*\\-+] (.*)$"), list_replace(3)],
|
||||
[regex("(?m)^[ ]{8,9}[*\\-+] (.*)$"), list_replace(4)],
|
||||
|
||||
# code
|
||||
[regex("``([\\s\\S]*?)``"), code_block("$1")],
|
||||
[regex("`([\\s\\S]*?)`{1,2}"), code_block("$1")],
|
||||
]
|
||||
|
||||
var code_block_patterns := [
|
||||
# code blocks, code blocks looks not like code blocks in richtext
|
||||
[regex("```(javascript|python|shell|gdscript|gd)([\\s\\S]*?\n)```"), code_block("$2", true)],
|
||||
]
|
||||
|
||||
var _img_replace_regex := RegEx.new()
|
||||
var _image_urls := PackedStringArray()
|
||||
var _on_table_tag := false
|
||||
var _client: GdUnitUpdateClient
|
||||
|
||||
|
||||
static func regex(pattern: String) -> RegEx:
|
||||
var regex_ := RegEx.new()
|
||||
var err := regex_.compile(pattern)
|
||||
if err != OK:
|
||||
push_error("error '%s' checked pattern '%s'" % [err, pattern])
|
||||
return null
|
||||
return regex_
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
@warning_ignore("return_value_discarded")
|
||||
_img_replace_regex.compile("\\[img\\]((.*?))\\[/img\\]")
|
||||
|
||||
|
||||
func set_http_client(client: GdUnitUpdateClient) -> void:
|
||||
_client = client
|
||||
|
||||
|
||||
@warning_ignore("return_value_discarded")
|
||||
func _notification(what: int) -> void:
|
||||
if what == NOTIFICATION_PREDELETE:
|
||||
# finally remove_at the downloaded images
|
||||
for image in _image_urls:
|
||||
DirAccess.remove_absolute(image)
|
||||
DirAccess.remove_absolute(image + ".import")
|
||||
|
||||
|
||||
func list_replace(indent: int) -> String:
|
||||
var replace_pattern := "[img=12x12]res://addons/gdUnit4/src/update/assets/dot2.png[/img]" if indent %2 else "[img=12x12]res://addons/gdUnit4/src/update/assets/dot1.png[/img]"
|
||||
replace_pattern += " $1"
|
||||
|
||||
for index in indent:
|
||||
replace_pattern = replace_pattern.insert(0, " ")
|
||||
return replace_pattern
|
||||
|
||||
|
||||
func code_block(replace: String, border: bool = false) -> String:
|
||||
if border:
|
||||
return """
|
||||
[img=1400x14]res://addons/gdUnit4/src/update/assets/border_top.png[/img]
|
||||
[indent][color=GRAY][font_size=16]%s[/font_size][/color][/indent]
|
||||
[img=1400x14]res://addons/gdUnit4/src/update/assets/border_bottom.png[/img]
|
||||
""".dedent() % replace
|
||||
return "[code][bgcolor=DARK_SLATE_GRAY][color=GRAY][font_size=16]%s[/font_size][/color][/bgcolor][/code]" % replace
|
||||
|
||||
|
||||
func convert_text(input: String) -> String:
|
||||
input = process_tables(input)
|
||||
|
||||
for pattern: Array in md_replace_patterns:
|
||||
var regex_: RegEx = pattern[0]
|
||||
var bb_replace: Variant = pattern[1]
|
||||
if bb_replace is Callable:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
input = await bb_replace.call(regex_, input)
|
||||
else:
|
||||
@warning_ignore("unsafe_cast")
|
||||
input = regex_.sub(input, bb_replace as String, true)
|
||||
return input
|
||||
|
||||
|
||||
func convert_code_block(input: String) -> String:
|
||||
for pattern: Array in code_block_patterns:
|
||||
var regex_: RegEx = pattern[0]
|
||||
var bb_replace: Variant = pattern[1]
|
||||
if bb_replace is Callable:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
input = await bb_replace.call(regex_, input)
|
||||
else:
|
||||
@warning_ignore("unsafe_cast")
|
||||
input = regex_.sub(input, bb_replace as String, true)
|
||||
return input
|
||||
|
||||
|
||||
func to_bbcode(input: String) -> String:
|
||||
var re := regex("(?m)```[\\s\\S]*?```")
|
||||
var current_pos := 0
|
||||
var as_bbcode := ""
|
||||
|
||||
# we split by code blocks to handle this blocks customized
|
||||
for result in re.search_all(input):
|
||||
# Add text before code block
|
||||
if result.get_start() > current_pos:
|
||||
as_bbcode += await convert_text(input.substr(current_pos, result.get_start() - current_pos))
|
||||
# Add code block
|
||||
as_bbcode += await convert_code_block(result.get_string())
|
||||
current_pos = result.get_end()
|
||||
|
||||
# Add remaining text after last code block
|
||||
if current_pos < input.length():
|
||||
as_bbcode += await convert_text(input.substr(current_pos))
|
||||
return as_bbcode
|
||||
|
||||
|
||||
func process_tables(input: String) -> String:
|
||||
var bbcode := PackedStringArray()
|
||||
var lines: Array[String] = Array(input.split("\n") as Array, TYPE_STRING, "", null)
|
||||
while not lines.is_empty():
|
||||
if is_table(lines[0]):
|
||||
bbcode.append_array(parse_table(lines))
|
||||
continue
|
||||
@warning_ignore("return_value_discarded", "unsafe_cast")
|
||||
bbcode.append(lines.pop_front() as String)
|
||||
return "\n".join(bbcode)
|
||||
|
||||
|
||||
class GdUnitMDReaderTable:
|
||||
var _columns: int
|
||||
var _rows: Array[Row] = []
|
||||
|
||||
class Row:
|
||||
var _cells := PackedStringArray()
|
||||
|
||||
|
||||
func _init(cells: PackedStringArray, columns: int) -> void:
|
||||
_cells = cells
|
||||
for i in range(_cells.size(), columns):
|
||||
@warning_ignore("return_value_discarded")
|
||||
_cells.append("")
|
||||
|
||||
|
||||
func to_bbcode(cell_sizes: PackedInt32Array, bold: bool) -> String:
|
||||
var cells := PackedStringArray()
|
||||
for cell_index in _cells.size():
|
||||
var cell: String = _cells[cell_index]
|
||||
if cell.strip_edges() == "--":
|
||||
cell = create_line(cell_sizes[cell_index])
|
||||
if bold:
|
||||
cell = "[b]%s[/b]" % cell
|
||||
@warning_ignore("return_value_discarded")
|
||||
cells.append("[cell]%s[/cell]" % cell)
|
||||
return "|".join(cells)
|
||||
|
||||
|
||||
func create_line(length: int) -> String:
|
||||
var line := ""
|
||||
for i in length:
|
||||
line += "-"
|
||||
return line
|
||||
|
||||
|
||||
func _init(columns: int) -> void:
|
||||
_columns = columns
|
||||
|
||||
|
||||
func parse_row(line :String) -> bool:
|
||||
# is line containing cells?
|
||||
if line.find("|") == -1:
|
||||
return false
|
||||
_rows.append(Row.new(line.split("|"), _columns))
|
||||
return true
|
||||
|
||||
|
||||
func calculate_max_cell_sizes() -> PackedInt32Array:
|
||||
var cells_size := PackedInt32Array()
|
||||
for column in _columns:
|
||||
@warning_ignore("return_value_discarded")
|
||||
cells_size.append(0)
|
||||
|
||||
for row_index in _rows.size():
|
||||
var row: Row = _rows[row_index]
|
||||
for cell_index in row._cells.size():
|
||||
var cell_size: int = cells_size[cell_index]
|
||||
var size := row._cells[cell_index].length()
|
||||
if size > cell_size:
|
||||
cells_size[cell_index] = size
|
||||
return cells_size
|
||||
|
||||
|
||||
@warning_ignore("return_value_discarded")
|
||||
func to_bbcode() -> PackedStringArray:
|
||||
var cell_sizes := calculate_max_cell_sizes()
|
||||
var bb_code := PackedStringArray()
|
||||
|
||||
bb_code.append("[table=%d]" % _columns)
|
||||
for row_index in _rows.size():
|
||||
bb_code.append(_rows[row_index].to_bbcode(cell_sizes, row_index==0))
|
||||
bb_code.append("[/table]\n")
|
||||
return bb_code
|
||||
|
||||
|
||||
func parse_table(lines: Array) -> PackedStringArray:
|
||||
var line: String = lines[0]
|
||||
var table := GdUnitMDReaderTable.new(line.count("|") + 1)
|
||||
while not lines.is_empty():
|
||||
line = lines.pop_front()
|
||||
if not table.parse_row(line):
|
||||
break
|
||||
return table.to_bbcode()
|
||||
|
||||
|
||||
func is_table(line: String) -> bool:
|
||||
return line.find("|") != -1
|
||||
|
||||
|
||||
func open_table(line: String) -> String:
|
||||
_on_table_tag = true
|
||||
return "[table=%d]" % (line.count("|") + 1)
|
||||
|
||||
|
||||
func close_table() -> String:
|
||||
_on_table_tag = false
|
||||
return "[/table]"
|
||||
|
||||
|
||||
func extract_cells(line: String, bold := false) -> String:
|
||||
var cells := ""
|
||||
for cell in line.split("|"):
|
||||
if bold:
|
||||
cell = "[b]%s[/b]" % cell
|
||||
cells += "[cell]%s[/cell]" % cell
|
||||
return cells
|
||||
|
||||
|
||||
func process_image_references(p_regex: RegEx, p_input: String) -> String:
|
||||
#return p_input
|
||||
|
||||
# exists references?
|
||||
var matches := p_regex.search_all(p_input)
|
||||
if matches.is_empty():
|
||||
return p_input
|
||||
# collect image references and remove_at it
|
||||
var references := Dictionary()
|
||||
var link_regex := regex("\\[(\\S+)\\]:(\\S+)([ ]\"(.*)\")?")
|
||||
# create copy of original source to replace checked it
|
||||
var input := p_input.replace("\r", "")
|
||||
var extracted_references := p_input.replace("\r", "")
|
||||
for reg_match in link_regex.search_all(input):
|
||||
var line := reg_match.get_string(0) + "\n"
|
||||
var ref := reg_match.get_string(1)
|
||||
#var topl_tip = reg_match.get_string(4)
|
||||
# collect reference and url
|
||||
references[ref] = reg_match.get_string(2)
|
||||
extracted_references = extracted_references.replace(line, "")
|
||||
|
||||
# replace image references by collected url's
|
||||
for reference_key: String in references.keys():
|
||||
var regex_key := regex("\\](\\[%s\\])" % reference_key)
|
||||
for reg_match in regex_key.search_all(extracted_references):
|
||||
var ref: String = reg_match.get_string(0)
|
||||
var image_url: String = "](%s)" % references.get(reference_key)
|
||||
extracted_references = extracted_references.replace(ref, image_url)
|
||||
return extracted_references
|
||||
|
||||
|
||||
@warning_ignore("return_value_discarded")
|
||||
func process_image(p_regex: RegEx, p_input: String) -> String:
|
||||
#return p_input
|
||||
var to_replace := PackedStringArray()
|
||||
var tool_tips := PackedStringArray()
|
||||
# find all matches
|
||||
var matches := p_regex.search_all(p_input)
|
||||
if matches.is_empty():
|
||||
return p_input
|
||||
for reg_match in matches:
|
||||
# grap the parts to replace and store temporay because a direct replace will distort the offsets
|
||||
to_replace.append(p_input.substr(reg_match.get_start(0), reg_match.get_end(0)))
|
||||
# grap optional tool tips
|
||||
tool_tips.append(reg_match.get_string(5))
|
||||
# finally replace all findings
|
||||
for replace in to_replace:
|
||||
var re := p_regex.sub(replace, "[img]$2[/img]")
|
||||
p_input = p_input.replace(replace, re)
|
||||
return await _process_external_image_resources(p_input)
|
||||
|
||||
|
||||
func _process_external_image_resources(input: String) -> String:
|
||||
@warning_ignore("return_value_discarded")
|
||||
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"):
|
||||
if OS.is_stdout_verbose():
|
||||
prints("download image:", image_url)
|
||||
var response := await _client.request_image(image_url)
|
||||
if response.status() == 200:
|
||||
var image := Image.new()
|
||||
var error := image.load_png_from_buffer(response.get_body())
|
||||
if error != OK:
|
||||
prints("Error creating image from response", error)
|
||||
# replace characters where format characters
|
||||
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)])
|
||||
@warning_ignore("return_value_discarded")
|
||||
_image_urls.append(new_url)
|
||||
input = input.replace(image_url, new_url)
|
||||
return input
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
uid://c2ii27n08li2k
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
class_name GdUnitPatch
|
||||
extends RefCounted
|
||||
|
||||
const PATCH_VERSION = "patch_version"
|
||||
|
||||
var _version :GdUnit4Version
|
||||
|
||||
|
||||
func _init(version_ :GdUnit4Version) -> void:
|
||||
_version = version_
|
||||
|
||||
|
||||
func version() -> GdUnit4Version:
|
||||
return _version
|
||||
|
||||
|
||||
# this function needs to be implement
|
||||
func execute() -> bool:
|
||||
push_error("The function 'execute()' is not implemented at %s" % self)
|
||||
return false
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
uid://drpr6gj1mlhxl
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
class_name GdUnitPatcher
|
||||
extends RefCounted
|
||||
|
||||
|
||||
const _base_dir := "res://addons/gdUnit4/src/update/patches/"
|
||||
|
||||
var _patches := Dictionary()
|
||||
|
||||
|
||||
func scan(current :GdUnit4Version) -> void:
|
||||
_scan(_base_dir, current)
|
||||
|
||||
|
||||
func _scan(scan_path :String, current :GdUnit4Version) -> void:
|
||||
_patches = Dictionary()
|
||||
var patch_paths := _collect_patch_versions(scan_path, current)
|
||||
for path in patch_paths:
|
||||
prints("scan for patches checked '%s'" % path)
|
||||
_patches[path] = _scan_patches(path)
|
||||
|
||||
|
||||
func patch_count() -> int:
|
||||
var count := 0
|
||||
for key :String in _patches.keys():
|
||||
@warning_ignore("unsafe_method_access")
|
||||
count += _patches[key].size()
|
||||
return count
|
||||
|
||||
|
||||
func execute() -> void:
|
||||
for key :String in _patches.keys():
|
||||
for path :String in _patches[key]:
|
||||
var patch :GdUnitPatch = (load(key + "/" + path) as GDScript).new()
|
||||
if patch:
|
||||
prints("execute patch", patch.version(), patch.get_script().resource_path)
|
||||
if not patch.execute():
|
||||
prints("error checked execution patch %s" % key + "/" + path)
|
||||
|
||||
|
||||
func _collect_patch_versions(scan_path :String, current :GdUnit4Version) -> PackedStringArray:
|
||||
if not DirAccess.dir_exists_absolute(scan_path):
|
||||
return PackedStringArray()
|
||||
var patches := Array()
|
||||
var dir := DirAccess.open(scan_path)
|
||||
if dir != null:
|
||||
@warning_ignore("return_value_discarded")
|
||||
dir.list_dir_begin() # TODO GODOT4 fill missing arguments https://github.com/godotengine/godot/pull/40547
|
||||
var next := "."
|
||||
while next != "":
|
||||
next = dir.get_next()
|
||||
if next.is_empty() or next == "." or next == "..":
|
||||
continue
|
||||
var version := GdUnit4Version.parse(next)
|
||||
if version.is_greater(current):
|
||||
patches.append(scan_path + next)
|
||||
patches.sort()
|
||||
return PackedStringArray(patches)
|
||||
|
||||
|
||||
func _scan_patches(path :String) -> PackedStringArray:
|
||||
var patches := Array()
|
||||
var dir := DirAccess.open(path)
|
||||
if dir != null:
|
||||
@warning_ignore("return_value_discarded")
|
||||
dir.list_dir_begin() # TODOGODOT4 fill missing arguments https://github.com/godotengine/godot/pull/40547
|
||||
var next := "."
|
||||
while next != "":
|
||||
next = dir.get_next()
|
||||
# step over directory links and .uid files
|
||||
if next.is_empty() or next == "." or next == ".." or next.ends_with(".uid"):
|
||||
continue
|
||||
patches.append(next)
|
||||
# make sorted from lowest to high version
|
||||
patches.sort()
|
||||
return PackedStringArray(patches)
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
uid://cxv57lcra5lsd
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
uid://2cvldn16wv2b
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
[gd_scene load_steps=6 format=3 uid="uid://2eahgaw88y6q"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/gdUnit4/src/update/GdUnitUpdate.gd" id="1"]
|
||||
|
||||
[sub_resource type="Gradient" id="Gradient_wilsr"]
|
||||
colors = PackedColorArray(0.151276, 0.151276, 0.151276, 1, 1, 1, 1, 1)
|
||||
|
||||
[sub_resource type="GradientTexture2D" id="GradientTexture2D_45cww"]
|
||||
gradient = SubResource("Gradient_wilsr")
|
||||
fill_to = Vector2(0.75641, 0)
|
||||
|
||||
[sub_resource type="Gradient" id="Gradient_i0qp8"]
|
||||
colors = PackedColorArray(1, 1, 1, 1, 0.20871, 0.20871, 0.20871, 1)
|
||||
|
||||
[sub_resource type="GradientTexture2D" id="GradientTexture2D_wilsr"]
|
||||
gradient = SubResource("Gradient_i0qp8")
|
||||
fill_from = Vector2(0.794872, 0)
|
||||
fill_to = Vector2(0, 0)
|
||||
|
||||
[node name="GdUnitUpdate" type="MarginContainer"]
|
||||
clip_contents = true
|
||||
custom_minimum_size = Vector2(0, 80)
|
||||
anchors_preset = 10
|
||||
anchor_right = 1.0
|
||||
offset_bottom = 80.0
|
||||
grow_horizontal = 2
|
||||
size_flags_horizontal = 3
|
||||
theme_override_constants/margin_left = 10
|
||||
theme_override_constants/margin_right = 10
|
||||
script = ExtResource("1")
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="."]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Panel" type="Panel" parent="VBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="message" type="RichTextLabel" parent="VBoxContainer/Panel"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
bbcode_enabled = true
|
||||
text = "aaaaa"
|
||||
fit_content = true
|
||||
scroll_active = false
|
||||
shortcut_keys_enabled = false
|
||||
|
||||
[node name="Panel2" type="Panel" parent="VBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="progress" type="TextureProgressBar" parent="VBoxContainer/Panel2"]
|
||||
unique_name_in_owner = true
|
||||
auto_translate_mode = 2
|
||||
clip_contents = true
|
||||
custom_minimum_size = Vector2(0, 20)
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
localize_numeral_system = false
|
||||
min_value = 1.0
|
||||
max_value = 5.0
|
||||
value = 1.0
|
||||
rounded = true
|
||||
allow_greater = true
|
||||
nine_patch_stretch = true
|
||||
texture_under = SubResource("GradientTexture2D_45cww")
|
||||
texture_progress = SubResource("GradientTexture2D_wilsr")
|
||||
tint_under = Color(0.0235294, 0.145098, 0.168627, 1)
|
||||
tint_progress = Color(0.288912, 0.233442, 0.533772, 1)
|
||||
|
||||
[node name="PanelContainer" type="MarginContainer" parent="VBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/PanelContainer"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 10
|
||||
alignment = 2
|
||||
|
||||
[node name="update" type="Button" parent="VBoxContainer/PanelContainer/HBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "Update"
|
||||
|
||||
[node name="cancel" type="Button" parent="VBoxContainer/PanelContainer/HBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "Cancel"
|
||||
|
||||
[connection signal="pressed" from="VBoxContainer/PanelContainer/HBoxContainer/update" to="." method="_on_update_pressed"]
|
||||
[connection signal="pressed" from="VBoxContainer/PanelContainer/HBoxContainer/cancel" to="." method="_on_cancel_pressed"]
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
@tool
|
||||
extends Node
|
||||
|
||||
signal request_completed(response: HttpResponse)
|
||||
|
||||
class HttpResponse:
|
||||
var _http_status: int
|
||||
var _body: PackedByteArray
|
||||
|
||||
|
||||
func _init(http_status: int, body: PackedByteArray) -> void:
|
||||
_http_status = http_status
|
||||
_body = body
|
||||
|
||||
|
||||
func status() -> int:
|
||||
return _http_status
|
||||
|
||||
|
||||
func response() -> Variant:
|
||||
if _http_status != 200:
|
||||
return _body.get_string_from_utf8()
|
||||
|
||||
var test_json_conv := JSON.new()
|
||||
@warning_ignore("return_value_discarded")
|
||||
var error := test_json_conv.parse(_body.get_string_from_utf8())
|
||||
if error != OK:
|
||||
return "HttpResponse: %s Error: %s" % [error_string(error), _body.get_string_from_utf8()]
|
||||
return test_json_conv.get_data()
|
||||
|
||||
func get_body() -> PackedByteArray:
|
||||
return _body
|
||||
|
||||
|
||||
var _http_request := HTTPRequest.new()
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
add_child(_http_request)
|
||||
@warning_ignore("return_value_discarded")
|
||||
_http_request.request_completed.connect(_on_request_completed)
|
||||
|
||||
|
||||
func _notification(what: int) -> void:
|
||||
if what == NOTIFICATION_PREDELETE:
|
||||
if is_instance_valid(_http_request):
|
||||
_http_request.queue_free()
|
||||
|
||||
|
||||
#func list_tags() -> void:
|
||||
# _http_request.connect("request_completed",Callable(self,"_response_request_tags"))
|
||||
# var error = _http_request.request("https://api.github.com/repos/MikeSchulze/gdUnit4/tags")
|
||||
# if error != OK:
|
||||
# push_error("An error occurred in the HTTP request.")
|
||||
|
||||
|
||||
func request_latest_version() -> HttpResponse:
|
||||
var error := _http_request.request("https://api.github.com/repos/MikeSchulze/gdUnit4/tags")
|
||||
if error != OK:
|
||||
var message := "Request latest version failed, %s" % error_string(error)
|
||||
return HttpResponse.new(error, message.to_utf8_buffer())
|
||||
return await self.request_completed
|
||||
|
||||
|
||||
func request_releases() -> HttpResponse:
|
||||
var error := _http_request.request("https://api.github.com/repos/MikeSchulze/gdUnit4/releases")
|
||||
if error != OK:
|
||||
var message := "request_releases failed: %d" % error
|
||||
return HttpResponse.new(error, message.to_utf8_buffer())
|
||||
return await self.request_completed
|
||||
|
||||
|
||||
func request_image(url: String) -> HttpResponse:
|
||||
var error := _http_request.request(url)
|
||||
if error != OK:
|
||||
var message := "request_image failed: %d" % error
|
||||
return HttpResponse.new(error, message.to_utf8_buffer())
|
||||
return await self.request_completed
|
||||
|
||||
|
||||
func request_zip_package(url: String, file: String) -> HttpResponse:
|
||||
_http_request.set_download_file(file)
|
||||
var error := _http_request.request(url)
|
||||
if error != OK:
|
||||
var message := "request_zip_package failed: %d" % error
|
||||
return HttpResponse.new(error, message.to_utf8_buffer())
|
||||
return await self.request_completed
|
||||
|
||||
|
||||
func extract_latest_version(response: HttpResponse) -> GdUnit4Version:
|
||||
var body: Array = response.response()
|
||||
return GdUnit4Version.parse(str(body[0]["name"]))
|
||||
|
||||
|
||||
func _on_request_completed(_result: int, response_http_status: int, _headers: PackedStringArray, body: PackedByteArray) -> void:
|
||||
if _http_request.get_http_client_status() != HTTPClient.STATUS_DISCONNECTED:
|
||||
_http_request.set_download_file("")
|
||||
request_completed.emit(HttpResponse.new(response_http_status, body))
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
uid://cybga3asuikuv
|
||||
|
||||
@@ -0,0 +1,206 @@
|
||||
@tool
|
||||
extends MarginContainer
|
||||
|
||||
#signal request_completed(response)
|
||||
|
||||
const GdMarkDownReader = preload("res://addons/gdUnit4/src/update/GdMarkDownReader.gd")
|
||||
const GdUnitUpdateClient = preload("res://addons/gdUnit4/src/update/GdUnitUpdateClient.gd")
|
||||
const GdUnitUpdateProgress = preload("res://addons/gdUnit4/src/update/GdUnitUpdate.gd")
|
||||
|
||||
@onready var _md_reader: GdMarkDownReader = GdMarkDownReader.new()
|
||||
@onready var _update_client: GdUnitUpdateClient = $GdUnitUpdateClient
|
||||
@onready var _header: Label = $Panel/GridContainer/PanelContainer/header
|
||||
@onready var _update_button: Button = $Panel/GridContainer/Panel/HBoxContainer/update
|
||||
@onready var _content: RichTextLabel = $Panel/GridContainer/PanelContainer2/ScrollContainer/MarginContainer/content
|
||||
@onready var _update_progress :GdUnitUpdateProgress = %update_banner
|
||||
|
||||
var _debug_mode := false
|
||||
var _patcher := GdUnitPatcher.new()
|
||||
var _current_version := GdUnit4Version.current()
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
_update_button.set_disabled(false)
|
||||
_md_reader.set_http_client(_update_client)
|
||||
@warning_ignore("return_value_discarded")
|
||||
#GdUnitFonts.init_fonts(_content)
|
||||
_update_progress.set_visible(false)
|
||||
_update_progress.hidden.connect(func() -> void:
|
||||
_update_button.set_disabled(false)
|
||||
)
|
||||
|
||||
|
||||
func request_releases() -> bool:
|
||||
if _debug_mode:
|
||||
_update_progress._debug_mode = _debug_mode
|
||||
_header.text = "A new version 'v4.4.4' is available"
|
||||
_update_button.set_disabled(false)
|
||||
return true
|
||||
|
||||
var response :GdUnitUpdateClient.HttpResponse = await _update_client.request_latest_version()
|
||||
if response.status() != 200:
|
||||
_header.text = "Update information cannot be retrieved from GitHub!"
|
||||
message_h4("\n\nError: %s" % response.response(), Color.INDIAN_RED)
|
||||
return false
|
||||
var latest_version := _update_client.extract_latest_version(response)
|
||||
# if same version exit here no update need
|
||||
if latest_version.is_greater(_current_version):
|
||||
_patcher.scan(_current_version)
|
||||
_header.text = "A new version '%s' is available" % latest_version
|
||||
var download_zip_url := extract_zip_url(response)
|
||||
_update_progress.setup(_update_client, download_zip_url)
|
||||
_update_button.set_disabled(false)
|
||||
return true
|
||||
else:
|
||||
_header.text = "No update is available."
|
||||
_update_button.set_disabled(true)
|
||||
return false
|
||||
|
||||
|
||||
func _colored(message_: String, color: Color) -> String:
|
||||
return "[color=#%s]%s[/color]" % [color.to_html(), message_]
|
||||
|
||||
|
||||
func message_h4(message_: String, color: Color, clear := true) -> void:
|
||||
if clear:
|
||||
_content.clear()
|
||||
_content.append_text("[font_size=16]%s[/font_size]" % _colored(message_, color))
|
||||
|
||||
|
||||
func message(message_: String, color: Color) -> void:
|
||||
_content.clear()
|
||||
_content.append_text(_colored(message_, color))
|
||||
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
if _content != null and _content.is_visible_in_tree():
|
||||
_content.queue_redraw()
|
||||
|
||||
|
||||
func show_update() -> void:
|
||||
if not GdUnitSettings.is_update_notification_enabled():
|
||||
_header.text = "No update is available."
|
||||
message_h4("The search for updates is deactivated.", Color.CORNFLOWER_BLUE)
|
||||
_update_button.set_disabled(true)
|
||||
return
|
||||
|
||||
if not await request_releases():
|
||||
return
|
||||
_update_button.set_disabled(true)
|
||||
|
||||
prints("Scan for GdUnit4 Update ...")
|
||||
message_h4("\n\n\nRequest release infos ... ", Color.SNOW)
|
||||
_content.add_image(GdUnitUiTools.get_spinner(), 32, 32)
|
||||
|
||||
var content: String
|
||||
if _debug_mode:
|
||||
await get_tree().create_timer(.2).timeout
|
||||
var template := FileAccess.open("res://addons/gdUnit4/test/update/resources/http_response_releases.txt", FileAccess.READ).get_as_text()
|
||||
content = await _md_reader.to_bbcode(template)
|
||||
else:
|
||||
var response :GdUnitUpdateClient.HttpResponse = await _update_client.request_releases()
|
||||
if response.status() == 200:
|
||||
content = await extract_releases(response, _current_version)
|
||||
else:
|
||||
message_h4("\n\n\nError checked request available releases!", Color.INDIAN_RED)
|
||||
return
|
||||
|
||||
# finally force rescan to import images as textures
|
||||
if Engine.is_editor_hint():
|
||||
await rescan()
|
||||
message(content, Color.CADET_BLUE)
|
||||
_update_button.set_disabled(false)
|
||||
|
||||
|
||||
|
||||
func extract_zip_url(response: GdUnitUpdateClient.HttpResponse) -> String:
|
||||
var body :Array = response.response()
|
||||
return body[0]["zipball_url"]
|
||||
|
||||
|
||||
func extract_releases(response: GdUnitUpdateClient.HttpResponse, current_version: GdUnit4Version) -> String:
|
||||
await get_tree().process_frame
|
||||
var result := ""
|
||||
for release :Dictionary in response.response():
|
||||
var release_version := str(release["tag_name"])
|
||||
if GdUnit4Version.parse(release_version).equals(current_version):
|
||||
break
|
||||
var release_description := _colored("<h1>GdUnit Release %s</h1>" % release_version, Color.CORNFLOWER_BLUE)
|
||||
release_description += "\n"
|
||||
release_description += release["body"]
|
||||
release_description += "\n\n"
|
||||
result += await _md_reader.to_bbcode(release_description)
|
||||
return result
|
||||
|
||||
|
||||
func rescan() -> void:
|
||||
if Engine.is_editor_hint():
|
||||
if OS.is_stdout_verbose():
|
||||
prints(".. reimport release resources")
|
||||
var fs := EditorInterface.get_resource_filesystem()
|
||||
fs.scan()
|
||||
while fs.is_scanning():
|
||||
if OS.is_stdout_verbose():
|
||||
progressBar(fs.get_scanning_progress() * 100 as int)
|
||||
await get_tree().process_frame
|
||||
await get_tree().process_frame
|
||||
await get_tree().create_timer(1).timeout
|
||||
|
||||
|
||||
func progressBar(p_progress: int) -> void:
|
||||
if p_progress < 0:
|
||||
p_progress = 0
|
||||
if p_progress > 100:
|
||||
p_progress = 100
|
||||
printraw("scan [%-50s] %-3d%%\r" % ["".lpad(int(p_progress/2.0), "#").rpad(50, "-"), p_progress])
|
||||
|
||||
|
||||
@warning_ignore("return_value_discarded")
|
||||
func _on_update_pressed() -> void:
|
||||
_update_button.set_disabled(true)
|
||||
# close all opend scripts before start the update
|
||||
if not _debug_mode:
|
||||
ScriptEditorControls.close_open_editor_scripts()
|
||||
# copy update source to a temp because the update is deleting the whole gdUnit folder
|
||||
DirAccess.make_dir_absolute("res://addons/.gdunit_update")
|
||||
DirAccess.copy_absolute("res://addons/gdUnit4/src/update/GdUnitUpdate.tscn", "res://addons/.gdunit_update/GdUnitUpdate.tscn")
|
||||
DirAccess.copy_absolute("res://addons/gdUnit4/src/update/GdUnitUpdate.gd", "res://addons/.gdunit_update/GdUnitUpdate.gd")
|
||||
var source := FileAccess.open("res://addons/gdUnit4/src/update/GdUnitUpdate.tscn", FileAccess.READ)
|
||||
var content := source.get_as_text().replace("res://addons/gdUnit4/src/update/GdUnitUpdate.gd", "res://addons/.gdunit_update/GdUnitUpdate.gd")
|
||||
var dest := FileAccess.open("res://addons/.gdunit_update/GdUnitUpdate.tscn", FileAccess.WRITE)
|
||||
dest.store_string(content)
|
||||
_update_progress.set_visible(true)
|
||||
|
||||
|
||||
func _on_show_next_toggled(enabled: bool) -> void:
|
||||
GdUnitSettings.set_update_notification(enabled)
|
||||
|
||||
|
||||
func _on_cancel_pressed() -> void:
|
||||
hide()
|
||||
|
||||
|
||||
func _on_content_meta_clicked(meta: String) -> void:
|
||||
var properties: Dictionary = str_to_var(meta)
|
||||
if properties.has("url"):
|
||||
@warning_ignore("return_value_discarded")
|
||||
OS.shell_open(str(properties.get("url")))
|
||||
|
||||
|
||||
func _on_content_meta_hover_started(meta: String) -> void:
|
||||
var properties: Dictionary = str_to_var(meta)
|
||||
if properties.has("tool_tip"):
|
||||
_content.set_tooltip_text(str(properties.get("tool_tip")))
|
||||
|
||||
|
||||
@warning_ignore("unused_parameter")
|
||||
func _on_content_meta_hover_ended(meta: String) -> void:
|
||||
_content.set_tooltip_text("")
|
||||
|
||||
|
||||
func _on_visibility_changed() -> void:
|
||||
if not is_visible_in_tree():
|
||||
return
|
||||
if _update_progress != null:
|
||||
_update_progress.set_visible(false)
|
||||
await show_update()
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
uid://crycbwg5hjrkl
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
[gd_scene load_steps=4 format=3 uid="uid://0xyeci1tqebj"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/gdUnit4/src/update/GdUnitUpdateNotify.gd" id="1_112wo"]
|
||||
[ext_resource type="Script" path="res://addons/gdUnit4/src/update/GdUnitUpdateClient.gd" id="2_18asx"]
|
||||
[ext_resource type="PackedScene" path="res://addons/gdUnit4/src/update/GdUnitUpdate.tscn" id="3_x87h6"]
|
||||
|
||||
[node name="Control" type="MarginContainer"]
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
script = ExtResource("1_112wo")
|
||||
|
||||
[node name="GdUnitUpdateClient" type="Node" parent="."]
|
||||
script = ExtResource("2_18asx")
|
||||
|
||||
[node name="Panel" type="Panel" parent="."]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="GridContainer" type="VBoxContainer" parent="Panel"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
alignment = 1
|
||||
|
||||
[node name="PanelContainer" type="MarginContainer" parent="Panel/GridContainer"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/margin_left = 4
|
||||
theme_override_constants/margin_top = 4
|
||||
theme_override_constants/margin_right = 4
|
||||
theme_override_constants/margin_bottom = 4
|
||||
|
||||
[node name="header" type="Label" parent="Panel/GridContainer/PanelContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 9
|
||||
|
||||
[node name="PanelContainer2" type="PanelContainer" parent="Panel/GridContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="ScrollContainer" type="ScrollContainer" parent="Panel/GridContainer/PanelContainer2"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="MarginContainer" type="MarginContainer" parent="Panel/GridContainer/PanelContainer2/ScrollContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="content" type="RichTextLabel" parent="Panel/GridContainer/PanelContainer2/ScrollContainer/MarginContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
bbcode_enabled = true
|
||||
|
||||
[node name="update_banner" parent="Panel/GridContainer" instance=ExtResource("3_x87h6")]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 1
|
||||
size_flags_vertical = 8
|
||||
|
||||
[node name="Panel" type="MarginContainer" parent="Panel/GridContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 8
|
||||
theme_override_constants/margin_left = 4
|
||||
theme_override_constants/margin_top = 4
|
||||
theme_override_constants/margin_right = 4
|
||||
theme_override_constants/margin_bottom = 4
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="Panel/GridContainer/Panel"]
|
||||
use_parent_material = true
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 4
|
||||
|
||||
[node name="update" type="Button" parent="Panel/GridContainer/Panel/HBoxContainer"]
|
||||
custom_minimum_size = Vector2(100, 40)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 4
|
||||
text = "Update"
|
||||
|
||||
[connection signal="visibility_changed" from="." to="." method="_on_visibility_changed"]
|
||||
[connection signal="meta_clicked" from="Panel/GridContainer/PanelContainer2/ScrollContainer/MarginContainer/content" to="." method="_on_content_meta_clicked"]
|
||||
[connection signal="meta_hover_ended" from="Panel/GridContainer/PanelContainer2/ScrollContainer/MarginContainer/content" to="." method="_on_content_meta_hover_ended"]
|
||||
[connection signal="meta_hover_started" from="Panel/GridContainer/PanelContainer2/ScrollContainer/MarginContainer/content" to="." method="_on_content_meta_hover_started"]
|
||||
[connection signal="pressed" from="Panel/GridContainer/Panel/HBoxContainer/update" to="." method="_on_update_pressed"]
|
||||
|
||||
BIN
addons/gdUnit4/src/update/assets/border_bottom.png
(Stored with Git LFS)
BIN
addons/gdUnit4/src/update/assets/border_bottom.png
(Stored with Git LFS)
Binary file not shown.
@@ -2,12 +2,16 @@
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cb4qnsd0po83o"
|
||||
valid=false
|
||||
uid="uid://cse3iwnghcmkn"
|
||||
path="res://.godot/imported/border_bottom.png-30d66a4c67e3a03ad191e37cdf16549d.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/gdUnit4/src/update/assets/border_bottom.png"
|
||||
dest_files=["res://.godot/imported/border_bottom.png-30d66a4c67e3a03ad191e37cdf16549d.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
|
||||
BIN
addons/gdUnit4/src/update/assets/border_top.png
(Stored with Git LFS)
BIN
addons/gdUnit4/src/update/assets/border_top.png
(Stored with Git LFS)
Binary file not shown.
@@ -2,12 +2,16 @@
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://p47gfqh53x1q"
|
||||
valid=false
|
||||
uid="uid://peoblq5q6qhm"
|
||||
path="res://.godot/imported/border_top.png-c47cbebdb755144731c6ae309e18bbaa.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/gdUnit4/src/update/assets/border_top.png"
|
||||
dest_files=["res://.godot/imported/border_top.png-c47cbebdb755144731c6ae309e18bbaa.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
|
||||
BIN
addons/gdUnit4/src/update/assets/dot1.png
(Stored with Git LFS)
BIN
addons/gdUnit4/src/update/assets/dot1.png
(Stored with Git LFS)
Binary file not shown.
@@ -2,12 +2,16 @@
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cdoj1yp8q7x5p"
|
||||
valid=false
|
||||
uid="uid://bhfi0qh1a2hwx"
|
||||
path="res://.godot/imported/dot1.png-380baf1b5247addda93bce3c799aa4e7.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/gdUnit4/src/update/assets/dot1.png"
|
||||
dest_files=["res://.godot/imported/dot1.png-380baf1b5247addda93bce3c799aa4e7.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
|
||||
BIN
addons/gdUnit4/src/update/assets/dot2.png
(Stored with Git LFS)
BIN
addons/gdUnit4/src/update/assets/dot2.png
(Stored with Git LFS)
Binary file not shown.
@@ -2,12 +2,16 @@
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bbpyk3jcipja7"
|
||||
valid=false
|
||||
uid="uid://dg86wkmvp1qyn"
|
||||
path="res://.godot/imported/dot2.png-86a9db80ef4413e353c4339ad8f68a5f.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/gdUnit4/src/update/assets/dot2.png"
|
||||
dest_files=["res://.godot/imported/dot2.png-86a9db80ef4413e353c4339ad8f68a5f.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
|
||||
BIN
addons/gdUnit4/src/update/assets/embedded.png
(Stored with Git LFS)
BIN
addons/gdUnit4/src/update/assets/embedded.png
(Stored with Git LFS)
Binary file not shown.
@@ -2,12 +2,16 @@
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://u74w6eoyl2mx"
|
||||
valid=false
|
||||
uid="uid://d160etflupwba"
|
||||
path="res://.godot/imported/embedded.png-29390948772209a603567d24f8766495.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/gdUnit4/src/update/assets/embedded.png"
|
||||
dest_files=["res://.godot/imported/embedded.png-29390948772209a603567d24f8766495.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
|
||||
BIN
addons/gdUnit4/src/update/assets/horizontal-line2.png
(Stored with Git LFS)
BIN
addons/gdUnit4/src/update/assets/horizontal-line2.png
(Stored with Git LFS)
Binary file not shown.
@@ -2,12 +2,16 @@
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://b2p4jgab5ehs3"
|
||||
valid=false
|
||||
uid="uid://bloqm443lywyi"
|
||||
path="res://.godot/imported/horizontal-line2.png-92618e6ee5cc9002847547a8c9deadbc.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/gdUnit4/src/update/assets/horizontal-line2.png"
|
||||
dest_files=["res://.godot/imported/horizontal-line2.png-92618e6ee5cc9002847547a8c9deadbc.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user