@tool extends Control signal gd_plug_loaded(gd_plug) signal updated() enum PLUGIN_STATUS { PLUGGED, UNPLUGGED, INSTALLED, CHANGED, UPDATE } const PLUGIN_STATUS_ICON = [ preload("../../assets/icons/add.png"), preload("../../assets/icons/import_fail.png"), preload("../../assets/icons/import_check.png"), preload("../../assets/icons/edit_internal.png"), preload("../../assets/icons/refresh.png") ] @onready var tree = $Tree @onready var init_btn = $"%InitBtn" @onready var check_for_update_btn = $"%CheckForUpdateBtn" @onready var update_section = $"%UpdateSection" @onready var force_check = $"%ForceCheck" @onready var production_check = $"%ProductionCheck" @onready var update_btn = $"%UpdateBtn" @onready var loading_overlay = $"%LoadingOverlay" @onready var loading_label = $"%LoadingLabel" var gd_plug var project_dir var _is_executing = false var _check_for_update_task_id = -1 func _ready(): project_dir = DirAccess.open("res://") load_gd_plug() update_plugin_list(get_plugged_plugins(), get_installed_plugins()) tree.set_column_title(0, "Name") tree.set_column_title(1, "Arguments") tree.set_column_title(2, "Status") connect("visibility_changed", _on_visibility_changed) func _process(delta): if not is_instance_valid(gd_plug): return if "threadpool" in gd_plug: gd_plug.threadpool.process(delta) if _check_for_update_task_id >= 0: if WorkerThreadPool.is_task_completed(_check_for_update_task_id): _check_for_update_task_id = -1 show_overlay(false) disable_ui(false) func _notification(what): match what: NOTIFICATION_PREDELETE: if is_instance_valid(gd_plug): gd_plug.threadpool.stop() gd_plug.free() NOTIFICATION_APPLICATION_FOCUS_IN: load_gd_plug() update_plugin_list(get_plugged_plugins(), get_installed_plugins()) func load_gd_plug(): if is_instance_valid(gd_plug): gd_plug.free() # Free instance in order to reload script if project_dir.file_exists("plug.gd"): init_btn.hide() check_for_update_btn.show() update_section.show() update_btn.show() # Not sure why it is always hidden var gd_plug_script = load("plug.gd") gd_plug_script.reload(true) # Reload gd-plug script to get updated gd_plug = gd_plug_script.new() gd_plug._plug_start() gd_plug._plugging() else: if project_dir.file_exists("addons/gd-plug/plug.gd"): init_btn.show() check_for_update_btn.hide() update_section.hide() gd_plug = load("addons/gd-plug/plug.gd").new() else: print("Missing dependency: gd-plug") if is_instance_valid(gd_plug): emit_signal("gd_plug_loaded", gd_plug) func update_plugin_list(plugged, installed): var plugin_names = [] for plugin_name in plugged.keys(): plugin_names.append(plugin_name) for plugin_name in installed.keys(): if plugin_name in plugin_names: continue plugin_names.append(plugin_name) tree.clear() tree.create_item() # root for plugin_name in plugin_names: var plugin_plugged = plugged.get(plugin_name, {}) var plugin_installed = installed.get(plugin_name, {}) var plugin = plugin_plugged if plugin_name in plugged else plugin_installed var plugin_status = get_plugin_status(plugin_name) var plugin_args = [] for plugin_arg in plugin.keys(): var value = plugin[plugin_arg] if value != null: if not (value is bool): if value.is_empty(): continue else: continue match plugin_arg: "install_root": plugin_args.append("install root: %s" % str(value)) "include": plugin_args.append("include %s" % str(value)) "exclude": plugin_args.append("exclude %s" % str(value)) "branch": plugin_args.append("branch: %s" % str(value)) "tag": plugin_args.append("tag: %s" % str(value)) "commit": plugin_args.append(str(value).left(8)) "dev": if value: plugin_args.append("dev") "on_updated": plugin_args.append("on_updated: %s" % str(value)) var plugin_args_text = "" for i in plugin_args.size(): var text = plugin_args[i] plugin_args_text += text if i < plugin_args.size() - 1: plugin_args_text += ", " var child = tree.create_item(tree.get_root()) child.set_text_alignment(0, HORIZONTAL_ALIGNMENT_LEFT) child.set_text_alignment(1, HORIZONTAL_ALIGNMENT_CENTER) child.set_text_alignment(2, HORIZONTAL_ALIGNMENT_CENTER) child.set_meta("plugin", plugin) child.set_text(0, plugin_name) child.set_tooltip_text(0, plugin.url) child.set_text(1, plugin_args_text) child.set_tooltip_text(2, PLUGIN_STATUS.keys()[plugin_status].capitalize()) child.set_icon(2, PLUGIN_STATUS_ICON[plugin_status]) func disable_ui(disabled=true): init_btn.disabled = disabled check_for_update_btn.disabled = disabled update_btn.disabled = disabled func show_overlay(show=true, text=""): loading_overlay.visible = show loading_label.text = text func gd_plug_execute_threaded(name): if not is_instance_valid(gd_plug): return if _is_executing: return _is_executing = true disable_ui(true) gd_plug._plug_start() gd_plug._plugging() gd_plug.call(name) await gd_plug.threadpool.all_thread_finished # Make sure to use call_deferred for thread safe function calling while waiting thread to finish gd_plug._plug_end() call_deferred("disable_ui", false) _is_executing = false clear_environment() call_deferred("update_plugin_list", get_plugged_plugins(), get_installed_plugins()) func gd_plug_execute(name): if not is_instance_valid(gd_plug): return if _is_executing: return _is_executing = true disable_ui(true) gd_plug._plug_start() gd_plug._plugging() gd_plug.call(name) gd_plug._plug_end() disable_ui(false) _is_executing = false clear_environment() update_plugin_list(get_plugged_plugins(), get_installed_plugins()) func clear_environment(): OS.unset_environment("production") OS.unset_environment("test") OS.unset_environment("force") func _on_visibility_changed(): if visible: load_gd_plug() update_plugin_list(get_plugged_plugins(), get_installed_plugins()) func _on_Init_pressed(): gd_plug_execute("_plug_init") load_gd_plug() func _on_CheckForUpdateBtn_pressed(): var children = tree.get_root().get_children() if tree.get_root().get_children().size() > 0: show_overlay(true, "Checking for Updates...") disable_ui(true) if _check_for_update_task_id < 0: var task_id = WorkerThreadPool.add_task(check_for_update.bind(children[0])) _check_for_update_task_id = (task_id) func _on_UpdateBtn_pressed(): if force_check.button_pressed: OS.set_environment("force", "true") if production_check.button_pressed: OS.set_environment("production", "true") show_overlay(true, "Updating...") gd_plug_execute_threaded("_plug_install") await gd_plug.threadpool.all_thread_finished # Make sure to use call_deferred for thread safe function calling while waiting thread to finish call_deferred("show_overlay", false) call_deferred("emit_signal", "updated") func get_plugged_plugins(): return gd_plug._plugged_plugins if is_instance_valid(gd_plug) else {} func get_installed_plugins(): return gd_plug.installation_config.get_value("plugin", "installed", {}) if is_instance_valid(gd_plug) else {} func get_plugin_status(plugin_name): var plugged_plugins = get_plugged_plugins() var installed_plugins = get_installed_plugins() var plugin_plugged = plugged_plugins.get(plugin_name, {}) var plugin_installed = installed_plugins.get(plugin_name, {}) var plugin = plugin_plugged if plugin_name in plugged_plugins else plugin_installed var is_plugged = plugin.name in plugged_plugins var is_installed = plugin.name in installed_plugins var changes = gd_plug.compare_plugins(plugin_plugged, plugin_installed) if is_installed else {} var is_changed = changes.size() > 0 var plugin_status = 0 if is_installed: if is_plugged: if is_changed: plugin_status = 3 else: plugin_status = 2 else: plugin_status = 1 else: plugin_status = 0 return plugin_status func has_update(plugin): if not is_instance_valid(gd_plug): return false if plugin == null: return false var git = gd_plug._GitExecutable.new(ProjectSettings.globalize_path(plugin.plug_dir), gd_plug.logger) var ahead_behind = [] if git.fetch("origin " + plugin.branch if plugin.branch else "origin").exit == OK: ahead_behind = git.get_commit_comparison("HEAD", "origin/" + plugin.branch if plugin.branch else "origin") var is_commit_behind = !!ahead_behind[1] if ahead_behind.size() == 2 else false if is_commit_behind: gd_plug.logger.info("%s %d commits behind, update required" % [plugin.name, ahead_behind[1]]) return true else: gd_plug.logger.info("%s up to date" % plugin.name) return false func check_for_update(child): var plugin = child.get_meta("plugin") var plugin_status = get_plugin_status(plugin.name) if plugin_status == PLUGIN_STATUS.INSTALLED: var has_update = has_update(plugin) if has_update: child.set_icon(2, PLUGIN_STATUS_ICON[PLUGIN_STATUS.UPDATE]) child.set_tooltip_text(2, PLUGIN_STATUS.keys()[PLUGIN_STATUS.UPDATE].capitalize()) if is_instance_valid(child): var next_child = child.get_next() if next_child: check_for_update(next_child)