Files
spothole/core/data_providers.py
T

124 lines
5.3 KiB
Python

from __future__ import annotations
import logging
import threading
import time
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: list[
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 t in threads:
t.start()
deadline = time.monotonic() + 15
for t in threads:
t.join(timeout=max(0.0, deadline - time.monotonic()))
still_running = [t for t in threads if t.is_alive()]
if still_running:
logger.warning("Some threads did not stop in time!")
# Global object
DATA_PROVIDERS = DataProviders()