Files
QGIS_BAPEBridge/plugin.py
2026-06-04 17:11:29 +02:00

168 lines
5.5 KiB
Python

from PyQt5.QtCore import QTimer
from PyQt5.QtWidgets import QAction
from qgis.core import Qgis, QgsProject
from qgis.gui import QgisInterface
from .api_client import ApiClient
from .layer_manager import LocationLayerManager
from .notification_server import NotificationServer
_RETRY_INTERVAL_MS = 30_000 # 30 seconds between automatic retries
_API_BASE_URL = "http://localhost:22650"
_NOTIF_PORT = 22651
class BAPEBridgePlugin:
"""Main QGIS plugin class.
Wires together ApiClient, LocationLayerManager and NotificationServer.
Provides a checkable toolbar button to toggle the layer on/off.
"""
def __init__(self, iface: QgisInterface):
self._iface = iface
self._active = False
self._layer_manager: LocationLayerManager | None = None
self._notif_server: NotificationServer | None = None
self._toggle_action: QAction | None = None
# Timer drives periodic retry when the initial API call fails.
self._retry_timer = QTimer()
self._retry_timer.setInterval(_RETRY_INTERVAL_MS)
self._retry_timer.timeout.connect(self._retry_load)
QgsProject.instance().cleared.connect(self._on_project_cleared)
# ------------------------------------------------------------------
# QGIS plugin lifecycle
# ------------------------------------------------------------------
def initGui(self) -> None:
"""Add entry to the Layer menu."""
self._toggle_action = QAction("BAPE Bridge", self._iface.mainWindow())
self._toggle_action.setCheckable(True)
self._toggle_action.setToolTip("Enable / disable the BAPE Bridge layer")
self._toggle_action.triggered.connect(self._on_toggle)
self._iface.addPluginToLayerMenu("BAPE Bridge", self._toggle_action)
def unload(self) -> None:
"""Clean up when the plugin is disabled or QGIS closes."""
self._deactivate()
self._iface.removePluginFromLayerMenu("BAPE Bridge", self._toggle_action)
# ------------------------------------------------------------------
# Toggle on / off
# ------------------------------------------------------------------
def _on_toggle(self, checked: bool) -> None:
if checked:
self._activate()
else:
self._deactivate()
def _activate(self) -> None:
if self._active:
return
api_client = ApiClient(_API_BASE_URL)
self._layer_manager = LocationLayerManager(api_client)
# Create the layer structure (always succeeds). Initial feature load
# may fail if the API is not yet reachable; we surface a message and
# start the retry timer in that case.
try:
self._layer_manager.create_layer()
except Exception:
self._retry_timer.start()
# Start the notification server regardless; the layer may be empty
# but the server should be ready to receive reload calls.
self._notif_server = NotificationServer(
self._on_reload_requested,
self._on_clear_requested,
port=_NOTIF_PORT,
)
try:
self._notif_server.start()
except RuntimeError as exc:
self._iface.messageBar().pushMessage(
"BAPE Bridge",
f"Notification server error: {exc}",
level=Qgis.Critical,
duration=0,
)
self._active = True
def _deactivate(self) -> None:
if not self._active:
return
self._retry_timer.stop()
if self._notif_server is not None:
self._notif_server.stop()
self._notif_server = None
if self._layer_manager is not None:
self._layer_manager.remove_layer()
self._layer_manager = None
self._active = False
if self._toggle_action is not None:
self._toggle_action.setChecked(False)
# ------------------------------------------------------------------
# Reload handling
# ------------------------------------------------------------------
def _on_reload_requested(self) -> None:
"""Called (in the main Qt thread) when POST /reload arrives."""
if self._layer_manager is None:
return
success = self._layer_manager.reload_layer()
if not success:
pass # layer name already shows 🔴
def _on_clear_requested(self) -> None:
"""Called (in the main Qt thread) when POST /clear arrives."""
if self._layer_manager is None:
return
self._layer_manager.clear_layer()
def _retry_load(self) -> None:
"""Periodic retry: stop the timer once features load successfully."""
if self._layer_manager is None:
self._retry_timer.stop()
return
if self._layer_manager.reload_layer():
self._retry_timer.stop()
def _on_project_cleared(self) -> None:
"""Called when QGIS clears the project (open new/different project).
The C++ layer objects are already deleted by this point, so we must
not touch them — just drop references and reset state.
"""
self._retry_timer.stop()
if self._notif_server is not None:
self._notif_server.stop()
self._notif_server = None
if self._layer_manager is not None:
self._layer_manager.layer = None
self._layer_manager = None
self._active = False
if self._toggle_action is not None:
self._toggle_action.setChecked(False)