randomgeon/addons/loggie/version_management/loggie_version_manager.gd
Dan Baker 02b3be35b0 Sets up initial project structure
Initializes the project with core files including:

- Editor configuration (.editorconfig, .gitattributes, .gitignore, .vscode/settings.json)
- Log.gd addon for enhanced debugging
- Loggie addon for advanced logging
- Project assets folder
2025-04-29 17:35:39 +01:00

225 lines
10 KiB
GDScript

@tool
## A class that can be used to inquire about, generate, and operate on [LoggieVersion]s.
## It is also responsible for notifying about an available update, and starting it, if configured to do so.
class_name LoggieVersionManager extends RefCounted
## Emitted when this version manager updates the known [member latest_version].
signal latest_version_updated()
## Emitted when this version manager has created a valid [LoggieUpdate] and is ready to use it.
signal update_ready()
## The URL where loggie releases on GitHub can be found.
const REMOTE_RELEASES_URL = "https://api.github.com/repos/Shiva-Shadowsong/loggie/releases"
## The domain from which [LoggieMsg]s from this version manager will be logged from.
const REPORTS_DOMAIN : String = "loggie_version_check_reports"
## Stores the result of reading the Loggie version with [method get_current_Version].
var version : LoggieVersion = null
## Stores the result of reading the latest Loggie version with [method get_latest_version].
var latest_version : LoggieVersion = null
## Stores a reference to a ConfigFile which will be loaded from [member CONFIG_PATH] during [method find_and_store_current_version].
var config : ConfigFile = ConfigFile.new()
## Stores a reference to the logger that's using this version manager.
var _logger : Variant = null
## Stores a reference to the [LoggieUpdate] that has been created to handle an available update.
var _update : LoggieUpdate = null
## Internal debug variable.
## If not null, this version manager will treat the [LoggieVersion] provided under this variable to be the current [param version].
## Useful for debugging this module when you want to simulate that the current version is something different than it actually is.
var _version_proxy : LoggieVersion = null
## Initializes this version manager, connecting it to the logger that's using it and updating the version cache based on that logger,
## which will further prompt the emission of signals in this class, and so on.
func connect_logger(logger : Variant) -> void:
self.latest_version_updated.connect(on_latest_version_updated)
self._logger = logger
# Set to true during development to enable debug prints related to version management.
self._logger.set_domain_enabled(self.REPORTS_DOMAIN, false)
update_version_cache()
## Returns a reference to the logger object that is using this version manager.
func get_logger() -> Variant:
return self._logger
## Reads the current version of Loggie from plugin.cfg and stores it in [member version].
func find_and_store_current_version():
var detected_version = self._logger.version
if self._version_proxy != null:
self.version = self._version_proxy
self.version.proxy_for = detected_version
else:
self.version = detected_version
## Reads the latest version of Loggie from a GitHub API response and stores it in [member latest_version].
func find_and_store_latest_version():
var loggie = self.get_logger()
var http_request = HTTPRequest.new()
loggie.add_child(http_request)
loggie.msg("Retrieving version(s) info from endpoint:", REMOTE_RELEASES_URL).domain(REPORTS_DOMAIN).debug()
http_request.request_completed.connect(_on_get_latest_version_request_completed, CONNECT_ONE_SHOT)
http_request.request(REMOTE_RELEASES_URL)
## Defines what happens once this version manager emits the signal saying that an update is available.
func on_update_available_detected() -> void:
var loggie = self.get_logger()
if loggie.settings.update_check_mode == LoggieEnums.UpdateCheckType.DONT_CHECK:
return
self._update = LoggieUpdate.new(self.version, self.latest_version)
self._update._logger = loggie
var github_data = self.latest_version.get_meta("github_data")
var latest_release_notes_url = github_data.html_url
self._update.set_release_notes_url(latest_release_notes_url)
loggie.add_child(self._update)
update_ready.emit()
# No plan to allow multiple updates to run during a single Engine session anyway so no need to start another one.
# Also, this helps with internal testing of the updater and prevents an updated plugin from auto-starting another update
# when dealing with proxy versions.
var hasUpdatedAlready = Engine.has_meta("LoggieUpdateSuccessful") and Engine.get_meta("LoggieUpdateSuccessful")
match loggie.settings.update_check_mode:
LoggieEnums.UpdateCheckType.CHECK_AND_SHOW_UPDATER_WINDOW:
if hasUpdatedAlready:
loggie.info("Update already performed. ✔️")
return
create_and_show_updater_widget(self._update)
LoggieEnums.UpdateCheckType.CHECK_AND_SHOW_MSG:
loggie.msg("👀 Loggie update available!").color(Color.ORANGE).header().msg(" > Current version: {version}, Latest version: {latest}".format({
"version" : self.version,
"latest" : self.latest_version
})).info()
LoggieEnums.UpdateCheckType.CHECK_DOWNLOAD_AND_SHOW_MSG:
if hasUpdatedAlready:
loggie.info("Update already performed. ✔️")
return
loggie.set_domain_enabled("loggie_update_status_reports", true)
self._update.try_start()
## Defines what happens when the request to GitHub API which grabs all the Loggie releases is completed.
func _on_get_latest_version_request_completed(result : int, response_code : int, headers : PackedStringArray, body: PackedByteArray):
var loggie = self.get_logger()
loggie.msg("Response for request received:", response_code).domain(REPORTS_DOMAIN).debug()
if result != HTTPRequest.RESULT_SUCCESS:
return
var response = JSON.parse_string(body.get_string_from_utf8())
if typeof(response) != TYPE_ARRAY:
loggie.error("The response parsed from GitHub was not an array. Response received in an unsupported format.")
return
var latest_version_data = response[0] # GitHub releases are in order of creation, so grab the first one from the response, that's the latest one.
self.latest_version = LoggieVersion.from_string(latest_version_data.tag_name)
self.latest_version.set_meta("github_data", latest_version_data)
loggie.msg("Current version of Loggie:", self.version).msg(" (proxy for: {value})".format({"value": self.version.proxy_for})).domain(REPORTS_DOMAIN).debug()
loggie.msg("Latest version of Loggie:", self.latest_version).domain(REPORTS_DOMAIN).debug()
latest_version_updated.emit()
## Executes every time this version manager updates the known latest_version.
func on_latest_version_updated() -> void:
var loggie = self.get_logger()
if loggie == null:
return
# Check if update is available.
if loggie.settings.update_check_mode != LoggieEnums.UpdateCheckType.DONT_CHECK:
loggie.msg("👀 Loggie:").bold().color("orange").msg(" Checking for updates...").info()
if is_update_available():
on_update_available_detected()
else:
loggie.msg("👀 Loggie:").bold().color("orange").msg(" Up to date. ✔️").color(Color.LIGHT_GREEN).info()
## Displays the widget which informs the user of the available update and offers actions that they can take next.
func create_and_show_updater_widget(update : LoggieUpdate) -> Window:
const PATH_TO_WIDGET_SCENE = "addons/loggie/version_management/update_prompt_window.tscn"
var WIDGET_SCENE = load(PATH_TO_WIDGET_SCENE)
if !is_instance_valid(WIDGET_SCENE):
push_error("Loggie Update Prompt Window scene not found on expected path: {path}".format({"path": PATH_TO_WIDGET_SCENE}))
return
var loggie = self.get_logger()
if loggie == null:
return
var popup_parent = null
if Engine.is_editor_hint() and Engine.has_meta("LoggieEditorInterfaceBaseControl"):
popup_parent = Engine.get_meta("LoggieEditorInterfaceBaseControl")
else:
popup_parent = SceneTree.current_scene
# Configure popup window.
var _popup = Window.new()
update.succeeded.connect(func():
_popup.queue_free()
var success_dialog = AcceptDialog.new()
var msg = "💬 You may see temporary errors in the console due to Loggie files being re-scanned and reloaded on the spot.\nIt should be safe to dismiss them, but for the best experience, reload the Godot editor (and the plugin, if something seems wrong).\n\n🚩 If you see a 'Files have been modified on disk' window pop up, choose 'Discard local changes and reload' to accept incoming changes."
success_dialog.dialog_text = msg
success_dialog.title = "Loggie Updater"
if is_instance_valid(popup_parent):
popup_parent.add_child(success_dialog)
success_dialog.popup_centered()
)
var on_close_requested = func():
_popup.queue_free()
_popup.close_requested.connect(on_close_requested, CONNECT_ONE_SHOT)
_popup.borderless = false
_popup.unresizable = true
_popup.transient = true
_popup.title = "Update Available"
# Configure window widget and add it as a child of the popup window.
var widget : LoggieUpdatePrompt = WIDGET_SCENE.instantiate()
widget.connect_to_update(update)
widget.set_anchors_preset(Control.PRESET_FULL_RECT)
widget._logger = loggie
widget.close_requested.connect(on_close_requested, CONNECT_ONE_SHOT)
if is_instance_valid(popup_parent):
popup_parent.add_child(_popup)
_popup.popup_centered(widget.host_window_size)
_popup.add_child(widget)
return _popup
## Updates the local variables which point to the current and latest version of Loggie.
func update_version_cache():
# Read and cache the current version of Loggie from plugin.cfg.
find_and_store_current_version()
# Read and cache the latest version of Loggie from GitHub.
# (Do it only if running in editor, no need for this if running in a game).
var logger = self.get_logger()
if logger is Node:
if !Engine.is_editor_hint():
return
if logger.is_node_ready():
find_and_store_latest_version()
else:
logger.ready.connect(func():
find_and_store_latest_version()
, CONNECT_ONE_SHOT)
## Checks if an update for Loggie is available. Run only after running [method update_version_cache].
func is_update_available() -> bool:
var loggie = self.get_logger()
if !(self.version is LoggieVersion and self.version.is_valid()):
loggie.error("The current version of Loggie is not valid. Run `find_and_store_current_version` once to obtain this value first.")
return false
if !(self.latest_version is LoggieVersion and self.latest_version.is_valid()):
loggie.error("The latest version of Loggie is not valid. Run `find_and_store_latest_version` once to obtain this value first.")
return false
return self.latest_version.is_higher_than(self.version)