Compare commits

...
6 Commits
21 changed files with 79 additions and 48 deletions
+1 -1
View File
@@ -4,7 +4,7 @@ from data.band import Band
from data.sig import SIG from data.sig import SIG
# General software # General software
SOFTWARE_VERSION = "2.1" SOFTWARE_VERSION = "2.1.2"
# HTTP headers used for spot providers that use HTTP # HTTP headers used for spot providers that use HTTP
HTTP_HEADERS = {"User-Agent": f"Spothole v{SOFTWARE_VERSION} (operated by {SERVER_OWNER_CALLSIGN})"} HTTP_HEADERS = {"User-Agent": f"Spothole v{SOFTWARE_VERSION} (operated by {SERVER_OWNER_CALLSIGN})"}
+6 -1
View File
@@ -77,7 +77,12 @@ class LiveDataCache:
logger.exception(f"Failed to write snapshot to {self._snapshot_dir}") logger.exception(f"Failed to write snapshot to {self._snapshot_dir}")
def _load_snapshot(self): def _load_snapshot(self):
data = self._disk_cache.get("snapshot") try:
data = self._disk_cache.get("snapshot")
except Exception:
logger.warning(f"Failed to load snapshot from {self._snapshot_dir}, clearing it.")
self._disk_cache.clear()
return
if not data: if not data:
return return
+7 -1
View File
@@ -21,7 +21,13 @@ class SingleObjectDataCache:
# This cache stores a single object, doesn't matter what it's called so "object" will do # This cache stores a single object, doesn't matter what it's called so "object" will do
if "object" not in self._cache: if "object" not in self._cache:
self._cache.add("object", object_if_empty) self._cache.add("object", object_if_empty)
self._obj = self._cache.get("object") try:
self._obj = self._cache.get("object")
except Exception:
logger.warning(f"Failed to load cache from {cache_dir}, clearing it.")
self._cache.clear()
self._cache.add("object", object_if_empty)
self._obj = object_if_empty
def get(self): def get(self):
"""Get the data object. This can then be manipulated as necessary across multiple threads. Any function """Get the data object. This can then be manipulated as necessary across multiple threads. Any function
+6 -6
View File
@@ -124,8 +124,8 @@ class HamQTH(APIQueryCallsignDataProvider):
lat = None lat = None
lon = None lon = None
if ( if (
"latitude" in data data.get("latitude") is not None
and "longitude" in data and data.get("longitude") is not None
and (float(data["latitude"]) != 0 or float(data["longitude"]) != 0) and (float(data["latitude"]) != 0 or float(data["longitude"]) != 0)
and -89.9 < float(data["latitude"]) < 89.9 and -89.9 < float(data["latitude"]) < 89.9
): ):
@@ -134,7 +134,7 @@ class HamQTH(APIQueryCallsignDataProvider):
# Check for sensible grids # Check for sensible grids
grid = None grid = None
if "grid" in data and not data["grid"].startswith("AA00"): if data.get("grid") and not data["grid"].startswith("AA00"):
grid = data["grid"] grid = data["grid"]
return Callsign( return Callsign(
@@ -147,8 +147,8 @@ class HamQTH(APIQueryCallsignDataProvider):
latitude=lat, latitude=lat,
longitude=lon, longitude=lon,
grid=grid, grid=grid,
dxcc_id=int(data["adif"]) if "adif" in data else None, dxcc_id=int(data["adif"]) if data.get("adif") is not None else None,
cq_zone=int(data["cq"]) if "cq" in data else None, cq_zone=int(data["cq"]) if data.get("cq") is not None else None,
itu_zone=int(data["itu"]) if "itu" in data else None, itu_zone=int(data["itu"]) if data.get("itu") is not None else None,
location_source=LocationSourceForCallsign.HOME_QTH, location_source=LocationSourceForCallsign.HOME_QTH,
) )
+6 -6
View File
@@ -150,8 +150,8 @@ class QRZ(APIQueryCallsignDataProvider):
lat = None lat = None
lon = None lon = None
if ( if (
"latitude" in data data.get("latitude") is not None
and "longitude" in data and data.get("longitude") is not None
and (float(data["latitude"]) != 0 or float(data["longitude"]) != 0) and (float(data["latitude"]) != 0 or float(data["longitude"]) != 0)
and -89.9 < float(data["latitude"]) < 89.9 and -89.9 < float(data["latitude"]) < 89.9
): ):
@@ -160,7 +160,7 @@ class QRZ(APIQueryCallsignDataProvider):
# Check for sensible grids # Check for sensible grids
grid = None grid = None
if "grid" in data and not data["grid"].startswith("AA00"): if data.get("grid") and not data["grid"].startswith("AA00"):
grid = data["grid"] grid = data["grid"]
return Callsign( return Callsign(
@@ -173,8 +173,8 @@ class QRZ(APIQueryCallsignDataProvider):
latitude=lat, latitude=lat,
longitude=lon, longitude=lon,
grid=grid, grid=grid,
dxcc_id=int(data["adif"]) if "adif" in data else None, dxcc_id=int(data["adif"]) if data.get("adif") is not None else None,
cq_zone=int(data["cqzone"]) if "cqzone" in data else None, cq_zone=int(data["cqzone"]) if data.get("cqzone") is not None else None,
itu_zone=int(data["ituzone"]) if "ituzone" in data else None, itu_zone=int(data["ituzone"]) if data.get("ituzone") is not None else None,
location_source=LocationSourceForCallsign.HOME_QTH, location_source=LocationSourceForCallsign.HOME_QTH,
) )
@@ -36,8 +36,10 @@ class SIGRefDataProvider:
def _add_data(self, new_data): def _add_data(self, new_data):
"""Add all the provided reference data objects to the data store.""" """Add all the provided reference data objects to the data store."""
# with transact() batches all writes together to save making thousands of individual sqlite writes # with transact() batches all writes together to save making thousands of individual sqlite writes. However,
with DATA_STORE.sigrefs.transact(): # that means that each provider holds the lock while it writes, and the default behaviour for other attempted
# transact()s is to fail if they can't get the lock (?!). This behaviour is fixed by retry=True.
with DATA_STORE.sigrefs.transact(retry=True):
for d in new_data: for d in new_data:
DATA_STORE.sigrefs.set(f"{self.sig_name}:{d.id}", d) DATA_STORE.sigrefs.set(f"{self.sig_name}:{d.id}", d)
+2 -2
View File
@@ -69,8 +69,8 @@ class APRSIS(SpotProvider):
de_call=de_call, de_call=de_call,
de_ssid=de_ssid, de_ssid=de_ssid,
comment=str(data["comment"]) if "comment" in data else None, comment=str(data["comment"]) if "comment" in data else None,
dx_latitude=float(data["latitude"]) if "latitude" in data else None, dx_latitude=float(data["latitude"]) if data.get("latitude") is not None else None,
dx_longitude=float(data["longitude"]) if "longitude" in data else None, dx_longitude=float(data["longitude"]) if data.get("longitude") is not None else None,
time=datetime.now(pytz.UTC).timestamp(), time=datetime.now(pytz.UTC).timestamp(),
) # APRS-IS spots are live so we can assume spot time is "now" ) # APRS-IS spots are live so we can assume spot time is "now"
+13 -4
View File
@@ -1,7 +1,7 @@
import logging import logging
import re import re
from datetime import datetime from datetime import datetime
from threading import Event, Thread from threading import Event, Lock, Thread
import pytz import pytz
import telnetlib3 import telnetlib3
@@ -40,6 +40,7 @@ class DXCluster(SpotProvider):
self._LINE_PATTERN_ALLOW_RBN if self._allow_rbn_spots else self._LINE_PATTERN_EXCLUDE_RBN self._LINE_PATTERN_ALLOW_RBN if self._allow_rbn_spots else self._LINE_PATTERN_EXCLUDE_RBN
) )
self._telnet = None self._telnet = None
self._telnet_lock = Lock()
self._thread = None self._thread = None
self._stop_event = Event() self._stop_event = Event()
@@ -49,8 +50,9 @@ class DXCluster(SpotProvider):
def stop(self): def stop(self):
self._stop_event.set() self._stop_event.set()
if self._telnet: with self._telnet_lock:
self._telnet.close() if self._telnet:
self._telnet.close()
if self._thread: if self._thread:
self._thread.join(timeout=15) self._thread.join(timeout=15)
if self._thread.is_alive(): if self._thread.is_alive():
@@ -63,7 +65,14 @@ class DXCluster(SpotProvider):
try: try:
self.status = "Connecting" self.status = "Connecting"
logger.info(f"DX Cluster {self._hostname} connecting...") logger.info(f"DX Cluster {self._hostname} connecting...")
self._telnet = telnetlib3.Telnet(self._hostname, self._port) new_telnet = telnetlib3.Telnet(self._hostname, self._port)
with self._telnet_lock:
self._telnet = new_telnet
if self._stop_event.is_set():
# stop() was called while we were connecting, close the connection rather than trying to
# read when we know it won't work
new_telnet.close()
break
self._telnet.read_until(self._login_prompt.encode("latin-1")) self._telnet.read_until(self._login_prompt.encode("latin-1"))
self._telnet.write(f"{self._login_callsign}\n".encode("latin-1")) self._telnet.write(f"{self._login_callsign}\n".encode("latin-1"))
connected = True connected = True
+13 -4
View File
@@ -1,7 +1,7 @@
import logging import logging
import re import re
from datetime import datetime from datetime import datetime
from threading import Event, Thread from threading import Event, Lock, Thread
import pytz import pytz
import telnetlib3 import telnetlib3
@@ -29,6 +29,7 @@ class RBN(SpotProvider):
super().__init__(name, provider_config) super().__init__(name, provider_config)
self._port = provider_config["port"] self._port = provider_config["port"]
self._telnet = None self._telnet = None
self._telnet_lock = Lock()
self._thread = None self._thread = None
self._stop_event = Event() self._stop_event = Event()
@@ -38,8 +39,9 @@ class RBN(SpotProvider):
def stop(self): def stop(self):
self._stop_event.set() self._stop_event.set()
if self._telnet: with self._telnet_lock:
self._telnet.close() if self._telnet:
self._telnet.close()
if self._thread: if self._thread:
self._thread.join(timeout=15) self._thread.join(timeout=15)
if self._thread.is_alive(): if self._thread.is_alive():
@@ -52,7 +54,14 @@ class RBN(SpotProvider):
try: try:
self.status = "Connecting" self.status = "Connecting"
logger.info(f"RBN port {self._port!s} connecting...") logger.info(f"RBN port {self._port!s} connecting...")
self._telnet = telnetlib3.Telnet("telnet.reversebeacon.net", self._port) new_telnet = telnetlib3.Telnet("telnet.reversebeacon.net", self._port)
with self._telnet_lock:
self._telnet = new_telnet
if self._stop_event.is_set():
# stop() was called while we were connecting, close the connection rather than trying to
# read when we know it won't work
new_telnet.close()
break
self._telnet.read_until("Please enter your call: ".encode("latin-1")) self._telnet.read_until("Please enter your call: ".encode("latin-1"))
self._telnet.write(f"{SERVER_OWNER_CALLSIGN}\n".encode("latin-1")) self._telnet.write(f"{SERVER_OWNER_CALLSIGN}\n".encode("latin-1"))
connected = True connected = True
+2 -2
View File
@@ -42,7 +42,7 @@ class UKPacketNet(HTTPSpotProvider):
) )
comment = ( comment = (
f"{comment} {listed_port['baud']!s} baud" f"{comment} {listed_port['baud']!s} baud"
if "baud" in listed_port and listed_port["baud"] > 0 if listed_port.get("baud") and listed_port["baud"] > 0
else comment else comment
) )
@@ -50,7 +50,7 @@ class UKPacketNet(HTTPSpotProvider):
# very hacky but a lot of node comments contain their frequency as the first or second # very hacky but a lot of node comments contain their frequency as the first or second
# word of their comment, but not in the proper data structure field. # word of their comment, but not in the proper data structure field.
freq = ( freq = (
listed_port["freq"] if "freq" in listed_port and listed_port["freq"] > 0 else None listed_port["freq"] if listed_port.get("freq") and listed_port["freq"] > 0 else None
) )
if not freq and comment: if not freq and comment:
possible_freq = comment.split(" ")[0].upper().replace("MHZ", "") possible_freq = comment.split(" ")[0].upper().replace("MHZ", "")
+1 -1
View File
@@ -36,7 +36,7 @@ class WWBOTA(SSESpotProvider):
dx_call=source_spot["call"].upper(), dx_call=source_spot["call"].upper(),
de_call=source_spot["spotter"].upper(), de_call=source_spot["spotter"].upper(),
freq=float(source_spot["freq"]) * 1000000, freq=float(source_spot["freq"]) * 1000000,
mode=Mode.from_name(source_spot["mode"].upper()) if "mode" in source_spot else None, mode=Mode.from_name(source_spot["mode"].upper()) if source_spot.get("mode") else None,
comment=source_spot["comment"], comment=source_spot["comment"],
sig="WWBOTA", sig="WWBOTA",
sig_refs=refs, sig_refs=refs,
+1 -1
View File
@@ -1,6 +1,6 @@
[project] [project]
name = "spothole" name = "spothole"
version = "2.1" version = "2.1.2"
authors = [ authors = [
{ name = "Ian Renton", email = "ian@ianrenton.com" }, { name = "Ian Renton", email = "ian@ianrenton.com" },
] ]
+2 -2
View File
@@ -414,8 +414,8 @@ div.band-spot:hover span.band-spot-info {
/* Make map stretch to horizontal screen edges */ /* Make map stretch to horizontal screen edges */
div#map, div#table-container, div#bands-container { div#map, div#table-container, div#bands-container {
margin-left: -1em; margin-left: -0.75rem;
margin-right: -1em; margin-right: -0.75rem;
} }
/* Avoid map page filters panel being larger than the map itself */ /* Avoid map page filters panel being larger than the map itself */
+1 -1
View File
@@ -77,7 +77,7 @@
</div> </div>
<script src="/static/js/add-spot.js?v=1789731849"></script> <script src="/static/js/add-spot.js?v=1789803195"></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=1789731849"></script> <script src="/static/js/alerts.js?v=1789803195"></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=1789731849"></script> <script src="/static/js/spotsbandsandmap.js?v=1789803195"></script>
<script src="/static/js/bands.js?v=1789731849"></script> <script src="/static/js/bands.js?v=1789803195"></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=1789731849" type="text/css"> <link rel="stylesheet" href="/static/css/style.css?v=1789803195" 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=1789731849"></script> <script src="/static/js/utils.js?v=1789803195"></script>
<script src="/static/js/ui-ham.js?v=1789731849"></script> <script src="/static/js/ui-ham.js?v=1789803195"></script>
<script src="/static/js/geo.js?v=1789731849"></script> <script src="/static/js/geo.js?v=1789803195"></script>
<script src="/static/js/common.js?v=1789731849"></script> <script src="/static/js/common.js?v=1789803195"></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=1789731849"></script> <script src="/static/js/conditions.js?v=1789803195"></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=1789731849"></script> <script src="/static/js/spotsbandsandmap.js?v=1789803195"></script>
<script src="/static/js/map.js?v=1789731849"></script> <script src="/static/js/map.js?v=1789803195"></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=1789731849"></script> <script src="/static/js/spotsbandsandmap.js?v=1789803195"></script>
<script src="/static/js/spots.js?v=1789731849"></script> <script src="/static/js/spots.js?v=1789803195"></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=1789731849"></script> <script src="/static/js/status.js?v=1789803195"></script>
<script> <script>
$(document).ready(function () { $(document).ready(function () {
$("#nav-link-status").addClass("active"); $("#nav-link-status").addClass("active");