mirror of
https://git.ianrenton.com/ian/spothole.git
synced 2026-09-21 14:57:42 +00:00
125 lines
5.3 KiB
Python
125 lines
5.3 KiB
Python
from __future__ import annotations
|
|
|
|
import logging
|
|
import threading
|
|
import time
|
|
from collections.abc import Sequence
|
|
from typing import TYPE_CHECKING
|
|
|
|
from core.config import config, create_provider_from_config
|
|
|
|
if TYPE_CHECKING:
|
|
# Can't find a way to resolve the circular dependency on types but apparently this is a way of managing that
|
|
# while still having type safety in method definitions.
|
|
from providers.activityrefdata.activity_ref_data_provider import ActivityRefDataProvider
|
|
from providers.alert.alert_provider import AlertProvider
|
|
from providers.callsigndata.callsign_data_provider import CallsignDataProvider
|
|
from providers.solarconditions.solar_conditions_provider import SolarConditionsProvider
|
|
from providers.spot.spot_provider import SpotProvider
|
|
from providers.staticdata.static_data_provider import StaticDataProvider
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class DataProviders:
|
|
"""Global object for storing data providers."""
|
|
|
|
def __init__(self) -> None:
|
|
self.spot_providers: list[SpotProvider] = []
|
|
self.alert_providers: list[AlertProvider] = []
|
|
self.solar_condition_providers: list[SolarConditionsProvider] = []
|
|
self.static_data_providers: list[StaticDataProvider] = []
|
|
self.sig_ref_data_providers: list[ActivityRefDataProvider] = []
|
|
self.callsign_data_providers: list[CallsignDataProvider] = []
|
|
self._startup_timers: list[threading.Timer] = []
|
|
|
|
def setup(self) -> None:
|
|
for entry in config["spot_providers"]:
|
|
self.spot_providers.append(create_provider_from_config("providers.spot", entry))
|
|
for entry in config["alert_providers"]:
|
|
self.alert_providers.append(create_provider_from_config("providers.alert", entry))
|
|
for entry in config.get("solar_condition_providers", []):
|
|
self.solar_condition_providers.append(create_provider_from_config("providers.solarconditions", entry))
|
|
for entry in config.get("static_data_providers", []):
|
|
self.static_data_providers.append(create_provider_from_config("providers.staticdata", entry))
|
|
for entry in config.get("sig_ref_data_providers", []):
|
|
self.sig_ref_data_providers.append(create_provider_from_config("providers.activityrefdata", entry))
|
|
for entry in config.get("callsign_data_providers", []):
|
|
self.callsign_data_providers.append(create_provider_from_config("providers.callsigndata", entry))
|
|
|
|
@staticmethod
|
|
def start_providers(
|
|
providers: Sequence[
|
|
SpotProvider | AlertProvider | SolarConditionsProvider | StaticDataProvider | ActivityRefDataProvider | CallsignDataProvider
|
|
],
|
|
provider_type: str,
|
|
) -> None:
|
|
"""Helper method to activate enabled providers in the list."""
|
|
|
|
logger.info(f"Starting {provider_type} providers...")
|
|
for p in providers:
|
|
if p.enabled:
|
|
p.start()
|
|
|
|
def start(self) -> None:
|
|
# Start data providers before spot/alert providers so the lookup data is there already for incoming spots.
|
|
# Each category is fired off after a small delay to give the rest of Spothole chance to start up.
|
|
self._startup_timers = [
|
|
threading.Timer(5.0, lambda: self.start_providers(self.static_data_providers, "static data")),
|
|
threading.Timer(10.0, lambda: self.start_providers(self.callsign_data_providers, "callsign data")),
|
|
threading.Timer(15.0, lambda: self.start_providers(self.spot_providers, "spot")),
|
|
threading.Timer(20.0, lambda: self.start_providers(self.alert_providers, "alert")),
|
|
threading.Timer(
|
|
25.0,
|
|
lambda: self.start_providers(self.solar_condition_providers, "solar condition"),
|
|
),
|
|
threading.Timer(30.0, lambda: self.start_providers(self.sig_ref_data_providers, "activity ref data")),
|
|
]
|
|
for t in self._startup_timers:
|
|
t.daemon = True
|
|
t.start()
|
|
|
|
def stop(self) -> None:
|
|
# Cancel any startup timers that haven't fired yet
|
|
for t in self._startup_timers:
|
|
t.cancel()
|
|
|
|
# Stop all providers
|
|
all_providers = [
|
|
p
|
|
for p in (
|
|
self.spot_providers
|
|
+ self.alert_providers
|
|
+ self.solar_condition_providers
|
|
+ self.sig_ref_data_providers
|
|
+ self.static_data_providers
|
|
+ self.callsign_data_providers
|
|
)
|
|
if p.enabled
|
|
]
|
|
if not all_providers:
|
|
return
|
|
|
|
def stop_provider(
|
|
p: SpotProvider | AlertProvider | SolarConditionsProvider | StaticDataProvider | ActivityRefDataProvider | CallsignDataProvider,
|
|
) -> None:
|
|
try:
|
|
p.stop()
|
|
except Exception:
|
|
logger.exception("Exception stopping provider")
|
|
|
|
threads = [threading.Thread(target=stop_provider, args=(p,), daemon=True) for p in all_providers]
|
|
for thread in threads:
|
|
thread.start()
|
|
|
|
deadline = time.monotonic() + 15
|
|
for thread in threads:
|
|
thread.join(timeout=max(0.0, deadline - time.monotonic()))
|
|
still_running = [thread for thread in threads if thread.is_alive()]
|
|
if still_running:
|
|
logger.warning("Some threads did not stop in time!")
|
|
|
|
|
|
# Global object
|
|
DATA_PROVIDERS = DataProviders()
|