updated plugin refactor and added FTP and SCP plugins , also hydrusnetwork plugin migration

This commit is contained in:
2026-04-27 21:17:53 -07:00
parent bfd5c20dc3
commit 8685fbb723
24 changed files with 3650 additions and 405 deletions
+147 -1
View File
@@ -1,3 +1,6 @@
import datetime
import sqlite3
from pathlib import Path
from typing import List, Dict, Any, Optional, Sequence
from SYS.cmdlet_spec import Cmdlet, CmdletArg
@@ -7,17 +10,20 @@ from SYS.config import (
save_config_and_verify,
set_nested_config_value,
)
from SYS.database import LOG_DB_PATH, db
from SYS.logger import log
from SYS import pipeline as ctx
from SYS.result_table import Table
from cmdnat._parsing import (
extract_piped_value as _extract_piped_value,
extract_value_arg as _extract_value_arg,
has_flag as _has_flag,
)
CMDLET = Cmdlet(
name=".config",
summary="Manage configuration settings",
usage=".config [key] [value]",
usage=".config [key] [value] | .config -log [count]",
arg=[
CmdletArg(
name="key",
@@ -33,6 +39,140 @@ CMDLET = Cmdlet(
)
def _extract_log_limit(args: Sequence[str], default: int = 30) -> int:
try:
tokens = [str(arg).strip() for arg in (args or []) if str(arg).strip()]
except Exception:
return default
for idx, token in enumerate(tokens):
lowered = token.lower()
if lowered in {"-log", "--log"}:
if idx + 1 < len(tokens):
candidate = tokens[idx + 1]
if candidate and not candidate.startswith("-"):
try:
return max(1, min(200, int(candidate)))
except Exception:
return default
return default
if lowered.startswith("-log=") or lowered.startswith("--log="):
_, value = lowered.split("=", 1)
try:
return max(1, min(200, int(value)))
except Exception:
return default
return default
def _fallback_log_path() -> Path:
return Path(db.db_path).with_name("logs") / "log_fallback.txt"
def _load_recent_config_logs(limit: int = 30) -> List[Dict[str, str]]:
rows: List[Dict[str, str]] = []
sql = """
SELECT timestamp, level, module, message
FROM logs
WHERE lower(module) LIKE ?
OR lower(message) LIKE ?
OR lower(message) LIKE ?
OR lower(message) LIKE ?
ORDER BY id DESC
LIMIT ?
"""
params = (
"%config%",
"%config%",
"%save failed%",
"%saving configuration failed%",
int(limit),
)
try:
with sqlite3.connect(str(LOG_DB_PATH), timeout=5.0) as conn:
conn.row_factory = sqlite3.Row
cur = conn.cursor()
cur.execute(sql, params)
fetched = cur.fetchall()
cur.close()
for row in fetched:
rows.append(
{
"timestamp": str(row["timestamp"] or ""),
"level": str(row["level"] or ""),
"module": str(row["module"] or ""),
"message": str(row["message"] or ""),
}
)
except Exception:
rows = []
if rows:
return rows
fallback = _fallback_log_path()
try:
if not fallback.exists():
return []
lines = fallback.read_text(encoding="utf-8", errors="replace").splitlines()
matches = [
line for line in lines
if any(term in line.lower() for term in ("config", "save failed", "saving configuration failed"))
]
for line in reversed(matches[-limit:]):
rows.append(
{
"timestamp": "",
"level": "FALLBACK",
"module": "fallback",
"message": line,
}
)
except Exception:
return []
return rows
def _format_log_timestamp_local(raw_value: str) -> str:
text = str(raw_value or "").strip()
if not text:
return ""
for pattern in ("%Y-%m-%d %H:%M:%S", "%Y-%m-%d %H:%M:%S.%f"):
try:
parsed = datetime.datetime.strptime(text, pattern).replace(tzinfo=datetime.timezone.utc)
return parsed.astimezone().strftime("%Y-%m-%d %H:%M:%S")
except Exception:
continue
return text
def _show_config_logs(args: Sequence[str]) -> int:
limit = _extract_log_limit(args)
rows = _load_recent_config_logs(limit=limit)
if not rows:
print(
f"No recent config/save logs found in {LOG_DB_PATH.name} or {_fallback_log_path().name}."
)
return 0
table = Table("Configuration Logs")
table.set_table("config.logs")
table.set_source_command(".config", ["-log", str(limit)])
for row_data in rows:
row = table.add_row()
row.add_column("Time (local)", _format_log_timestamp_local(row_data.get("timestamp", "")))
row.add_column("Level", row_data.get("level", ""))
row.add_column("Module", row_data.get("module", ""))
row.add_column("Message", row_data.get("message", ""))
ctx.set_last_result_table_overlay(table, rows)
ctx.set_current_stage_table(table)
print(f"Showing {len(rows)} recent configuration log entries.")
return 0
def flatten_config(config: Dict[str, Any], parent_key: str = "", sep: str = ".") -> List[Dict[str, Any]]:
items: List[Dict[str, Any]] = []
for k, v in config.items():
@@ -108,6 +248,9 @@ def _strip_value_quotes(value: str) -> str:
def _run(piped_result: Any, args: List[str], config: Dict[str, Any]) -> int:
import sys
if _has_flag(args, "-log") or _has_flag(args, "--log"):
return _show_config_logs(args)
# Load configuration from the database
current_config = load_config()
@@ -135,6 +278,7 @@ def _run(piped_result: Any, args: List[str], config: Dict[str, Any]) -> int:
try:
save_config_and_verify(current_config)
except Exception as exc:
log(f"Configuration save verification failed for '{selection_key}': {exc}")
print(f"Error saving configuration (verification failed): {exc}")
return 1
else:
@@ -142,6 +286,7 @@ def _run(piped_result: Any, args: List[str], config: Dict[str, Any]) -> int:
print(f"Updated '{selection_key}' to '{new_value}'")
return 0
except Exception as exc:
log(f"Error updating config '{selection_key}': {exc}")
print(f"Error updating config: {exc}")
return 1
@@ -201,6 +346,7 @@ def _run(piped_result: Any, args: List[str], config: Dict[str, Any]) -> int:
print(f"Updated '{key}' to '{value}'")
return 0
except Exception as exc:
log(f"Error updating config '{key}': {exc}")
print(f"Error updating config: {exc}")
return 1