mirror of
https://github.com/pdxlocations/contact.git
synced 2026-07-27 20:12:31 +02:00
Enhance reconnect logic and error handling in UI and interface modules
- Implement automatic reconnection attempts in the main UI when disconnected. - Add a non-blocking connection status dialog to inform users during reconnection. - Improve error handling when sending messages and during interface reconnections. - Update the reconnect_interface function to increase the number of attempts and refine connection checks.
This commit is contained in:
@@ -383,6 +383,41 @@ def main_ui(stdscr: curses.window) -> None:
|
||||
handle_resize(stdscr, True)
|
||||
|
||||
while True:
|
||||
interface = interface_state.interface
|
||||
if (
|
||||
hasattr(interface, "stream")
|
||||
and interface.stream is None
|
||||
and not ui_state.reconnect_attempted
|
||||
):
|
||||
ui_state.reconnect_attempted = True
|
||||
status_win = show_connection_status(stdscr, "Disconnected", "Trying to reconnect…")
|
||||
try:
|
||||
from contact.ui.control_ui import reconnect_interface_with_splash
|
||||
|
||||
reconnect_interface_with_splash(stdscr, interface)
|
||||
if status_win is not None:
|
||||
status_win.erase()
|
||||
status_win.refresh()
|
||||
ui_state.reconnect_attempted = False
|
||||
handle_resize(stdscr, False)
|
||||
except Exception:
|
||||
if status_win is not None:
|
||||
status_win.erase()
|
||||
status_win.refresh()
|
||||
handle_resize(stdscr, False)
|
||||
logging.exception("Automatic reconnect after transport disconnect failed")
|
||||
retry = get_list_input(
|
||||
"Contact could not reconnect after 20 seconds.",
|
||||
"Retry",
|
||||
["Retry", "Cancel"],
|
||||
mandatory=True,
|
||||
)
|
||||
if retry == "Retry":
|
||||
ui_state.reconnect_attempted = False
|
||||
handle_resize(stdscr, False)
|
||||
continue
|
||||
handle_resize(stdscr, False)
|
||||
|
||||
with app_state.lock:
|
||||
process_pending_ui_updates(stdscr)
|
||||
entry_display = f"{ui_state.reply_context}{input_text or ''}"
|
||||
@@ -652,12 +687,27 @@ def handle_enter(input_text: str) -> str:
|
||||
)
|
||||
return input_text
|
||||
# Enter key pressed, send user input as message
|
||||
send_message(
|
||||
input_text,
|
||||
channel=ui_state.selected_channel,
|
||||
reply_id=ui_state.reply_id,
|
||||
reply_context=ui_state.reply_context,
|
||||
)
|
||||
try:
|
||||
send_message(
|
||||
input_text,
|
||||
channel=ui_state.selected_channel,
|
||||
reply_id=ui_state.reply_id,
|
||||
reply_context=ui_state.reply_context,
|
||||
)
|
||||
except Exception as exc:
|
||||
logging.warning("Message send failed; reconnecting interface: %s", exc)
|
||||
try:
|
||||
from contact.ui.control_ui import reconnect_interface_with_splash
|
||||
|
||||
reconnect_interface_with_splash(root_win, interface_state.interface)
|
||||
contact.ui.dialog.dialog("Reconnected", "Connection restored. Your message was not sent; press Enter to retry.")
|
||||
except Exception as reconnect_exc:
|
||||
logging.exception("Automatic reconnect failed")
|
||||
contact.ui.dialog.dialog(
|
||||
"Reconnect failed",
|
||||
"Contact could not reconnect after 20 seconds. Check the radio connection and retry your message.",
|
||||
)
|
||||
return input_text
|
||||
ui_state.reply_id = None
|
||||
ui_state.reply_context = ""
|
||||
ui_state.reply_id_unavailable = False
|
||||
@@ -1012,6 +1062,23 @@ def show_remote_admin_wait(stdscr: curses.window, node_name: str) -> curses.wind
|
||||
return None
|
||||
|
||||
|
||||
def show_connection_status(stdscr: curses.window, title: str, message: str) -> curses.window | None:
|
||||
"""Display a non-blocking connection-status dialog over the current UI."""
|
||||
try:
|
||||
height, width = stdscr.getmaxyx()
|
||||
box_width = min(width - 4, max(len(message) + 4, len(title) + 6, 32))
|
||||
win = curses.newwin(5, box_width, max(0, (height - 5) // 2), max(0, (width - box_width) // 2))
|
||||
win.bkgd(get_color("background"))
|
||||
win.attrset(get_color("window_frame"))
|
||||
win.border()
|
||||
win.addstr(0, 2, f" {title} ", get_color("settings_default"))
|
||||
win.addstr(2, 2, message[: box_width - 4], get_color("settings_default"))
|
||||
win.refresh()
|
||||
return win
|
||||
except curses.error:
|
||||
return None
|
||||
|
||||
|
||||
def update_remote_admin_wait(wait_win: curses.window | None, message: str) -> None:
|
||||
"""Append the current remote-admin request to the waiting overlay."""
|
||||
if wait_win is None:
|
||||
|
||||
@@ -26,7 +26,6 @@ from contact.ui.colors import get_color
|
||||
from contact.ui.dialog import dialog
|
||||
from contact.ui.menus import generate_menu_from_protobuf
|
||||
from contact.ui.nav_utils import move_highlight, draw_arrows, update_help_window
|
||||
from contact.ui.splash import draw_splash
|
||||
from contact.ui.user_config import json_editor
|
||||
from contact.utilities.arg_parser import setup_parser
|
||||
from contact.utilities.singleton import interface_state, menu_state
|
||||
@@ -236,9 +235,6 @@ def reconnect_interface_with_splash(stdscr: object, interface: object) -> object
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
stdscr.clear()
|
||||
stdscr.refresh()
|
||||
draw_splash(stdscr)
|
||||
new_interface = reconnect_interface(setup_parser().parse_args())
|
||||
interface_state.interface = new_interface
|
||||
redraw_main_ui_after_reconnect(stdscr)
|
||||
|
||||
@@ -39,6 +39,7 @@ class ChatUIState:
|
||||
redraw_full_ui: bool = False
|
||||
scroll_messages_to_bottom: bool = False
|
||||
preserve_message_selection: bool = False
|
||||
reconnect_attempted: bool = False
|
||||
oldest_message_rowid: Dict[Union[str, int], int] = field(default_factory=dict)
|
||||
has_older_messages: Dict[Union[str, int], bool] = field(default_factory=dict)
|
||||
message_line_ranges: Dict[Union[str, int], List[tuple]] = field(default_factory=dict)
|
||||
|
||||
@@ -44,15 +44,21 @@ def initialize_interface(args):
|
||||
logging.critical(f"Fatal error initializing interface: {ex}")
|
||||
|
||||
|
||||
def reconnect_interface(args, attempts: int = 15, delay_seconds: float = 1.0):
|
||||
def reconnect_interface(args, attempts: int = 20, delay_seconds: float = 1.0):
|
||||
last_error = None
|
||||
|
||||
for attempt in range(attempts):
|
||||
try:
|
||||
interface = initialize_interface(args)
|
||||
if interface is not None:
|
||||
if interface is not None and getattr(interface, "localNode", None) is not None and getattr(
|
||||
interface.localNode, "localConfig", None
|
||||
) is not None and (not hasattr(interface, "stream") or interface.stream is not None):
|
||||
return interface
|
||||
last_error = RuntimeError("initialize_interface returned None")
|
||||
last_error = RuntimeError("interface did not complete connection setup")
|
||||
try:
|
||||
interface.close()
|
||||
except Exception:
|
||||
pass
|
||||
except Exception as ex:
|
||||
last_error = ex
|
||||
|
||||
|
||||
Reference in New Issue
Block a user