Initial commit
This commit is contained in:
167
plugin.py
Normal file
167
plugin.py
Normal file
@@ -0,0 +1,167 @@
|
||||
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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user