diff --git a/core/itch_client.gd b/core/itch_client.gd index 38c2397..68ed7b4 100644 --- a/core/itch_client.gd +++ b/core/itch_client.gd @@ -495,11 +495,12 @@ func _ensure_install_location() -> String: ## Installs (or updates, if a caveId is given) the given game. -func install(game: Dictionary, cave_id: String = "") -> void: - await thread_group.exec(_install.bind(game, cave_id)) +## Returns true when the operation finished without errors. +func install(game: Dictionary, cave_id: String = "") -> bool: + return await thread_group.exec(_install.bind(game, cave_id)) -func _install(game: Dictionary, cave_id: String) -> void: +func _install(game: Dictionary, cave_id: String) -> bool: var game_id: int = game.get("id", 0) var reason := "update" if cave_id != "" else "install" @@ -508,7 +509,7 @@ func _install(game: Dictionary, cave_id: String) -> void: if "error" in uploads_res: logger.warn("Fetch.GameUploads failed: " + str(uploads_res["error"])) emit_signal.call_deferred("app_installed", cave_id, false) - return + return false # Uploads are cached by butlerd just like owned keys, so retry fresh when # the cached result is stale, otherwise a cold database would report every # game as having no compatible upload. @@ -519,12 +520,12 @@ func _install(game: Dictionary, cave_id: String) -> void: if "error" in uploads_res: logger.warn("Fetch.GameUploads (fresh) failed: " + str(uploads_res["error"])) emit_signal.call_deferred("app_installed", cave_id, false) - return + return false var uploads: Array = uploads_res.get("uploads", []) if uploads.is_empty(): logger.error("No compatible uploads found for game id " + str(game_id)) emit_signal.call_deferred("app_installed", cave_id, false) - return + return false var upload: Dictionary = uploads[0] var location_id := await _ensure_install_location() @@ -543,7 +544,7 @@ func _install(game: Dictionary, cave_id: String) -> void: if "error" in queue_res: logger.error("Install.Queue failed: " + str(queue_res["error"])) emit_signal.call_deferred("app_installed", cave_id, false) - return + return false var task_id: String = queue_res.get("id", "") var staging_folder: String = queue_res.get("stagingFolder", "") @@ -572,19 +573,21 @@ func _install(game: Dictionary, cave_id: String) -> void: emit_signal.call_deferred("app_updated", cave_id, success) else: emit_signal.call_deferred("app_installed", cave_id, success) + return success -## Uninstalls the given cave. -func uninstall(cave_id: String) -> void: - await thread_group.exec(_uninstall.bind(cave_id)) +## Uninstalls the given cave. Returns true on success. +func uninstall(cave_id: String) -> bool: + return await thread_group.exec(_uninstall.bind(cave_id)) -func _uninstall(cave_id: String) -> void: +func _uninstall(cave_id: String) -> bool: var res := await _rpc_call("Uninstall.Perform", {"caveId": cave_id}) var success := not ("error" in res) if not success: logger.error("Uninstall.Perform failed: " + str(res.get("error"))) emit_signal.call_deferred("app_uninstalled", cave_id, success) + return success ## Launches the given cave. This call blocks (on the background thread) for diff --git a/core/library_itch.gd b/core/library_itch.gd index a8d4840..e50fb72 100644 --- a/core/library_itch.gd +++ b/core/library_itch.gd @@ -5,6 +5,11 @@ const _apps_cache_file: String = "apps.json" @onready var itch: ItchClient = get_tree().get_first_node_in_group("itch_client") +## The item currently being installed/updated/uninstalled. OGPU's InstallManager +## only runs one install at a time, so a single slot is enough to map butlerd's +## install_progressed/app_* signals back to the LibraryLaunchItem. +var _active_item: LibraryLaunchItem + func _ready() -> void: super() @@ -22,16 +27,28 @@ func get_library_launch_items() -> Array[LibraryLaunchItem]: func install_to(item: LibraryLaunchItem, _location: InstallLocation = null, _options: Dictionary = {}) -> void: var game := (item.metadata.get("game", {}) as Dictionary) - itch.install(game) + _active_item = item + var success: bool = await itch.install(game) + _active_item = null + install_completed.emit(item, success) + logger.info("Install of '" + item.name + "' completed with status: " + str(success)) func update(item: LibraryLaunchItem) -> void: var game := (item.metadata.get("game", {}) as Dictionary) - itch.install(game, item.provider_app_id) + _active_item = item + var success: bool = await itch.install(game, item.provider_app_id) + _active_item = null + update_completed.emit(item, success) + logger.info("Update of '" + item.name + "' completed with status: " + str(success)) func uninstall(item: LibraryLaunchItem) -> void: - itch.uninstall(item.provider_app_id) + _active_item = item + var success: bool = await itch.uninstall(item.provider_app_id) + _active_item = null + uninstall_completed.emit(item, success) + logger.info("Uninstall of '" + item.name + "' completed with status: " + str(success)) ## itch.io's CheckUpdate call is async (and rate-limited), so we don't poll it @@ -54,16 +71,17 @@ func _on_logged_in(status: ItchClient.LOGIN_STATUS, _profile: Dictionary) -> voi return -## Re-emits itch.io's raw (id, current, total) install progress under the -## base [Library] signal contract (item, percent_completed) so the UI's -## generic install-progress widgets pick it up regardless of provider. -## TODO: verify the exact LibraryManager lookup method/key for resolving a -## provider_app_id back to its LibraryLaunchItem (this plugin was written -## against the public Library/LibraryLaunchItem API, but LibraryManager's -## internals weren't available while writing this) and wire the emit below -## through it instead of dropping the notification. -func _on_install_progressed(_id: String, _current: int, _total: int) -> void: - pass +## Forwards butlerd's install progress (current/total are percentage points, +## 0-100) to the base [Library] install_progressed contract (a fraction, +## 0.0-1.0) that OGPU's InstallManager and launch menu render as a progress +## bar. +func _on_install_progressed(_id: String, current: int, total: int) -> void: + if _active_item == null: + return + if total <= 0: + return + logger.info("Install progressing: " + str(current) + "/" + str(total)) + install_progressed.emit(_active_item, float(current) / float(total)) ## Builds a LibraryLaunchItem for a game, or null when the entry isn't an diff --git a/plugin.json b/plugin.json index 40ae027..4d48bfa 100644 --- a/plugin.json +++ b/plugin.json @@ -1,7 +1,7 @@ { "plugin.id": "itch", "plugin.name": "itch.io", - "plugin.version": "0.1.7", + "plugin.version": "0.1.8", "plugin.min-api-version": "1.1.0", "plugin.link": "https://github.com/YOUR_USERNAME/OpenGamepadUI-itch", "plugin.source": "https://github.com/YOUR_USERNAME/OpenGamepadUI-itch",