From da5f5bff398f12fd14534ee8d59c6351b84077e8 Mon Sep 17 00:00:00 2001 From: stason200711 <91085195+stason200711@users.noreply.github.com> Date: Wed, 25 Mar 2026 23:20:36 +0300 Subject: [PATCH 1/4] Update db_handler.py Fix file descriptor leaks --- contact/utilities/db_handler.py | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/contact/utilities/db_handler.py b/contact/utilities/db_handler.py index 1fb14a1..f220e49 100644 --- a/contact/utilities/db_handler.py +++ b/contact/utilities/db_handler.py @@ -31,7 +31,8 @@ def save_message_to_db(channel: str, user_id: str, message_text: str) -> Optiona """ ensure_table_exists(quoted_table_name, schema) - with sqlite3.connect(config.db_file_path) as db_connection: + with sqlite3.connect(config.db_file_path, timeout=10.0) as db_connection: + db_connection.execute("PRAGMA busy_timeout=10000") db_cursor = db_connection.cursor() timestamp = int(time.time()) @@ -53,7 +54,8 @@ def save_message_to_db(channel: str, user_id: str, message_text: str) -> Optiona def update_ack_nak(channel: str, timestamp: int, message: str, ack: str) -> None: try: - with sqlite3.connect(config.db_file_path) as db_connection: + with sqlite3.connect(config.db_file_path, timeout=10.0) as db_connection: + db_connection.execute("PRAGMA busy_timeout=10000") db_cursor = db_connection.cursor() update_query = f""" UPDATE {get_table_name(channel)} @@ -76,7 +78,8 @@ def update_ack_nak(channel: str, timestamp: int, message: str, ack: str) -> None def load_messages_from_db() -> None: """Load messages from the database for all channels and update ui_state.all_messages and ui_state.channel_list.""" try: - with sqlite3.connect(config.db_file_path) as db_connection: + with sqlite3.connect(config.db_file_path, timeout=10.0) as db_connection: + db_connection.execute("PRAGMA busy_timeout=10000") db_cursor = db_connection.cursor() query = "SELECT name FROM sqlite_master WHERE type='table' AND name LIKE ?" @@ -92,6 +95,7 @@ def load_messages_from_db() -> None: if "ack_type" not in table_columns: update_table_query = f"ALTER TABLE {quoted_table_name} ADD COLUMN ack_type TEXT" db_cursor.execute(update_table_query) + db_connection.commit() query = f"SELECT user_id, message_text, timestamp, ack_type FROM {quoted_table_name}" @@ -228,7 +232,8 @@ def update_node_info_in_db( try: ensure_node_table_exists() # Ensure the table exists before any operation - with sqlite3.connect(config.db_file_path) as db_connection: + with sqlite3.connect(config.db_file_path, timeout=10.0) as db_connection: + db_connection.execute("PRAGMA busy_timeout=10000") db_cursor = db_connection.cursor() table_name = f'"{interface_state.myNodeNum}_nodedb"' # Quote in case of numeric names @@ -236,6 +241,7 @@ def update_node_info_in_db( if "chat_archived" not in table_columns: update_table_query = f"ALTER TABLE {table_name} ADD COLUMN chat_archived INTEGER" db_cursor.execute(update_table_query) + db_connection.commit() # Fetch existing values to preserve unchanged fields db_cursor.execute(f"SELECT * FROM {table_name} WHERE user_id = ?", (user_id,)) @@ -311,7 +317,8 @@ def ensure_node_table_exists() -> None: def ensure_table_exists(table_name: str, schema: str) -> None: """Ensure the given table exists in the database.""" try: - with sqlite3.connect(config.db_file_path) as db_connection: + with sqlite3.connect(config.db_file_path, timeout=10.0) as db_connection: + db_connection.execute("PRAGMA busy_timeout=10000") db_cursor = db_connection.cursor() create_table_query = f"CREATE TABLE IF NOT EXISTS {table_name} ({schema})" db_cursor.execute(create_table_query) @@ -331,7 +338,8 @@ def get_name_from_database(user_id: int, type: str = "long") -> str: :return: The retrieved name or the hex of the user id """ try: - with sqlite3.connect(config.db_file_path) as db_connection: + with sqlite3.connect(config.db_file_path, timeout=10.0) as db_connection: + db_connection.execute("PRAGMA busy_timeout=10000") db_cursor = db_connection.cursor() # Construct table name @@ -359,7 +367,8 @@ def get_name_from_database(user_id: int, type: str = "long") -> str: def is_chat_archived(user_id: int) -> int: try: - with sqlite3.connect(config.db_file_path) as db_connection: + with sqlite3.connect(config.db_file_path, timeout=10.0) as db_connection: + db_connection.execute("PRAGMA busy_timeout=10000") db_cursor = db_connection.cursor() table_name = f"{str(interface_state.myNodeNum)}_nodedb" nodeinfo_table = f'"{table_name}"' @@ -371,8 +380,8 @@ def is_chat_archived(user_id: int) -> int: except sqlite3.Error as e: logging.error(f"SQLite error in is_chat_archived: {e}") - return "Unknown" + return "0" except Exception as e: logging.error(f"Unexpected error in is_chat_archived: {e}") - return "Unknown" + return "0" From 80b93119591ddfb38042c6ad4912f7255614a750 Mon Sep 17 00:00:00 2001 From: stason200711 <91085195+stason200711@users.noreply.github.com> Date: Thu, 26 Mar 2026 09:52:08 +0300 Subject: [PATCH 2/4] Update demo_data.py Fix file descriptor leaks --- contact/utilities/demo_data.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contact/utilities/demo_data.py b/contact/utilities/demo_data.py index 2e76f68..c4e843b 100644 --- a/contact/utilities/demo_data.py +++ b/contact/utilities/demo_data.py @@ -141,7 +141,8 @@ def seed_demo_messages() -> None: ack_type TEXT """ - with sqlite3.connect(config.db_file_path) as db_connection: + with sqlite3.connect(config.db_file_path, timeout=10.0) as db_connection: + db_connection.execute("PRAGMA busy_timeout=10000") cursor = db_connection.cursor() for channel_name, rows in _demo_messages().items(): From 2d4b407caa8e0b75d70a274579d3c664069c93d0 Mon Sep 17 00:00:00 2001 From: stason200711 <91085195+stason200711@users.noreply.github.com> Date: Thu, 26 Mar 2026 09:55:00 +0300 Subject: [PATCH 3/4] Update default_config.py Allow overriding DATA_DIR via environment variable --- contact/ui/default_config.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/contact/ui/default_config.py b/contact/ui/default_config.py index 6b56f57..1ca15d9 100644 --- a/contact/ui/default_config.py +++ b/contact/ui/default_config.py @@ -56,9 +56,13 @@ def _get_config_root(preferred_dir: str, fallback_name: str = ".contact_client") return fallback_dir - -# Pick the root now. -config_root = _get_config_root(parent_dir) +# Allow overriding config root via environment variable +config_root = os.getenv("CONTACT_CONFIG_ROOT") +if config_root: + if not _is_writable_dir(config_root): + raise RuntimeError(f"CONTACT_CONFIG_ROOT={config_root} is not writable") +else: + config_root = _get_config_root(parent_dir) # Paths (derived from the chosen root) json_file_path = os.path.join(config_root, "config.json") From cd0b114f1339e9f3dcf83ed74c923ce8fd4e95a5 Mon Sep 17 00:00:00 2001 From: stason200711 <91085195+stason200711@users.noreply.github.com> Date: Sat, 28 Mar 2026 09:52:37 +0300 Subject: [PATCH 4/4] Update db_handler.py --- contact/utilities/db_handler.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contact/utilities/db_handler.py b/contact/utilities/db_handler.py index f220e49..d10fb26 100644 --- a/contact/utilities/db_handler.py +++ b/contact/utilities/db_handler.py @@ -380,8 +380,8 @@ def is_chat_archived(user_id: int) -> int: except sqlite3.Error as e: logging.error(f"SQLite error in is_chat_archived: {e}") - return "0" + return "Unknown" except Exception as e: logging.error(f"Unexpected error in is_chat_archived: {e}") - return "0" + return "Unknown"