Refactor PageNode class to improve page and file registration logic

- Consolidated page and file scanning methods to return lists of served pages and files.
- Improved error handling in file reading operations.
- Updated the announce loop to use a more efficient waiting mechanism.
- Improved command-line argument handling for log level configuration.
This commit is contained in:
2025-12-02 09:58:31 -06:00
parent 382413dc08
commit 71bd49bd7d

View File

@@ -124,9 +124,10 @@ class PageNode:
def register_pages(self): def register_pages(self):
"""Scan pages directory and register request handlers for all .mu files.""" """Scan pages directory and register request handlers for all .mu files."""
pages = self._scan_pages(self.pagespath)
with self._lock: with self._lock:
self.servedpages = [] self.servedpages = pages
self._scan_pages(self.pagespath)
pagespath = Path(self.pagespath) pagespath = Path(self.pagespath)
@@ -137,7 +138,7 @@ class PageNode:
allow=RNS.Destination.ALLOW_ALL, allow=RNS.Destination.ALLOW_ALL,
) )
for full_path in self.servedpages: for full_path in pages:
rel = full_path[len(str(pagespath)) :] rel = full_path[len(str(pagespath)) :]
if not rel.startswith("/"): if not rel.startswith("/"):
rel = "/" + rel rel = "/" + rel
@@ -150,13 +151,14 @@ class PageNode:
def register_files(self): def register_files(self):
"""Scan files directory and register request handlers for all files.""" """Scan files directory and register request handlers for all files."""
files = self._scan_files(self.filespath)
with self._lock: with self._lock:
self.servedfiles = [] self.servedfiles = files
self._scan_files(self.filespath)
filespath = Path(self.filespath) filespath = Path(self.filespath)
for full_path in self.servedfiles: for full_path in files:
rel = full_path[len(str(filespath)) :] rel = full_path[len(str(filespath)) :]
if not rel.startswith("/"): if not rel.startswith("/"):
rel = "/" + rel rel = "/" + rel
@@ -170,23 +172,31 @@ class PageNode:
def _scan_pages(self, base): def _scan_pages(self, base):
base_path = Path(base) base_path = Path(base)
if not base_path.exists():
return []
served = []
for entry in base_path.iterdir(): for entry in base_path.iterdir():
if entry.name.startswith("."): if entry.name.startswith("."):
continue continue
if entry.is_dir(): if entry.is_dir():
self._scan_pages(str(entry)) served.extend(self._scan_pages(entry))
elif entry.is_file() and not entry.name.endswith(".allowed"): elif entry.is_file() and not entry.name.endswith(".allowed"):
self.servedpages.append(str(entry)) served.append(str(entry))
return served
def _scan_files(self, base): def _scan_files(self, base):
base_path = Path(base) base_path = Path(base)
if not base_path.exists():
return []
served = []
for entry in base_path.iterdir(): for entry in base_path.iterdir():
if entry.name.startswith("."): if entry.name.startswith("."):
continue continue
if entry.is_dir(): if entry.is_dir():
self._scan_files(str(entry)) served.extend(self._scan_files(entry))
elif entry.is_file(): elif entry.is_file():
self.servedfiles.append(str(entry)) served.append(str(entry))
return served
@staticmethod @staticmethod
def serve_default_index( def serve_default_index(
@@ -217,16 +227,21 @@ class PageNode:
if not str(file_path).startswith(str(pagespath)): if not str(file_path).startswith(str(pagespath)):
return DEFAULT_NOTALLOWED.encode("utf-8") return DEFAULT_NOTALLOWED.encode("utf-8")
try: try:
with file_path.open("rb") as _f: with file_path.open("rb") as file_handle:
first_line = _f.readline() first_line = file_handle.readline()
is_script = first_line.startswith(b"#!") is_script = first_line.startswith(b"#!")
except Exception: if not is_script:
is_script = False file_handle.seek(0)
return file_handle.read()
except FileNotFoundError:
return DEFAULT_NOTALLOWED.encode("utf-8")
except OSError as err:
RNS.log(f"Error reading page {file_path}: {err}", RNS.LOG_ERROR)
return DEFAULT_NOTALLOWED.encode("utf-8")
if is_script and os.access(str(file_path), os.X_OK): if is_script and os.access(str(file_path), os.X_OK):
try: try:
env_map = {} env_map = os.environ.copy()
if "PATH" in os.environ:
env_map["PATH"] = os.environ["PATH"]
if _link_id is not None: if _link_id is not None:
env_map["link_id"] = RNS.hexrep(_link_id, delimit=False) env_map["link_id"] = RNS.hexrep(_link_id, delimit=False)
if remote_identity is not None: if remote_identity is not None:
@@ -249,8 +264,18 @@ class PageNode:
return result.stdout return result.stdout
except Exception as e: except Exception as e:
RNS.log(f"Error executing script page: {e}", RNS.LOG_ERROR) RNS.log(f"Error executing script page: {e}", RNS.LOG_ERROR)
with file_path.open("rb") as f: try:
return f.read() return self._read_file_bytes(file_path)
except FileNotFoundError:
return DEFAULT_NOTALLOWED.encode("utf-8")
except OSError as err:
RNS.log(f"Error reading page fallback {file_path}: {err}", RNS.LOG_ERROR)
return DEFAULT_NOTALLOWED.encode("utf-8")
@staticmethod
def _read_file_bytes(file_path):
with file_path.open("rb") as file_handle:
return file_handle.read()
def serve_file( def serve_file(
self, self,
@@ -278,15 +303,33 @@ class PageNode:
"""Handle new link connections.""" """Handle new link connections."""
def _announce_loop(self): def _announce_loop(self):
interval_seconds = max(self.announce_interval, 0) * 60
try: try:
while not self._stop_event.is_set(): while not self._stop_event.is_set():
if time.time() - self.last_announce > self.announce_interval * 60: now = time.time()
if self.name: if (
self.destination.announce(app_data=self.name.encode("utf-8")) self.last_announce == 0
else: or now - self.last_announce >= interval_seconds
self.destination.announce() ):
self.last_announce = time.time() try:
time.sleep(1) if self.name:
self.destination.announce(
app_data=self.name.encode("utf-8"),
)
else:
self.destination.announce()
self.last_announce = time.time()
except Exception as announce_error:
RNS.log(
f"Error during announce: {announce_error}", RNS.LOG_ERROR,
)
wait_time = max(
(self.last_announce + interval_seconds) - time.time()
if self.last_announce
else 0,
1,
)
self._stop_event.wait(min(wait_time, 60))
except Exception as e: except Exception as e:
RNS.log(f"Error in announce loop: {e}", RNS.LOG_ERROR) RNS.log(f"Error in announce loop: {e}", RNS.LOG_ERROR)
@@ -296,17 +339,37 @@ class PageNode:
now = time.time() now = time.time()
if ( if (
self.page_refresh_interval > 0 self.page_refresh_interval > 0
and now - self.last_page_refresh > self.page_refresh_interval and now - self.last_page_refresh >= self.page_refresh_interval
): ):
self.register_pages() self.register_pages()
self.last_page_refresh = now self.last_page_refresh = time.time()
if ( if (
self.file_refresh_interval > 0 self.file_refresh_interval > 0
and now - self.last_file_refresh > self.file_refresh_interval and now - self.last_file_refresh >= self.file_refresh_interval
): ):
self.register_files() self.register_files()
self.last_file_refresh = now self.last_file_refresh = time.time()
time.sleep(1)
wait_candidates = []
if self.page_refresh_interval > 0:
wait_candidates.append(
max(
(self.last_page_refresh + self.page_refresh_interval)
- time.time(),
0.5,
),
)
if self.file_refresh_interval > 0:
wait_candidates.append(
max(
(self.last_file_refresh + self.file_refresh_interval)
- time.time(),
0.5,
),
)
wait_time = min(wait_candidates) if wait_candidates else 1.0
self._stop_event.wait(min(wait_time, 60))
except Exception as e: except Exception as e:
RNS.log(f"Error in refresh loop: {e}", RNS.LOG_ERROR) RNS.log(f"Error in refresh loop: {e}", RNS.LOG_ERROR)
@@ -430,19 +493,40 @@ def main():
files_dir = get_config_value(args.files_dir, str(Path.cwd() / "files"), "files-dir") files_dir = get_config_value(args.files_dir, str(Path.cwd() / "files"), "files-dir")
node_name = get_config_value(args.node_name, None, "node-name") node_name = get_config_value(args.node_name, None, "node-name")
announce_interval = get_config_value( announce_interval = get_config_value(
args.announce_interval, 360, "announce-interval", int, args.announce_interval,
360,
"announce-interval",
int,
) )
identity_dir = get_config_value( identity_dir = get_config_value(
args.identity_dir, str(Path.cwd() / "node-config"), "identity-dir", args.identity_dir,
str(Path.cwd() / "node-config"),
"identity-dir",
) )
page_refresh_interval = get_config_value( page_refresh_interval = get_config_value(
args.page_refresh_interval, 0, "page-refresh-interval", int, args.page_refresh_interval,
0,
"page-refresh-interval",
int,
) )
file_refresh_interval = get_config_value( file_refresh_interval = get_config_value(
args.file_refresh_interval, 0, "file-refresh-interval", int, args.file_refresh_interval,
0,
"file-refresh-interval",
int,
) )
log_level = get_config_value(args.log_level, "INFO", "log-level") log_level = get_config_value(args.log_level, "INFO", "log-level")
# Set RNS log level based on command line argument
log_level_map = {
"CRITICAL": RNS.LOG_CRITICAL,
"ERROR": RNS.LOG_ERROR,
"WARNING": RNS.LOG_WARNING,
"INFO": RNS.LOG_INFO,
"DEBUG": RNS.LOG_DEBUG,
}
RNS.loglevel = log_level_map.get(log_level.upper(), RNS.LOG_INFO)
RNS.Reticulum(configpath) RNS.Reticulum(configpath)
Path(identity_dir).mkdir(parents=True, exist_ok=True) Path(identity_dir).mkdir(parents=True, exist_ok=True)
identity_file = Path(identity_dir) / "identity" identity_file = Path(identity_dir) / "identity"