More code cleanup and optimization

This commit is contained in:
Jack Kingsman
2026-02-24 19:59:46 -08:00
parent 1b76211d53
commit 561c8cf9c0
8 changed files with 42 additions and 95 deletions

View File

@@ -2,7 +2,7 @@ import asyncio
import glob
import logging
import platform
from contextlib import asynccontextmanager
from contextlib import asynccontextmanager, nullcontext
from pathlib import Path
from meshcore import MeshCore
@@ -24,12 +24,6 @@ class RadioDisconnectedError(RadioOperationError):
"""Raised when the radio disconnects between pre-check and lock acquisition."""
@asynccontextmanager
async def _noop_context():
"""No-op async context manager for optional nesting."""
yield
def detect_serial_devices() -> list[str]:
"""Detect available serial devices based on platform."""
devices: list[str] = []
@@ -196,7 +190,7 @@ class RadioManager:
self._release_operation_lock(name)
raise RadioDisconnectedError("Radio disconnected")
poll_context = _noop_context()
poll_context = nullcontext()
if pause_polling:
from app.radio_sync import pause_polling as pause_polling_context

View File

@@ -59,16 +59,14 @@ class ContactRepository:
""",
(
contact.get("public_key", "").lower(),
contact.get("name") or contact.get("adv_name"),
contact.get("name"),
contact.get("type", 0),
contact.get("flags", 0),
contact.get("last_path") or contact.get("out_path"),
contact.get("last_path_len")
if "last_path_len" in contact
else contact.get("out_path_len", -1),
contact.get("last_path"),
contact.get("last_path_len", -1),
contact.get("last_advert"),
contact.get("lat") if contact.get("lat") is not None else contact.get("adv_lat"),
contact.get("lon") if contact.get("lon") is not None else contact.get("adv_lon"),
contact.get("lat"),
contact.get("lon"),
contact.get("last_seen", int(time.time())),
contact.get("on_radio", False),
contact.get("last_contacted"),

View File

@@ -177,6 +177,33 @@ async def send_advertisement() -> dict:
return {"status": "ok"}
async def _attempt_reconnect() -> dict:
"""Shared reconnection logic for reboot and reconnect endpoints."""
if radio_manager.is_reconnecting:
return {
"status": "pending",
"message": "Reconnection already in progress",
"connected": False,
}
success = await radio_manager.reconnect()
if not success:
raise HTTPException(
status_code=503, detail="Failed to reconnect. Check radio connection and power."
)
try:
await radio_manager.post_connect_setup()
except Exception as e:
logger.exception("Post-connect setup failed after reconnect")
raise HTTPException(
status_code=503,
detail=f"Radio connected but setup failed: {e}",
) from e
return {"status": "ok", "message": "Reconnected successfully", "connected": True}
@router.post("/reboot")
async def reboot_radio() -> dict:
"""Reboot the radio, or reconnect if not currently connected.
@@ -184,7 +211,6 @@ async def reboot_radio() -> dict:
If connected: sends reboot command, connection will temporarily drop and auto-reconnect.
If not connected: attempts to reconnect (same as /reconnect endpoint).
"""
# If connected, send reboot command
if radio_manager.is_connected:
logger.info("Rebooting radio")
async with radio_manager.radio_operation("reboot_radio") as mc:
@@ -194,32 +220,8 @@ async def reboot_radio() -> dict:
"message": "Reboot command sent. Radio will reconnect automatically.",
}
# Not connected - attempt to reconnect
if radio_manager.is_reconnecting:
return {
"status": "pending",
"message": "Reconnection already in progress",
"connected": False,
}
logger.info("Radio not connected, attempting reconnect")
success = await radio_manager.reconnect()
if success:
try:
await radio_manager.post_connect_setup()
except Exception as e:
logger.exception("Post-connect setup failed after reconnect")
raise HTTPException(
status_code=503,
detail=f"Radio connected but setup failed: {e}",
) from e
return {"status": "ok", "message": "Reconnected successfully", "connected": True}
else:
raise HTTPException(
status_code=503, detail="Failed to reconnect. Check radio connection and power."
)
return await _attempt_reconnect()
@router.post("/reconnect")
@@ -234,7 +236,6 @@ async def reconnect_radio() -> dict:
if radio_manager.is_setup_complete:
return {"status": "ok", "message": "Already connected", "connected": True}
# Connected but setup incomplete — retry setup
logger.info("Radio connected but setup incomplete, retrying setup")
try:
await radio_manager.post_connect_setup()
@@ -246,28 +247,5 @@ async def reconnect_radio() -> dict:
detail=f"Radio connected but setup failed: {e}",
) from e
if radio_manager.is_reconnecting:
return {
"status": "pending",
"message": "Reconnection already in progress",
"connected": False,
}
logger.info("Manual reconnect requested")
success = await radio_manager.reconnect()
if success:
try:
await radio_manager.post_connect_setup()
except Exception as e:
logger.exception("Post-connect setup failed after reconnect")
raise HTTPException(
status_code=503,
detail=f"Radio connected but setup failed: {e}",
) from e
return {"status": "ok", "message": "Reconnected successfully", "connected": True}
else:
raise HTTPException(
status_code=503, detail="Failed to reconnect. Check radio connection and power."
)
return await _attempt_reconnect()

View File

@@ -6,7 +6,7 @@ import {
type Conversation,
type Favorite,
} from '../types';
import { getStateKey, type ConversationTimes } from '../utils/conversationState';
import { getStateKey, type ConversationTimes, type SortOrder } from '../utils/conversationState';
import { getContactDisplayName } from '../utils/pubkey';
import { ContactAvatar } from './ContactAvatar';
import { isFavorite } from '../utils/favorites';
@@ -14,8 +14,6 @@ import { Input } from './ui/input';
import { Button } from './ui/button';
import { cn } from '@/lib/utils';
type SortOrder = 'alpha' | 'recent';
type FavoriteItem = { type: 'channel'; channel: Channel } | { type: 'contact'; contact: Contact };
type ConversationRow = {

View File

@@ -32,12 +32,6 @@ body,
}
body {
font-family:
system-ui,
-apple-system,
BlinkMacSystemFont,
'Segoe UI',
sans-serif;
/* Prevent overscroll/bounce on mobile */
overscroll-behavior: none;
padding: var(--safe-area-top) var(--safe-area-right) var(--safe-area-bottom-capped)
@@ -50,13 +44,6 @@ body {
box-sizing: border-box;
}
/* Fallback for browsers without dvh support */
@supports not (height: 1dvh) {
.h-dvh {
height: 100vh;
}
}
/* Mobile sidebar override - ensures sidebar fills Sheet container */
[data-state] .sidebar {
width: 100%;

View File

@@ -15,8 +15,8 @@ const REPEATER_AVATAR = {
textColor: '#ffffff',
};
// Simple hash function for strings
function hashString(str: string): number {
// DJB2 hash function for strings
export function hashString(str: string): number {
let hash = 0;
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i);

View File

@@ -13,7 +13,7 @@ const LAST_MESSAGE_KEY = 'remoteterm-lastMessageTime';
const SORT_ORDER_KEY = 'remoteterm-sortOrder';
export type ConversationTimes = Record<string, number>;
type SortOrder = 'recent' | 'alpha';
export type SortOrder = 'recent' | 'alpha';
// In-memory cache of last message times (loaded from server on init)
let lastMessageTimesCache: ConversationTimes = {};

View File

@@ -1,5 +1,6 @@
import { MeshCoreDecoder, PayloadType } from '@michaelhart/meshcore-decoder';
import { CONTACT_TYPE_REPEATER, type Contact, type RawPacket } from '../types';
import { hashString } from './contactAvatar';
// =============================================================================
// TYPES
@@ -114,15 +115,6 @@ export const PACKET_LEGEND_ITEMS = [
// UTILITY FUNCTIONS (Data Layer)
// =============================================================================
function simpleHash(str: string): string {
let hash = 0;
for (let i = 0; i < str.length; i++) {
hash = (hash << 5) - hash + str.charCodeAt(i);
hash = hash & hash;
}
return Math.abs(hash).toString(16).padStart(8, '0');
}
export function parsePacket(hexData: string): ParsedPacket | null {
try {
const decoded = MeshCoreDecoder.decode(hexData);
@@ -182,7 +174,7 @@ export function getPacketLabel(payloadType: number): PacketLabel {
}
export function generatePacketKey(parsed: ParsedPacket, rawPacket: RawPacket): string {
const contentHash = (parsed.messageHash || simpleHash(rawPacket.data)).slice(0, 8);
const contentHash = (parsed.messageHash || hashString(rawPacket.data).toString(16).padStart(8, '0')).slice(0, 8);
if (parsed.payloadType === PayloadType.Advert && parsed.advertPubkey) {
return `ad:${parsed.advertPubkey.slice(0, 12)}`;