From ee521a9f60a235f4c4d7cefbba0a5a5c855f5e4a Mon Sep 17 00:00:00 2001 From: Ivan Date: Mon, 22 Sep 2025 13:55:52 -0500 Subject: [PATCH] ruff formatting and fixes --- ren_browser/announces/announces.py | 9 +++-- ren_browser/app.py | 57 ++++++++++++++++++++++++++--- ren_browser/controls/shortcuts.py | 1 + ren_browser/logs.py | 5 +++ ren_browser/pages/page_request.py | 18 +++++++--- ren_browser/renderer/micron.py | 1 + ren_browser/renderer/plaintext.py | 1 + ren_browser/storage/storage.py | 21 ++++++++--- ren_browser/tabs/tabs.py | 58 ++++++++++++++++++++++-------- ren_browser/ui/settings.py | 29 ++++++++++++--- ren_browser/ui/ui.py | 33 +++++++++++++++-- 11 files changed, 195 insertions(+), 38 deletions(-) diff --git a/ren_browser/announces/announces.py b/ren_browser/announces/announces.py index d646396..d754955 100644 --- a/ren_browser/announces/announces.py +++ b/ren_browser/announces/announces.py @@ -3,13 +3,13 @@ This module provides services for listening to and collecting network announces from the Reticulum network. """ + import time from dataclasses import dataclass import RNS - @dataclass class Announce: """Represents a Reticulum network announce. @@ -21,6 +21,7 @@ class Announce: display_name: str | None timestamp: int + class AnnounceService: """Service to listen for Reticulum announces and collect them. @@ -60,7 +61,11 @@ class AnnounceService: except UnicodeDecodeError: pass announce = Announce(destination_hash.hex(), display_name, ts) - self.announces = [ann for ann in self.announces if ann.destination_hash != announce.destination_hash] + self.announces = [ + ann + for ann in self.announces + if ann.destination_hash != announce.destination_hash + ] self.announces.insert(0, announce) if self.update_callback: self.update_callback(self.announces) diff --git a/ren_browser/app.py b/ren_browser/app.py index a49025d..c73d1b6 100644 --- a/ren_browser/app.py +++ b/ren_browser/app.py @@ -3,6 +3,7 @@ This module provides the entry point and platform-specific launchers for the Ren Browser, a browser for the Reticulum Network built with Flet. """ + import argparse import flet as ft @@ -15,25 +16,44 @@ from ren_browser.ui.ui import build_ui RENDERER = "plaintext" RNS_CONFIG_DIR = None + async def main(page: Page): """Initialize and launch the Ren Browser application. Sets up the loading screen, initializes Reticulum network, and builds the main UI. """ + page.title = "Ren Browser" + page.theme_mode = ft.ThemeMode.DARK + loader = ft.Container( expand=True, alignment=ft.alignment.center, + bgcolor=ft.Colors.SURFACE, content=ft.Column( - [ft.ProgressRing(), ft.Text("Initializing reticulum network")], + [ + ft.ProgressRing(color=ft.Colors.PRIMARY, width=50, height=50), + ft.Container(height=20), + ft.Text( + "Initializing Reticulum Network...", + size=16, + color=ft.Colors.ON_SURFACE, + text_align=ft.TextAlign.CENTER, + ), + ], alignment=ft.MainAxisAlignment.CENTER, horizontal_alignment=ft.CrossAxisAlignment.CENTER, + spacing=10, ), ) page.add(loader) page.update() def init_ret(): + import time + + time.sleep(0.5) + # Initialize storage system storage = initialize_storage(page) @@ -45,6 +65,7 @@ async def main(page: Page): try: # Set up logging capture first, before RNS init import ren_browser.logs + ren_browser.logs.setup_rns_logging() RNS.Reticulum(str(config_dir)) except (OSError, ValueError): @@ -55,14 +76,31 @@ async def main(page: Page): page.run_thread(init_ret) + def run(): """Run Ren Browser with command line argument parsing.""" global RENDERER, RNS_CONFIG_DIR parser = argparse.ArgumentParser(description="Ren Browser") - parser.add_argument("-r", "--renderer", choices=["plaintext", "micron"], default=RENDERER, help="Select renderer (plaintext or micron)") - parser.add_argument("-w", "--web", action="store_true", help="Launch in web browser mode") - parser.add_argument("-p", "--port", type=int, default=None, help="Port for web server") - parser.add_argument("-c", "--config-dir", type=str, default=None, help="RNS config directory (default: ~/.reticulum/)") + parser.add_argument( + "-r", + "--renderer", + choices=["plaintext", "micron"], + default=RENDERER, + help="Select renderer (plaintext or micron)", + ) + parser.add_argument( + "-w", "--web", action="store_true", help="Launch in web browser mode" + ) + parser.add_argument( + "-p", "--port", type=int, default=None, help="Port for web server" + ) + parser.add_argument( + "-c", + "--config-dir", + type=str, + default=None, + help="RNS config directory (default: ~/.reticulum/)", + ) args = parser.parse_args() RENDERER = args.renderer @@ -71,6 +109,7 @@ def run(): RNS_CONFIG_DIR = args.config_dir else: import pathlib + RNS_CONFIG_DIR = str(pathlib.Path.home() / ".reticulum") if args.web: @@ -81,33 +120,41 @@ def run(): else: ft.app(main) + if __name__ == "__main__": run() + def web(): """Launch Ren Browser in web mode.""" ft.app(main, view=AppView.WEB_BROWSER) + def android(): """Launch Ren Browser in Android mode.""" ft.app(main, view=AppView.FLET_APP_WEB) + def ios(): """Launch Ren Browser in iOS mode.""" ft.app(main, view=AppView.FLET_APP_WEB) + def run_dev(): """Launch Ren Browser in desktop mode.""" ft.app(main) + def web_dev(): """Launch Ren Browser in web mode.""" ft.app(main, view=AppView.WEB_BROWSER) + def android_dev(): """Launch Ren Browser in Android mode.""" ft.app(main, view=AppView.FLET_APP_WEB) + def ios_dev(): """Launch Ren Browser in iOS mode.""" ft.app(main, view=AppView.FLET_APP_WEB) diff --git a/ren_browser/controls/shortcuts.py b/ren_browser/controls/shortcuts.py index 8438229..8b53986 100644 --- a/ren_browser/controls/shortcuts.py +++ b/ren_browser/controls/shortcuts.py @@ -3,6 +3,7 @@ Provides keyboard event handling and delegation to tab manager and UI components. """ + import flet as ft diff --git a/ren_browser/logs.py b/ren_browser/logs.py index 4c4f942..a8ad3a1 100644 --- a/ren_browser/logs.py +++ b/ren_browser/logs.py @@ -3,6 +3,7 @@ Provides centralized logging for application events, errors, and Reticulum network activities. """ + import datetime import RNS @@ -12,6 +13,7 @@ ERROR_LOGS: list[str] = [] RET_LOGS: list[str] = [] _original_rns_log = RNS.log + def log_ret(msg, *args, **kwargs): """Log Reticulum messages with timestamp. @@ -25,6 +27,7 @@ def log_ret(msg, *args, **kwargs): RET_LOGS.append(f"[{timestamp}] {msg}") return _original_rns_log(msg, *args, **kwargs) + def setup_rns_logging(): """Set up RNS log replacement. Call this after RNS.Reticulum initialization.""" global _original_rns_log @@ -33,6 +36,7 @@ def setup_rns_logging(): _original_rns_log = RNS.log RNS.log = log_ret + def log_error(msg: str): """Log error messages to both error and application logs. @@ -44,6 +48,7 @@ def log_error(msg: str): ERROR_LOGS.append(f"[{timestamp}] {msg}") APP_LOGS.append(f"[{timestamp}] ERROR: {msg}") + def log_app(msg: str): """Log application messages. diff --git a/ren_browser/pages/page_request.py b/ren_browser/pages/page_request.py index 07e4b2a..4c3d229 100644 --- a/ren_browser/pages/page_request.py +++ b/ren_browser/pages/page_request.py @@ -3,6 +3,7 @@ Handles downloading pages from the Reticulum network using the nomadnetwork protocol. """ + import threading import time from dataclasses import dataclass @@ -10,7 +11,6 @@ from dataclasses import dataclass import RNS - @dataclass class PageRequest: """Represents a request for a page from the Reticulum network. @@ -22,6 +22,7 @@ class PageRequest: page_path: str field_data: dict | None = None + class PageFetcher: """Fetcher to download pages from the Reticulum network.""" @@ -43,7 +44,9 @@ class PageFetcher: Exception: If no path to destination or identity not found. """ - RNS.log(f"PageFetcher: starting fetch of {req.page_path} from {req.destination_hash}") + RNS.log( + f"PageFetcher: starting fetch of {req.page_path} from {req.destination_hash}" + ) dest_bytes = bytes.fromhex(req.destination_hash) if not RNS.Transport.has_path(dest_bytes): RNS.Transport.request_path(dest_bytes) @@ -79,9 +82,16 @@ class PageFetcher: ev.set() link.set_link_established_callback( - lambda link: link.request(req.page_path, req.field_data, response_callback=on_response, failed_callback=on_failed) + lambda link: link.request( + req.page_path, + req.field_data, + response_callback=on_response, + failed_callback=on_failed, + ) ) ev.wait(timeout=15) data_str = result["data"] or "No content received" - RNS.log(f"PageFetcher: received data for {req.destination_hash}:{req.page_path}") + RNS.log( + f"PageFetcher: received data for {req.destination_hash}:{req.page_path}" + ) return data_str diff --git a/ren_browser/renderer/micron.py b/ren_browser/renderer/micron.py index 1c8b5a9..e8bf710 100644 --- a/ren_browser/renderer/micron.py +++ b/ren_browser/renderer/micron.py @@ -3,6 +3,7 @@ Provides rendering capabilities for micron markup content, currently implemented as a placeholder. """ + import flet as ft diff --git a/ren_browser/renderer/plaintext.py b/ren_browser/renderer/plaintext.py index aa9f6d0..295ef8f 100644 --- a/ren_browser/renderer/plaintext.py +++ b/ren_browser/renderer/plaintext.py @@ -2,6 +2,7 @@ Provides fallback rendering for plaintext content and source viewing. """ + import flet as ft diff --git a/ren_browser/storage/storage.py b/ren_browser/storage/storage.py index a9b1456..f3fdd22 100644 --- a/ren_browser/storage/storage.py +++ b/ren_browser/storage/storage.py @@ -3,6 +3,7 @@ Provides persistent storage for configuration, bookmarks, history, and other application data across different platforms. """ + import json import os import pathlib @@ -38,7 +39,9 @@ class StorageManager: if os.name == "posix" and "ANDROID_ROOT" in os.environ: # Android - use app's private files directory storage_dir = pathlib.Path("/data/data/com.ren_browser/files") - elif hasattr(os, "uname") and "iOS" in str(getattr(os, "uname", lambda: "")()).replace("iPhone", "iOS"): + elif hasattr(os, "uname") and "iOS" in str( + getattr(os, "uname", lambda: "")() + ).replace("iPhone", "iOS"): # iOS - use app's documents directory storage_dir = pathlib.Path.home() / "Documents" / "ren_browser" else: @@ -46,7 +49,9 @@ class StorageManager: if "APPDATA" in os.environ: # Windows storage_dir = pathlib.Path(os.environ["APPDATA"]) / "ren_browser" elif "XDG_CONFIG_HOME" in os.environ: # Linux XDG standard - storage_dir = pathlib.Path(os.environ["XDG_CONFIG_HOME"]) / "ren_browser" + storage_dir = ( + pathlib.Path(os.environ["XDG_CONFIG_HOME"]) / "ren_browser" + ) else: storage_dir = pathlib.Path.home() / ".ren_browser" @@ -58,6 +63,7 @@ class StorageManager: self._storage_dir.mkdir(parents=True, exist_ok=True) except (OSError, PermissionError): import tempfile + self._storage_dir = pathlib.Path(tempfile.gettempdir()) / "ren_browser" self._storage_dir.mkdir(parents=True, exist_ok=True) @@ -71,6 +77,7 @@ class StorageManager: # Check for global override from app try: from ren_browser.app import RNS_CONFIG_DIR + if RNS_CONFIG_DIR: return pathlib.Path(RNS_CONFIG_DIR) except ImportError: @@ -111,7 +118,9 @@ class StorageManager: try: if self.page and hasattr(self.page, "client_storage"): self.page.client_storage.set("ren_browser_config", config_content) - self.page.client_storage.set("ren_browser_config_error", f"File save failed: {error}") + self.page.client_storage.set( + "ren_browser_config_error", f"File save failed: {error}" + ) return True try: @@ -122,6 +131,7 @@ class StorageManager: pass import tempfile + temp_path = pathlib.Path(tempfile.gettempdir()) / "ren_browser_config.txt" temp_path.write_text(config_content, encoding="utf-8") return True @@ -163,7 +173,9 @@ class StorageManager: json.dump(bookmarks, f, indent=2) if self.page and hasattr(self.page, "client_storage"): - self.page.client_storage.set("ren_browser_bookmarks", json.dumps(bookmarks)) + self.page.client_storage.set( + "ren_browser_bookmarks", json.dumps(bookmarks) + ) return True except Exception: @@ -267,6 +279,7 @@ def get_rns_config_directory() -> str: """Get the RNS config directory, checking for global override.""" try: from ren_browser.app import RNS_CONFIG_DIR + if RNS_CONFIG_DIR: return RNS_CONFIG_DIR except ImportError: diff --git a/ren_browser/tabs/tabs.py b/ren_browser/tabs/tabs.py index f86c0b9..67300cb 100644 --- a/ren_browser/tabs/tabs.py +++ b/ren_browser/tabs/tabs.py @@ -3,6 +3,7 @@ Provides tab creation, switching, and content management functionality for the browser interface. """ + from types import SimpleNamespace import flet as ft @@ -25,15 +26,26 @@ class TabsManager: """ import ren_browser.app as app_module + self.page = page self.manager = SimpleNamespace(tabs=[], index=0) self.tab_bar = ft.Row(spacing=4) - self.content_container = ft.Container(expand=True, bgcolor=ft.Colors.BLACK, padding=ft.padding.all(5)) + self.content_container = ft.Container( + expand=True, bgcolor=ft.Colors.BLACK, padding=ft.padding.all(5) + ) - default_content = render_micron("Welcome to Ren Browser") if app_module.RENDERER == "micron" else render_plaintext("Welcome to Ren Browser") + 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) - 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) + 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 + ) self.tab_bar.controls.extend([self.add_btn, self.close_btn]) self.select_tab(0) @@ -43,9 +55,13 @@ class TabsManager: value=title, expand=True, text_style=ft.TextStyle(size=12), - content_padding=ft.padding.only(top=8, bottom=8, left=8, right=8) + content_padding=ft.padding.only(top=8, bottom=8, left=8, right=8), + ) + go_btn = ft.IconButton( + ft.Icons.OPEN_IN_BROWSER, + tooltip="Load URL", + on_click=lambda e, i=idx: self._on_tab_go(e, i), ) - go_btn = ft.IconButton(ft.Icons.OPEN_IN_BROWSER, tooltip="Load URL", on_click=lambda e, i=idx: self._on_tab_go(e, i)) content_control = content tab_content = ft.Column( expand=True, @@ -53,13 +69,15 @@ class TabsManager: content_control, ], ) - self.manager.tabs.append({ - "title": title, - "url_field": url_field, - "go_btn": go_btn, - "content_control": content_control, - "content": tab_content, - }) + self.manager.tabs.append( + { + "title": title, + "url_field": url_field, + "go_btn": go_btn, + "content_control": content_control, + "content": tab_content, + } + ) btn = ft.Container( content=ft.Text(title), on_click=lambda e, i=idx: self.select_tab(i), @@ -74,7 +92,12 @@ class TabsManager: title = f"Tab {len(self.manager.tabs) + 1}" 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) + + content = ( + render_micron(content_text) + if app_module.RENDERER == "micron" + else render_plaintext(content_text) + ) self._add_tab_internal(title, content) self.select_tab(len(self.manager.tabs) - 1) self.page.update() @@ -114,7 +137,12 @@ class TabsManager: return 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) + + new_control = ( + render_micron(placeholder_text) + if app_module.RENDERER == "micron" + else render_plaintext(placeholder_text) + ) tab["content_control"] = new_control tab["content"].controls[0] = new_control if self.manager.index == idx: diff --git a/ren_browser/ui/settings.py b/ren_browser/ui/settings.py index 872905c..e2b7954 100644 --- a/ren_browser/ui/settings.py +++ b/ren_browser/ui/settings.py @@ -3,6 +3,7 @@ Provides configuration management, log viewing, and storage information display. """ + import flet as ft from ren_browser.logs import ERROR_LOGS, RET_LOGS @@ -35,12 +36,20 @@ def open_settings_tab(page: ft.Page, tab_manager): try: success = storage.save_config(config_field.value) if success: - page.snack_bar = ft.SnackBar(ft.Text("Config saved successfully. Please restart the app."), open=True) + page.snack_bar = ft.SnackBar( + ft.Text("Config saved successfully. Please restart the app."), + open=True, + ) else: - page.snack_bar = ft.SnackBar(ft.Text("Error saving config: Storage operation failed"), open=True) + page.snack_bar = ft.SnackBar( + ft.Text("Error saving config: Storage operation failed"), open=True + ) except Exception as ex: - page.snack_bar = ft.SnackBar(ft.Text(f"Error saving config: {ex}"), open=True) + 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_field = ft.TextField( label="Error Logs", @@ -69,22 +78,29 @@ def open_settings_tab(page: ft.Page, tab_manager): ) content_placeholder = ft.Container(expand=True) + def show_config(ev): content_placeholder.content = config_field page.update() + def show_errors(ev): error_field.value = "\n".join(ERROR_LOGS) or "No errors logged." content_placeholder.content = error_field page.update() + def show_ret_logs(ev): ret_field.value = "\n".join(RET_LOGS) or "No Reticulum logs." content_placeholder.content = ret_field page.update() + def show_storage_info(ev): storage_info = storage.get_storage_info() - storage_field.value = "\n".join([f"{key}: {value}" for key, value in storage_info.items()]) + storage_field.value = "\n".join( + [f"{key}: {value}" for key, value in storage_info.items()] + ) content_placeholder.content = storage_field page.update() + def refresh_current_view(ev): # Refresh the currently displayed content if content_placeholder.content == error_field: @@ -95,12 +111,15 @@ def open_settings_tab(page: ft.Page, tab_manager): show_storage_info(ev) elif content_placeholder.content == config_field: show_config(ev) + btn_config = ft.ElevatedButton("Config", on_click=show_config) btn_errors = ft.ElevatedButton("Errors", on_click=show_errors) btn_ret = ft.ElevatedButton("Ret Logs", on_click=show_ret_logs) btn_storage = ft.ElevatedButton("Storage", on_click=show_storage_info) btn_refresh = ft.ElevatedButton("Refresh", on_click=refresh_current_view) - button_row = ft.Row(controls=[btn_config, btn_errors, btn_ret, btn_storage, btn_refresh]) + button_row = ft.Row( + controls=[btn_config, btn_errors, btn_ret, btn_storage, btn_refresh] + ) content_placeholder.content = config_field settings_content = ft.Column( expand=True, diff --git a/ren_browser/ui/ui.py b/ren_browser/ui/ui.py index 4c8b3d8..f9f77ac 100644 --- a/ren_browser/ui/ui.py +++ b/ren_browser/ui/ui.py @@ -3,6 +3,7 @@ Builds the complete browser interface including tabs, navigation, announce handling, and content rendering. """ + import flet as ft from flet import Page @@ -27,10 +28,12 @@ def build_ui(page: Page): page_fetcher = PageFetcher() announce_list = ft.ListView(expand=True, spacing=1) + def update_announces(ann_list): announce_list.controls.clear() for ann in ann_list: label = ann.display_name or ann.destination_hash + def on_click_ann(e, dest=ann.destination_hash, disp=ann.display_name): title = disp or "Anonymous" full_url = f"{dest}:/page/index.mu" @@ -41,12 +44,14 @@ def build_ui(page: Page): tab["url_field"].value = full_url tab_manager.select_tab(idx) page.update() + def fetch_and_update(): req = PageRequest(destination_hash=dest, page_path="/page/index.mu") try: result = page_fetcher.fetch_page(req) except Exception as ex: import ren_browser.app as app_module + app_module.log_error(str(ex)) result = f"Error: {ex}" try: @@ -62,13 +67,21 @@ def build_ui(page: Page): if tab_manager.manager.index == idx: tab_manager.content_container.content = tab["content"] page.update() + page.run_thread(fetch_and_update) + announce_list.controls.append(ft.TextButton(label, on_click=on_click_ann)) page.update() + AnnounceService(update_callback=update_announces) page.drawer = ft.NavigationDrawer( controls=[ - ft.Text("Announcements", weight=ft.FontWeight.BOLD, text_align=ft.TextAlign.CENTER, expand=True), + ft.Text( + "Announcements", + weight=ft.FontWeight.BOLD, + text_align=ft.TextAlign.CENTER, + expand=True, + ), ft.Divider(), announce_list, ], @@ -76,12 +89,22 @@ def build_ui(page: Page): page.appbar.leading = ft.IconButton( ft.Icons.MENU, tooltip="Toggle sidebar", - on_click=lambda e: (setattr(page.drawer, "open", not page.drawer.open), page.update()), + on_click=lambda e: ( + setattr(page.drawer, "open", not page.drawer.open), + page.update(), + ), ) tab_manager = TabsManager(page) from ren_browser.ui.settings import open_settings_tab - page.appbar.actions = [ft.IconButton(ft.Icons.SETTINGS, tooltip="Settings", on_click=lambda e: open_settings_tab(page, tab_manager))] + + page.appbar.actions = [ + ft.IconButton( + ft.Icons.SETTINGS, + tooltip="Settings", + on_click=lambda e: open_settings_tab(page, tab_manager), + ) + ] Shortcuts(page, tab_manager) url_bar = ft.Row( controls=[ @@ -91,15 +114,19 @@ def build_ui(page: Page): ) page.appbar.title = url_bar orig_select_tab = tab_manager.select_tab + def _select_tab_and_update_url(i): orig_select_tab(i) tab = tab_manager.manager.tabs[i] url_bar.controls.clear() url_bar.controls.extend([tab["url_field"], tab["go_btn"]]) page.update() + tab_manager.select_tab = _select_tab_and_update_url + def _update_content_width(e=None): tab_manager.content_container.width = page.width + _update_content_width() page.on_resized = lambda e: (_update_content_width(), page.update()) main_area = ft.Column(