Update the game from inside it (#13)
Build / test (push) Successful in 1m53s
Build / build (push) Successful in 1m15s

"Version X is available" in the top bar is now a button that opens an
Update popup:
- Update and restart gets the download link from the server (the redirect
  is not followed, as the Gitea link needs no token), downloads the new game
  next to the running one with a progress bar, checks it (complete, and an
  MZ or ELF file), swaps it in and restarts. The quiz is saved first and
  continues. On Windows the running .exe is renamed to .old, put back if
  the swap fails, and deleted at the next start.
- Download in browser opens the .exe, the Linux package, or the release
  page on other platforms.
- From the editor, an unwritable folder or another platform, only the
  browser download is offered, with the reason.

The message now fits between the tabs and the buttons in every theme; in
Nord and Christmas it ran into Sync. The macOS export preset is removed,
and the unused download_client_* functions are replaced by
fetch_client_download_url.

Closes #13

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
SansanandClaude Opus 5.5 committed 2026-10-15 21:23:26 +02:00
1 parent c03c0e4efe
commit f39865fffc
30 files changed
+2363 -354

No files matched your search

File diff suppressed because it is too large. Load diff
+23 -4
View File
@@ -31,6 +31,9 @@ var requested_bodies: Array[String] = []
## The body of each request as bytes, e.g. an uploaded picture, in the same order as
## [member requested_paths].
var requested_raw_bodies: Array[PackedByteArray] = []
## Whether each request followed redirects, in the same order as
## [member requested_paths].
var requested_follow_redirects: Array[bool] = []
## How many tokens have been asked for.
var token_requests: int = 0
## The JSON body of the last token request.
@@ -67,6 +70,14 @@ func uninstall() -> void:
Settings.queue_ids = _previous_queue_ids
## Answers requests to [param path] with a redirect to [param location], like the
## server's download routes. Requests that follow redirects get an empty 200 instead, as
## if they had followed it.
func respond_redirect(path: String, location: String) -> void:
_responses[path] = {"code": 302, "body": PackedByteArray(), "location": location}
_successes_left.erase(path)
## Answers requests to [param path] with [param data] encoded as JSON.
func respond_json(path: String, data: Variant, response_code: int = 200) -> void:
respond_bytes(path, JSON.stringify(data).to_utf8_buffer(), response_code)
@@ -114,6 +125,7 @@ func request(
on_complete: Callable,
headers: PackedStringArray = PackedStringArray(),
body: PackedByteArray = PackedByteArray(),
follow_redirects: bool = true,
) -> void:
var path: String = url.trim_prefix(Settings.default_path + MusicServerClient.API_PATH)
if path == "/token" && method == HTTPClient.METHOD_POST:
@@ -129,17 +141,18 @@ func request(
requested_headers.append(headers)
requested_bodies.append(body.get_string_from_utf8())
requested_raw_bodies.append(body)
requested_follow_redirects.append(follow_redirects)
if !OPEN_PATHS.has(path.get_slice("?", 0)) && !_has_valid_token(headers):
on_complete.call(401, JSON.stringify({"error": "Unauthorized"}).to_utf8_buffer(),
PackedStringArray())
return
if _held_paths.has(path):
_held_paths[path].append(_answer.bind(path, on_complete))
_held_paths[path].append(_answer.bind(path, on_complete, follow_redirects))
return
_answer(path, on_complete)
_answer(path, on_complete, follow_redirects)
func _answer(path: String, on_complete: Callable) -> void:
func _answer(path: String, on_complete: Callable, follow_redirects: bool = true) -> void:
var headers: PackedStringArray = []
if !queue_id.is_empty() && path != "/token":
headers.append("X-Queue-Id: " + queue_id)
@@ -148,7 +161,13 @@ func _answer(path: String, on_complete: Callable) -> void:
on_complete.call(503, PackedByteArray(), headers)
return
_successes_left[path] -= 1
if _responses.has(path):
if _responses.has(path) && _responses[path].has("location"):
if follow_redirects:
on_complete.call(200, PackedByteArray(), headers)
else:
headers.append("Location: " + _responses[path].location)
on_complete.call(302, PackedByteArray(), headers)
elif _responses.has(path):
on_complete.call(_responses[path].code, _responses[path].body, headers)
else:
on_complete.call(404, PackedByteArray(), headers)
+21
View File
@@ -276,6 +276,27 @@ func run(on_state: Callable) -> void:
top_bar.show_latest_version("99.10.10")
await _settle()
await on_state.call("update_available", null)
var update_popup: UpdatePopup = _node("UpdatePopup")
var updater: Updater = update_popup.updater
updater.in_editor = false
updater.os_name = "Windows"
DirAccess.make_dir_recursive_absolute(Settings.config_dir())
updater.executable_path = Settings.config_dir().path_join("MusicPlayer.exe")
update_popup.show_update("99.10.10")
await _settle()
await on_state.call("popup_UpdateWindow", update_popup)
update_popup.download_progress.visible = true
update_popup._set_busy(true)
update_popup._show_status("")
update_popup._on_progress(31 << 20, 80 << 20)
await _settle()
await on_state.call("popup_UpdateWindow_downloading", update_popup)
updater.in_editor = true
update_popup.show_update("99.10.10")
await _settle()
await on_state.call("popup_UpdateWindow_browser_only", update_popup)
update_popup.close()
await _settle()
top_bar.show_latest_version("1.0.0")
await _settle()
+2 -2
View File
@@ -7,7 +7,7 @@ extends MainWindowTestSuite
const MAIN_WINDOW_CHILDREN: Array[String] = [
"Background", "TopBar", "Quiz", "PopupBackdrop", "SearchPopup", "SyncPopup", "QRPopup",
"ShortcutsPopup", "StatisticsView", "AboutPopup", "LogPopup", "SoundtrackStatsPopup",
"SettingsPopup",
"SettingsPopup", "UpdatePopup",
]
## The parts of the quiz and the scene each one comes from.
@@ -28,7 +28,7 @@ const LOCKED_BY_SYNC: Array[String] = [
]
const TOP_BAR_CHILDREN: Array[String] = [
"TopPanelContainer", "Title", "DebugLabel", "UpdateLabel", "OfflineLabel", "Buttons",
"TopPanelContainer", "Title", "DebugLabel", "UpdateBox", "OfflineLabel", "Buttons",
"QuizTab", "StatisticsTab",
]
## The buttons in the top bar's row on the right, in order.
+1
View File
@@ -47,6 +47,7 @@ const STYLES: Dictionary = {
"PlayerButtonPrimary": "Button",
"PlayerTime": "Label",
"PlayerIcon": "Button",
"UpdateButton": "Button",
"ProgressSlider": "HSlider",
"VolumeBar": "HSlider",
"StatisticsCard": "PanelContainer",
+27 -9
View File
@@ -10,7 +10,7 @@ func before_test() -> void:
func test_no_message_when_this_is_the_newest_version() -> void:
assert_bool(_top_bar.update_label.visible).is_false()
assert_bool(_top_bar.update_box.visible).is_false()
assert_int(_server.count("/download")).is_equal(1)
@@ -19,9 +19,9 @@ func test_newer_release_shows_a_message() -> void:
_top_bar.check_for_update()
assert_bool(_top_bar.update_label.is_visible_in_tree()).is_true()
assert_str(_top_bar.update_label.text).is_equal("Version 99.0.0 is available")
assert_str(_top_bar.update_label.tooltip_text).contains(Settings.version)
assert_bool(_top_bar.update_button.is_visible_in_tree()).is_true()
assert_str(_top_bar.update_button.text).is_equal("Version 99.0.0 is available")
assert_str(_top_bar.update_button.tooltip_text).contains(Settings.version)
func test_message_fits_between_the_tabs_and_the_buttons() -> void:
@@ -29,8 +29,10 @@ func test_message_fits_between_the_tabs_and_the_buttons() -> void:
_top_bar.check_for_update()
await _runner.simulate_frames(1)
var label: Rect2 = _top_bar.update_label.get_global_rect()
var text_width: float = _top_bar.update_label.get_minimum_size().x
var label: Rect2 = _top_bar.update_box.get_global_rect()
var button: Button = _top_bar.update_button
var text_width: float = button.get_theme_font(&"font").get_string_size(button.text,
HORIZONTAL_ALIGNMENT_LEFT, -1, button.get_theme_font_size(&"font_size")).x
assert_float(text_width).is_less_equal(label.size.x)
assert_float(label.position.x).is_greater(
(_top_bar.get_node("%StatisticsTab") as Control).get_global_rect().end.x)
@@ -44,7 +46,7 @@ func test_message_stays_on_the_statistics_tab() -> void:
_window.show_tab(&"statistics")
assert_bool(_top_bar.update_label.is_visible_in_tree()).is_true()
assert_bool(_top_bar.update_button.is_visible_in_tree()).is_true()
func test_server_change_checks_again() -> void:
@@ -52,7 +54,7 @@ func test_server_change_checks_again() -> void:
_quiz.on_server_changed()
assert_bool(_top_bar.update_label.visible).is_true()
assert_bool(_top_bar.update_box.visible).is_true()
func test_failed_check_shows_nothing() -> void:
@@ -60,7 +62,7 @@ func test_failed_check_shows_nothing() -> void:
_top_bar.check_for_update()
assert_bool(_top_bar.update_label.visible).is_false()
assert_bool(_top_bar.update_box.visible).is_false()
func test_version_comparison() -> void:
@@ -72,3 +74,19 @@ func test_version_comparison() -> void:
assert_bool(TopBar.is_newer_version("2.0", "2.0.0")).is_false()
assert_bool(TopBar.is_newer_version("1.9.0", "2.0.0")).is_false()
assert_bool(TopBar.is_newer_version("1.9.9", "1.10.0")).is_false()
@warning_ignore("unused_parameter")
func test_message_stays_clear_of_the_buttons_in_every_theme(theme_id: String,
test_parameters: Array = [["classic"], ["original_web"], ["nord"], ["christmas"]]) -> void:
Settings.apply_theme(theme_id)
_server.respond_json("/download", "99.10.10")
_top_bar.check_for_update()
await _runner.simulate_frames(2)
var message: Rect2 = _top_bar.update_box.get_global_rect()
assert_float(message.end.x).is_less(
(_top_bar.get_node("%SyncButton") as Control).get_global_rect().position.x)
assert_float(message.position.x).is_greater(
(_top_bar.get_node("%StatisticsTab") as Control).get_global_rect().end.x)
Settings.apply_theme("classic")
+160
View File
@@ -0,0 +1,160 @@
extends MainWindowTestSuite
## Getting the newest version from "Version X is available" in the top bar: updating in
## place and restarting, or downloading in the browser.
const LINK: String = "https://gitea.example/releases/download/99.0.0/MusicPlayer_99.0.0.exe"
var _top_bar: TopBar
var _popup: UpdatePopup
var _updater: FakeUpdater
var _opened: Array[String] = []
var _dir: String
## Downloads nothing: hands over a prepared file, or fails, at once.
class FakeUpdater:
extends Updater
var downloaded: PackedByteArray = [0x4D, 0x5A, 0x90, 0x00]
var fail_with: String = ""
var urls: Array[String] = []
var started: Array[String] = []
var quits: int = 0
func _init() -> void:
super()
start_process = func(path: String, _arguments: PackedStringArray) -> int:
started.append(path)
return 1
quit = func() -> void: quits += 1
func download(url: String, on_progress: Callable, on_done: Callable,
on_failed: Callable) -> void:
urls.append(url)
if !fail_with.is_empty():
on_failed.call(fail_with)
return
on_progress.call(downloaded.size() / 2, downloaded.size())
var file: FileAccess = FileAccess.open(update_path(), FileAccess.WRITE)
file.store_buffer(downloaded)
file.close()
on_done.call(update_path())
func _before_window_starts() -> void:
_server.respond_json("/download", "99.0.0")
func before_test() -> void:
# The quiz is saved before restarting, next to the settings
OS.set_environment(Settings.CONFIG_DIR_ENV, create_temp_dir("update_%d" % randi()))
await super()
_top_bar = _window.get_node("TopBar")
_popup = _window.get_node("UpdatePopup")
_opened = []
_popup.open_url = func(url: String) -> Error:
_opened.append(url)
return OK
_dir = create_temp_dir("game_%d" % randi())
_updater = FakeUpdater.new()
_updater.in_editor = false
_updater.os_name = "Windows"
_updater.executable_path = _dir.path_join("MusicPlayer.exe")
var file: FileAccess = FileAccess.open(_updater.executable_path, FileAccess.WRITE)
file.store_string("old game")
file.close()
_popup.add_child(_updater)
_popup.updater = _updater
func _open_popup() -> void:
_top_bar.update_button.pressed.emit()
func test_clicking_the_message_opens_the_popup() -> void:
_open_popup()
assert_bool(_popup.visible).is_true()
assert_str(_popup.title_label.text).is_equal("Version 99.0.0 is available")
assert_str(_popup.info_label.text).contains(Settings.version)
assert_bool(_popup.update_now_button.visible).is_true()
func test_update_downloads_swaps_and_restarts() -> void:
_server.respond_redirect("/download/windows", LINK)
_open_popup()
_popup.update_now()
assert_array(_updater.urls).contains_exactly([LINK])
var exe: String = _updater.executable_path
assert_array(FileAccess.get_file_as_bytes(exe)).is_equal(_updater.downloaded)
assert_str(FileAccess.get_file_as_string(exe + Updater.OLD_SUFFIX)).is_equal("old game")
assert_array(_updater.started).contains_exactly([exe])
assert_int(_updater.quits).is_equal(1)
# The quiz continues after the restart
assert_bool(FileAccess.file_exists(QuizSave.path())).is_true()
func test_failed_download_says_why_and_keeps_the_game() -> void:
_server.respond_redirect("/download/windows", LINK)
_updater.fail_with = "The download failed. Check the connection and try again."
_open_popup()
_popup.update_now()
assert_str(_popup.status_label.text).is_equal(_updater.fail_with)
assert_bool(_popup.update_now_button.disabled).is_false()
assert_bool(_popup.browser_button.disabled).is_false()
assert_str(FileAccess.get_file_as_string(_updater.executable_path)).is_equal("old game")
assert_int(_updater.quits).is_equal(0)
func test_no_download_on_the_server_says_so() -> void:
_open_popup()
_popup.update_now()
assert_array(_updater.urls).is_empty()
assert_str(_popup.status_label.text).contains("no download")
assert_int(_updater.quits).is_equal(0)
func test_browser_gets_the_package_on_linux() -> void:
var package: String = LINK.replace(".exe", "_linux.tar.gz")
_server.respond_redirect("/download/linux-package", package)
_updater.os_name = "Linux"
_open_popup()
_popup.download_in_browser()
assert_array(_opened).contains_exactly([package])
func test_browser_gets_the_release_page_on_other_platforms() -> void:
_updater.os_name = "macOS"
_open_popup()
_popup.download_in_browser()
assert_array(_opened).contains_exactly([Updater.RELEASES_PAGE])
assert_int(_server.count("/download/windows")).is_equal(0)
func test_browser_gets_the_release_page_when_the_server_has_no_link() -> void:
_open_popup()
_popup.download_in_browser()
assert_array(_opened).contains_exactly([Updater.RELEASES_PAGE])
func test_from_the_editor_only_the_browser_is_offered() -> void:
_updater.in_editor = true
_open_popup()
assert_bool(_popup.update_now_button.visible).is_false()
assert_bool(_popup.reason_label.visible).is_true()
assert_str(_popup.reason_label.text).contains("editor")
assert_bool(_popup.browser_button.visible).is_true()
@@ -0,0 +1 @@
uid://d4lsj4jjqht4d
+38 -2
View File
@@ -81,6 +81,40 @@ func test_fetch_song_passes_raw_bytes() -> void:
assert_array(received).contains_exactly([mp3])
func test_download_url_is_where_the_server_redirects() -> void:
var link: String = "https://gitea.example/releases/download/2.0.0/MusicPlayer_2.0.0.exe"
_server.respond_redirect("/download/windows", link)
var urls: Array[String] = []
MusicServerClient.fetch_client_download_url("windows",
func(url: String) -> void: urls.append(url))
assert_array(urls).contains_exactly([link])
# The token is only for the server, so the redirect is not followed with it
assert_array(_server.requested_follow_redirects).contains_exactly([false])
func test_missing_download_calls_on_failed() -> void:
var failed: Array[bool] = []
MusicServerClient.fetch_client_download_url("linux-package",
func(_url: String) -> void: pass, func() -> void: failed.append(true))
assert_array(failed).has_size(1)
@warning_ignore("unused_parameter")
func test_header_value(name: String, value: String, test_parameters: Array = [
["Location", "https://x/y"],
["location", "https://x/y"],
["X-Queue-Id", "abc"],
["Missing", ""],
]) -> void:
var headers: PackedStringArray = ["Location: https://x/y", "x-queue-id: abc "]
assert_str(MusicServerClient.header_value(headers, name)).is_equal(value)
func test_failed_request_does_not_call_back() -> void:
_server.respond_json("/music/list", [], 500)
var called: Array[bool] = [false]
@@ -402,9 +436,9 @@ func test_endpoint_paths_and_methods() -> void:
"/download", HTTPClient.METHOD_GET],
[func() -> void: MusicServerClient.fetch_client_assets(noop),
"/download/list", HTTPClient.METHOD_GET],
[func() -> void: MusicServerClient.download_client_windows(noop),
[func() -> void: MusicServerClient.fetch_client_download_url("windows", noop),
"/download/windows", HTTPClient.METHOD_GET],
[func() -> void: MusicServerClient.download_client_linux(noop),
[func() -> void: MusicServerClient.fetch_client_download_url("linux", noop),
"/download/linux", HTTPClient.METHOD_GET],
[func() -> void: MusicServerClient.cleanup_tokens(noop),
"/token/cleanup", HTTPClient.METHOD_POST],
@@ -640,6 +674,7 @@ class _HeaderTransport:
on_complete: Callable,
_request_headers: PackedStringArray = PackedStringArray(),
_body: PackedByteArray = PackedByteArray(),
_follow_redirects: bool = true,
) -> void:
on_complete.call(200, JSON.stringify({"token": "t"} if _url.ends_with("/token") else [])
.to_utf8_buffer(), _headers)
@@ -660,6 +695,7 @@ class _RecordingTransport:
on_complete: Callable,
_headers: PackedStringArray = PackedStringArray(),
_body: PackedByteArray = PackedByteArray(),
_follow_redirects: bool = true,
) -> void:
_urls.append(url)
if url.ends_with("/token"):
+215
View File
@@ -0,0 +1,215 @@
extends GdUnitTestSuite
## Updating the game in place: which file to get, checking it, swapping it in, and
## cleaning up after.
const WINDOWS_GAME: PackedByteArray = [0x4D, 0x5A, 0x90, 0x00, 0x03]
const LINUX_GAME: PackedByteArray = [0x7F, 0x45, 0x4C, 0x46, 0x02]
var _dir: String
var _updater: Updater
func before_test() -> void:
_dir = create_temp_dir("updater_%d" % randi())
_updater = auto_free(Updater.new())
_updater.in_editor = false
_updater.os_name = "Windows"
_updater.executable_path = _dir.path_join("MusicPlayer.exe")
func _write(path: String, bytes: PackedByteArray) -> String:
var file: FileAccess = FileAccess.open(path, FileAccess.WRITE)
file.store_buffer(bytes)
file.close()
return path
func _read(path: String) -> PackedByteArray:
return FileAccess.get_file_as_bytes(path)
@warning_ignore("unused_parameter")
func test_asset_for(os_name: String, in_place: bool, asset: String, test_parameters: Array = [
["Windows", true, "windows"],
["Windows", false, "windows"],
["Linux", true, "linux"],
["Linux", false, "linux-package"],
["macOS", true, ""],
["macOS", false, ""],
]) -> void:
assert_str(Updater.asset_for(os_name, in_place)).is_equal(asset)
@warning_ignore("unused_parameter")
func test_check_file(bytes: PackedByteArray, os_name: String, ok: bool,
test_parameters: Array = [
[WINDOWS_GAME, "Windows", true],
[LINUX_GAME, "Linux", true],
[LINUX_GAME, "Windows", false],
[WINDOWS_GAME, "Linux", false],
["<html>Not Found</html>".to_utf8_buffer(), "Windows", false],
[PackedByteArray(), "Linux", false],
]) -> void:
var path: String = _write(_dir.path_join("game"), bytes)
assert_bool(Updater.check_file(path, os_name)).is_equal(ok)
func test_missing_file_is_not_the_game() -> void:
assert_bool(Updater.check_file(_dir.path_join("nothing"), "Windows")).is_false()
func test_windows_moves_the_running_game_aside() -> void:
var exe: String = _write(_dir.path_join("MusicPlayer.exe"), "old".to_utf8_buffer())
var update: String = _write(exe + Updater.UPDATE_SUFFIX, "new".to_utf8_buffer())
assert_int(Updater.install(update, exe, "Windows")).is_equal(OK)
assert_str(_read(exe).get_string_from_utf8()).is_equal("new")
assert_str(_read(exe + Updater.OLD_SUFFIX).get_string_from_utf8()).is_equal("old")
assert_bool(FileAccess.file_exists(update)).is_false()
func test_windows_replaces_an_old_version_left_from_before() -> void:
var exe: String = _write(_dir.path_join("MusicPlayer.exe"), "old".to_utf8_buffer())
_write(exe + Updater.OLD_SUFFIX, "older".to_utf8_buffer())
var update: String = _write(exe + Updater.UPDATE_SUFFIX, "new".to_utf8_buffer())
assert_int(Updater.install(update, exe, "Windows")).is_equal(OK)
assert_str(_read(exe + Updater.OLD_SUFFIX).get_string_from_utf8()).is_equal("old")
func test_windows_puts_the_game_back_when_the_new_one_cannot_be_moved_in() -> void:
var exe: String = _write(_dir.path_join("MusicPlayer.exe"), "old".to_utf8_buffer())
var error: Error = Updater.install(exe + Updater.UPDATE_SUFFIX, exe, "Windows")
assert_int(error).is_not_equal(OK)
assert_str(_read(exe).get_string_from_utf8()).is_equal("old")
assert_bool(FileAccess.file_exists(exe + Updater.OLD_SUFFIX)).is_false()
func test_linux_replaces_the_game_and_makes_it_runnable() -> void:
var exe: String = _write(_dir.path_join("MusicPlayer.x86_64"), "old".to_utf8_buffer())
var update: String = _write(exe + Updater.UPDATE_SUFFIX, "new".to_utf8_buffer())
assert_int(Updater.install(update, exe, "Linux")).is_equal(OK)
assert_str(_read(exe).get_string_from_utf8()).is_equal("new")
assert_bool(FileAccess.file_exists(update)).is_false()
assert_int(FileAccess.get_unix_permissions(exe) & FileAccess.UNIX_EXECUTE_OWNER) \
.is_not_equal(0)
func test_leftovers_are_deleted_at_start() -> void:
var exe: String = _write(_dir.path_join("MusicPlayer.exe"), "new".to_utf8_buffer())
_write(exe + Updater.OLD_SUFFIX, "old".to_utf8_buffer())
_write(exe + Updater.UPDATE_SUFFIX, "half".to_utf8_buffer())
Updater.remove_old_files(exe)
assert_bool(FileAccess.file_exists(exe)).is_true()
assert_bool(FileAccess.file_exists(exe + Updater.OLD_SUFFIX)).is_false()
assert_bool(FileAccess.file_exists(exe + Updater.UPDATE_SUFFIX)).is_false()
func test_can_update_in_a_writable_folder() -> void:
assert_str(_updater.can_update_in_place()).is_empty()
assert_array(DirAccess.get_files_at(_dir)).is_empty()
func test_not_from_the_editor() -> void:
_updater.in_editor = true
assert_str(_updater.can_update_in_place()).contains("editor")
func test_not_on_other_platforms() -> void:
_updater.os_name = "macOS"
assert_str(_updater.can_update_in_place()).contains("Windows and Linux")
func test_not_when_the_folder_cannot_be_written_to() -> void:
_updater.executable_path = _dir.path_join("missing/MusicPlayer.exe")
assert_str(_updater.can_update_in_place()).contains("can't be written to")
func test_restart_starts_the_game_and_quits() -> void:
var started: Array[String] = []
var quits: Array[bool] = []
_updater.start_process = func(path: String, _arguments: PackedStringArray) -> int:
started.append(path)
return 1
_updater.quit = func() -> void: quits.append(true)
_updater.restart()
assert_array(started).contains_exactly([_updater.executable_path])
assert_array(quits).has_size(1)
# Answers one HTTP request on a local port with [param status] and [param body], and
# downloads it with the updater. Returns [done paths, failure reasons].
func _download_from_local_server(status: String, body: PackedByteArray) -> Array:
var server: TCPServer = TCPServer.new()
assert_int(server.listen(0, "127.0.0.1")).is_equal(OK)
add_child(_updater)
var done: Array[String] = []
var failed: Array[String] = []
var progress: Array[int] = []
_updater.download("http://127.0.0.1:%d/MusicPlayer.exe" % server.get_local_port(),
func(bytes: int, _total: int) -> void: progress.append(bytes),
func(path: String) -> void: done.append(path),
func(reason: String) -> void: failed.append(reason))
var connection: StreamPeerTCP = null
for i: int in 100:
await await_millis(20)
if connection == null && server.is_connection_available():
connection = server.take_connection()
if connection != null && connection.get_available_bytes() > 0:
connection.get_data(connection.get_available_bytes())
var head: String = "HTTP/1.1 %s\r\nContent-Length: %d\r\nConnection: close\r\n\r\n" \
% [status, body.size()]
connection.put_data(head.to_ascii_buffer() + body)
break
for i: int in 100:
await await_millis(20)
if !done.is_empty() || !failed.is_empty():
break
if connection != null:
connection.disconnect_from_host()
server.stop()
remove_child(_updater)
return [done, failed, progress]
func test_download_keeps_a_checked_game() -> void:
var game: PackedByteArray = WINDOWS_GAME.duplicate()
game.resize(4096)
var result: Array = await _download_from_local_server("200 OK", game)
assert_array(result[1]).is_empty()
assert_array(result[0]).contains_exactly([_updater.update_path()])
assert_array(_read(_updater.update_path())).is_equal(game)
func test_download_of_an_error_page_is_deleted() -> void:
var result: Array = await _download_from_local_server("404 Not Found",
"Not Found".to_utf8_buffer())
assert_array(result[0]).is_empty()
assert_str(result[1][0]).contains("404")
assert_bool(FileAccess.file_exists(_updater.update_path())).is_false()
func test_download_that_is_not_the_game_is_deleted() -> void:
var result: Array = await _download_from_local_server("200 OK",
"<html>Sign in</html>".to_utf8_buffer())
assert_array(result[0]).is_empty()
assert_str(result[1][0]).contains("not the game")
assert_bool(FileAccess.file_exists(_updater.update_path())).is_false()
+1
View File
@@ -0,0 +1 @@
uid://ctxevissal7tm