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
+122 -1
View File
@@ -32,7 +32,7 @@ from SYS.plugin_config import (
get_item_schema_map,
get_required_config_keys,
)
from ProviderCore.registry import get_plugin
from ProviderCore.registry import get_plugin, get_plugin_class
from TUI.modalscreen.matrix_room_picker import MatrixRoomPicker
from TUI.modalscreen.selection_modal import SelectionModal
import logging
@@ -164,9 +164,12 @@ class ConfigModal(ModalScreen):
self.editing_item_type = None # 'store' or 'provider'
self.editing_item_name = None
self._button_id_map = {}
self._provider_button_map: Dict[str, tuple[str, str]] = {}
self._input_id_map = {}
self._matrix_status: Optional[Static] = None
self._matrix_test_running = False
self._provider_status: Optional[Static] = None
self._provider_action_running = False
self._editor_snapshot: Optional[Dict[str, Any]] = None
# Inline matrix rooms controls
self._matrix_inline_list: Optional[ListView] = None
@@ -256,6 +259,7 @@ class ConfigModal(ModalScreen):
return
self._button_id_map.clear()
self._provider_button_map.clear()
self._input_id_map.clear()
# Clear existing
@@ -594,6 +598,33 @@ class ConfigModal(ModalScreen):
row.mount(Button("Paste", id=f"paste-{inp_id}", classes="paste-btn"))
idx += 1
if item_type == "provider" and isinstance(item_name, str):
provider = self._instantiate_provider_for_editor(item_name, self.config_data)
if provider is not None:
provider_actions = provider.config_actions() or []
if provider_actions:
container.mount(Rule())
container.mount(Label(f"{provider.label} helpers", classes="config-label"))
helper_text = str(provider.config_helper_text() or "Use these helpers to validate provider settings.").strip()
status = Static(helper_text, id="provider-status")
container.mount(status)
self._provider_status = status
row = Horizontal(classes="field-row")
container.mount(row)
for action in provider_actions:
action_id = str(action.get("id") or "").strip()
if not action_id:
continue
button_id = f"provider-action-{item_name}-{action_id}".replace(" ", "-")
self._provider_button_map[button_id] = (item_name, action_id)
row.mount(
Button(
str(action.get("label") or action_id.replace("_", " ").title()),
id=button_id,
variant=str(action.get("variant") or "default"),
)
)
if (
item_type == "provider"
and isinstance(item_name, str)
@@ -755,6 +786,10 @@ class ConfigModal(ModalScreen):
self.editing_item_type = None
self.refresh_view()
except Exception as exc:
try:
log(f"Configuration save failed: {exc}")
except Exception:
logger.exception("Failed to write save failure to logs")
self.notify(f"Save failed: {exc}", severity="error", timeout=10)
elif bid == "save-durable-btn":
# Perform a synchronous, verified save and notify status to the user.
@@ -788,6 +823,10 @@ class ConfigModal(ModalScreen):
self.refresh_view()
self._editor_snapshot = None
except Exception as exc:
try:
log(f"Durable configuration save failed: {exc}")
except Exception:
logger.exception("Failed to write durable save failure to logs")
self.notify(f"Durable save failed: {exc}", severity="error", timeout=10)
try:
log(f"Durable save failed: {exc}")
@@ -823,8 +862,15 @@ class ConfigModal(ModalScreen):
saved = self.save_all()
self.notify("Saving configuration...", timeout=3)
except Exception as exc:
try:
log(f"Configuration save failed while deleting config entry: {exc}")
except Exception:
logger.exception("Failed to write config delete save failure to logs")
self.notify(f"Save failed: {exc}", severity="error", timeout=10)
self.refresh_view()
elif bid in self._provider_button_map:
provider_name, action_id = self._provider_button_map[bid]
self._request_provider_action(provider_name, action_id)
elif bid == "add-store-btn":
options = get_configurable_store_types()
self.app.push_screen(SelectionModal("Select Store Type", options), callback=self.on_store_type_selected)
@@ -882,6 +928,10 @@ class ConfigModal(ModalScreen):
try:
entries = save_config(self.config_data)
except Exception as exc:
try:
log(f"Saving Matrix default rooms failed: {exc}")
except Exception:
logger.exception("Failed to write Matrix room save failure to logs")
if self._matrix_status:
self._matrix_status.update(f"Saving default rooms failed: {exc}")
return
@@ -921,6 +971,69 @@ class ConfigModal(ModalScreen):
else:
self.notify("Clipboard not supported in this terminal", severity="warning")
def _instantiate_provider_for_editor(self, provider_name: str, config_data: Optional[Dict[str, Any]] = None) -> Optional[Any]:
try:
provider_class = get_plugin_class(provider_name)
except Exception:
provider_class = None
if provider_class is None:
return None
try:
return provider_class(config_data or self.config_data)
except Exception:
logger.exception("Failed to instantiate provider '%s' for config helper", provider_name)
return None
def _request_provider_action(self, provider_name: str, action_id: str) -> None:
if self._provider_action_running:
return
self._synchronize_inputs_to_config()
self._provider_action_running = True
if self._provider_status is not None:
self._provider_status.update(f"Running {action_id.replace('_', ' ')}")
self._provider_action_background(provider_name, action_id, deepcopy(self.config_data))
@work(thread=True)
def _provider_action_background(self, provider_name: str, action_id: str, config_snapshot: Dict[str, Any]) -> None:
try:
provider = self._instantiate_provider_for_editor(provider_name, config_snapshot)
if provider is None:
raise RuntimeError(f"Provider '{provider_name}' is unavailable")
result = provider.run_config_action(action_id)
if not isinstance(result, dict):
result = {"ok": False, "message": f"Provider '{provider_name}' returned an invalid config action result."}
except Exception as exc:
result = {"ok": False, "message": str(exc) or f"Provider action '{action_id}' failed."}
try:
self.app.call_from_thread(self._provider_action_complete, provider_name, action_id, result)
except Exception:
self._provider_action_complete(provider_name, action_id, result)
def _provider_action_complete(self, provider_name: str, action_id: str, result: Dict[str, Any]) -> None:
self._provider_action_running = False
ok = bool(result.get("ok"))
message = str(result.get("message") or f"Provider action '{action_id}' finished.")
updates = result.get("config_updates")
if ok and isinstance(updates, dict):
provider_block = self.config_data.setdefault("provider", {}).setdefault(provider_name, {})
if isinstance(provider_block, dict):
provider_block.update(updates)
message = f"{message}"
try:
self.refresh_view()
except Exception:
logger.exception("Failed to refresh config view after provider action")
if self._provider_status is not None:
self._provider_status.update(message)
try:
self.notify(message, severity="error" if not ok else "information", timeout=8)
except Exception:
logger.exception("Failed to notify provider action result for %s/%s", provider_name, action_id)
# Backup/restore helpers removed: forensics/audit mode disabled and restore UI removed.
def on_store_type_selected(self, stype: str) -> None:
@@ -1130,6 +1243,10 @@ class ConfigModal(ModalScreen):
try:
entries = save_config(self.config_data)
except Exception as exc:
try:
log(f"Saving configuration before Matrix test failed: {exc}")
except Exception:
logger.exception("Failed to write Matrix test pre-save failure to logs")
if self._matrix_status:
self._matrix_status.update(f"Saving configuration failed: {exc}")
self._matrix_test_running = False
@@ -1290,6 +1407,10 @@ class ConfigModal(ModalScreen):
try:
entries = save_config(self.config_data)
except Exception as exc:
try:
log(f"Saving configuration before Matrix room load failed: {exc}")
except Exception:
logger.exception("Failed to write Matrix load pre-save failure to logs")
if self._matrix_status:
self._matrix_status.update(f"Saving configuration failed: {exc}")
self._matrix_test_running = False