Refactor app initialization and enhance logging; add Reticulum logs and improve error handling

This commit is contained in:
Sudo-Ivan
2025-05-29 02:30:56 -05:00
parent b62d855e54
commit 8eb21e71ea
7 changed files with 70 additions and 71 deletions

View File

@@ -1,5 +1,6 @@
import time
from dataclasses import dataclass
import pathlib
import RNS
@@ -17,22 +18,20 @@ class AnnounceService:
"""
def __init__(self, update_callback):
# Accept all announce aspects
self.aspect_filter = "nomadnetwork.node"
self.receive_path_responses = True
self.announces: list[Announce] = []
self.update_callback = update_callback
# Initialize Reticulum transport once
config_dir = pathlib.Path(__file__).resolve().parents[2] / "config"
try:
RNS.Reticulum()
except OSError:
# Already initialized
RNS.Reticulum(str(config_dir))
except (OSError, ValueError):
pass
# Register self as announce handler
RNS.Transport.register_announce_handler(self)
RNS.log("AnnounceService: registered announce handler")
def received_announce(self, destination_hash, announced_identity, app_data):
# Called by RNS when an announce is received
RNS.log(f"AnnounceService: received announce from {destination_hash.hex()}")
ts = int(time.time())
display_name = None
if app_data:
@@ -41,12 +40,8 @@ class AnnounceService:
except:
pass
announce = Announce(destination_hash.hex(), display_name, ts)
# Deduplicate and move announce to top
# Remove any existing announces with same destination_hash
self.announces = [ann for ann in self.announces if ann.destination_hash != announce.destination_hash]
# Insert new announce at front of list
self.announces.insert(0, announce)
# Notify UI of new announce
if self.update_callback:
self.update_callback(self.announces)

View File

@@ -2,25 +2,36 @@ import argparse
import subprocess
import sys
import pathlib
import datetime
import flet as ft
from flet import AppView, Page
from ren_browser.ui.ui import build_ui
import RNS
# Current renderer name
RENDERER = "plaintext"
ERROR_LOGS: list[str] = []
def log_error(msg: str):
timestamp = datetime.datetime.now().isoformat()
ERROR_LOGS.append(f"[{timestamp}] {msg}")
async def main(page: Page):
# Build the main UI layout
build_ui(page)
loader = ft.Column(
[ft.ProgressRing(), ft.Text("Initializing reticulum network")],
alignment=ft.MainAxisAlignment.CENTER,
horizontal_alignment=ft.CrossAxisAlignment.CENTER,
expand=True,
)
page.add(loader)
page.update()
def init_ret():
config_dir = pathlib.Path(__file__).resolve().parents[1] / "config"
try:
RNS.Reticulum(str(config_dir))
except (OSError, ValueError):
pass
page.controls.clear()
build_ui(page)
page.update()
page.run_thread(init_ret)
def run():
global RENDERER
@@ -32,7 +43,6 @@ def run():
RENDERER = args.renderer
if args.web:
# Run web mode on optional fixed port
if args.port is not None:
ft.app(main, view=AppView.WEB_BROWSER, port=args.port)
else:

20
ren_browser/logs.py Normal file
View File

@@ -0,0 +1,20 @@
import datetime
import RNS
APP_LOGS: list[str] = []
ERROR_LOGS: list[str] = []
RET_LOGS: list[str] = []
_original_RNS_log = RNS.log
def log_ret(msg, *args, **kwargs):
timestamp = datetime.datetime.now().isoformat()
RET_LOGS.append(f"[{timestamp}] {msg}")
return _original_RNS_log(msg, *args, **kwargs)
RNS.log = log_ret
def log_error(msg: str):
timestamp = datetime.datetime.now().isoformat()
ERROR_LOGS.append(f"[{timestamp}] {msg}")
APP_LOGS.append(f"[{timestamp}] ERROR: {msg}")
def log_app(msg: str):
timestamp = datetime.datetime.now().isoformat()
APP_LOGS.append(f"[{timestamp}] {msg}")

View File

@@ -1,5 +1,6 @@
import threading
import time
import pathlib
import RNS
from dataclasses import dataclass
@@ -17,34 +18,29 @@ class PageFetcher:
"""
def __init__(self):
# Initialize Reticulum with default config (singleton)
config_dir = pathlib.Path(__file__).resolve().parents[2] / "config"
try:
RNS.Reticulum()
except OSError:
# Already initialized
RNS.Reticulum(str(config_dir))
except (OSError, ValueError):
pass
def fetch_page(self, req: PageRequest) -> str:
RNS.log(f"PageFetcher: starting fetch of {req.page_path} from {req.destination_hash}")
"""
Download page content for the given PageRequest.
Placeholder implementation: replace with real network logic.
"""
# Establish path and identity
dest_bytes = bytes.fromhex(req.destination_hash)
# Request path if needed, with timeout
if not RNS.Transport.has_path(dest_bytes):
RNS.Transport.request_path(dest_bytes)
start = time.time()
# Wait up to 30 seconds for path discovery
while not RNS.Transport.has_path(dest_bytes):
if time.time() - start > 30:
raise Exception(f"No path to destination {req.destination_hash}")
time.sleep(0.1)
# Recall identity
identity = RNS.Identity.recall(dest_bytes)
if not identity:
raise Exception('Identity not found')
# Create client destination and announce so the server learns our path
destination = RNS.Destination(
identity,
RNS.Destination.OUT,
@@ -54,7 +50,6 @@ class PageFetcher:
)
link = RNS.Link(destination)
# Prepare sync fetch
result = {'data': None}
ev = threading.Event()
@@ -69,10 +64,10 @@ class PageFetcher:
def on_failed(_):
ev.set()
# Set up request on link establishment
link.set_link_established_callback(
lambda l: l.request(req.page_path, req.field_data, response_callback=on_response, failed_callback=on_failed)
)
# Wait for response or timeout
ev.wait(timeout=15)
return result['data'] or 'No content received'
data_str = result['data'] or 'No content received'
RNS.log(f"PageFetcher: received data for {req.destination_hash}:{req.page_path}")
return data_str

View File

@@ -5,7 +5,6 @@ def render_plaintext(content: str) -> ft.Control:
"""
Fallback plaintext renderer: displays raw text safely in a monospace, selectable control.
"""
# Use monospace font and make text selectable
return ft.Text(
content,
selectable=True,

View File

@@ -10,30 +10,21 @@ class TabsManager:
def __init__(self, page: ft.Page):
import ren_browser.app as app_module
self.page = page
# State: list of tabs and current index
self.manager = SimpleNamespace(tabs=[], index=0)
# UI components
self.tab_bar = ft.Row(spacing=4)
# Add padding inside content area to avoid text touching the border
self.content_container = ft.Container(expand=True, bgcolor=ft.Colors.BLACK, padding=ft.padding.all(10))
# Initialize with default "Home" tab only, using selected renderer
default_content = render_micron("Welcome to Ren Browser") if app_module.RENDERER == "micron" else render_plaintext("Welcome to Ren Browser")
self._add_tab_internal("Home", default_content)
# Action buttons
self.add_btn = ft.IconButton(ft.Icons.ADD, tooltip="New Tab", on_click=self._on_add_click)
self.close_btn = ft.IconButton(ft.Icons.CLOSE, tooltip="Close Tab", on_click=self._on_close_click)
# Append add and close buttons
self.tab_bar.controls.extend([self.add_btn, self.close_btn])
# Select the first tab
self.select_tab(0)
def _add_tab_internal(self, title: str, content: ft.Control):
idx = len(self.manager.tabs)
# Create per-tab URL bar and GO button
url_field = ft.TextField(label="URL", value=title, expand=True)
go_btn = ft.IconButton(ft.Icons.OPEN_IN_BROWSER, tooltip="Load URL", on_click=lambda e, i=idx: self._on_tab_go(e, i))
# Wrap the content in a Column: initial content only (URL bar is handled externally)
content_control = content
tab_content = ft.Column(
expand=True,
@@ -41,7 +32,6 @@ class TabsManager:
content_control,
],
)
# Store tab data
self.manager.tabs.append({
"title": title,
"url_field": url_field,
@@ -49,7 +39,6 @@ class TabsManager:
"content_control": content_control,
"content": tab_content,
})
# Create stylable tab button container
btn = ft.Container(
content=ft.Text(title),
on_click=lambda e, i=idx: self.select_tab(i),
@@ -57,63 +46,50 @@ class TabsManager:
border_radius=5,
bgcolor=ft.Colors.SURFACE_CONTAINER_HIGHEST,
)
# Insert before the add and close buttons
insert_pos = max(0, len(self.tab_bar.controls) - 2)
self.tab_bar.controls.insert(insert_pos, btn)
def _on_add_click(self, e):
title = f"Tab {len(self.manager.tabs) + 1}"
# Render new tab content based on selected renderer
content_text = f"Content for {title}"
import ren_browser.app as app_module
content = render_micron(content_text) if app_module.RENDERER == "micron" else render_plaintext(content_text)
self._add_tab_internal(title, content)
# Select the new tab
self.select_tab(len(self.manager.tabs) - 1)
self.page.update()
def _on_close_click(self, e):
# Do not allow closing all tabs
if len(self.manager.tabs) <= 1:
return
idx = self.manager.index
# Remove tab data and button
self.manager.tabs.pop(idx)
self.tab_bar.controls.pop(idx)
# Reassign on_click handlers to correct indices
for i, control in enumerate(self.tab_bar.controls[:-2]):
control.on_click = lambda e, i=i: self.select_tab(i)
# Adjust selected index
new_idx = min(idx, len(self.manager.tabs) - 1)
self.select_tab(new_idx)
self.page.update()
def select_tab(self, idx: int):
self.manager.index = idx
# Highlight active tab and dim others
for i, control in enumerate(self.tab_bar.controls[:-2]):
if i == idx:
control.bgcolor = ft.Colors.PRIMARY_CONTAINER
else:
control.bgcolor = ft.Colors.SURFACE_CONTAINER_HIGHEST
# Update displayed content
self.content_container.content = self.manager.tabs[idx]["content"]
self.page.update()
def _on_tab_go(self, e, idx: int):
"""Handle loading a new URL in a specific tab (placeholder logic)."""
tab = self.manager.tabs[idx]
url = tab["url_field"].value.strip()
if not url:
return
# Placeholder: update the content_control using selected renderer
placeholder_text = f"Loading content for {url}"
import ren_browser.app as app_module
new_control = render_micron(placeholder_text) if app_module.RENDERER == "micron" else render_plaintext(placeholder_text)
tab["content_control"] = new_control
# Replace the content control in the tab's Column
tab["content"].controls[0] = new_control
# Refresh the displayed content if this tab is active
if self.manager.index == idx:
self.content_container.content = tab["content"]
self.page.update()

View File

@@ -1,22 +1,19 @@
import flet as ft
import pathlib
from ren_browser.app import ERROR_LOGS
from ren_browser.logs import ERROR_LOGS, RET_LOGS
def open_settings_tab(page: ft.Page, tab_manager):
# Locate Reticulum config file
config_path = pathlib.Path(__file__).resolve().parents[2] / "config" / "config"
try:
config_text = config_path.read_text()
except Exception as ex:
config_text = f"Error reading config: {ex}"
# Config editor
config_field = ft.TextField(
label="Reticulum config",
value=config_text,
expand=True,
multiline=True,
)
# Save button handler
def on_save_config(ev):
try:
config_path.write_text(config_field.value)
@@ -25,7 +22,6 @@ def open_settings_tab(page: ft.Page, tab_manager):
page.snack_bar = ft.SnackBar(ft.Text(f"Error saving config: {ex}"), open=True)
page.update()
save_btn = ft.ElevatedButton("Save and Restart", on_click=on_save_config)
# Error logs viewer
error_text = "\n".join(ERROR_LOGS) or "No errors logged."
error_field = ft.TextField(
label="Error Logs",
@@ -34,21 +30,29 @@ def open_settings_tab(page: ft.Page, tab_manager):
multiline=True,
read_only=True,
)
# Placeholder for content switching
ret_text = "\n".join(RET_LOGS) or "No Reticulum logs."
ret_field = ft.TextField(
label="Reticulum logs",
value=ret_text,
expand=True,
multiline=True,
read_only=True,
)
content_placeholder = ft.Container(expand=True)
# View switch handlers
def show_config(ev):
content_placeholder.content = config_field
page.update()
def show_errors(ev):
content_placeholder.content = error_field
page.update()
def show_ret_logs(ev):
content_placeholder.content = ret_field
page.update()
btn_config = ft.ElevatedButton("Config", on_click=show_config)
btn_errors = ft.ElevatedButton("Errors", on_click=show_errors)
button_row = ft.Row(controls=[btn_config, btn_errors])
# Initialize to config view
btn_ret = ft.ElevatedButton("Ret Logs", on_click=show_ret_logs)
button_row = ft.Row(controls=[btn_config, btn_errors, btn_ret])
content_placeholder.content = config_field
# Assemble settings UI
settings_content = ft.Column(
expand=True,
controls=[
@@ -60,4 +64,4 @@ def open_settings_tab(page: ft.Page, tab_manager):
tab_manager._add_tab_internal("Settings", settings_content)
idx = len(tab_manager.manager.tabs) - 1
tab_manager.select_tab(idx)
page.update()
page.update()