Code review fixes

This commit is contained in:
Ian Renton
2026-09-18 07:56:08 +01:00
parent 0fa8cd763d
commit a03e1336c8
18 changed files with 113 additions and 66 deletions
+1
View File
@@ -106,6 +106,7 @@ class DataStore:
and testing the callsign every time is expensive. So instead we build a separate in-memory lookup of compiled and testing the callsign every time is expensive. So instead we build a separate in-memory lookup of compiled
regex against DXCC entity code, as a list of tuples we can iterate through.""" regex against DXCC entity code, as a list of tuples we can iterate through."""
self.dxcc_lookup_by_call_regex = []
for entry in [DATA_STORE.dxcc_data[key] for key in DATA_STORE.dxcc_data]: for entry in [DATA_STORE.dxcc_data[key] for key in DATA_STORE.dxcc_data]:
self.dxcc_lookup_by_call_regex.append((re.compile(entry["prefixRegex"]), entry["entityCode"])) self.dxcc_lookup_by_call_regex.append((re.compile(entry["prefixRegex"]), entry["entityCode"]))
+4 -3
View File
@@ -22,6 +22,7 @@ class LiveDataCache:
self._listeners_lock = threading.Lock() self._listeners_lock = threading.Lock()
self._snapshot_dir = snapshot_dir self._snapshot_dir = snapshot_dir
self._disk_cache = diskcache.Cache(str(snapshot_dir)) self._disk_cache = diskcache.Cache(str(snapshot_dir))
self._stop_event = threading.Event()
self._load_snapshot() self._load_snapshot()
self._start_periodic_snapshot(snapshot_interval_sec) self._start_periodic_snapshot(snapshot_interval_sec)
@@ -89,13 +90,13 @@ class LiveDataCache:
def _start_periodic_snapshot(self, interval): def _start_periodic_snapshot(self, interval):
def loop(): def loop():
while True: while not self._stop_event.wait(timeout=interval):
time.sleep(interval)
self.save_snapshot() self.save_snapshot()
t = threading.Thread(target=loop, name=f"LiveDataCache-Snapshot-{self._snapshot_dir}") t = threading.Thread(target=loop, name=f"LiveDataCache-Snapshot-{self._snapshot_dir}", daemon=True)
t.start() t.start()
def close(self): def close(self):
self._stop_event.set()
self.save_snapshot() self.save_snapshot()
self._disk_cache.close() self._disk_cache.close()
+4 -4
View File
@@ -24,7 +24,7 @@ def get_sig_ref_info(sig_name, ref_id):
# Sometimes we allow spaces instead of dashes in references due to common usage that way, but official reference # Sometimes we allow spaces instead of dashes in references due to common usage that way, but official reference
# lists never do, so convert them here. # lists never do, so convert them here.
ref_id.replace(" ", "-") ref_id = ref_id.replace(" ", "-")
# Prepare the object to be returned # Prepare the object to be returned
sig_ref = SIGRef(sig=sig_name, id=ref_id) sig_ref = SIGRef(sig=sig_name, id=ref_id)
@@ -117,9 +117,9 @@ def get_sig_ref_info(sig_name, ref_id):
try: try:
lookup_data = DATA_STORE.sigrefs.get(key) if key in DATA_STORE.sigrefs else None lookup_data = DATA_STORE.sigrefs.get(key) if key in DATA_STORE.sigrefs else None
if lookup_data: if lookup_data:
for key, value in lookup_data.__dict__.items(): for attr, value in lookup_data.__dict__.items():
if value is not None and sig_ref.__dict__.get(key) is None: if value is not None and sig_ref.__dict__.get(attr) is None:
sig_ref.__dict__[key] = value sig_ref.__dict__[attr] = value
else: else:
# Maybe a super new reference we don't know about yet, but more likely a typo or a test reference, # Maybe a super new reference we don't know about yet, but more likely a typo or a test reference,
+1 -2
View File
@@ -14,14 +14,13 @@ class URLDataCache(CachedSession):
used across multiple threads, though note that URL lookups will block each other this way, so it is still better to used across multiple threads, though note that URL lookups will block each other this way, so it is still better to
create one of these objects per thread if possible.""" create one of these objects per thread if possible."""
_lock = threading.Lock()
def __init__(self, name): def __init__(self, name):
super().__init__( super().__init__(
f"{CACHE_DIR}urls/{name}", f"{CACHE_DIR}urls/{name}",
expire_after=timedelta(days=1), expire_after=timedelta(days=1),
allowable_codes=(200, 400, 401, 403, 404), allowable_codes=(200, 400, 401, 403, 404),
) )
self._lock = threading.Lock()
def get(self, *args, **kwargs): def get(self, *args, **kwargs):
with self._lock: with self._lock:
+1 -1
View File
@@ -158,7 +158,7 @@ class Alert:
self.icon = "fa-globe-africa" self.icon = "fa-globe-africa"
elif self.alert_type == AlertType.CONTEST: elif self.alert_type == AlertType.CONTEST:
self.icon = "fa-trophy" self.icon = "fa-trophy"
elif self.alert_type == AlertType.CONTEST: elif self.alert_type == AlertType.SATELLITE:
self.icon = "fa-satellite" self.icon = "fa-satellite"
elif self.sig_refs and self.sig_refs[0].icon: elif self.sig_refs and self.sig_refs[0].icon:
self.icon = self.sig_refs[0].icon self.icon = self.sig_refs[0].icon
+14 -2
View File
@@ -12,6 +12,7 @@ from pyhamtools.locator import latlong_to_locator, locator_to_latlong
from core.call_lookup_helper import get_call_info from core.call_lookup_helper import get_call_info
from core.config import MAX_SPOT_AGE from core.config import MAX_SPOT_AGE
from core.constants import PROPAGATION_MODES, SIGS from core.constants import PROPAGATION_MODES, SIGS
from core.data_store import DATA_STORE
from core.enums import Continent, LocationSourceForSpot, Mode, ModeSource, ModeType from core.enums import Continent, LocationSourceForSpot, Mode, ModeSource, ModeType
from core.geo_utils import lat_lon_to_cq_zone, lat_lon_to_itu_zone from core.geo_utils import lat_lon_to_cq_zone, lat_lon_to_itu_zone
from core.sig_lookup_helper import populate_missing_sig_ref_info from core.sig_lookup_helper import populate_missing_sig_ref_info
@@ -363,11 +364,13 @@ class Spot:
if self.propagation_mode == "Satellite": if self.propagation_mode == "Satellite":
if not self.sig: if not self.sig:
self.sig = "AMSAT" self.sig = "AMSAT"
self.sig_refs.append(SIGRef(sig="AMSAT")) if not any(sig_ref.sig == "AMSAT" for sig_ref in self.sig_refs):
self.sig_refs.append(SIGRef(sig="AMSAT"))
if self.propagation_mode == "Earth-Moon-Earth": if self.propagation_mode == "Earth-Moon-Earth":
if not self.sig: if not self.sig:
self.sig = "EME" self.sig = "EME"
self.sig_refs.append(SIGRef(sig="EME")) if not any(sig_ref.sig == "EME" for sig_ref in self.sig_refs):
self.sig_refs.append(SIGRef(sig="EME"))
# Parse "de_grid -> dx_grid" structures from the comment # Parse "de_grid -> dx_grid" structures from the comment
if self.comment: if self.comment:
@@ -445,6 +448,15 @@ class Spot:
elif self.dx_call: elif self.dx_call:
self.dx_itu_zone = dx_call_info.itu_zone self.dx_itu_zone = dx_call_info.itu_zone
# DXCC lookup from callsign if nothing else has provided it
if self.dx_call and not self.dx_dxcc_id:
for regex, entity_code in DATA_STORE.dxcc_lookup_by_call_regex:
if regex.pattern and regex.match(self.dx_call):
self.dx_dxcc_id = entity_code
break
if self.dx_dxcc_id and not self.dx_flag:
self.dx_flag = get_flag_for_dxcc(self.dx_dxcc_id)
# DX Location is "good" if it is from a spot, or from QRZ if the callsign doesn't contain a slash, so the operator # DX Location is "good" if it is from a spot, or from QRZ if the callsign doesn't contain a slash, so the operator
# is likely at home. # is likely at home.
self.dx_location_good = bool( self.dx_location_good = bool(
+52 -33
View File
@@ -1,6 +1,6 @@
import logging import logging
from datetime import datetime from datetime import datetime
from threading import Thread from threading import Event, Thread
import aprslib import aprslib
import pytz import pytz
@@ -17,49 +17,68 @@ class APRSIS(SpotProvider):
def __init__(self, provider_config): def __init__(self, provider_config):
super().__init__("APRS-IS", provider_config) super().__init__("APRS-IS", provider_config)
self._thread = Thread(target=self._connect, name="APRSISSpotProvider") self._thread = Thread(target=self._run, name="APRSISSpotProvider")
self._thread.daemon = True self._thread.daemon = True
self._aprsis = None self._aprsis = None
self._running = True
self._stop_event = Event()
def start(self): def start(self):
self._thread.start() self._thread.start()
def _connect(self): def _run(self):
self._aprsis = aprslib.IS(SERVER_OWNER_CALLSIGN) while self._running:
self.status = "Connecting" try:
logger.info("APRS-IS connecting...") self._aprsis = aprslib.IS(SERVER_OWNER_CALLSIGN)
self._aprsis.connect() self.status = "Connecting"
self._aprsis.consumer(self._handle) logger.info("APRS-IS connecting...")
logger.info("APRS-IS connected.") self._aprsis.connect()
logger.info("APRS-IS connected.")
self._aprsis.consumer(self._handle, immortal=True)
except Exception:
if self._running:
self.status = "Error"
logger.exception("Exception in APRS-IS provider")
if self._running:
self._stop_event.wait(timeout=5)
def stop(self): def stop(self):
self._running = False
self.status = "Shutting down" self.status = "Shutting down"
self._aprsis.close() self._stop_event.set()
if self._aprsis:
self._aprsis.close()
self._thread.join() self._thread.join()
def _handle(self, data): def _handle(self, data):
# Split SSID in "from" call and store separately try:
from_parts = str(data["from"]).split("-") # Split SSID in "from" call and store separately
dx_call = from_parts[0].upper() from_parts = str(data["from"]).split("-")
dx_ssid = from_parts[1].upper() if len(from_parts) > 1 else None dx_call = from_parts[0].upper()
via_parts = str(data["via"]).split("-") dx_ssid = from_parts[1].upper() if len(from_parts) > 1 else None
de_call = via_parts[0].upper() via_parts = str(data["via"]).split("-")
de_ssid = via_parts[1].upper() if len(via_parts) > 1 else None de_call = via_parts[0].upper()
spot = Spot( de_ssid = via_parts[1].upper() if len(via_parts) > 1 else None
source="APRS-IS", spot = Spot(
dx_call=dx_call, source="APRS-IS",
dx_ssid=dx_ssid, dx_call=dx_call,
de_call=de_call, dx_ssid=dx_ssid,
de_ssid=de_ssid, de_call=de_call,
comment=str(data["comment"]) if "comment" in data else None, de_ssid=de_ssid,
dx_latitude=float(data["latitude"]) if "latitude" in data else None, comment=str(data["comment"]) if "comment" in data else None,
dx_longitude=float(data["longitude"]) if "longitude" in data else None, dx_latitude=float(data["latitude"]) if "latitude" in data else None,
time=datetime.now(pytz.UTC).timestamp(), dx_longitude=float(data["longitude"]) if "longitude" in data else None,
) # APRS-IS spots are live so we can assume spot time is "now" time=datetime.now(pytz.UTC).timestamp(),
) # APRS-IS spots are live so we can assume spot time is "now"
# Add to our list # Add to our list
self._submit(spot) self._submit(spot)
self.status = "OK" self.status = "OK"
self.last_update_time = datetime.now(pytz.UTC) self.last_update_time = datetime.now(pytz.UTC)
logger.debug("Data received from APRS-IS.") logger.debug("Data received from APRS-IS.")
except Exception:
logger.exception("Exception handling APRS-IS packet")
+17 -3
View File
@@ -50,8 +50,13 @@ class WebsocketSpotProvider(SpotProvider):
self.status = "Connecting" self.status = "Connecting"
self._ws = create_connection(self._url, header=HTTP_HEADERS) self._ws = create_connection(self._url, header=HTTP_HEADERS)
self.status = "Connected" self.status = "Connected"
data = self._ws.recv()
if data: # Keep reading from this same connection until it drops or we're asked to stop, rather than
# reconnecting for every message.
while not self._stopped:
data = self._ws.recv()
if not data:
break
try: try:
new_spot = self._ws_message_to_spot(data) new_spot = self._ws_message_to_spot(data)
if new_spot: if new_spot:
@@ -69,7 +74,16 @@ class WebsocketSpotProvider(SpotProvider):
logger.exception(f"Exception in Websocket Spot Provider ({self.name})") logger.exception(f"Exception in Websocket Spot Provider ({self.name})")
else: else:
self.status = "Disconnected" self.status = "Disconnected"
sleep(5) # Wait before trying to reconnect finally:
if self._ws:
try:
self._ws.close()
except Exception:
# No problem, we were getting rid of this object anyway.
pass
self._ws = None
if not self._stopped:
sleep(5) # Wait before trying to reconnect
def _ws_message_to_spot(self, b): def _ws_message_to_spot(self, b):
"""Convert a WS message received from the API into a spot. The exact message data (in bytes) is provided here so the """Convert a WS message received from the API into a spot. The exact message data (in bytes) is provided here so the
+3 -2
View File
@@ -163,9 +163,10 @@ class TelnetServer:
# Ensure ASCII formatting for telnet clients # Ensure ASCII formatting for telnet clients
encoded_line = self._format_dxspider_spot(spot).encode("ascii", errors="ignore") encoded_line = self._format_dxspider_spot(spot).encode("ascii", errors="ignore")
# Try to write to all clients, and in the process find the ones that are disconnected # Try to write to all clients, and in the process find the ones that are disconnected. Iterate over a copy
# since a new client can connect (mutating self._clients) while we're awaiting a write below.
disconnected_clients = set() disconnected_clients = set()
for writer in self._clients: for writer in list(self._clients):
try: try:
writer.write(encoded_line) writer.write(encoded_line)
await writer.drain() await writer.drain()
+1 -1
View File
@@ -77,7 +77,7 @@
</div> </div>
<script src="/static/js/add-spot.js?v=1789713510"></script> <script src="/static/js/add-spot.js?v=1789714568"></script>
<script>$(document).ready(function () { <script>$(document).ready(function () {
$("#nav-link-add-spot").addClass("active"); $("#nav-link-add-spot").addClass("active");
}); <!-- highlight active page in nav --></script> }); <!-- highlight active page in nav --></script>
+1 -1
View File
@@ -83,7 +83,7 @@
</div> </div>
<script src="/static/js/alerts.js?v=1789713511"></script> <script src="/static/js/alerts.js?v=1789714568"></script>
<script>$(document).ready(function () { <script>$(document).ready(function () {
$("#nav-link-alerts").addClass("active"); $("#nav-link-alerts").addClass("active");
}); <!-- highlight active page in nav --></script> }); <!-- highlight active page in nav --></script>
+2 -2
View File
@@ -76,8 +76,8 @@
</div> </div>
<script src="/static/js/spotsbandsandmap.js?v=1789713510"></script> <script src="/static/js/spotsbandsandmap.js?v=1789714568"></script>
<script src="/static/js/bands.js?v=1789713510"></script> <script src="/static/js/bands.js?v=1789714568"></script>
<script>$(document).ready(function () { <script>$(document).ready(function () {
$("#nav-link-bands").addClass("active"); $("#nav-link-bands").addClass("active");
}); <!-- highlight active page in nav --></script> }); <!-- highlight active page in nav --></script>
+5 -5
View File
@@ -1,6 +1,6 @@
{% extends "skeleton.html" %} {% extends "skeleton.html" %}
{% block head_extra %} {% block head_extra %}
<link rel="stylesheet" href="/static/css/style.css?v=1789713510" type="text/css"> <link rel="stylesheet" href="/static/css/style.css?v=1789714568" type="text/css">
<link href="/static/vendor/css/bootstrap-5.3.8.min.css" rel="stylesheet"> <link href="/static/vendor/css/bootstrap-5.3.8.min.css" rel="stylesheet">
<link href="/static/vendor/css/fontawesome-6.7.2.min.css" rel="stylesheet"> <link href="/static/vendor/css/fontawesome-6.7.2.min.css" rel="stylesheet">
<link href="/static/vendor/css/solid-6.7.2.min.css" rel="stylesheet"> <link href="/static/vendor/css/solid-6.7.2.min.css" rel="stylesheet">
@@ -16,10 +16,10 @@
window.fetchEventSource = fetchEventSource; window.fetchEventSource = fetchEventSource;
</script> </script>
<script src="/static/js/utils.js?v=1789713510"></script> <script src="/static/js/utils.js?v=1789714568"></script>
<script src="/static/js/ui-ham.js?v=1789713510"></script> <script src="/static/js/ui-ham.js?v=1789714568"></script>
<script src="/static/js/geo.js?v=1789713510"></script> <script src="/static/js/geo.js?v=1789714568"></script>
<script src="/static/js/common.js?v=1789713510"></script> <script src="/static/js/common.js?v=1789714568"></script>
{% end %} {% end %}
{% block body %} {% block body %}
<div class="container"> <div class="container">
+1 -1
View File
@@ -284,7 +284,7 @@
</div> </div>
<script src="/static/vendor/js/chart-4.4.9.umd.min.js"></script> <script src="/static/vendor/js/chart-4.4.9.umd.min.js"></script>
<script src="/static/js/conditions.js?v=1789713510"></script> <script src="/static/js/conditions.js?v=1789714568"></script>
<script>$(document).ready(function () { <script>$(document).ready(function () {
$("#nav-link-conditions").addClass("active"); $("#nav-link-conditions").addClass("active");
}); <!-- highlight active page in nav --></script> }); <!-- highlight active page in nav --></script>
+2 -2
View File
@@ -113,8 +113,8 @@
const CARTODB_API_KEY = "{{ web_ui_options.get('cartodb_api_key', '') }}"; const CARTODB_API_KEY = "{{ web_ui_options.get('cartodb_api_key', '') }}";
</script> </script>
<script src="/static/js/spotsbandsandmap.js?v=1789713511"></script> <script src="/static/js/spotsbandsandmap.js?v=1789714568"></script>
<script src="/static/js/map.js?v=1789713511"></script> <script src="/static/js/map.js?v=1789714568"></script>
<script>$(document).ready(function () { <script>$(document).ready(function () {
$("#nav-link-map").addClass("active"); $("#nav-link-map").addClass("active");
}); <!-- highlight active page in nav --></script> }); <!-- highlight active page in nav --></script>
+2 -2
View File
@@ -125,8 +125,8 @@
</div> </div>
<script src="/static/js/spotsbandsandmap.js?v=1789713510"></script> <script src="/static/js/spotsbandsandmap.js?v=1789714568"></script>
<script src="/static/js/spots.js?v=1789713510"></script> <script src="/static/js/spots.js?v=1789714568"></script>
<script>$(document).ready(function () { <script>$(document).ready(function () {
$("#nav-link-spots").addClass("active"); $("#nav-link-spots").addClass("active");
}); <!-- highlight active page in nav --></script> }); <!-- highlight active page in nav --></script>
+1 -1
View File
@@ -96,7 +96,7 @@
</div> </div>
</div> </div>
<script src="/static/js/status.js?v=1789713510"></script> <script src="/static/js/status.js?v=1789714568"></script>
<script> <script>
$(document).ready(function () { $(document).ready(function () {
$("#nav-link-status").addClass("active"); $("#nav-link-status").addClass("active");
+1 -1
View File
@@ -34,7 +34,7 @@ class V1RedirectHandler(tornado.web.RequestHandler):
response = await client.fetch( response = await client.fetch(
new_url, new_url,
method=self.request.method, method=self.request.method,
headers=self.request.headers, headers=headers,
body=None if self.request.method == "GET" else (self.request.body or b""), body=None if self.request.method == "GET" else (self.request.body or b""),
raise_error=False, raise_error=False,
follow_redirects=False, follow_redirects=False,