From 1f7b19ba80f9a672d4f93839dd651e1431cc6a67 Mon Sep 17 00:00:00 2001 From: tryiou Date: Thu, 22 May 2025 10:30:47 +0200 Subject: [PATCH 01/15] refactor: Simplify GUI initialization and improve type annotations throughout the codebase --- blocknet_aio_monitor.py | 319 ++++++++++++++++------------------ gui/binary_frame_manager.py | 18 +- gui/binary_manager.py | 6 +- gui/blockdx_frame_manager.py | 10 +- gui/blockdx_manager.py | 6 +- gui/blocknet_frame_manager.py | 8 +- gui/blocknet_manager.py | 6 +- gui/xbridge_bot_manager.py | 27 +-- gui/xlite_frame_manager.py | 8 +- gui/xlite_manager.py | 6 +- 10 files changed, 199 insertions(+), 215 deletions(-) diff --git a/blocknet_aio_monitor.py b/blocknet_aio_monitor.py index 80bdc59..e0e09e7 100644 --- a/blocknet_aio_monitor.py +++ b/blocknet_aio_monitor.py @@ -3,7 +3,6 @@ import os import signal -import PIL._tkinter_finder import customtkinter as ctk from PIL import Image from psutil import process_iter @@ -27,7 +26,10 @@ class Blocknet_AIO_GUI(ctk.CTk): + """Main GUI class for Blocknet AIO application.""" + def __init__(self): + """Initialize the Blocknet AIO GUI application.""" super().__init__() self.install_greyed_img = None self.install_img = None @@ -40,12 +42,12 @@ def __init__(self): self.transparent_img = None self.theme_img = None - self.disable_daemons_conf_check = False + self.disable_daemons_conf_check: bool = False - self.cfg = utils.load_cfg_json() + self.cfg: dict = utils.load_cfg_json() self.adjust_theme() - self.custom_path = None - self.stored_password = None + self.custom_path: str = None + self.stored_password: str = None if self.cfg: if 'custom_path' in self.cfg: self.custom_path = self.cfg['custom_path'] @@ -56,26 +58,17 @@ def __init__(self): logging.error(f"Error decrypting XLite password: {e}") self.stored_password = None - self.binary_manager = None - self.blocknet_manager = None - self.blockdx_manager = None - self.xlite_manager = None - - self.time_disable_button = 3000 + self.time_disable_button: int = 3000 - # frames - self.bins_download_frame = None - self.bins_title_frame = None - self.blocknet_core_frame = None - self.blocknet_title_frame = None - self.blockdx_frame = None - self.blockdx_title_frame = None - self.xlite_frame = None - self.xlite_title_frame = None + self.tooltip_manager: TooltipManager = TooltipManager(self) - self.tooltip_manager = TooltipManager(self) + self.blocknet_manager = BlocknetManager(self) + self.binary_manager = BinaryManager(self) + self.blockdx_manager = BlockDXManager(self) + self.xlite_manager = XliteManager(self) - async def setup_management_sections(self): + async def setup_management_sections(self) -> None: + """Initialize and setup all management sections asynchronously.""" await asyncio.gather( self.binary_manager.setup(), self.blocknet_manager.setup(), @@ -83,18 +76,14 @@ async def setup_management_sections(self): self.xlite_manager.setup() ) - def create_managers(self): - self.blocknet_manager = BlocknetManager(self, self.blocknet_core_frame, self.blocknet_title_frame) - self.binary_manager = BinaryManager(self, self.bins_download_frame, self.bins_title_frame) - self.blockdx_manager = BlockDXManager(self, self.blockdx_frame, self.blockdx_title_frame) - self.xlite_manager = XliteManager(self, self.xlite_frame, self.xlite_title_frame) + def create_managers(self) -> None: + """Create instances of manager classes for different components.""" - def init_setup(self): + def init_setup(self) -> None: + """Initialize the GUI setup, including layout, images, and frame configuration.""" self.title(widgets_strings.app_title_string) self.resizable(False, False) self.setup_load_images() - self.init_frames() - self.create_managers() self.after(0, self.check_processes) asyncio.run(self.setup_management_sections()) self.setup_tooltips() @@ -104,199 +93,196 @@ def init_setup(self): signal.signal(signal.SIGINT, self.handle_signal) signal.signal(signal.SIGTERM, self.handle_signal) - def init_frames(self): - self.bins_download_frame = ctk.CTkFrame(master=self) - self.bins_title_frame = ctk.CTkFrame(self.bins_download_frame) - - self.blocknet_core_frame = ctk.CTkFrame(master=self) - self.blocknet_title_frame = ctk.CTkFrame(self.blocknet_core_frame) - - self.blockdx_frame = ctk.CTkFrame(master=self) - self.blockdx_title_frame = ctk.CTkFrame(self.blockdx_frame) - - self.xlite_frame = ctk.CTkFrame(master=self) - self.xlite_title_frame = ctk.CTkFrame(self.xlite_frame) - - def setup_load_images(self): + def setup_load_images(self) -> None: + """Load and set up images for use in the GUI.""" resize = (65, 30) self.theme_img = ctk.CTkImage( - light_image=PIL.Image.open(os.path.join(global_variables.DIRPATH, "img", "light.png")).resize(resize, - PIL.Image.LANCZOS), - dark_image=PIL.Image.open(os.path.join(global_variables.DIRPATH, "img", "dark.png")).resize(resize, - PIL.Image.LANCZOS), - size=resize) + light_image=Image.open(os.path.join(global_variables.DIRPATH, "img", "light.png")).resize(resize, + Image.LANCZOS), + dark_image=Image.open(os.path.join(global_variables.DIRPATH, "img", "dark.png")).resize(resize, + Image.LANCZOS), + size=resize + ) resize = (50, 50) self.transparent_img = ctk.CTkImage( - light_image=PIL.Image.open(os.path.join(global_variables.DIRPATH, "img", "transparent.png")).resize(resize, - PIL.Image.LANCZOS)) + light_image=Image.open(os.path.join(global_variables.DIRPATH, "img", "transparent.png")).resize(resize, + Image.LANCZOS) + ) self.start_img = ctk.CTkImage( - light_image=PIL.Image.open(os.path.join(global_variables.DIRPATH, "img", "start-50.png")).resize(resize, - PIL.Image.LANCZOS)) + light_image=Image.open(os.path.join(global_variables.DIRPATH, "img", "start-50.png")).resize(resize, + Image.LANCZOS) + ) self.start_greyed_img = ctk.CTkImage( - light_image=PIL.Image.open(os.path.join(global_variables.DIRPATH, "img", "start-50_greyed.png")).resize( - resize, - PIL.Image.LANCZOS)) + light_image=Image.open(os.path.join(global_variables.DIRPATH, "img", "start-50_greyed.png")).resize(resize, + Image.LANCZOS) + ) self.stop_img = ctk.CTkImage( - light_image=PIL.Image.open(os.path.join(global_variables.DIRPATH, "img", "stop-50.png")).resize(resize, - PIL.Image.LANCZOS)) + light_image=Image.open(os.path.join(global_variables.DIRPATH, "img", "stop-50.png")).resize(resize, + Image.LANCZOS) + ) self.stop_greyed_img = ctk.CTkImage( - light_image=PIL.Image.open(os.path.join(global_variables.DIRPATH, "img", "stop-50_greyed.png")).resize( - resize, - PIL.Image.LANCZOS)) + light_image=Image.open(os.path.join(global_variables.DIRPATH, "img", "stop-50_greyed.png")).resize(resize, + Image.LANCZOS) + ) self.delete_img = ctk.CTkImage( - light_image=PIL.Image.open(os.path.join(global_variables.DIRPATH, "img", "delete-50.png")).resize(resize, - PIL.Image.LANCZOS)) + light_image=Image.open(os.path.join(global_variables.DIRPATH, "img", "delete-50.png")).resize(resize, + Image.LANCZOS) + ) self.delete_greyed_img = ctk.CTkImage( - light_image=PIL.Image.open(os.path.join(global_variables.DIRPATH, "img", "delete-50_greyed.png")).resize( - resize, - PIL.Image.LANCZOS)) + light_image=Image.open(os.path.join(global_variables.DIRPATH, "img", "delete-50_greyed.png")).resize(resize, + Image.LANCZOS) + ) self.install_img = ctk.CTkImage( - light_image=PIL.Image.open(os.path.join(global_variables.DIRPATH, "img", "installer-50.png")).resize(resize, - PIL.Image.LANCZOS)) + light_image=Image.open(os.path.join(global_variables.DIRPATH, "img", "installer-50.png")).resize(resize, + Image.LANCZOS) + ) self.install_greyed_img = ctk.CTkImage( - light_image=PIL.Image.open(os.path.join(global_variables.DIRPATH, "img", "installer-50_greyed.png")).resize( - resize, - PIL.Image.LANCZOS)) + light_image=Image.open(os.path.join(global_variables.DIRPATH, "img", "installer-50_greyed.png")).resize( + resize, Image.LANCZOS) + ) - def setup_tooltips(self): - self.tooltip_manager.register_tooltip(self.blocknet_core_frame, - msg=widgets_strings.tooltip_howtouse, - delay=1, follow=True, bg_color=tooltip_bg_color, border_width=2, - justify="left") - self.tooltip_manager.register_tooltip(self.blockdx_frame, - msg=widgets_strings.tooltip_howtouse, - delay=1, follow=True, bg_color=tooltip_bg_color, border_width=2, - justify="left") - self.tooltip_manager.register_tooltip(self.xlite_frame, - msg=widgets_strings.tooltip_howtouse, - delay=1, follow=True, bg_color=tooltip_bg_color, border_width=2, - justify="left") - self.tooltip_manager.register_tooltip(self.bins_download_frame, - msg=widgets_strings.tooltip_howtouse, - delay=1, follow=True, bg_color=tooltip_bg_color, border_width=2, - justify="left") - self.tooltip_manager.register_tooltip(self.bins_title_frame, - msg=widgets_strings.tooltip_bins_title_msg, - delay=1, follow=True, bg_color=tooltip_bg_color, border_width=2, - justify="left") + def setup_tooltips(self) -> None: + """Set up tooltips for various GUI components.""" + self.tooltip_manager.register_tooltip(self.blocknet_manager.frame_manager.master_frame, + msg=widgets_strings.tooltip_howtouse, delay=1, follow=True, + bg_color=tooltip_bg_color, border_width=2, justify="left") + self.tooltip_manager.register_tooltip(self.blockdx_manager.frame_manager.master_frame, + msg=widgets_strings.tooltip_howtouse, delay=1, follow=True, + bg_color=tooltip_bg_color, border_width=2, justify="left") + self.tooltip_manager.register_tooltip(self.xlite_manager.frame_manager.master_frame, + msg=widgets_strings.tooltip_howtouse, delay=1, follow=True, + bg_color=tooltip_bg_color, border_width=2, justify="left") + self.tooltip_manager.register_tooltip(self.binary_manager.frame_manager.master_frame, + msg=widgets_strings.tooltip_howtouse, delay=1, follow=True, + bg_color=tooltip_bg_color, border_width=2, justify="left") + self.tooltip_manager.register_tooltip(self.binary_manager.frame_manager.title_frame, + msg=widgets_strings.tooltip_bins_title_msg, delay=1, follow=True, + bg_color=tooltip_bg_color, border_width=2, justify="left") self.tooltip_manager.register_tooltip(self.binary_manager.frame_manager.header_label, - msg=widgets_strings.tooltip_bins_title_msg, - delay=1, follow=True, bg_color=tooltip_bg_color, border_width=2, - justify="left") + msg=widgets_strings.tooltip_bins_title_msg, delay=1, follow=True, + bg_color=tooltip_bg_color, border_width=2, justify="left") self.tooltip_manager.register_tooltip(self.xlite_manager.frame_manager.xlite_label, - msg=widgets_strings.tooltip_xlite_label_msg, - delay=1.0, border_width=2, follow=True, bg_color=tooltip_bg_color) + msg=widgets_strings.tooltip_xlite_label_msg, delay=1.0, border_width=2, + follow=True, bg_color=tooltip_bg_color) self.tooltip_manager.register_tooltip(self.binary_manager.frame_manager.blocknet_label, - msg=widgets_strings.tooltip_blocknet_core_label_msg, delay=1, - follow=True, bg_color=tooltip_bg_color, border_width=2, - justify="left") + msg=widgets_strings.tooltip_blocknet_core_label_msg, delay=1, follow=True, + bg_color=tooltip_bg_color, border_width=2, justify="left") self.tooltip_manager.register_tooltip(self.binary_manager.frame_manager.blockdx_label, msg=widgets_strings.tooltip_blockdx_label_msg, delay=1, follow=True, bg_color=tooltip_bg_color, border_width=2, justify="left") self.tooltip_manager.register_tooltip(self.binary_manager.frame_manager.xlite_label, - msg=widgets_strings.tooltip_xlite_label_msg, - delay=1, follow=True, bg_color=tooltip_bg_color, border_width=2, - justify="left") - self.tooltip_manager.register_tooltip(self.binary_manager.frame_manager.install_delete_blocknet_button, - msg='', delay=1, width=1, follow=True, bg_color=tooltip_bg_color, - border_width=2, justify="left") - self.tooltip_manager.register_tooltip(self.binary_manager.frame_manager.install_delete_blockdx_button, - msg=global_variables.blockdx_release_url, + msg=widgets_strings.tooltip_xlite_label_msg, delay=1, follow=True, + bg_color=tooltip_bg_color, border_width=2, justify="left") + self.tooltip_manager.register_tooltip(self.binary_manager.frame_manager.install_delete_blocknet_button, msg='', delay=1, width=1, follow=True, bg_color=tooltip_bg_color, border_width=2, justify="left") + self.tooltip_manager.register_tooltip(self.binary_manager.frame_manager.install_delete_blockdx_button, + msg=global_variables.blockdx_release_url, delay=1, width=1, follow=True, + bg_color=tooltip_bg_color, border_width=2, justify="left") self.tooltip_manager.register_tooltip(self.binary_manager.frame_manager.install_delete_xlite_button, - msg=global_variables.xlite_release_url, - delay=1, follow=True, bg_color=tooltip_bg_color, border_width=2, - justify="left") - self.tooltip_manager.register_tooltip(self.binary_manager.frame_manager.blocknet_start_close_button, - msg='', + msg=global_variables.xlite_release_url, delay=1, follow=True, + bg_color=tooltip_bg_color, border_width=2, justify="left") + self.tooltip_manager.register_tooltip(self.binary_manager.frame_manager.blocknet_start_close_button, msg='', delay=1, follow=True, bg_color=tooltip_bg_color, border_width=2, justify="left") - self.tooltip_manager.register_tooltip(self.binary_manager.frame_manager.blockdx_start_close_button, - msg='', + self.tooltip_manager.register_tooltip(self.binary_manager.frame_manager.blockdx_start_close_button, msg='', delay=1, follow=True, bg_color=tooltip_bg_color, border_width=2, justify="left") - self.tooltip_manager.register_tooltip(self.binary_manager.frame_manager.xlite_toggle_execution_button, - msg='', + self.tooltip_manager.register_tooltip(self.binary_manager.frame_manager.xlite_toggle_execution_button, msg='', delay=1, follow=True, bg_color=tooltip_bg_color, border_width=2, justify="left") self.tooltip_manager.register_tooltip(self.blocknet_manager.frame_manager.label, - msg=widgets_strings.tooltip_blocknet_core_label_msg, - delay=1.0, border_width=2, follow=True, bg_color=tooltip_bg_color) + msg=widgets_strings.tooltip_blocknet_core_label_msg, delay=1.0, + border_width=2, follow=True, bg_color=tooltip_bg_color) self.tooltip_manager.register_tooltip(self.blockdx_manager.frame_manager.label, - msg=widgets_strings.tooltip_blockdx_label_msg, - delay=1.0, border_width=2, follow=True, bg_color=tooltip_bg_color) - - def init_grid(self): - x = 0 - y = 0 - padx_main_frame = 10 - pady_main_frame = 5 + msg=widgets_strings.tooltip_blockdx_label_msg, delay=1.0, border_width=2, + follow=True, bg_color=tooltip_bg_color) + + def init_grid(self) -> None: + """Initialize the grid layout for GUI components.""" + x: int = 0 + y: int = 0 + padx_main_frame: int = 10 + pady_main_frame: int = 5 self.grid_frames(x, y, padx_main_frame, pady_main_frame) self.binary_manager.frame_manager.grid_widgets(x, y) self.blocknet_manager.frame_manager.grid_widgets(x, y) self.blockdx_manager.frame_manager.grid_widgets(x, y) self.xlite_manager.frame_manager.grid_widgets(x, y) - def grid_frames(self, x, y, padx_main_frame, pady_main_frame): - self.bins_download_frame.grid(row=x, column=y, padx=padx_main_frame, pady=pady_main_frame, - sticky=MAIN_FRAMES_STICKY) + def grid_frames(self, x: int, y: int, padx_main_frame: int, pady_main_frame: int) -> None: + """Grid layout for frames in the GUI.""" + self.binary_manager.frame_manager.master_frame.grid(row=x, column=y, padx=padx_main_frame, pady=pady_main_frame, + sticky=MAIN_FRAMES_STICKY) # bin panel have 5 buttons per row - self.bins_title_frame.grid(row=0, column=0, columnspan=5, padx=5, pady=5, sticky=TITLE_FRAMES_STICKY) - - self.blocknet_core_frame.grid(row=x + 1, column=y, padx=padx_main_frame, pady=pady_main_frame, - sticky=MAIN_FRAMES_STICKY) - self.blocknet_title_frame.grid(row=0, column=0, columnspan=2, padx=5, pady=5, sticky=TITLE_FRAMES_STICKY) - - self.blockdx_frame.grid(row=x + 2, column=y, padx=padx_main_frame, pady=pady_main_frame, - sticky=MAIN_FRAMES_STICKY) - self.blockdx_title_frame.grid(row=0, column=0, columnspan=2, padx=5, pady=5, sticky=TITLE_FRAMES_STICKY) - - self.xlite_frame.grid(row=x + 3, column=y, padx=padx_main_frame, pady=pady_main_frame, - sticky=MAIN_FRAMES_STICKY) - self.xlite_title_frame.grid(row=0, column=0, columnspan=2, padx=5, pady=5, sticky=TITLE_FRAMES_STICKY) - - def handle_signal(self, signum, frame): - print("Signal {} received.".format(signum)) + self.binary_manager.frame_manager.title_frame.grid(row=0, column=0, columnspan=5, padx=5, pady=5, + sticky=TITLE_FRAMES_STICKY) + + self.blocknet_manager.frame_manager.master_frame.grid(row=x + 1, column=y, padx=padx_main_frame, + pady=pady_main_frame, + sticky=MAIN_FRAMES_STICKY) + self.blocknet_manager.frame_manager.title_frame.grid(row=0, column=0, columnspan=2, padx=5, pady=5, + sticky=TITLE_FRAMES_STICKY) + + self.blockdx_manager.frame_manager.master_frame.grid(row=x + 2, column=y, padx=padx_main_frame, + pady=pady_main_frame, + sticky=MAIN_FRAMES_STICKY) + self.blockdx_manager.frame_manager.title_frame.grid(row=0, column=0, columnspan=2, padx=5, pady=5, + sticky=TITLE_FRAMES_STICKY) + + self.xlite_manager.frame_manager.master_frame.grid(row=x + 3, column=y, padx=padx_main_frame, + pady=pady_main_frame, + sticky=MAIN_FRAMES_STICKY) + self.xlite_manager.frame_manager.title_frame.grid(row=0, column=0, columnspan=2, padx=5, pady=5, + sticky=TITLE_FRAMES_STICKY) + + def handle_signal(self, signum: int, frame) -> None: + """Handle signals like SIGINT and SIGTERM.""" + print(f"Signal {signum} received.") self.on_close() - def on_close(self): + def on_close(self) -> None: + """Handle application close event.""" logging.info("Closing application...") utils.terminate_all_threads() logging.info("Threads terminated.") os._exit(0) - def adjust_theme(self): + def adjust_theme(self) -> None: + """Adjust the theme of the application based on the configuration.""" if self.cfg and 'theme' in self.cfg: - actual = ctk.get_appearance_mode() + actual: str = ctk.get_appearance_mode() if self.cfg['theme'] != actual: if actual == "Dark": - new_theme = "Light" + new_theme: str = "Light" else: - new_theme = "Dark" + new_theme: str = "Dark" ctk.set_appearance_mode(new_theme) - def switch_theme_command(self): - actual = ctk.get_appearance_mode() + def switch_theme_command(self) -> None: + """Switch the application theme to the opposite of the current theme.""" + actual: str = ctk.get_appearance_mode() if actual == "Dark": - new_theme = "Light" + new_theme: str = "Light" else: - new_theme = "Dark" + new_theme: str = "Dark" ctk.set_appearance_mode(new_theme) utils.save_cfg_json("theme", new_theme) - def check_processes(self): - blocknet_bin = global_variables.blocknet_bin - blockdx_bin = global_variables.blockdx_bin[-1] if global_variables.system == "Darwin" \ + def check_processes(self) -> None: + """Check for running processes related to Blocknet, BlockDX, and Xlite.""" + blocknet_bin: str = global_variables.blocknet_bin + blockdx_bin: str = global_variables.blockdx_bin[-1] if global_variables.system == "Darwin" \ else global_variables.blockdx_bin - xlite_bin = global_variables.xlite_bin[-1] if global_variables.system == "Darwin" \ + xlite_bin: str = global_variables.xlite_bin[-1] if global_variables.system == "Darwin" \ else global_variables.xlite_bin - xlite_daemon_bin = global_variables.xlite_daemon_bin - blocknet_processes = [] - blockdx_processes = [] - xlite_processes = [] - xlite_daemon_processes = [] + xlite_daemon_bin: str = global_variables.xlite_daemon_bin + + blocknet_processes: list = [] + blockdx_processes: list = [] + xlite_processes: list = [] + xlite_daemon_processes: list = [] try: # Get all processes @@ -332,10 +318,11 @@ def check_processes(self): self.xlite_manager.daemon_process_running = bool(xlite_daemon_processes) self.xlite_manager.utility.xlite_daemon_pids = xlite_daemon_processes - self.after(2000, self.check_processes) + self.after(2000, func=self.check_processes) -def run_gui(): +def run_gui() -> None: + """Run the Blocknet AIO GUI application.""" app = Blocknet_AIO_GUI() # try: app.init_setup() diff --git a/gui/binary_frame_manager.py b/gui/binary_frame_manager.py index c3fcc20..b38d7e6 100644 --- a/gui/binary_frame_manager.py +++ b/gui/binary_frame_manager.py @@ -7,23 +7,21 @@ class BinaryFrameManager: - def __init__(self, parent, master_frame, title_frame): + def __init__(self, parent): self.root_gui = parent.root_gui self.parent = parent - self.master_frame = master_frame - self.title_frame = title_frame - self.xbridge_bot_manager = XBridgeBotManager() + self.master_frame = ctk.CTkFrame(master=self.root_gui) + self.title_frame = ctk.CTkFrame(self.master_frame) + self.xbridge_bot_manager = XBridgeBotManager() self.header_label = ctk.CTkLabel(self.title_frame, text="Binaries Control panel:", anchor=HEADER_FRAMES_STICKY, width=BINS_FRAME_WIDTH) self.title_frame.columnconfigure(1, weight=1) - self.found_label = ctk.CTkLabel(self.title_frame, text="Found:", anchor='s') - self.button_switch_theme = ctk.CTkButton(self.title_frame, image=self.root_gui.theme_img, command=self.root_gui.switch_theme_command, @@ -167,6 +165,14 @@ def toggle_bots_execution_command(self): utilities.utils.disable_button(self.install_delete_bots_button, self.root_gui.install_greyed_img) utilities.utils.disable_button(self.bots_toggle_execution_button, self.root_gui.start_greyed_img) self.xbridge_bot_manager.toggle_execution(branch) + if not self.xbridge_bot_manager.repo_management.venv: + self.run_after_setup() + + def run_after_setup(self): + if self.xbridge_bot_manager.repo_management.venv: + self.xbridge_bot_manager.toggle_execution() + else: + self.root_gui.after(1000, self.run_after_setup) def grid_widgets(self, x, y): # bin diff --git a/gui/binary_manager.py b/gui/binary_manager.py index dd36d94..b10e5c3 100644 --- a/gui/binary_manager.py +++ b/gui/binary_manager.py @@ -9,10 +9,8 @@ class BinaryManager: - def __init__(self, root_gui, master_frame, title_frame): + def __init__(self, root_gui): self.root_gui = root_gui - self.title_frame = title_frame - self.master_frame = master_frame self.frame_manager = None self.disable_start_blocknet_button = False @@ -26,7 +24,7 @@ def __init__(self, root_gui, master_frame, title_frame): self.tooltip_manager = self.root_gui.tooltip_manager async def setup(self): - self.frame_manager = BinaryFrameManager(self, self.master_frame, self.title_frame) + self.frame_manager = BinaryFrameManager(self) self.root_gui.after(0, self.bins_check_aio_folder) self.root_gui.after(0, self.update_blocknet_buttons) diff --git a/gui/blockdx_frame_manager.py b/gui/blockdx_frame_manager.py index 5bbad0a..11618cd 100644 --- a/gui/blockdx_frame_manager.py +++ b/gui/blockdx_frame_manager.py @@ -10,20 +10,18 @@ class BlockDxFrameManager: - def __init__(self, parent, master_frame, title_frame): + def __init__(self, parent): self.root_gui = parent.root_gui self.parent = parent - self.master_frame = master_frame - self.title_frame = title_frame - # Label for Block-dx frame + self.master_frame = ctk.CTkFrame(master=self.root_gui) + self.title_frame = ctk.CTkFrame(self.master_frame) + self.label = ctk.CTkLabel(self.title_frame, text=widgets_strings.blockdx_frame_title_string, anchor=HEADER_FRAMES_STICKY, width=BLOCKDX_FRAME_WIDTH) - # Checkboxes - # width_mod = 35 self.process_status_checkbox_state = ctk.BooleanVar() self.process_status_checkbox_string_var = ctk.StringVar(value='') self.process_status_checkbox = ctkCheckBoxMod.CTkCheckBox(self.master_frame, diff --git a/gui/blockdx_manager.py b/gui/blockdx_manager.py index 8a30405..2f4483d 100644 --- a/gui/blockdx_manager.py +++ b/gui/blockdx_manager.py @@ -7,18 +7,16 @@ class BlockDXManager: - def __init__(self, root_gui, master_frame, title_frame): + def __init__(self, root_gui): self.frame_manager = None self.root_gui = root_gui - self.title_frame = title_frame - self.master_frame = master_frame self.utility = BlockdxUtility() self.version = [global_variables.blockdx_release_url.split('/')[7]] self.process_running = False self.is_config_sync = None async def setup(self): - self.frame_manager = BlockDxFrameManager(self, self.master_frame, self.title_frame) + self.frame_manager = BlockDxFrameManager(self) self.root_gui.after(0, self.update_status_blockdx) def blockdx_check_config(self): diff --git a/gui/blocknet_frame_manager.py b/gui/blocknet_frame_manager.py index 4f2616c..7549109 100644 --- a/gui/blocknet_frame_manager.py +++ b/gui/blocknet_frame_manager.py @@ -11,18 +11,16 @@ class BlocknetCoreFrameManager: - def __init__(self, parent, master_frame, title_frame): + def __init__(self, parent): self.root_gui = parent.root_gui self.parent = parent - self.master_frame = master_frame - self.title_frame = title_frame + self.master_frame = ctk.CTkFrame(master=self.root_gui) + self.title_frame = ctk.CTkFrame(self.master_frame) - # Create all Blocknet Core widgets here self.label = ctk.CTkLabel(self.title_frame, text=widgets_strings.blocknet_frame_title_string, anchor=HEADER_FRAMES_STICKY) # , width=FRAME_WIDTH) - # Label for Data Path self.data_path_label = ctk.CTkLabel(self.title_frame, text="Data Path: ") self.data_path_entry_string_var = ctk.StringVar(value=self.parent.utility.data_folder) diff --git a/gui/blocknet_manager.py b/gui/blocknet_manager.py index c4642c9..a4e12f5 100644 --- a/gui/blocknet_manager.py +++ b/gui/blocknet_manager.py @@ -4,11 +4,9 @@ class BlocknetManager: - def __init__(self, root_gui, master_frame, title_frame): + def __init__(self, root_gui): self.frame_manager = None self.root_gui = root_gui - self.title_frame = title_frame - self.master_frame = master_frame self.version = [global_variables.blocknet_release_url.split('/')[7]] self.blocknet_process_running = False @@ -17,7 +15,7 @@ def __init__(self, root_gui, master_frame, title_frame): self.utility = BlocknetUtility(custom_path=self.root_gui.custom_path) async def setup(self): - self.frame_manager = BlocknetCoreFrameManager(self, self.master_frame, self.title_frame) + self.frame_manager = BlocknetCoreFrameManager(self) self.root_gui.after(0, self.update_status_blocknet_core) diff --git a/gui/xbridge_bot_manager.py b/gui/xbridge_bot_manager.py index fa460f9..8bb641d 100644 --- a/gui/xbridge_bot_manager.py +++ b/gui/xbridge_bot_manager.py @@ -27,9 +27,10 @@ def get_available_branches(self) -> list: """Return list of available branches from remote repo""" try: if not self.repo_management: - self.repo_management = GitRepoManagement(self.repo_url, self.target_dir, branch=self.current_branch, - workdir=aio_folder) - return self.repo_management.get_remote_branches() + logging.error(f"GitRepoManagement not initialized ?") + return ["main"] + else: + return self.repo_management.get_remote_branches() except Exception as e: logging.error(f"Error fetching branches: {e}") return ["main"] @@ -101,23 +102,23 @@ def toggle_execution(self, branch=None) -> None: if not self.repo_exists() or not self.repo_management.venv or branch != self.current_branch: self.install_or_update(branch) - if not self.repo_management.venv: - logging.error("failed to set repo") - return - - if not self.started: - self._start_execution() - self.started = True + if self.repo_management.venv: + if not self.process or self.process and self.process.poll() is not None: + self._start_execution() + self.started = True + else: + self._stop_execution() + self.started = False else: - self._stop_execution() - self.started = False + logging.info("Venv setup in progress") def _start_execution(self) -> None: """Start script execution in background""" while self.thread and self.thread.is_alive(): # wait for update tread to close completely. + logging.info("waiting thread end") time.sleep(0.5) - + # self.thread = threading.Thread(target=self._run_script) self.thread.start() diff --git a/gui/xlite_frame_manager.py b/gui/xlite_frame_manager.py index 59e5a58..ac1f96c 100644 --- a/gui/xlite_frame_manager.py +++ b/gui/xlite_frame_manager.py @@ -12,10 +12,12 @@ class XliteFrameManager: - def __init__(self, parent, master_frame, title_frame): + def __init__(self, parent): + self.root_gui = parent.root_gui self.parent = parent - self.master_frame = master_frame - self.title_frame = title_frame + self.master_frame = ctk.CTkFrame(master=self.root_gui) + self.title_frame = ctk.CTkFrame(self.master_frame) + self.xlite_label = ctk.CTkLabel(self.title_frame, text=widgets_strings.xlite_frame_title_string, anchor=HEADER_FRAMES_STICKY, diff --git a/gui/xlite_manager.py b/gui/xlite_manager.py index 6def602..ab7c809 100644 --- a/gui/xlite_manager.py +++ b/gui/xlite_manager.py @@ -6,10 +6,8 @@ class XliteManager: - def __init__(self, root_gui, master_frame, title_frame): + def __init__(self, root_gui): self.root_gui = root_gui - self.title_frame = title_frame - self.master_frame = master_frame self.frame_manager = None self.utility = XliteUtility() @@ -20,7 +18,7 @@ def __init__(self, root_gui, master_frame, title_frame): self.stored_password = None async def setup(self): - self.frame_manager = XliteFrameManager(self, self.master_frame, self.title_frame) + self.frame_manager = XliteFrameManager(self) self.root_gui.after(0, self.update_status_xlite) def refresh_xlite_confs(self): From 2e89146d338080e22983170342e0dc9c4974914d Mon Sep 17 00:00:00 2001 From: tryiou Date: Thu, 22 May 2025 10:32:20 +0200 Subject: [PATCH 02/15] refactor: add type hints to manager assignments in Blocknet_AIO_GUI --- blocknet_aio_monitor.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/blocknet_aio_monitor.py b/blocknet_aio_monitor.py index e0e09e7..e4f595a 100644 --- a/blocknet_aio_monitor.py +++ b/blocknet_aio_monitor.py @@ -62,10 +62,10 @@ def __init__(self): self.tooltip_manager: TooltipManager = TooltipManager(self) - self.blocknet_manager = BlocknetManager(self) - self.binary_manager = BinaryManager(self) - self.blockdx_manager = BlockDXManager(self) - self.xlite_manager = XliteManager(self) + self.blocknet_manager: BlocknetManager = BlocknetManager(self) + self.binary_manager: BinaryManager = BinaryManager(self) + self.blockdx_manager: BlockDXManager = BlockDXManager(self) + self.xlite_manager: XliteManager = XliteManager(self) async def setup_management_sections(self) -> None: """Initialize and setup all management sections asynchronously.""" @@ -76,9 +76,6 @@ async def setup_management_sections(self) -> None: self.xlite_manager.setup() ) - def create_managers(self) -> None: - """Create instances of manager classes for different components.""" - def init_setup(self) -> None: """Initialize the GUI setup, including layout, images, and frame configuration.""" self.title(widgets_strings.app_title_string) From 8419d2440336e19ce9c45b3513ded2a04453ac66 Mon Sep 17 00:00:00 2001 From: tryiou Date: Thu, 22 May 2025 17:17:25 +0200 Subject: [PATCH 03/15] feat: Implement file system monitoring and rate limiting for binary updates --- blocknet_aio_monitor.py | 35 +-------- gui/binary_manager.py | 144 ++++++++++++++++++------------------- gui/xlite_frame_manager.py | 6 +- gui/xlite_manager.py | 1 - requirements.txt | 3 +- utilities/utils.py | 46 ++++++++++++ 6 files changed, 122 insertions(+), 113 deletions(-) diff --git a/blocknet_aio_monitor.py b/blocknet_aio_monitor.py index e4f595a..677a810 100644 --- a/blocknet_aio_monitor.py +++ b/blocknet_aio_monitor.py @@ -5,7 +5,6 @@ import customtkinter as ctk from PIL import Image -from psutil import process_iter import widgets_strings from gui.binary_manager import BinaryManager @@ -268,37 +267,7 @@ def switch_theme_command(self) -> None: utils.save_cfg_json("theme", new_theme) def check_processes(self) -> None: - """Check for running processes related to Blocknet, BlockDX, and Xlite.""" - blocknet_bin: str = global_variables.blocknet_bin - blockdx_bin: str = global_variables.blockdx_bin[-1] if global_variables.system == "Darwin" \ - else global_variables.blockdx_bin - xlite_bin: str = global_variables.xlite_bin[-1] if global_variables.system == "Darwin" \ - else global_variables.xlite_bin - xlite_daemon_bin: str = global_variables.xlite_daemon_bin - - blocknet_processes: list = [] - blockdx_processes: list = [] - xlite_processes: list = [] - xlite_daemon_processes: list = [] - - try: - # Get all processes - for proc in process_iter(['pid', 'name']): - # Check if any process matches the Blocknet process name - if blocknet_bin == proc.info['name']: - blocknet_processes.append(proc.info['pid']) - # Check if any process matches the Block DX process name - if blockdx_bin == proc.info['name']: - blockdx_processes.append(proc.info['pid']) - # Check if any process matches the Xlite process name - if xlite_bin == proc.info['name']: - xlite_processes.append(proc.info['pid']) - # Check if any process matches the Xlite-daemon process name - if xlite_daemon_bin == proc.info['name']: - xlite_daemon_processes.append(proc.info['pid']) - except Exception as e: - logging.warning(f"Error while checking processes: {e}") - + blocknet_processes, blockdx_processes, xlite_processes, xlite_daemon_processes = utils.processes_check() # Update Blocknet process status and store the PIDs self.blocknet_manager.blocknet_process_running = bool(blocknet_processes) self.blocknet_manager.utility.blocknet_pids = blocknet_processes @@ -315,7 +284,7 @@ def check_processes(self) -> None: self.xlite_manager.daemon_process_running = bool(xlite_daemon_processes) self.xlite_manager.utility.xlite_daemon_pids = xlite_daemon_processes - self.after(2000, func=self.check_processes) + self.after(5000, func=self.check_processes) def run_gui() -> None: diff --git a/gui/binary_manager.py b/gui/binary_manager.py index b10e5c3..b832959 100644 --- a/gui/binary_manager.py +++ b/gui/binary_manager.py @@ -1,13 +1,68 @@ import logging import os import shutil +import time from threading import Thread +from watchdog.events import FileSystemEvent, FileSystemEventHandler +from watchdog.observers import Observer + import widgets_strings from gui.binary_frame_manager import BinaryFrameManager from utilities import utils, global_variables +class BinaryFileHandler(FileSystemEventHandler): + """ + Handles file modification events with rate limiting for binary updates. + """ + + def __init__(self, binary_manager: 'BinaryManager'): + """ + Initializes the handler. + :param binary_manager: The manager responsible for binary updates. + """ + super().__init__() + self.binary_manager: 'BinaryManager' = binary_manager + self.max_delay: float = 2 # seconds + self.last_run: float = 0 + self.scheduled: bool = False + + def on_modified(self, event: 'FileSystemEvent') -> None: + """ + Called when a file is modified. Executes binary check/update with rate limiting. + """ + # logging.info("File modified detected: %s", event.src_path) + + if self.scheduled: + # logging.debug("Update already scheduled, skipping immediate execution.") + return + + time_since_last = time.time() - self.last_run + # logging.debug("Time since last run: %.2f seconds", time_since_last) + + if time_since_last >= self.max_delay: + # Execute immediately + # logging.info("Executing check_and_update_aio_folder immediately.") + self.binary_manager.check_and_update_aio_folder() + self.last_run = time.time() + else: + # Schedule for later + delay_ms = int((self.max_delay - time_since_last) * 1000) + # logging.info("Scheduling check_and_update_aio_folder in %d ms.", delay_ms) + self.scheduled = True + self.binary_manager.root_gui.after(delay_ms, self._execute_scheduled) + + def _execute_scheduled(self) -> None: + """ + Executes the scheduled update and resets the schedule flag. + """ + # logging.info("Executing scheduled check_and_update_aio_folder.") + self.binary_manager.check_and_update_aio_folder() + self.last_run = time.time() + self.scheduled = False + + class BinaryManager: def __init__(self, root_gui): self.root_gui = root_gui @@ -21,12 +76,17 @@ def __init__(self, root_gui): self.download_blockdx_thread = None self.download_xlite_thread = None + self.observer = Observer() + self.handler = BinaryFileHandler(self) + self.observer.schedule(self.handler, global_variables.aio_folder, recursive=False) + self.observer.start() + self.tooltip_manager = self.root_gui.tooltip_manager async def setup(self): self.frame_manager = BinaryFrameManager(self) - self.root_gui.after(0, self.bins_check_aio_folder) + self.root_gui.after(0, self.check_and_update_aio_folder) self.root_gui.after(0, self.update_blocknet_buttons) self.root_gui.after(0, self.update_blockdx_buttons) self.root_gui.after(0, self.update_xlite_buttons) @@ -69,11 +129,11 @@ def start_or_close_blockdx(self): ) def start_or_close_xlite(self): - if not self.root_gui.xlite_manager.process_running: - if self.root_gui.stored_password: - env_vars = [{"CC_WALLET_PASS": self.root_gui.stored_password}, {"CC_WALLET_AUTOLOGIN": 'true'}] - else: - env_vars = [] + if not self.root_gui.xlite_manager.process_running and self.root_gui.stored_password: + env_vars = [{"CC_WALLET_PASS": self.root_gui.stored_password}, {"CC_WALLET_AUTOLOGIN": 'true'}] + else: + env_vars = [] + self._start_or_close_binary( process_running=self.root_gui.xlite_manager.process_running, stop_func=self.root_gui.xlite_manager.utility.close_xlite, @@ -165,8 +225,8 @@ def delete_xlite_command(self): logging.info(f"deleting {item_path}") shutil.rmtree(item_path) - def bins_check_aio_folder(self): - # logging.info("bins_check_aio_folder") + def check_and_update_aio_folder(self): + logging.info("check_and_update_aio_folder") # Get system information and versions is_darwin = global_variables.system == "Darwin" @@ -215,16 +275,13 @@ def bins_check_aio_folder(self): # logging.info(app_info) app_info["boolvar"].set(app_info["found"]) - # Schedule next check - self.root_gui.after(5000, self.bins_check_aio_folder) - def _prune_version(self, version): """Remove 'v' prefix from version string.""" return version[0].replace('v', '') def _log_incorrect_target(self, target): """Log incorrect version found.""" - # logging.info(f"incorrect version: {target}") + logging.info(f"incorrect version: {target}") # shutil.rmtree(target) if os.path.isdir(target) else os.remove(target) return @@ -243,69 +300,6 @@ def _check_app_version(self, app_info, item, full_path): else: self._log_incorrect_target(full_path) - def bins_check_aio_folder_original(self): - blocknet_pruned_version = self.root_gui.blocknet_manager.version[0].replace('v', '') - blockdx_pruned_version = self.root_gui.blockdx_manager.version[0].replace('v', '') - xlite_pruned_version = self.root_gui.xlite_manager.version[0].replace('v', '') - - blocknet_present = False - blockdx_present = False - xlite_present = False - - for item in os.listdir(global_variables.aio_folder): - if global_variables.system == "Darwin": - blockdx_filename = os.path.basename(global_variables.blockdx_release_url) - xlite_filename = os.path.basename(global_variables.xlite_release_url) - item_path = os.path.join(global_variables.aio_folder, item) - if os.path.isdir(item_path): - if 'blocknet-' in item: - if blocknet_pruned_version in item: - blocknet_present = True - else: - logging.info(f"deleting outdated version: {item_path}") - shutil.rmtree(item_path) - elif os.path.isfile(item_path): - if 'BLOCK-DX-' in item: - if blockdx_filename in item: - blockdx_present = True - else: - logging.info(f"deleting outdated version: {item_path}") - os.remove(item_path) - elif 'XLite-' in item: - if xlite_filename in item: - xlite_present = True - else: - logging.info(f"deleting outdated version: {item_path}") - os.remove(item_path) - else: - item_path = os.path.join(global_variables.aio_folder, item) - if os.path.isdir(item_path): - # if a wrong version is found, delete it. - if 'blocknet-' in item: - if blocknet_pruned_version in item: - blocknet_present = True - else: - logging.info(f"deleting outdated version: {item_path}") - shutil.rmtree(item_path) - elif 'BLOCK-DX-' in item: - if blockdx_pruned_version in item: - blockdx_present = True - else: - logging.info(f"deleting outdated version: {item_path}") - shutil.rmtree(item_path) - elif 'XLite-' in item: - if xlite_pruned_version in item: - xlite_present = True - else: - logging.info(f"deleting outdated version: {item_path}") - shutil.rmtree(item_path) - - self.root_gui.binary_manager.frame_manager.blocknet_installed_boolvar.set(blocknet_present) - self.root_gui.binary_manager.frame_manager.blockdx_installed_boolvar.set(blockdx_present) - self.root_gui.binary_manager.frame_manager.xlite_installed_boolvar.set(xlite_present) - - self.root_gui.after(2000, self.bins_check_aio_folder_original) - def update_blocknet_buttons(self): # BLOCKNET self.update_blocknet_start_close_button() diff --git a/gui/xlite_frame_manager.py b/gui/xlite_frame_manager.py index ac1f96c..92753b8 100644 --- a/gui/xlite_frame_manager.py +++ b/gui/xlite_frame_manager.py @@ -94,7 +94,7 @@ def xlite_store_password_button_mouse_click(self, event=None): # Prevent the right-click event from propagating further utils.remove_cfg_json_key("salt") utils.remove_cfg_json_key("xl_pass") - self.parent.stored_password = None + self.root_gui.stored_password = None # Delete CC_WALLET_PASS variable if "CC_WALLET_PASS" in os.environ: os.environ.pop("CC_WALLET_PASS") @@ -120,7 +120,7 @@ def xlite_store_password_button_mouse_click(self, event=None): utils.save_cfg_json(key="salt", data=encryption_key.decode()) utils.save_cfg_json(key="xl_pass", data=salted_pass) # Store the password in a variable - self.parent.stored_password = password + self.root_gui.stored_password = password else: logging.info("No password entered.") # Perform actions for left-click (if needed) @@ -145,7 +145,7 @@ def update_xlite_process_status_checkbox(self): def update_xlite_store_password_button(self): # xlite_store_password_button - var = widgets_strings.xlite_stored_password_string if self.parent.stored_password else widgets_strings.xlite_store_password_string + var = widgets_strings.xlite_stored_password_string if self.root_gui.stored_password else widgets_strings.xlite_store_password_string self.store_password_button_string_var.set(var) def update_xlite_daemon_process_status(self): diff --git a/gui/xlite_manager.py b/gui/xlite_manager.py index ab7c809..9e66228 100644 --- a/gui/xlite_manager.py +++ b/gui/xlite_manager.py @@ -15,7 +15,6 @@ def __init__(self, root_gui): self.version = [global_variables.xlite_release_url.split('/')[7]] self.process_running = False self.daemon_process_running = False - self.stored_password = None async def setup(self): self.frame_manager = XliteFrameManager(self) diff --git a/requirements.txt b/requirements.txt index d490863..aca68b1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,4 +4,5 @@ cryptography~=44.0.1 customtkinter~=5.2.2 CTkToolTip~=0.8 pillow -pygit2==1.18.0 \ No newline at end of file +pygit2==1.18.0 +watchdog \ No newline at end of file diff --git a/utilities/utils.py b/utilities/utils.py index 10bc2af..1cefb9b 100644 --- a/utilities/utils.py +++ b/utilities/utils.py @@ -4,6 +4,7 @@ from threading import enumerate, current_thread import customtkinter as ctk +import psutil from cryptography.fernet import Fernet from utilities import global_variables @@ -116,3 +117,48 @@ def disable_button(button, img=None): button.configure(state=ctk.DISABLED) if img: button.configure(image=img) + + +def processes_check(): + """Check for running processes related to Blocknet, BlockDX, and Xlite.""" + + # Initialize process lists + process_lists: dict = { + global_variables.blocknet_bin: [], + global_variables.blockdx_bin: [], + global_variables.xlite_bin: [], + global_variables.xlite_daemon_bin: [] + } + + # Process all running processes + for proc in psutil.process_iter(['pid', 'name', 'status']): + pid = proc.info['pid'] + name = proc.info['name'] + status = proc.info['status'] + + # Check against each target process type + for target_name, process_list in process_lists.items(): + result_pid = handle_process(pid, name, status, target_name) + if result_pid is not None: + process_list.append(result_pid) + break # Process matched, no need to check other types + + return ( + process_lists[global_variables.blocknet_bin], + process_lists[global_variables.blockdx_bin], + process_lists[global_variables.xlite_bin], + process_lists[global_variables.xlite_daemon_bin] + ) + + +def handle_process(pid, name, status, target_name): + """Helper function to handle individual process logic.""" + if name == target_name: + if status == "zombie": + # the app was closed by user manually, clean zombie process + process = psutil.Process(pid) + process.wait() + return None # Don't add zombie processes to the list + else: + return pid + return None From 71d054f4032cef63cb896c070d2a959ad53d6871 Mon Sep 17 00:00:00 2001 From: tryiou <73646876+tryiou@users.noreply.github.com> Date: Fri, 23 May 2025 10:39:27 +0200 Subject: [PATCH 04/15] build.yml ci: update and upgrade homebrew packages in build workflow --- .github/workflows/build.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 352b50c..1270de6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -97,6 +97,8 @@ jobs: - name: Set up Python using Homebrew run: | + brew update + brew upgrade brew install python@3.10 brew install python-tk@3.10 python3.10 -m pip install --upgrade pip From a96002adb74c1c18daed7c8414a5331dc1382cdd Mon Sep 17 00:00:00 2001 From: tryiou <73646876+tryiou@users.noreply.github.com> Date: Fri, 23 May 2025 11:05:27 +0200 Subject: [PATCH 05/15] build.yml try to fix macos build error, use latest python3.13 in place of 3.10 --- .github/workflows/build.yml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1270de6..0d1ac2f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -99,13 +99,16 @@ jobs: run: | brew update brew upgrade - brew install python@3.10 - brew install python-tk@3.10 - python3.10 -m pip install --upgrade pip + brew install python + brew install python-tk + python3 -m pip install --upgrade pip + # brew install python@3.10 + # brew install python-tk@3.10 + # python3.10 -m pip install --upgrade pip - name: Install dependencies / Build executable on macOS run: | - python3.10 -m venv venv + python3 -m venv venv source venv/bin/activate pip install -r requirements.txt pip install pyinstaller From 291fba61c3f8bd43e56df733f8bae1f5fe80fb25 Mon Sep 17 00:00:00 2001 From: tryiou <73646876+tryiou@users.noreply.github.com> Date: Fri, 23 May 2025 11:17:33 +0200 Subject: [PATCH 06/15] Update build.yml try to fix macos build error --- .github/workflows/build.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0d1ac2f..e770155 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -98,7 +98,8 @@ jobs: - name: Set up Python using Homebrew run: | brew update - brew upgrade + brew upgrade || true + brew link --overwrite python@3.12 brew install python brew install python-tk python3 -m pip install --upgrade pip From da15e8d21e26edd9430507d4745f0699e644f1e3 Mon Sep 17 00:00:00 2001 From: tryiou Date: Thu, 22 May 2025 22:14:56 +0200 Subject: [PATCH 07/15] feat: refactor utilities to use helper_util for common tasks --- gui/binary_manager.py | 4 +- utilities/blockdx_util.py | 108 +++---------------- utilities/blocknet_util.py | 214 +++++++------------------------------ utilities/helper_util.py | 81 ++++++++++++++ utilities/xlite_util.py | 178 ++++-------------------------- 5 files changed, 156 insertions(+), 429 deletions(-) create mode 100644 utilities/helper_util.py diff --git a/gui/binary_manager.py b/gui/binary_manager.py index b832959..a080acb 100644 --- a/gui/binary_manager.py +++ b/gui/binary_manager.py @@ -24,7 +24,7 @@ def __init__(self, binary_manager: 'BinaryManager'): """ super().__init__() self.binary_manager: 'BinaryManager' = binary_manager - self.max_delay: float = 2 # seconds + self.max_delay: float = 5 # seconds self.last_run: float = 0 self.scheduled: bool = False @@ -226,7 +226,7 @@ def delete_xlite_command(self): shutil.rmtree(item_path) def check_and_update_aio_folder(self): - logging.info("check_and_update_aio_folder") + # logging.info("check_and_update_aio_folder") # Get system information and versions is_darwin = global_variables.system == "Darwin" diff --git a/utilities/blockdx_util.py b/utilities/blockdx_util.py index 98a747b..ca4b3b3 100644 --- a/utilities/blockdx_util.py +++ b/utilities/blockdx_util.py @@ -3,20 +3,17 @@ import logging import os import subprocess -import tarfile import time -import zipfile - -import psutil -import requests from utilities import global_variables +from utilities.helper_util import UtilityHelper logging.basicConfig(level=logging.DEBUG) class BlockdxUtility: def __init__(self): + self.helper = UtilityHelper() if global_variables.system == "Darwin": self.dmg_mount_path = f"/Volumes/{global_variables.blockdx_volume_name}" self.blockdx_exe = os.path.join(global_variables.aio_folder, os.path.basename(global_variables.blockdx_url)) @@ -98,14 +95,7 @@ def compare_and_update_local_conf(self, xbridgeconfpath, rpc_user, rpc_password) logging.info("No changes detected in Blockdx config.") def unmount_dmg(self): - if os.path.ismount(self.dmg_mount_path): - try: - subprocess.run(["hdiutil", "detach", self.dmg_mount_path], check=True) - logging.info("DMG unmounted successfully.") - except subprocess.CalledProcessError as e: - logging.error(f"Error: Failed to unmount DMG: {e}") - else: - logging.error("Error: DMG is not mounted.") + self.helper.handle_dmg(None, self.dmg_mount_path, "unmount") def start_blockdx(self): if not os.path.exists(self.blockdx_exe): @@ -118,13 +108,7 @@ def start_blockdx(self): # Start the BLOCK-DX process using subprocess if global_variables.system == "Darwin": # mac mod - - # Check if the volume is already mounted - if not os.path.ismount(self.dmg_mount_path): - # Mount the DMG file - os.system(f'hdiutil attach "{self.blockdx_exe}"') - else: - logging.info("Volume is already mounted.") + self.helper.handle_dmg(self.blockdx_exe, self.dmg_mount_path, "mount") full_path = os.path.join(self.dmg_mount_path, *global_variables.conf_data.blockdx_bin_name[global_variables.system]) logging.info( @@ -182,25 +166,7 @@ def kill_blockdx(self): logging.error(f"Error: {e}") def close_blockdx_pids(self): - # Close the blockdx processes using their PIDs - for pid in self.blockdx_pids: - try: - # Get the process object corresponding to the PID - proc = psutil.Process(pid) - proc.terminate() - logging.info(f"Initiated termination of blockdx process with PID {pid}.") - proc.wait(timeout=60) # Wait for the process to terminate with a timeout of 60 seconds - logging.info(f"blockdx process with PID {pid} has been terminated.") - except psutil.NoSuchProcess: - logging.warning(f"blockdx process with PID {pid} not found.") - except psutil.TimeoutExpired: - logging.warning(f"Force terminating blockdx process with PID {pid}.") - if proc: - proc.kill() - proc.wait() - logging.info(f"blockdx process with PID {pid} has been force terminated.") - except Exception as e: - logging.error(f"Error: {e}") + self.helper.terminate_processes(self.blockdx_pids, "BlockDX") def download_blockdx_bin(self): self.downloading_bin = True @@ -209,56 +175,14 @@ def download_blockdx_bin(self): if url is None: raise ValueError(f"Unsupported OS or architecture {global_variables.system} {global_variables.machine}") - # Set timeout values in seconds - connection_timeout = 10 - read_timeout = 30 - response = requests.get(url, stream=True, timeout=(connection_timeout, read_timeout)) - response.raise_for_status() # Raise an exception for 4xx and 5xx status codes - if response.status_code == 200: - file_name = os.path.basename(url) - tmp_file_path = os.path.join(global_variables.aio_folder, "tmp_dx_bin") - try: - remote_file_size = int(response.headers.get('Content-Length', 0)) - logging.info(f"Downloading {url} to {tmp_file_path}, remote size: {int(remote_file_size / 1024)} kb") - bytes_downloaded = 0 - total = remote_file_size - with open(tmp_file_path, "wb") as f: - for chunk in response.iter_content(chunk_size=8192): # Iterate over response content in chunks - if chunk: # Filter out keep-alive new chunks - f.write(chunk) - bytes_downloaded += len(chunk) - self.binary_percent_download = (bytes_downloaded / total) * 100 - except requests.exceptions.RequestException as e: - logging.error(f"Error occurred during download: {str(e)}") - - self.binary_percent_download = None - - if os.path.getsize(tmp_file_path) != remote_file_size: - os.remove(tmp_file_path) - raise ValueError( - f"Downloaded {os.path.basename(url)} size doesn't match the expected size. Deleting it") + tmp_path = os.path.join(global_variables.aio_folder, "tmp_dx_bin") + final_path = self.blockdx_exe # For DMG + extract_to = global_variables.aio_folder # For zip/tar.gz - logging.info(f"{os.path.basename(url)} downloaded successfully.") - - # Extract the archive - if url.endswith(".zip"): - with zipfile.ZipFile(tmp_file_path, "r") as zip_ref: - local_path = os.path.join(global_variables.aio_folder, - global_variables.conf_data.blockdx_bin_path[global_variables.system]) - zip_ref.extractall(local_path) - logging.info("Zip file extracted successfully.") - os.remove(tmp_file_path) - elif url.endswith(".tar.gz"): - with tarfile.open(tmp_file_path, "r:gz") as tar: - tar.extractall(global_variables.aio_folder) - logging.info("Tar.gz file extracted successfully.") - os.remove(tmp_file_path) - elif url.endswith(".dmg"): - file_path = os.path.join(global_variables.aio_folder, file_name) - os.rename(tmp_file_path, file_path) - logging.info("DMG file saved successfully.") - else: - logging.error("Failed to download the Blockdx binary.") + self.helper.download_file( + url, tmp_path, final_path, extract_to, + global_variables.system, "binary_percent_download", self + ) self.downloading_bin = False @@ -268,11 +192,3 @@ def get_blockdx_data_folder(): return os.path.expandvars(os.path.expanduser(path)) else: raise ValueError("Unsupported system") - -# async def main(): -# blockdx_utility = BlockdxUtility() -# # blockdx_utility.compare_and_update_local_conf() -# -# -# if __name__ == "__main__": -# asyncio.run(main()) diff --git a/utilities/blocknet_util.py b/utilities/blocknet_util.py index 968ee34..9e314f7 100644 --- a/utilities/blocknet_util.py +++ b/utilities/blocknet_util.py @@ -5,21 +5,15 @@ import shutil import string import subprocess -import tarfile import threading import time import zipfile -from subprocess import check_output -import psutil import requests -# from utilities.conf_data import (remote_blocknet_conf_url, blocknet_default_paths, base_xbridge_conf, blocknet_bin_path, -# blocknet_bootstrap_url, nodes_to_add, remote_xbridge_conf_url, remote_manifest_url, -# remote_blockchain_configuration_repo) from utilities import global_variables +from utilities.helper_util import UtilityHelper -# Configure logging logging.basicConfig(level=logging.DEBUG) # Disable log entries from the urllib3 module (used by requests) @@ -44,22 +38,16 @@ def send_rpc_request(self, method=None, params=None): "id": 1, } try: - # logging.debug( - # f"Sending RPC request to URL: {url}, Method: {data['method']}, Params: {data['params']}, Auth: {auth}") response = requests.post(url, json=data, headers=headers, auth=auth) - # Check status code explicitly if response.status_code != 200: - # logging.error(f"Error sending RPC request: HTTP status code {response.status_code}") return None json_answer = response.json() - # logging.debug(f"RPC request successful. Response: {json}") if 'result' in json_answer: return json_answer['result'] else: logging.error(f"No result in json: {json_answer}") except requests.RequestException as e: - # logging.error(f"Error sending RPC request: {e}") return None except Exception as ex: logging.exception(f"An unexpected error occurred while sending RPC request: {ex}") @@ -68,9 +56,10 @@ def send_rpc_request(self, method=None, params=None): class BlocknetUtility: def __init__(self, custom_path=None): - self.blocknet_exe = global_variables.os.path.join(global_variables.aio_folder, - *global_variables.conf_data.blocknet_bin_path, - global_variables.blocknet_bin) + self.helper = UtilityHelper() + self.blocknet_exe = os.path.join(global_variables.aio_folder, + *global_variables.conf_data.blocknet_bin_path, + global_variables.blocknet_bin) self.binary_percent_download = None self.parsed_wallet_confs = {} self.parsed_xbridge_confs = {} @@ -108,26 +97,22 @@ def check_blocknet_rpc(self): valid = True self.valid_rpc = valid - # logging.info(result) time.sleep(2) def init_blocknet_rpc(self): - # Retrieve RPC user, password, and port from blocknet_conf_local with error handling if 'global' in self.blocknet_conf_local: global_conf = self.blocknet_conf_local['global'] rpc_user = global_conf.get('rpcuser') rpc_password = global_conf.get('rpcpassword') - rpc_port = int(global_conf.get('rpcport', 0)) # Assuming default port is 0, change as per requirement + rpc_port = int(global_conf.get('rpcport', 0)) else: rpc_user = None rpc_password = None rpc_port = 0 - # Initialize BlocknetRPCClient if RPC user, password, and port are available if rpc_user is not None and rpc_password is not None and rpc_port != 0: self.blocknet_rpc = BlocknetRPCClient(rpc_user, rpc_password, rpc_port) else: - # Handle the case when RPC user, password, or port is missing logging.error("RPC user, password, or port not found in the configuration.") self.blocknet_rpc = None @@ -137,7 +122,6 @@ def start_blocknet(self): logging.info(f"Blocknet executable not found at {self.blocknet_exe}. Downloading...") self.download_blocknet_bin() try: - # Start the Blocknet process using subprocess with custom data folder argument self.blocknet_process = subprocess.Popen([self.blocknet_exe, f"-datadir={self.data_folder}"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, @@ -148,12 +132,10 @@ def start_blocknet(self): logging.error(f"Error: {e}") def close_blocknet(self): - # Close the Blocknet subprocess if it exists if self.blocknet_process: try: self.blocknet_process.terminate() - # logging.info(f"Terminating Blocknet subprocess.") - self.blocknet_process.wait(timeout=60) # Wait for the process to terminate with a timeout of 60 seconds + self.blocknet_process.wait(timeout=60) logging.info(f"Closed Blocknet subprocess.") self.blocknet_process = None return @@ -169,7 +151,6 @@ def close_blocknet(self): self.close_blocknet_pids() def kill_blocknet(self): - # Kill the Blocknet subprocess if it exists if self.blocknet_process: try: self.blocknet_process.kill() @@ -180,31 +161,14 @@ def kill_blocknet(self): logging.error(f"Error: {e}") def close_blocknet_pids(self): - # Close the Blocknet processes using their PIDs - for pid in self.blocknet_pids: - try: - # Get the process object corresponding to the PID - proc = psutil.Process(pid) - proc.terminate() - logging.info(f"Initiated termination of Blocknet process with PID {pid}.") - proc.wait(timeout=60) # Wait for the process to terminate with a timeout of 60 seconds - logging.info(f"Blocknet process with PID {pid} has been terminated.") - except psutil.NoSuchProcess: - logging.warning(f"Blocknet process with PID {pid} not found.") - except psutil.TimeoutExpired: - logging.warning(f"Force terminating Blocknet process with PID {pid}.") - proc.kill() - proc.wait() - logging.info(f"Blocknet process with PID {pid} has been force terminated.") - except Exception as e: - logging.error(f"Error: {e}") + self.helper.terminate_processes(self.blocknet_pids, "Blocknet") def check_data_folder_existence(self): return os.path.exists(self.data_folder) def set_custom_data_path(self, custom_path): if not os.path.exists(custom_path): - os.makedirs(custom_path) # Recursively create the folder if it doesn't exist + os.makedirs(custom_path) logging.info(f"Custom data path created: {custom_path}") self.data_folder = custom_path logging.debug(f"Custom data path set: {custom_path}") @@ -241,9 +205,6 @@ def save_xbridge_conf(self): def check_blocknet_conf(self): self.parse_blocknet_conf() - # logging.info(f"Current remote configuration:\n{self.blocknet_conf_remote}") - # logging.info(f"Current local configuration:\n{self.blocknet_conf_local}") - old_local_json = json.dumps(self.blocknet_conf_local, sort_keys=True) if self.blocknet_conf_remote is None: @@ -274,7 +235,6 @@ def check_blocknet_conf(self): if not isinstance(addnode_value, list): addnode_value = [addnode_value] - # Add the nodes to the end of the file if not already existing for node in global_variables.conf_data.nodes_to_add: if node not in addnode_value: addnode_value.append(node) @@ -283,35 +243,24 @@ def check_blocknet_conf(self): self.blocknet_conf_local[section_name]['addnode'] = addnode_value for section, options in self.blocknet_conf_remote.items(): - # if section not in self.blocknet_conf_local: - # self.blocknet_conf_local[section] = {} - for key, value in options.items(): if key == 'rpcuser' or key == 'rpcpassword': if key not in self.blocknet_conf_local[section]: self.blocknet_conf_local[section][key] = generate_random_string(32) - # logging.info(f"Generated {key} value: {self.blocknet_conf_local[section][key]}") else: if self.blocknet_conf_local[section][key] == '': self.blocknet_conf_local[section][key] = generate_random_string(32) - # logging.info( - # f"Value for {key} is empty. Generated new value: {self.blocknet_conf_local[section][key]}") - # CHECK IF VALUE IS NOT EMPTY STRING ELSE GENERATE NEW VALUE else: if key == "rpcallowip": self.blocknet_conf_local[section][key] = "127.0.0.1" elif key not in self.blocknet_conf_local[section] or self.blocknet_conf_local[section][ key] != value: self.blocknet_conf_local[section][key] = value - # logging.debug(f"Updated {key} value: {value}") logging.info("Local blocknet.conf updated successfully.") new_local_json = json.dumps(self.blocknet_conf_local, sort_keys=True) - # logging.info(f"Old local configuration:\n{old_local_json}") - # logging.info(f"Updated local configuration:\n{new_local_json}") - # logging.info(new_local_json) if old_local_json != new_local_json: logging.info("Local blocknet.conf has been updated. Saving...") self.save_blocknet_conf() @@ -337,26 +286,18 @@ def retrieve_coin_conf(self, coin): xbridge_url = f"{global_variables.conf_data.remote_blockchain_configuration_repo}/xbridge-confs/{xbridge_conf}" wallet_conf = latest_version['wallet_conf'] wallet_conf_url = f"{global_variables.conf_data.remote_blockchain_configuration_repo}/wallet-confs/{wallet_conf}" - # download_remote_conf() parsed_xbridge_conf = retrieve_remote_conf(xbridge_url, "xbridge-confs", xbridge_conf) parsed_wallet_conf = retrieve_remote_conf(wallet_conf_url, "wallet-confs", wallet_conf) self.parsed_xbridge_confs[coin] = parsed_xbridge_conf self.parsed_wallet_confs[coin] = parsed_wallet_conf - # logging.info(parsed_xbridge_conf) - # logging.info(parsed_wallet_conf) - # Do whatever you need to do with the highest version entry else: logging.error("No entries found in the manifest. " + coin) def check_xbridge_conf(self, xlite_daemon_conf): self.parse_xbridge_conf() - # logging.info(f"Current local configuration:\n{self.xbridge_conf_local}") - # logging.info(f"Current remote configuration:\n{self.xbridge_conf_remote}") - old_local_json = json.dumps(self.xbridge_conf_local, sort_keys=True) if 'Main' not in self.xbridge_conf_local: - # We want this on 'top' of file, add it if missing self.xbridge_conf_local['Main'] = global_variables.conf_data.base_xbridge_conf if self.blocknet_xbridge_conf_remote is None: @@ -366,9 +307,7 @@ def check_xbridge_conf(self, xlite_daemon_conf): if self.xbridge_conf_local is None: logging.error("Local xbridge.conf not available.") return False - # section = 'global' if xlite_daemon_conf: - # XLITE SESSION DETECTED, USE XLITE RPC PARAMS for coin in xlite_daemon_conf: if coin == "master": continue @@ -376,7 +315,6 @@ def check_xbridge_conf(self, xlite_daemon_conf): if coin in self.parsed_xbridge_confs: if coin not in self.xbridge_conf_local: self.xbridge_conf_local[coin] = {} - # logging.warning(self.parsed_xbridge_confs[coin]) for section, options in self.parsed_xbridge_confs[coin].items(): for key, value in options.items(): if key == 'Username': @@ -388,12 +326,9 @@ def check_xbridge_conf(self, xlite_daemon_conf): else: if key not in self.xbridge_conf_local[section] or self.xbridge_conf_local[section][ key] != value: - # logging.warning(f"value: {value}") - # exit() self.xbridge_conf_local[section][key] = str(value) if not (xlite_daemon_conf and "BLOCK" in xlite_daemon_conf): - # NO XLITE SESSION DETECTED, SET XBRIDGE TO USE BLOCKNET CORE RPC for section, options in self.blocknet_xbridge_conf_remote.items(): if section not in self.xbridge_conf_local: self.xbridge_conf_local[section] = {} @@ -410,10 +345,8 @@ def check_xbridge_conf(self, xlite_daemon_conf): key] != value: self.xbridge_conf_local[section][key] = str(value) - # Prepare the string of sections (excluding 'Main') sections_string = ','.join(section for section in self.xbridge_conf_local.keys() if section != 'Main') - # Update the 'ExchangeWallets' value with the sections string if 'Main' in self.xbridge_conf_local: self.xbridge_conf_local['Main']['ExchangeWallets'] = sections_string else: @@ -452,10 +385,8 @@ def download_bootstrap(self): filename = "Blocknet.zip" local_file_path = os.path.join(global_variables.aio_folder, filename) remote_file_size = get_remote_file_size(global_variables.conf_data.blocknet_bootstrap_url) - # Check if the file already exists on disk need_to_download = True if os.path.exists(local_file_path): - # Compare the size of the local file with the remote file local_file_size = os.path.getsize(local_file_path) if local_file_size == remote_file_size: @@ -463,28 +394,22 @@ def download_bootstrap(self): need_to_download = False else: logging.info("Local bootstrap file exists but does not match the remote file. Re-downloading...") - os.remove(local_file_path) # Remove the local file and proceed with download + os.remove(local_file_path) try: if need_to_download: with open(local_file_path, 'wb') as f: - # Set timeout values in seconds - connection_timeout = 10 - read_timeout = 30 response = requests.get(global_variables.conf_data.blocknet_bootstrap_url, stream=True, - timeout=(connection_timeout, read_timeout)) + timeout=(10, 30)) response.raise_for_status() if response.status_code == 200: - try: - logging.info( - f"Downloading {global_variables.conf_data.blocknet_bootstrap_url} to {local_file_path}, remote size: {int(remote_file_size / 1024)} kb") - bytes_downloaded = 0 - for chunk in response.iter_content(chunk_size=8192): - if chunk: - f.write(chunk) - bytes_downloaded += len(chunk) - self.bootstrap_percent_download = (bytes_downloaded / remote_file_size) * 100 - except requests.exceptions.RequestException as e: - logging.error(f"Error occurred during download: {str(e)}") + logging.info( + f"Downloading {global_variables.conf_data.blocknet_bootstrap_url} to {local_file_path}, remote size: {int(remote_file_size / 1024)} kb") + bytes_downloaded = 0 + for chunk in response.iter_content(chunk_size=8192): + if chunk: + f.write(chunk) + bytes_downloaded += len(chunk) + self.bootstrap_percent_download = (bytes_downloaded / remote_file_size) * 100 else: logging.error("Failed to download the Blocknet Bootstrap.") @@ -527,83 +452,29 @@ def download_blocknet_bin(self): if url is None: raise ValueError(f"Unsupported OS or architecture {global_variables.system} {global_variables.machine}") - # Set timeout values in seconds - connection_timeout = 10 - read_timeout = 30 - response = requests.get(url, stream=True, timeout=(connection_timeout, read_timeout)) - response.raise_for_status() - if response.status_code == 200: - local_file_path = os.path.join(global_variables.aio_folder, os.path.basename(url)) - try: - remote_file_size = int(response.headers.get('Content-Length', 0)) - logging.info(f"Downloading {url} to {local_file_path}, remote size: {int(remote_file_size / 1024)} kb") - bytes_downloaded = 0 - with open(local_file_path, "wb") as f: - for chunk in response.iter_content(chunk_size=8192): # Iterate over response content in chunks - if chunk: # Filter out keep-alive new chunks - f.write(chunk) - bytes_downloaded += len(chunk) - self.binary_percent_download = (bytes_downloaded / remote_file_size) * 100 - except requests.exceptions.RequestException as e: - logging.error(f"Error occurred during download: {str(e)}") - - self.binary_percent_download = None - - if os.path.getsize(local_file_path) != remote_file_size: - os.remove(local_file_path) - raise ValueError( - f"Downloaded {os.path.basename(url)} size doesn't match the expected size. Deleting it") - - logging.info(f"{os.path.basename(url)} downloaded successfully.") + tmp_path = os.path.join(global_variables.aio_folder, os.path.basename(url)) + final_path = self.blocknet_exe # For DMG + extract_to = global_variables.aio_folder # For zip/tar.gz - if url.endswith(".zip"): - with zipfile.ZipFile(local_file_path, "r") as zip_ref: - zip_ref.extractall(global_variables.aio_folder) - logging.info("Zip file extracted successfully.") - os.remove(local_file_path) - elif url.endswith(".tar.gz"): - with tarfile.open(local_file_path, "r:gz") as tar: - tar.extractall(global_variables.aio_folder) - logging.info("Tar.gz file extracted successfully.") - os.remove(local_file_path) - else: - print("Failed to download the Blocknet binary.") + self.helper.download_file( + url, tmp_path, final_path, extract_to, + global_variables.system, "binary_percent_download", self + ) self.downloading_bin = False def get_remote_file_size(url): - """ - Fetches the size of a remote file specified by its URL. - """ r = requests.head(url) r.raise_for_status() return int(r.headers.get('content-length', 0)) -def get_pid(name): - return map(int, check_output(["pidof", name]).split()) - - -def get_blocknet_data_folder(custom_path=None): - if custom_path: - path = custom_path - else: - path = global_variables.conf_data.blocknet_default_paths.get(global_variables.system) - if path: - expanded_path = os.path.expandvars(os.path.expanduser(path)) - # logging.info(f"\n path {norm_path} \n") - return os.path.normpath(expanded_path) # Normalize path separators - else: - logging.error(f"invalid blocknet data folder path: {path}") - - def generate_random_string(length): return ''.join(random.choices(string.ascii_letters + string.digits, k=length)) def save_conf_to_file(conf_data, file_path): try: - # Create missing directories if needed os.makedirs(os.path.dirname(file_path), exist_ok=True) with open(file_path, 'w') as f: for section, options in conf_data.items(): @@ -650,7 +521,6 @@ def download_remote_conf(url, filepath): conf_data = response.text parsed_conf = parse_conf_file(input_string=conf_data) if parsed_conf: - # Save the remote configuration to a local file save_conf_to_file(parsed_conf, filepath) logging.info(f"retrieved and parsed ok: [{filepath}]") return parsed_conf @@ -667,28 +537,17 @@ def download_remote_conf(url, filepath): def retrieve_xb_manifest(): - # remote_manifest_url folder = "xb_conf" filename = os.path.basename(global_variables.conf_data.remote_manifest_url) local_manifest_file = os.path.join(global_variables.aio_folder, folder, filename) - # if os.path.exists(local_manifest_file): - # try: - # with open(local_manifest_file, 'r') as f: - # json_data = f.read() - # parsed_json = json.loads(json_data) - # logging.info(f"REMOTE: Found and parsed successfully: {local_manifest_file}") - # return parsed_json - # except Exception as e: - # logging.error(f"{local_manifest_file} Error opening or parsing file: {e}") - try: response = requests.get(global_variables.conf_data.remote_manifest_url) if response.status_code == 200: parsed_json = response.json() os.makedirs(os.path.dirname(local_manifest_file), exist_ok=True) with open(local_manifest_file, 'w') as f: - f.write(json.dumps(parsed_json, indent=4)) # Save the JSON data to local file + f.write(json.dumps(parsed_json, indent=4)) logging.info(f"REMOTE: Retrieved and parsed ok: [{local_manifest_file}]") return parsed_json else: @@ -712,7 +571,7 @@ def retrieve_remote_blocknet_xbridge_conf(): def parse_conf_file(file_path=None, input_string=None): conf_data = {} - current_section = 'global' # Set a default section + current_section = 'global' if file_path: with open(file_path, 'r') as f: @@ -737,15 +596,22 @@ def parse_conf_file(file_path=None, input_string=None): if not line or line.startswith('#'): continue if '=' in line: - conf_data.setdefault(current_section.strip('[]'), {}) key, value = line.split('=', 1) - conf_data[current_section.strip('[]')][key.strip()] = value.strip() + conf_data.setdefault(current_section.strip('[]'), {})[key.strip()] = value.strip() else: current_section = line.strip() conf_data.setdefault(current_section.strip('[]'), {}) return conf_data -# if __name__ == "__main__": -# a = BlocknetUtility() -# a.retrieve_coin_conf('BTC') + +def get_blocknet_data_folder(custom_path=None): + if custom_path: + path = custom_path + else: + path = global_variables.conf_data.blocknet_default_paths.get(global_variables.system) + if path: + expanded_path = os.path.expandvars(os.path.expanduser(path)) + return os.path.normpath(expanded_path) + else: + logging.error(f"invalid blocknet data folder path: {path}") diff --git a/utilities/helper_util.py b/utilities/helper_util.py new file mode 100644 index 0000000..7bbad5a --- /dev/null +++ b/utilities/helper_util.py @@ -0,0 +1,81 @@ +import logging +import os +import subprocess +import tarfile +import zipfile + +import psutil +import requests + +logging.basicConfig(level=logging.DEBUG) + + +class UtilityHelper: + def __init__(self): + pass + + # Shared by all 3 utilities + def download_file(self, url, tmp_path, final_path, extract_to, system, progress_attr, instance): + logging.info(f"Starting download from {url}") + response = requests.get(url, stream=True, timeout=(10, 30)) + response.raise_for_status() + + remote_size = int(response.headers.get('Content-Length', 0)) + with open(tmp_path, 'wb') as f: + bytes_downloaded = 0 + for chunk in response.iter_content(chunk_size=8192): + if chunk: + f.write(chunk) + bytes_downloaded += len(chunk) + if progress_attr and instance: + setattr(instance, progress_attr, (bytes_downloaded / remote_size) * 100) + logging.debug(f"Downloaded {bytes_downloaded}/{remote_size} bytes") + + if os.path.getsize(tmp_path) != remote_size: + os.remove(tmp_path) + raise ValueError("Download size mismatch") + logging.info(f"File downloaded successfully to {tmp_path}") + + if url.endswith(".zip"): + with zipfile.ZipFile(tmp_path, 'r') as zip_ref: + zip_ref.extractall(extract_to) + os.remove(tmp_path) + logging.info(f"Extracted ZIP file to {extract_to}") + elif url.endswith(".tar.gz"): + with tarfile.open(tmp_path, 'r:gz') as tar: + tar.extractall(extract_to) + os.remove(tmp_path) + logging.info(f"Extracted TAR.GZ file to {extract_to}") + elif url.endswith(".dmg") and system == "Darwin": + os.rename(tmp_path, final_path) + logging.info(f"Renamed DMG file to {final_path}") + + # Shared by all 3 utilities + def terminate_processes(self, pids, name): + for pid in pids: + try: + proc = psutil.Process(pid) + proc.terminate() + proc.wait(timeout=10) + logging.info(f"Process {name} PID {pid} terminated successfully") + except (psutil.NoSuchProcess, psutil.TimeoutExpired) as e: + if isinstance(e, psutil.TimeoutExpired): + proc.kill() + logging.warning(f"Process {name} PID {pid}: Timeout expired, killed process") + else: + logging.warning(f"Process {name} PID {pid}: {str(e)}") + + # Shared by BlockdxUtility and XliteUtility + def handle_dmg(self, dmg_path, mount_path, action): + if action == "mount": + if not os.path.ismount(mount_path): + subprocess.run(["hdiutil", "attach", dmg_path], check=True) + logging.info(f"Mounted DMG {dmg_path} to {mount_path}") + else: + logging.warning(f"{mount_path} is already mounted") + elif action == "unmount": + if os.path.ismount(mount_path): + subprocess.run(["hdiutil", "detach", mount_path], check=True) + logging.info(f"Unmounted DMG from {mount_path}") + else: + logging.warning(f"{mount_path} is not mounted") diff --git a/utilities/xlite_util.py b/utilities/xlite_util.py index e6d599f..14bd3da 100644 --- a/utilities/xlite_util.py +++ b/utilities/xlite_util.py @@ -2,16 +2,13 @@ import logging import os import subprocess -import tarfile import threading import time -import zipfile -import psutil import requests -# from utilities.conf_data import (xlite_bin_path, xlite_default_paths, xlite_daemon_default_paths, vc_redist_win_url) from utilities import global_variables +from utilities.helper_util import UtilityHelper logging.basicConfig(level=logging.DEBUG) @@ -20,7 +17,6 @@ def check_vc_redist_installed(): - # Define the base key path base_key_path = r"SOFTWARE\Classes\Installer\Dependencies\Microsoft.VS.VC_RuntimeMinimumVSU_amd64,v14" value_name = "DisplayName" @@ -28,7 +24,7 @@ def check_vc_redist_installed(): if display_name is not None: return True else: - logging.info("No vc_redist found. installing") + logging.info("No vc_redist found. Installing") install_vc_redist(global_variables.conf_data.vc_redist_win_url) @@ -40,32 +36,27 @@ def check_registry_value(key_path, value_name): except FileNotFoundError: return None except Exception as e: - print(f"Error: {e}") + logging.error(f"Error: {e}") return None def install_vc_redist(url): try: - # Parse filename from URL installer_name = os.path.basename(url) - # Download the installer with open(installer_name, 'wb') as file: response = requests.get(url) file.write(response.content) - # Command to run the installer silently command = f"{installer_name} /install /quiet /norestart" - # Run the installer silently subprocess.run(command, shell=True, check=True) - print("Visual C++ Redistributable installed successfully.") + logging.info("Visual C++ Redistributable installed successfully.") - # Remove the installer file after installation os.remove(installer_name) except Exception as e: - print(f"Error: {e}") + logging.error(f"Error: {e}") class XliteRPCClient: @@ -85,22 +76,16 @@ def send_rpc_request(self, method=None, params=None): "id": 1, } try: - # logging.debug( - # f"Sending RPC request to URL: {url}, Method: {data['method']}, Params: {data['params']}, Auth: {auth}") response = requests.post(url, json=data, headers=headers, auth=auth) - # Check status code explicitly if response.status_code != 200: - # logging.error(f"Error sending RPC request: HTTP status code {response.status_code}") return None json_answer = response.json() - # logging.debug(f"RPC request successful. Response: {json}") if 'result' in json_answer: return json_answer['result'] else: logging.error(f"No result in json: {json_answer}") except requests.RequestException as e: - # logging.error(f"Error sending RPC request: {e}") return None except Exception as ex: logging.exception(f"An unexpected error occurred while sending RPC request: {ex}") @@ -109,6 +94,7 @@ def send_rpc_request(self, method=None, params=None): class XliteUtility: def __init__(self): + self.helper = UtilityHelper() if global_variables.system == "Darwin": self.xlite_exe = os.path.join(global_variables.aio_folder, os.path.basename(global_variables.xlite_url)) self.dmg_mount_path = f"/Volumes/{global_variables.xlite_volume_name}" @@ -125,7 +111,6 @@ def __init__(self): self.xlite_process = None self.xlite_daemon_process = None self.xlite_conf_local = {} - self.xlite_daemon_confs_local = {} self.running = True # flag for async funcs self.xlite_pids = [] self.xlite_daemon_pids = [] @@ -150,18 +135,15 @@ def check_xlite_daemon_confs(self): def check_valid_xlite_coins_rpc(self, runonce=False): while self.running: - # logging.debug(f"valid_coins_rpc: {self.valid_coins_rpc}, runonce: {runonce}") valid = False if self.coins_rpc: for coin, rpc_server in self.coins_rpc.items(): if coin != "master" and coin != "TBLOCK": - # logging.info(self.xlite_daemon_confs_local[coin]['rpcEnabled']) if self.xlite_daemon_confs_local[coin]['rpcEnabled'] is True: res = rpc_server.send_rpc_request("getinfo") if res is not None: valid = True if not valid: - # logging.debug(f"coin {coin} not valid") break if valid: self.valid_coins_rpc = True @@ -189,41 +171,31 @@ def parse_xlite_conf(self): try: with open(file_path, 'r') as file: meta_data = json.load(file) - logging.info(f"XLITE: Loaded JSON data from [{file_path}]") #: {meta_data}") + logging.info(f"XLITE: Loaded JSON data from [{file_path}]") except Exception as e: logging.error(f"Error parsing {file_path}: {e}, repairing file") - else: - # logging.warning(f"{file_path} doesn't exist") - pass self.xlite_conf_local = meta_data def parse_xlite_daemon_conf(self, silent=False): - # Assuming daemon_data_path and confs_folder are defined earlier in your code daemon_data_path = os.path.expandvars( os.path.expanduser( global_variables.conf_data.xlite_daemon_default_paths.get(global_variables.system, None))) confs_folder = os.path.join(daemon_data_path, "settings") - # List all files in the confs_folder if not os.path.exists(confs_folder): - # logging.warning(f"{confs_folder} doesn't exist") self.xlite_daemon_confs_local = {} return files_in_folder = os.listdir(confs_folder) - # Filter out only JSON files json_files = [file for file in files_in_folder if file.endswith('.json')] - # Parse each JSON file for json_file in json_files: json_file_path = os.path.join(confs_folder, json_file) coin = str(json_file).split("-")[1].split(".")[0] try: with open(json_file_path, 'r') as file: data = json.load(file) - # Do something with the parsed JSON data - # logging.debug(f"Parsed data from {coin} {json_file}: {data}") self.xlite_daemon_confs_local[coin] = data except Exception as e: self.xlite_daemon_confs_local[coin] = "ERROR PARSING" @@ -234,13 +206,10 @@ def parse_xlite_daemon_conf(self, silent=False): def start_xlite(self, env_vars=[]): if global_variables.system == "Windows": - # check vcredist - # install_vc_redist(vc_redist_win_url) check_vc_redist_installed() for var_dict in env_vars: for var_name, var_value in var_dict.items(): - # logging.info(f"var_name: {var_name} var_value: {var_value}") os.environ[var_name] = var_value if not os.path.exists(self.xlite_exe): @@ -249,16 +218,7 @@ def start_xlite(self, env_vars=[]): try: if global_variables.system == "Darwin": - # mac mod - # https://github.com/blocknetdx/xlite/releases/download/v1.0.7/XLite-1.0.7-mac.dmg - # Path to the application inside the DMG file - - # Check if the volume is already mounted - if not os.path.ismount(self.dmg_mount_path): - # Mount the DMG file - os.system(f'hdiutil attach "{self.xlite_exe}"') - else: - logging.info("Volume is already mounted.") + self.helper.handle_dmg(self.xlite_exe, self.dmg_mount_path, "mount") full_path = os.path.join(self.dmg_mount_path, *global_variables.conf_data.xlite_bin_name[global_variables.system]) logging.info( @@ -269,15 +229,13 @@ def start_xlite(self, env_vars=[]): stdin=subprocess.PIPE, start_new_session=True) else: - # Start the Blocknet process using subprocess self.xlite_process = subprocess.Popen([self.xlite_exe], stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE, start_new_session=True) - # Check if the process has started while self.xlite_process.pid is None: - time.sleep(1) # Wait for 1 second before checking again + time.sleep(1) pid = self.xlite_process.pid logging.info(f"Started Xlite process with PID {pid}: {self.xlite_exe}") @@ -285,11 +243,10 @@ def start_xlite(self, env_vars=[]): logging.error(f"Error: {e}") def close_xlite(self): - # Close the Xlite subprocess if it exists if self.xlite_process: try: self.xlite_process.terminate() - self.xlite_process.wait(timeout=10) # Wait for the process to terminate with a timeout of 60 seconds + self.xlite_process.wait(timeout=10) logging.info(f"Closed Xlite") self.xlite_process = None except subprocess.TimeoutExpired: @@ -304,7 +261,6 @@ def close_xlite(self): self.close_xlite_daemon_pids() def kill_xlite(self): - # Kill the Xlite subprocess if it exists if self.xlite_process: try: self.xlite_process.kill() @@ -315,48 +271,10 @@ def kill_xlite(self): logging.error(f"Error: {e}") def close_xlite_pids(self): - # Close the Xlite processes using their PIDs - for pid in self.xlite_pids: - try: - # Get the process object corresponding to the PID - proc = psutil.Process(pid) - proc.terminate() - logging.info(f"Initiated termination of Xlite process with PID {pid}.") - proc.wait(timeout=10) # Wait for the process to terminate with a timeout of 60 seconds - logging.info(f"Xlite process with PID {pid} has been terminated.") - except psutil.NoSuchProcess: - logging.warning(f"Xlite process with PID {pid} not found.") - except psutil.TimeoutExpired: - logging.warning(f"Force terminating Xlite process with PID {pid}.") - if proc: - proc.kill() - proc.wait() - logging.info(f"Xlite process with PID {pid} has been force terminated.") - except Exception as e: - logging.error(f"Error: {e}") + self.helper.terminate_processes(self.xlite_pids, "XLite") def close_xlite_daemon_pids(self): - - # Close the Xlite-daemon processes using their PIDs - for pid in self.xlite_daemon_pids: - try: - # Get the process object corresponding to the PID - proc = psutil.Process(pid) - proc.terminate() - logging.info(f"Initiated termination of Xlite-daemon process with PID {pid}.") - proc.wait(timeout=10) # Wait for the process to terminate with a timeout of 60 seconds - logging.info(f"Xlite-daemon process with PID {pid} has been terminated.") - except psutil.NoSuchProcess: - logging.warning(f"Xlite-daemon process with PID {pid} not found.") - except psutil.TimeoutExpired: - logging.warning(f"Force terminating Xlite-daemon process with PID {pid}.") - if proc: - proc.kill() - proc.wait() - logging.info(f"Xlite-daemon process with PID {pid} has been force terminated.") - except Exception as e: - logging.error(f"Error: {e}") - logging.info(f"Closed Xlite daemon") + self.helper.terminate_processes(self.xlite_daemon_pids, "Xlite-daemon") def download_xlite_bin(self): self.downloading_bin = True @@ -364,69 +282,15 @@ def download_xlite_bin(self): if url is None: raise ValueError(f"Unsupported OS or architecture {global_variables.system} {global_variables.machine}") - # Set timeout values in seconds - connection_timeout = 5 - read_timeout = 30 - response = requests.get(url, stream=True, timeout=(connection_timeout, read_timeout)) - response.raise_for_status() # Raise an exception for 4xx and 5xx status codes - if response.status_code == 200: - file_name = os.path.basename(url) - tmp_file_path = os.path.join(global_variables.aio_folder, "tmp_xl_bin") - try: - remote_file_size = int(response.headers.get('Content-Length', 0)) - # tmp_file_path = os.path.join(aio_folder, file_name + "_tmp") - logging.info(f"Downloading {url} to {tmp_file_path}, remote size: {int(remote_file_size / 1024)} kb") - bytes_downloaded = 0 - with open(tmp_file_path, "wb") as f: - for chunk in response.iter_content(chunk_size=8192): # Iterate over response content in chunks - if chunk: # Filter out keep-alive new chunks - f.write(chunk) - bytes_downloaded += len(chunk) - self.binary_percent_download = (bytes_downloaded / remote_file_size) * 100 - # print(self.binary_percent_download) - except requests.exceptions.RequestException as e: - logging.error(f"Error occurred during download: {str(e)}") - - self.binary_percent_download = None - - local_file_size = os.path.getsize(tmp_file_path) - if local_file_size != remote_file_size: - os.remove(tmp_file_path) - raise ValueError( - f"Downloaded {os.path.basename(url)} size doesn't match the expected size. Deleting it") - - logging.info(f"{os.path.basename(url)} downloaded successfully.") - - # Extract the archive - if url.endswith(".zip"): - with zipfile.ZipFile(tmp_file_path, "r") as zip_ref: - local_path = os.path.join(global_variables.aio_folder, - global_variables.conf_data.xlite_bin_path[global_variables.system]) - zip_ref.extractall(local_path) - logging.info("Zip file extracted successfully.") - os.remove(tmp_file_path) - elif url.endswith(".tar.gz"): - with tarfile.open(tmp_file_path, "r:gz") as tar: - tar.extractall(global_variables.aio_folder) - logging.info("Tar.gz file extracted successfully.") - os.remove(tmp_file_path) - elif url.endswith(".dmg"): - file_path = os.path.join(global_variables.aio_folder, file_name) - os.rename(tmp_file_path, file_path) - logging.info("DMG file saved successfully.") - else: - print("Failed to download the Xlite binary.") + tmp_path = os.path.join(global_variables.aio_folder, "tmp_xl_bin") + final_path = self.xlite_exe # For DMG + extract_to = global_variables.aio_folder # For zip/tar.gz + + self.helper.download_file( + url, tmp_path, final_path, extract_to, + global_variables.system, "binary_percent_download", self + ) self.downloading_bin = False def unmount_dmg(self): - if os.path.ismount(self.dmg_mount_path): - try: - subprocess.run(["hdiutil", "detach", self.dmg_mount_path], check=True) - logging.info("DMG unmounted successfully.") - except subprocess.CalledProcessError as e: - logging.error(f"Error: Failed to unmount DMG: {e}") - else: - logging.error("Error: DMG is not mounted.") - -# if __name__ == "__main__": -# install_vc_redist(vc_redist_win_url) + self.helper.handle_dmg(None, self.dmg_mount_path, "unmount") From 9b1b0e9a67797e76e22b8f78ce1c2b091b23f82e Mon Sep 17 00:00:00 2001 From: tryiou Date: Sat, 31 May 2025 16:08:01 +0200 Subject: [PATCH 08/15] feat: add hidden import for PIL and refactor process check logic --- .github/workflows/build.yml | 1 + utilities/utils.py | 14 ++++++++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e770155..f4b64f6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -53,6 +53,7 @@ jobs: scriptPath="${{ github.workspace }}/blocknet_aio_monitor.py" cmd=(pyinstaller --noconfirm --onefile \ + --hidden-import='PIL._tkinter_finder' \ --add-data "theme:theme" \ --add-data "img:img" \ --clean \ diff --git a/utilities/utils.py b/utilities/utils.py index 1cefb9b..0d5d3d4 100644 --- a/utilities/utils.py +++ b/utilities/utils.py @@ -122,12 +122,18 @@ def disable_button(button, img=None): def processes_check(): """Check for running processes related to Blocknet, BlockDX, and Xlite.""" + blocknet_bin = global_variables.blocknet_bin + blockdx_bin = global_variables.blockdx_bin[-1] if global_variables.system == "Darwin" \ + else global_variables.blockdx_bin + xlite_bin = global_variables.xlite_bin[-1] if global_variables.system == "Darwin" \ + else global_variables.xlite_bin + xlite_daemon_bin = global_variables.xlite_daemon_bin # Initialize process lists process_lists: dict = { - global_variables.blocknet_bin: [], - global_variables.blockdx_bin: [], - global_variables.xlite_bin: [], - global_variables.xlite_daemon_bin: [] + blocknet_bin: [], + blockdx_bin: [], + xlite_bin: [], + xlite_daemon_bin: [] } # Process all running processes From ecdfca5214eb0cfb9c24a362dca820a3ad54b933 Mon Sep 17 00:00:00 2001 From: tryiou Date: Sat, 31 May 2025 22:15:28 +0200 Subject: [PATCH 09/15] feat: handle config folder conflicts and improve repo setup error handling --- gui/xbridge_bot_manager.py | 33 +++++++++++++++++++++++++++++++- utilities/git_repo_management.py | 9 ++++++--- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/gui/xbridge_bot_manager.py b/gui/xbridge_bot_manager.py index 8bb641d..c030e37 100644 --- a/gui/xbridge_bot_manager.py +++ b/gui/xbridge_bot_manager.py @@ -1,3 +1,4 @@ +from datetime import datetime import logging import os import subprocess @@ -70,11 +71,41 @@ def _do_install_update(self, branch: str) -> None: self.current_branch = branch logging.info(f"Successfully updated to branch {branch}") except Exception as e: - logging.error(f"Failed to update: {str(e)}", exc_info=True) + error_msg = str(e) + if "conflict prevents checkout" in error_msg: + self.handle_config_folder_rename() + else: + logging.error(f"Failed to update: {error_msg}") logging.debug(f"Repository URL: {self.repo_url}") logging.debug(f"Target directory: {self.target_dir}") logging.debug(f"Branch: {branch}") + def handle_config_folder_rename(self): + logging.info("Successfully updated after resolving config conflict") + config_path = os.path.join(self.target_dir, "config") + + if not os.path.exists(config_path): + logging.warning("Config folder not found, cannot rename") + return + + # Generate a unique backup folder name with timestamp + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + config_bak_path = os.path.join(self.target_dir, f"config_bak_{timestamp}") + + try: + os.rename(config_path, config_bak_path) + logging.info(f"Renamed config folder to {config_bak_path} to resolve conflict") + except Exception as e: + logging.error(f"Failed to rename config folder: {e}") + return + + # Retry setup once + try: + self.repo_management.setup() + logging.info("Successfully updated after resolving config conflict") + except Exception as retry_e: + logging.error(f"Failed to update even after config rename: {retry_e}") + def delete_local_repo(self) -> None: """Delete local repository""" if self.repo_exists(): diff --git a/utilities/git_repo_management.py b/utilities/git_repo_management.py index 50c9561..aa60bd0 100644 --- a/utilities/git_repo_management.py +++ b/utilities/git_repo_management.py @@ -264,6 +264,11 @@ def _update_repo(self): logging.error(f"Remote branch '{branch}' not found in '{remote_name}'") return + current_branch = self.repo.head.shorthand + logging.info(f"current_branch: {current_branch}, self.remote_branch: {self.remote_branch}") + if current_branch != self.remote_branch: + self._checkout_branch() + # Ensure local branch exists try: repo_branch = self.repo.lookup_reference(f"refs/heads/{branch}") @@ -391,11 +396,9 @@ def setup(self) -> bool: self.venv.install_requirements(self.target_dir / "requirements.txt") logging.info(f"Repository setup complete") - return True except Exception as e: - logging.error(f"Repository setup failed: {e}") - return False + raise Exception(f"Repository setup failed: {e}") def run_script(self, script_path: str, script_args: Optional[List[str]] = None, timeout: Optional[int] = None) -> Optional[subprocess.Popen]: From 91efd250161047d0c96f949a5b4efdedf820f972 Mon Sep 17 00:00:00 2001 From: tryiou Date: Sat, 31 May 2025 22:37:18 +0200 Subject: [PATCH 10/15] fix: enhance error handling in XBridgeBotManager and update return type of setup method --- gui/xbridge_bot_manager.py | 4 ++-- utilities/git_repo_management.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gui/xbridge_bot_manager.py b/gui/xbridge_bot_manager.py index c030e37..6b2b528 100644 --- a/gui/xbridge_bot_manager.py +++ b/gui/xbridge_bot_manager.py @@ -1,9 +1,9 @@ -from datetime import datetime import logging import os import subprocess import threading import time +from datetime import datetime from utilities.git_repo_management import GitRepoManagement from utilities.global_variables import aio_folder @@ -72,7 +72,7 @@ def _do_install_update(self, branch: str) -> None: logging.info(f"Successfully updated to branch {branch}") except Exception as e: error_msg = str(e) - if "conflict prevents checkout" in error_msg: + if "conflict prevents checkout" in error_msg or "conflicts prevent checkout" in error_msg: self.handle_config_folder_rename() else: logging.error(f"Failed to update: {error_msg}") diff --git a/utilities/git_repo_management.py b/utilities/git_repo_management.py index aa60bd0..183510b 100644 --- a/utilities/git_repo_management.py +++ b/utilities/git_repo_management.py @@ -366,7 +366,7 @@ def __init__(self, repo_url: str, target_dir: str, branch: str = "main", workdir self.git_repo = GitRepository(repo_url, self.target_dir, branch) self.venv = None - def setup(self) -> bool: + def setup(self) -> None: """ Clone/update the repository and set up the virtual environment. From d8e2684a82663578b2071930f8c7bf809664e99b Mon Sep 17 00:00:00 2001 From: tryiou Date: Sun, 1 Jun 2025 08:57:23 +0200 Subject: [PATCH 11/15] refactor: initialize repo details within XBridgeBotManager constructor --- gui/xbridge_bot_manager.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/gui/xbridge_bot_manager.py b/gui/xbridge_bot_manager.py index 6b2b528..1f71aea 100644 --- a/gui/xbridge_bot_manager.py +++ b/gui/xbridge_bot_manager.py @@ -10,9 +10,10 @@ class XBridgeBotManager: - def __init__(self, repo_url: str = "https://github.com/tryiou/xbridge_trading_bots", current_branch: str = "main"): - - self.repo_url = repo_url + def __init__(self, current_branch: str = "main"): + self.author = "tryiou" + self.repo_name = "xbridge_trading_bots" + self.repo_url = "https://github.com/" + self.author + "/" + self.repo_name self.target_dir = os.path.join(aio_folder, "xbridge_trading_bots") self.started = False self.current_branch = current_branch From 8e2eec07a8c9f3ca69fffc1209b4c4f9502955be Mon Sep 17 00:00:00 2001 From: tryiou Date: Sun, 1 Jun 2025 13:59:34 +0200 Subject: [PATCH 12/15] fix: MacOS failed to detect running processes, processes_check function --- utilities/conf_data.py | 1 - utilities/utils.py | 8 ++++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/utilities/conf_data.py b/utilities/conf_data.py index 001c221..24af5eb 100644 --- a/utilities/conf_data.py +++ b/utilities/conf_data.py @@ -72,7 +72,6 @@ "Windows": "XLite.exe", "Linux": "xlite", "Darwin": ["XLite.app", "Contents", "MacOS", "XLite"] - # ["BLOCK-DX-1.9.5-mac", "BLOCK DX.app", "Contents", "MacOS"] # List of folders for Darwin } xlite_daemon_bin_name = { ("Linux", "x86_64"): "xlite-daemon-linux64", diff --git a/utilities/utils.py b/utilities/utils.py index 0d5d3d4..74eeb66 100644 --- a/utilities/utils.py +++ b/utilities/utils.py @@ -150,10 +150,10 @@ def processes_check(): break # Process matched, no need to check other types return ( - process_lists[global_variables.blocknet_bin], - process_lists[global_variables.blockdx_bin], - process_lists[global_variables.xlite_bin], - process_lists[global_variables.xlite_daemon_bin] + process_lists[blocknet_bin], + process_lists[blockdx_bin], + process_lists[xlite_bin], + process_lists[xlite_daemon_bin] ) From 1b60eea2fd1e6f9851c040de4313a4689ca3e65e Mon Sep 17 00:00:00 2001 From: tryiou Date: Sun, 1 Jun 2025 17:44:17 +0200 Subject: [PATCH 13/15] fix: update Darwin path in conf_data.py --- utilities/conf_data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utilities/conf_data.py b/utilities/conf_data.py index 24af5eb..828842c 100644 --- a/utilities/conf_data.py +++ b/utilities/conf_data.py @@ -2,7 +2,7 @@ aio_blocknet_data_path = { "Windows": "%appdata%\\AIO_Blocknet", "Linux": "~/.AIO_Blocknet", - "Darwin": "~/Library/Application Support/AIO_Blocknet" + "Darwin": "~/Library/AIO_Blocknet" } blocknet_bootstrap_url = "https://utils.blocknet.org/Blocknet.zip" From 0556766a230b61e99f8eeab7439db3b328fbe949 Mon Sep 17 00:00:00 2001 From: tryiou Date: Sun, 1 Jun 2025 18:04:04 +0200 Subject: [PATCH 14/15] fix: simplify conda path determination and add Linux-specific tkinter fix --- utilities/miniforge_portable.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/utilities/miniforge_portable.py b/utilities/miniforge_portable.py index 72c4b54..bb557cf 100644 --- a/utilities/miniforge_portable.py +++ b/utilities/miniforge_portable.py @@ -78,14 +78,10 @@ def install(self): # logging.info(f"🔹 pip: {python_bin} -m pip") # logging.info(f"🔹 venv: {python_bin} -m venv {self.venv}") # After successful installation - if self.system != "Windows": + if self.system == "Linux": + # fix for tkinter low quality display. linux only. try: - # Construct conda path based on OS - if self.system == "Windows": - conda_path = install_path / "Scripts" / "conda.exe" - else: - conda_path = install_path / "bin" / "conda" - + conda_path = install_path / "bin" / "conda" # Run conda install command, update tk packages for GUI app logging.info("Installing tk with xft_* support...") conda_cmd = [str(conda_path), "install", "-c", "conda-forge", "tk=*=xft_*", "-y"] From 5cff70d79ee715c9df90b2599ee2939f1eb80d09 Mon Sep 17 00:00:00 2001 From: tryiou Date: Sun, 1 Jun 2025 18:32:14 +0200 Subject: [PATCH 15/15] update README image --- img/blocknet_aio_monitor.png | Bin 67474 -> 76142 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/img/blocknet_aio_monitor.png b/img/blocknet_aio_monitor.png index 7f5f0578cee8371d92e53f5093e8e5482764638b..7f42c584e39fbcb6868a42c7fd5ecf74eb9428c7 100644 GIT binary patch literal 76142 zcmb5V19V?O_xGDLw%ZtO)YwL2tFf)dw$s>7W81bG+qP}%J?ZoO*TuT;UH5jaw9U!< z&dk}fG5hm936_->L4d`E1pxs;5EB*r2?Fvd8u%K4h6Jv(avp90|A5=^izz?@f83!B zf`IR_?1fb9<*khDopfytL5wY}EDWjb^lc3dE$vLK?9ahEcz{9VAAs?15;D*+FcZ?VaWK+xFta7JlWKr~5Q2ya@+ml{o~%0Aqu;*0 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