diff --git a/db_handler.py b/db_handler.py index 30f622f..171d0cc 100644 --- a/db_handler.py +++ b/db_handler.py @@ -1,8 +1,7 @@ - import sqlite3 import time -from datetime import datetime import logging +from datetime import datetime from utilities.utils import decimal_to_hex import default_config as config @@ -14,26 +13,22 @@ def get_table_name(channel): quoted_table_name = f'"{table_name}"' # Quote the table name becuase we begin with numerics and contain spaces return quoted_table_name + def save_message_to_db(channel, user_id, message_text): """Save messages to the database, ensuring the table exists.""" try: + quoted_table_name = get_table_name(channel) + + schema = ''' + user_id TEXT, + message_text TEXT, + timestamp INTEGER, + ack_type TEXT + ''' + ensure_table_exists(quoted_table_name, schema) + with sqlite3.connect(config.db_file_path) as db_connection: db_cursor = db_connection.cursor() - - quoted_table_name = get_table_name(channel) - - # Ensure the table exists - create_table_query = f''' - CREATE TABLE IF NOT EXISTS {quoted_table_name} ( - user_id TEXT, - message_text TEXT, - timestamp INTEGER, - ack_type TEXT - ) - ''' - - db_cursor.execute(create_table_query) - timestamp = int(time.time()) # Insert the message @@ -48,10 +43,10 @@ def save_message_to_db(channel, user_id, message_text): except sqlite3.Error as e: logging.error(f"SQLite error in save_message_to_db: {e}") - except Exception as e: logging.error(f"Unexpected error in save_message_to_db: {e}") + def update_ack_nak(channel, timestamp, message, ack): try: with sqlite3.connect(config.db_file_path) as db_connection: @@ -74,15 +69,12 @@ def update_ack_nak(channel, timestamp, message, ack): logging.error(f"Unexpected error in update_ack_nak: {e}") -from datetime import datetime - def load_messages_from_db(): """Load messages from the database for all channels and update globals.all_messages and globals.channel_list.""" try: with sqlite3.connect(config.db_file_path) as db_connection: db_cursor = db_connection.cursor() - # Retrieve all table names that match the pattern query = "SELECT name FROM sqlite_master WHERE type='table' AND name LIKE ?" db_cursor.execute(query, (f"{str(globals.myNodeNum)}_%_messages",)) tables = [row[0] for row in db_cursor.fetchall()] @@ -105,7 +97,7 @@ def load_messages_from_db(): # Extract the channel name from the table name channel = table_name.split("_")[1] - # Convert the channel to an integer if it's numeric, otherwise keep it as a string + # Convert the channel to an integer if it's numeric, otherwise keep it as a string (nodenum vs channel name) channel = int(channel) if channel.isdigit() else channel # Add the channel to globals.channel_list if not already present @@ -153,139 +145,121 @@ def load_messages_from_db(): def init_nodedb(): """Initialize the node database and update it with nodes from the interface.""" try: - with sqlite3.connect(config.db_file_path) as db_connection: - db_cursor = db_connection.cursor() + if not globals.interface.nodes: + return # No nodes to initialize - # Table name construction - table_name = f"{str(globals.myNodeNum)}_nodedb" - nodeinfo_table = f'"{table_name}"' # Quote the table name because it might begin with numerics + ensure_node_table_exists() # Ensure the table exists before insertion - # Create the table if it doesn't exist - create_table_query = f''' - CREATE TABLE IF NOT EXISTS {nodeinfo_table} ( - user_id TEXT PRIMARY KEY, - long_name TEXT, - short_name TEXT, - hw_model TEXT, - is_licensed TEXT, - role TEXT, - public_key TEXT - ) - ''' - db_cursor.execute(create_table_query) + # Insert or update all nodes + for node in globals.interface.nodes.values(): + update_node_info_in_db( + user_id=node['num'], + long_name=node['user'].get('longName', ''), + short_name=node['user'].get('shortName', ''), + hw_model=node['user'].get('hwModel', ''), + is_licensed=node['user'].get('isLicensed', '0'), + role=node['user'].get('role', 'CLIENT'), + public_key=node['user'].get('publicKey', '') + ) - # Iterate over nodes and insert them into the database - if globals.interface.nodes: - for node in globals.interface.nodes.values(): - role = node['user'].get('role', 'CLIENT') - is_licensed = node['user'].get('isLicensed', '0') - public_key = node['user'].get('publicKey', '') - - insert_query = f''' - INSERT OR IGNORE INTO {nodeinfo_table} (user_id, long_name, short_name, hw_model, is_licensed, role, public_key) - VALUES (?, ?, ?, ?, ?, ?, ?) - ''' - - db_cursor.execute(insert_query, ( - node['num'], - node['user']['longName'], - node['user']['shortName'], - node['user']['hwModel'], - is_licensed, - role, - public_key - )) - - db_connection.commit() + logging.info("Node database initialized successfully.") except sqlite3.Error as e: logging.error(f"SQLite error in init_nodedb: {e}") except Exception as e: logging.error(f"Unexpected error in init_nodedb: {e}") + def maybe_store_nodeinfo_in_db(packet): - """Save nodeinfo unless that record is already there.""" + """Save nodeinfo unless that record is already there, updating if necessary.""" try: - with sqlite3.connect(config.db_file_path) as db_connection: - - table_name = f"{str(globals.myNodeNum)}_nodedb" - nodeinfo_table = f'"{table_name}"' # Quote the table name becuase we might begin with numerics - db_cursor = db_connection.cursor() - - # Check if a record with the same user_id already exists - existing_record = db_cursor.execute(f'SELECT * FROM {nodeinfo_table} WHERE user_id=?', (packet['from'],)).fetchone() - - if existing_record is None: - role = packet['decoded']['user'].get('role', 'CLIENT') - is_licensed = packet['decoded']['user'].get('isLicensed', '0') - public_key = packet['decoded']['user'].get('publicKey', '') - - # No existing record, insert the new record - insert_query = f''' - INSERT INTO {nodeinfo_table} (user_id, long_name, short_name, hw_model, is_licensed, role, public_key) - VALUES (?, ?, ?, ?, ?, ?, ?) - ''' - - db_cursor.execute(insert_query, ( - packet['from'], - packet['decoded']['user']['longName'], - packet['decoded']['user']['shortName'], - packet['decoded']['user']['hwModel'], - is_licensed, - role, - public_key - )) - - db_connection.commit() - - else: - # Check if values are different, update if necessary - # Extract existing values - existing_long_name = existing_record[1] - existing_short_name = existing_record[2] - existing_is_licensed = existing_record[4] - existing_role = existing_record[5] - existing_public_key = existing_record[6] - - # Extract new values from the packet - new_long_name = packet['decoded']['user']['longName'] - new_short_name = packet['decoded']['user']['shortName'] - new_is_licensed = packet['decoded']['user'].get('isLicensed', '0') - new_role = packet['decoded']['user'].get('role', 'CLIENT') - new_public_key = packet['decoded']['user'].get('publicKey', '') - - # Check for any differences - if ( - existing_long_name != new_long_name or - existing_short_name != new_short_name or - existing_is_licensed != new_is_licensed or - existing_role != new_role or - existing_public_key != new_public_key - ): - # Perform necessary updates - update_query = f''' - UPDATE {nodeinfo_table} - SET long_name = ?, short_name = ?, is_licensed = ?, role = ?, public_key = ? - WHERE user_id = ? - ''' - db_cursor.execute(update_query, ( - new_long_name, - new_short_name, - new_is_licensed, - new_role, - new_public_key, - packet['from'] - )) - - db_connection.commit() - - # TODO display new node name in nodelist + user_id = packet['from'] + long_name = packet['decoded']['user']['longName'] + short_name = packet['decoded']['user']['shortName'] + hw_model = packet['decoded']['user']['hwModel'] + is_licensed = packet['decoded']['user'].get('isLicensed', '0') + role = packet['decoded']['user'].get('role', 'CLIENT') + public_key = packet['decoded']['user'].get('publicKey', '') + update_node_info_in_db(user_id, long_name, short_name, hw_model, is_licensed, role, public_key) except sqlite3.Error as e: logging.error(f"SQLite error in maybe_store_nodeinfo_in_db: {e}") - finally: - db_connection.close() + except Exception as e: + logging.error(f"Unexpected error in maybe_store_nodeinfo_in_db: {e}") + + +def update_node_info_in_db(user_id, long_name=None, short_name=None, hw_model=None, is_licensed=None, role=None, public_key=None): + """Update or insert node information into the database, preserving unchanged fields.""" + try: + ensure_node_table_exists() # Ensure the table exists before any operation + + with sqlite3.connect(config.db_file_path) as db_connection: + db_cursor = db_connection.cursor() + table_name = f'"{globals.myNodeNum}_nodedb"' # Quote in case of numeric names + + # Fetch existing values to preserve unchanged fields + db_cursor.execute(f'SELECT * FROM {table_name} WHERE user_id = ?', (user_id,)) + existing_record = db_cursor.fetchone() + + if existing_record: + existing_long_name, existing_short_name, existing_hw_model, existing_is_licensed, existing_role, existing_public_key = existing_record[1:] + + long_name = long_name if long_name is not None else existing_long_name + short_name = short_name if short_name is not None else existing_short_name + hw_model = hw_model if hw_model is not None else existing_hw_model + is_licensed = is_licensed if is_licensed is not None else existing_is_licensed + role = role if role is not None else existing_role + public_key = public_key if public_key is not None else existing_public_key + + # Upsert logic + upsert_query = f''' + INSERT INTO {table_name} (user_id, long_name, short_name, hw_model, is_licensed, role, public_key) + VALUES (?, ?, ?, ?, ?, ?, ?) + ON CONFLICT(user_id) DO UPDATE SET + long_name = excluded.long_name, + short_name = excluded.short_name, + hw_model = excluded.hw_model, + is_licensed = excluded.is_licensed, + role = excluded.role, + public_key = excluded.public_key + ''' + db_cursor.execute(upsert_query, (user_id, long_name, short_name, hw_model, is_licensed, role, public_key)) + db_connection.commit() + + except sqlite3.Error as e: + logging.error(f"SQLite error in update_node_info_in_db: {e}") + except Exception as e: + logging.error(f"Unexpected error in update_node_info_in_db: {e}") + + +def ensure_node_table_exists(): + """Ensure the node database table exists.""" + table_name = f'"{globals.myNodeNum}_nodedb"' # Quote for safety + schema = ''' + user_id TEXT PRIMARY KEY, + long_name TEXT, + short_name TEXT, + hw_model TEXT, + is_licensed TEXT, + role TEXT, + public_key TEXT + ''' + ensure_table_exists(table_name, schema) + + +def ensure_table_exists(table_name, schema): + """Ensure the given table exists in the database.""" + try: + with sqlite3.connect(config.db_file_path) as db_connection: + db_cursor = db_connection.cursor() + create_table_query = f"CREATE TABLE IF NOT EXISTS {table_name} ({schema})" + db_cursor.execute(create_table_query) + db_connection.commit() + except sqlite3.Error as e: + logging.error(f"SQLite error in ensure_table_exists({table_name}): {e}") + except Exception as e: + logging.error(f"Unexpected error in ensure_table_exists({table_name}): {e}") def get_name_from_database(user_id, type="long"): diff --git a/save_to_radio.py b/save_to_radio.py index 0ab7b4d..0b6c1b0 100644 --- a/save_to_radio.py +++ b/save_to_radio.py @@ -2,6 +2,8 @@ from meshtastic.protobuf import channel_pb2 from google.protobuf.message import Message import logging import base64 +from db_handler import update_node_info_in_db +import globals def save_changes(interface, menu_path, modified_settings): """ @@ -29,16 +31,20 @@ def save_changes(interface, menu_path, modified_settings): logging.info(f"Updated {config_category} with Latitude: {lat} and Longitude {lon} and Altitude {alt}") return - elif menu_path[1] == "User Settings": # for user configs - config_category = "User Settings" + elif menu_path[1] == "User Settings": # for user configs + config_category = "User Settings" long_name = modified_settings.get("longName") short_name = modified_settings.get("shortName") is_licensed = modified_settings.get("isLicensed") - is_licensed = is_licensed == "True" or is_licensed is True + is_licensed = is_licensed == "True" or is_licensed is True # Normalize boolean node.setOwner(long_name, short_name, is_licensed) - logging.info(f"Updated {config_category} with Long Name: {long_name} and Short Name {short_name} and Licensed Mode {is_licensed}") + # Update only the changed fields and preserve others + update_node_info_in_db(globals.myNodeNum, long_name=long_name, short_name=short_name, is_licensed=is_licensed) + + logging.info(f"Updated {config_category} with Long Name: {long_name}, Short Name: {short_name}, Licensed Mode: {is_licensed}") + return elif menu_path[1] == "Channels": # for channel configs