readability: rename constants, variables, extract enums across plugins
- itch: UPPER_CASE constants, CACHE_NAMESPACE, butler_process/rpc_socket/connection_state, LOGIN_METHOD enum, SETTINGS_SECTION, signal handler renames - artprovider: UPPER_CASE constants, _enriched_meta, _find_game_dict, dead code removal, named poll intervals - session-switch: MAX_POLL_FRAMES, _poll_for_power_menu, BUTTON_LABEL const
This commit is contained in:
parent
7ee08ac571
commit
97c1519833
4 changed files with 61 additions and 56 deletions
|
|
@ -15,11 +15,11 @@ extends NodeThread
|
|||
## https://pkg.go.dev/github.com/itchio/butler/butlerd). If itch.io ships a
|
||||
## butler update that changes a field, diff against that spec.
|
||||
|
||||
const broth_base := "https://broth.itch.zone/butler"
|
||||
const BROTH_BASE := "https://broth.itch.zone/butler"
|
||||
## Persistent storage for the downloaded butler binary + butler.db. Kept OUT
|
||||
## of user://plugins/<id>/ because OGPU wipes that directory (and everything
|
||||
## inside it) whenever the plugin is updated or re-extracted.
|
||||
const butler_dir := "user://butler"
|
||||
const BUTLER_DIR := "user://butler"
|
||||
## Where installed games live. This is intentionally NOT user://plugins/itch:
|
||||
## OGPU moves the entire extracted plugin directory (plugins/<id>/) to the
|
||||
## trash on every plugin update, so any install kept under it silently loses
|
||||
|
|
@ -27,8 +27,8 @@ const butler_dir := "user://butler"
|
|||
## used to break both fresh installs ("That upload is already installed!")
|
||||
## and launches. Keeping games next to butler.db (user://butler) means they
|
||||
## survive plugin updates untouched.
|
||||
const games_dir := "user://butler/games"
|
||||
const CACHE_DIR := "itch"
|
||||
const GAMES_DIR := "user://butler/games"
|
||||
const CACHE_NAMESPACE := "itch"
|
||||
const CONNECTION_TIMEOUT_ITERATIONS := 50
|
||||
const CONNECTION_POLL_DELAY_MS := 20
|
||||
const PERCENTAGE_SCALE := 100
|
||||
|
|
@ -60,9 +60,9 @@ signal app_updated(cave_id: String, success: bool)
|
|||
signal app_uninstalled(cave_id: String, success: bool)
|
||||
signal launch_exited(cave_id: String)
|
||||
|
||||
var proc: InteractiveProcess
|
||||
var socket: StreamPeerTCP
|
||||
var state: STATE = STATE.BOOT
|
||||
var butler_process: InteractiveProcess
|
||||
var rpc_socket: StreamPeerTCP
|
||||
var connection_state: STATE = STATE.BOOT
|
||||
var client_started := false
|
||||
var is_logged_in := false
|
||||
var profile: Dictionary = {}
|
||||
|
|
@ -70,7 +70,7 @@ var profile: Dictionary = {}
|
|||
var _proc_buffer := ""
|
||||
var _recv_buffer := ""
|
||||
var _next_id := 1
|
||||
var _install_location_id := ""
|
||||
var _cached_install_location_id := ""
|
||||
|
||||
var logger := Log.get_logger("ItchClient", Log.LEVEL.INFO)
|
||||
|
||||
|
|
@ -84,7 +84,7 @@ func _ready() -> void:
|
|||
|
||||
## Bootstraps the butler binary if it isn't present, then spawns butlerd.
|
||||
func bootstrap() -> void:
|
||||
var butler_dir_global := ProjectSettings.globalize_path(butler_dir)
|
||||
var butler_dir_global := ProjectSettings.globalize_path(BUTLER_DIR)
|
||||
_migrate_butler(butler_dir_global)
|
||||
var butler_bin := "/".join([butler_dir_global, "butler"])
|
||||
if not FileAccess.file_exists(butler_bin):
|
||||
|
|
@ -108,12 +108,12 @@ func bootstrap() -> void:
|
|||
"--destiny-pid", str(OS.get_process_id()),
|
||||
]
|
||||
|
||||
proc = InteractiveProcess.new(butler_bin, args)
|
||||
if proc.start() != OK:
|
||||
butler_process = InteractiveProcess.new(butler_bin, args)
|
||||
if butler_process.start() != OK:
|
||||
logger.error("Unable to spawn butlerd")
|
||||
return
|
||||
client_started = true
|
||||
state = STATE.WAITING_HANDSHAKE
|
||||
connection_state = STATE.WAITING_HANDSHAKE
|
||||
bootstrap_finished.emit()
|
||||
|
||||
|
||||
|
|
@ -156,7 +156,7 @@ func _install_butler() -> bool:
|
|||
arch_name = "arm64"
|
||||
|
||||
var platform_slug := os_name + "-" + arch_name
|
||||
var latest_url := "/".join([broth_base, platform_slug, "LATEST"])
|
||||
var latest_url := "/".join([BROTH_BASE, platform_slug, "LATEST"])
|
||||
|
||||
var http := HTTPRequest.new()
|
||||
add_child.call_deferred(http)
|
||||
|
|
@ -177,7 +177,7 @@ func _install_butler() -> bool:
|
|||
return false
|
||||
var version := body.get_string_from_utf8().strip_edges()
|
||||
|
||||
var archive_url := "/".join([broth_base, platform_slug, version, "archive", "default"])
|
||||
var archive_url := "/".join([BROTH_BASE, platform_slug, version, "archive", "default"])
|
||||
if http.request(archive_url) != OK:
|
||||
logger.error("Error downloading butler: " + archive_url)
|
||||
remove_child(http)
|
||||
|
|
@ -193,7 +193,7 @@ func _install_butler() -> bool:
|
|||
logger.error("butler couldn't be downloaded: " + archive_url)
|
||||
return false
|
||||
|
||||
var globalized_dir := ProjectSettings.globalize_path(butler_dir)
|
||||
var globalized_dir := ProjectSettings.globalize_path(BUTLER_DIR)
|
||||
DirAccess.make_dir_recursive_absolute(globalized_dir)
|
||||
var zip_path := "/tmp/butler-" + version + ".zip"
|
||||
var file := FileAccess.open(zip_path, FileAccess.WRITE_READ)
|
||||
|
|
@ -223,8 +223,8 @@ func _get_user_agent() -> String:
|
|||
# ---------------------------------------------------------------------------
|
||||
|
||||
func _thread_process(_delta: float) -> void:
|
||||
if state == STATE.WAITING_HANDSHAKE and proc:
|
||||
_proc_buffer += proc.read()
|
||||
if connection_state == STATE.WAITING_HANDSHAKE and butler_process:
|
||||
_proc_buffer += butler_process.read()
|
||||
if not _proc_buffer.contains("\n"):
|
||||
return
|
||||
var lines := _proc_buffer.split("\n")
|
||||
|
|
@ -244,12 +244,12 @@ func _thread_process(_delta: float) -> void:
|
|||
_connect_and_authenticate.call_deferred(address, secret)
|
||||
return
|
||||
|
||||
if state == STATE.CONNECTED and socket:
|
||||
socket.poll()
|
||||
var available := socket.get_available_bytes()
|
||||
if connection_state == STATE.CONNECTED and rpc_socket:
|
||||
rpc_socket.poll()
|
||||
var available := rpc_socket.get_available_bytes()
|
||||
if available <= 0:
|
||||
return
|
||||
var chunk := socket.get_partial_data(available)
|
||||
var chunk := rpc_socket.get_partial_data(available)
|
||||
if chunk[0] != OK:
|
||||
return
|
||||
_recv_buffer += (chunk[1] as PackedByteArray).get_string_from_utf8()
|
||||
|
|
@ -274,22 +274,22 @@ func _connect_and_authenticate(address: String, secret: String) -> void:
|
|||
var host: String = parts[0]
|
||||
var port: int = int(parts[1])
|
||||
|
||||
socket = StreamPeerTCP.new()
|
||||
if socket.connect_to_host(host, port) != OK:
|
||||
rpc_socket = StreamPeerTCP.new()
|
||||
if rpc_socket.connect_to_host(host, port) != OK:
|
||||
logger.error("Unable to connect to butlerd at " + address)
|
||||
return
|
||||
|
||||
# Wait for the connection to establish
|
||||
var timeout := CONNECTION_TIMEOUT_ITERATIONS
|
||||
while socket.get_status() == StreamPeerTCP.STATUS_CONNECTING and timeout > 0:
|
||||
socket.poll()
|
||||
while rpc_socket.get_status() == StreamPeerTCP.STATUS_CONNECTING and timeout > 0:
|
||||
rpc_socket.poll()
|
||||
OS.delay_msec(CONNECTION_POLL_DELAY_MS)
|
||||
timeout -= 1
|
||||
if socket.get_status() != StreamPeerTCP.STATUS_CONNECTED:
|
||||
if rpc_socket.get_status() != StreamPeerTCP.STATUS_CONNECTED:
|
||||
logger.error("Timed out connecting to butlerd")
|
||||
return
|
||||
|
||||
state = STATE.CONNECTED
|
||||
connection_state = STATE.CONNECTED
|
||||
|
||||
var res := await _rpc_call("Meta.Authenticate", {"secret": secret})
|
||||
if "error" in res:
|
||||
|
|
@ -305,9 +305,9 @@ func _connect_and_authenticate(address: String, secret: String) -> void:
|
|||
# ---------------------------------------------------------------------------
|
||||
|
||||
func _send_line(text: String) -> void:
|
||||
if not socket:
|
||||
if not rpc_socket:
|
||||
return
|
||||
socket.put_data((text + "\n").to_utf8_buffer())
|
||||
rpc_socket.put_data((text + "\n").to_utf8_buffer())
|
||||
|
||||
|
||||
## Godot's JSON.parse decodes every JSON number as a float (so 7670 becomes
|
||||
|
|
@ -589,17 +589,17 @@ func _get_caves() -> Variant:
|
|||
## every plugin update; existing locations pointing there are left alone (their
|
||||
## orphaned caves are cleaned up by [method _resolve_cave]) but never reused.
|
||||
func _ensure_install_location() -> String:
|
||||
if _install_location_id != "":
|
||||
return _install_location_id
|
||||
if _cached_install_location_id != "":
|
||||
return _cached_install_location_id
|
||||
|
||||
var target_path := ProjectSettings.globalize_path(games_dir)
|
||||
var target_path := ProjectSettings.globalize_path(GAMES_DIR)
|
||||
var res := await _rpc_call("Install.Locations.List", {})
|
||||
var locations: Array = res.get("installLocations", [])
|
||||
for loc in locations:
|
||||
var location: Dictionary = loc
|
||||
if location.get("path", "") == target_path:
|
||||
_install_location_id = location.get("id", "")
|
||||
return _install_location_id
|
||||
_cached_install_location_id = location.get("id", "")
|
||||
return _cached_install_location_id
|
||||
|
||||
DirAccess.make_dir_recursive_absolute(target_path)
|
||||
# Let butlerd generate the id so we never collide with a stale location
|
||||
|
|
@ -608,8 +608,8 @@ func _ensure_install_location() -> String:
|
|||
if "error" in add_res:
|
||||
logger.error("Install.Locations.Add failed: " + str(add_res["error"]))
|
||||
return ""
|
||||
_install_location_id = add_res.get("installLocation", {}).get("id", "")
|
||||
return _install_location_id
|
||||
_cached_install_location_id = add_res.get("installLocation", {}).get("id", "")
|
||||
return _cached_install_location_id
|
||||
|
||||
|
||||
## Returns every configured install location, each as:
|
||||
|
|
@ -629,7 +629,7 @@ func _get_install_locations() -> Array:
|
|||
logger.warn("Install.Locations.List failed: " + str(res["error"]))
|
||||
return []
|
||||
var locations: Array = res.get("installLocations", [])
|
||||
var preferred_path := ProjectSettings.globalize_path(games_dir)
|
||||
var preferred_path := ProjectSettings.globalize_path(GAMES_DIR)
|
||||
var plugin_dir := ProjectSettings.globalize_path("user://plugins/itch")
|
||||
var preferred: Array = []
|
||||
var others: Array = []
|
||||
|
|
@ -998,8 +998,8 @@ func _logout() -> void:
|
|||
|
||||
|
||||
func _exit_tree() -> void:
|
||||
if socket:
|
||||
socket.disconnect_from_host()
|
||||
if not proc:
|
||||
if rpc_socket:
|
||||
rpc_socket.disconnect_from_host()
|
||||
if not butler_process:
|
||||
return
|
||||
proc.stop()
|
||||
butler_process.stop()
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ var settings_manager := load("res://core/global/settings_manager.tres") as Setti
|
|||
var notification_manager := load("res://core/global/notification_manager.tres") as NotificationManager
|
||||
const icon := preload("res://plugins/itch/assets/itch.svg")
|
||||
|
||||
enum LOGIN_METHOD { API_KEY = 0, PASSWORD = 1 }
|
||||
|
||||
@onready var status := $%Status as StatusPanel
|
||||
@onready var connected_status := $%ConnectedStatus as StatusPanel
|
||||
@onready var logged_in_status := $%LoggedInStatus as StatusPanel
|
||||
|
|
@ -44,7 +46,7 @@ func _ready() -> void:
|
|||
|
||||
# Configure connected status
|
||||
connected_status.status = connected_status.STATUS.ACTIVE
|
||||
if itch.state != itch.STATE.BOOT:
|
||||
if itch.connection_state != itch.STATE.BOOT:
|
||||
connected_status.color = "green"
|
||||
itch.client_ready.connect(func():
|
||||
connected_status.color = "green"
|
||||
|
|
@ -82,8 +84,8 @@ func _ready() -> void:
|
|||
show_purchases_check.button_pressed = settings_manager.get_value("plugin.itch", "show_purchases", true) as bool
|
||||
|
||||
# Connect signals
|
||||
save_button.pressed.connect(_on_save_button)
|
||||
logout_button.pressed.connect(_on_logout_button)
|
||||
save_button.pressed.connect(_on_save_pressed)
|
||||
logout_button.pressed.connect(_on_logout_pressed)
|
||||
login_method.item_selected.connect(_on_login_method_changed)
|
||||
filter_check.toggled.connect(_on_filter_toggled)
|
||||
show_purchases_check.toggled.connect(_on_show_purchases_toggled)
|
||||
|
|
@ -105,17 +107,17 @@ func _on_login(login_status: ItchClient.LOGIN_STATUS, _profile: Dictionary) -> v
|
|||
func _on_login_method_changed(idx: int) -> void:
|
||||
# API key mode: show API key input, hide user/pass.
|
||||
# Password mode: show user/pass, hide API key.
|
||||
var is_api := idx == 0
|
||||
var is_api := idx == LOGIN_METHOD.API_KEY
|
||||
api_key_box.visible = is_api
|
||||
username_box.visible = not is_api
|
||||
password_box.visible = not is_api
|
||||
|
||||
|
||||
func _on_save_button() -> void:
|
||||
func _on_save_pressed() -> void:
|
||||
var method: int = login_method.selected
|
||||
settings_manager.set_value("plugin.itch", "login_method", method)
|
||||
|
||||
if method == 0:
|
||||
if method == LOGIN_METHOD.API_KEY:
|
||||
# API key login
|
||||
settings_manager.set_value("plugin.itch", "username", "")
|
||||
var api_key: String = api_key_box.text.strip_edges()
|
||||
|
|
@ -133,11 +135,11 @@ func _on_save_button() -> void:
|
|||
itch.login_with_password(uname, password)
|
||||
|
||||
|
||||
func _on_logout_button() -> void:
|
||||
func _on_logout_pressed() -> void:
|
||||
var method: int = login_method.selected
|
||||
|
||||
# Clear saved credentials
|
||||
if method == 0:
|
||||
if method == LOGIN_METHOD.API_KEY:
|
||||
settings_manager.set_value("plugin.itch", "api_key", "")
|
||||
api_key_box.text = ""
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -509,7 +509,7 @@ func _load_library(caching_flags: int = Cache.FLAGS.LOAD | Cache.FLAGS.SAVE) ->
|
|||
cached_installed = true
|
||||
break
|
||||
var attempts := 0
|
||||
while cached_installed and attempts < BUTLERD_BOOT_MAX_ATTEMPTS and (caves == null or (caves.is_empty() and itch.state != ItchClient.STATE.CONNECTED)):
|
||||
while cached_installed and attempts < BUTLERD_BOOT_MAX_ATTEMPTS and (caves == null or (caves.is_empty() and itch.connection_state != ItchClient.STATE.CONNECTED)):
|
||||
await get_tree().create_timer(BUTLERD_BOOT_RETRY_DELAY_SEC).timeout
|
||||
caves = await itch.get_caves()
|
||||
attempts += 1
|
||||
|
|
|
|||
15
plugin.gd
15
plugin.gd
|
|
@ -14,10 +14,13 @@ var notification_manager := load("res://core/global/notification_manager.tres")
|
|||
var settings_menu := load("res://plugins/itch/core/itch_settings.tscn") as PackedScene
|
||||
var icon := preload("res://plugins/itch/assets/itch.svg")
|
||||
|
||||
const SETTINGS_SECTION := "plugin.itch"
|
||||
enum LOGIN_METHOD { API_KEY = 0, PASSWORD = 1 }
|
||||
|
||||
var itch: ItchClient
|
||||
var api_key := settings_manager.get_value("plugin.itch", "api_key", "") as String
|
||||
var login_method := settings_manager.get_value("plugin.itch", "login_method", 0) as int
|
||||
var username := settings_manager.get_value("plugin.itch", "username", "") as String
|
||||
var api_key := settings_manager.get_value(SETTINGS_SECTION, "api_key", "") as String
|
||||
var login_method := settings_manager.get_value(SETTINGS_SECTION, "login_method", 0) as int
|
||||
var username := settings_manager.get_value(SETTINGS_SECTION, "username", "") as String
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
|
|
@ -26,7 +29,7 @@ func _ready() -> void:
|
|||
# Load the itch client. This spawns butlerd in the background and
|
||||
# manages the JSON-RPC connection to it.
|
||||
itch = load("res://plugins/itch/core/itch_client.tscn").instantiate()
|
||||
itch.bootstrap_finished.connect(_on_client_start)
|
||||
itch.bootstrap_finished.connect(_on_bootstrap_finished)
|
||||
itch.client_ready.connect(_on_client_ready)
|
||||
itch.logged_in.connect(_on_client_logged_in)
|
||||
add_child(itch)
|
||||
|
|
@ -37,7 +40,7 @@ func _ready() -> void:
|
|||
|
||||
|
||||
# Triggers when butlerd has been spawned
|
||||
func _on_client_start() -> void:
|
||||
func _on_bootstrap_finished() -> void:
|
||||
if not itch.client_started:
|
||||
var notify := Notification.new("Unable to start the itch.io (butlerd) client")
|
||||
notify.icon = icon
|
||||
|
|
@ -52,7 +55,7 @@ func _on_client_ready() -> void:
|
|||
# logins from previous runs without any stored tokens on our side.
|
||||
if await itch.try_saved_login():
|
||||
return
|
||||
if login_method == 0:
|
||||
if login_method == LOGIN_METHOD.API_KEY:
|
||||
# API key login
|
||||
if api_key == "":
|
||||
var notify := Notification.new("itch.io API key required")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue