From 6d3258e833d6653047f99a1a3c8cbf0bce265ea9 Mon Sep 17 00:00:00 2001
From: Hemna
Date: Mon, 3 May 2021 10:28:31 -0400
Subject: [PATCH 1/5] Send Message via admin Web interface
This patch adds the ability to send a message from the
admin interface's send-message.html page.
---
aprsd/client.py | 5 ++
aprsd/flask.py | 70 ++++++++++++++++++++++++++-
aprsd/messaging.py | 7 +--
aprsd/web/templates/send-message.html | 25 ++++++++++
4 files changed, 103 insertions(+), 4 deletions(-)
create mode 100644 aprsd/web/templates/send-message.html
diff --git a/aprsd/client.py b/aprsd/client.py
index a2d5e45..157f4ef 100644
--- a/aprsd/client.py
+++ b/aprsd/client.py
@@ -38,6 +38,11 @@ class Client:
if config:
self.config = config
+ def new(self):
+ obj = super().__new__(Client)
+ obj.config = self.config
+ return obj
+
@property
def client(self):
if not self.aprs_client:
diff --git a/aprsd/flask.py b/aprsd/flask.py
index e850899..e87e695 100644
--- a/aprsd/flask.py
+++ b/aprsd/flask.py
@@ -4,14 +4,17 @@ import logging
from logging import NullHandler
from logging.handlers import RotatingFileHandler
import sys
+import time
+from aprslib.exceptions import LoginError
import flask
+from flask import request
import flask_classful
from flask_httpauth import HTTPBasicAuth
from werkzeug.security import check_password_hash, generate_password_hash
import aprsd
-from aprsd import kissclient, messaging, packets, plugin, stats, utils
+from aprsd import client, kissclient, messaging, packets, plugin, stats, utils
LOG = logging.getLogger("APRSD")
@@ -115,6 +118,70 @@ class APRSDFlask(flask_classful.FlaskView):
return flask.render_template("messages.html", messages=json.dumps(msgs))
+ def setup_connection(self):
+ user = self.config["aprs"]["login"]
+ password = self.config["aprs"]["password"]
+ host = self.config["aprs"].get("host", "rotate.aprs.net")
+ port = self.config["aprs"].get("port", 14580)
+ connected = False
+ backoff = 1
+ while not connected:
+ try:
+ LOG.info("Creating aprslib client")
+ aprs_client = client.Aprsdis(
+ user,
+ passwd=password,
+ host=host,
+ port=port,
+ )
+ # Force the logging to be the same
+ aprs_client.logger = LOG
+ aprs_client.connect()
+ connected = True
+ backoff = 1
+ except LoginError as e:
+ LOG.error("Failed to login to APRS-IS Server '{}'".format(e))
+ connected = False
+ raise e
+ except Exception as e:
+ LOG.error("Unable to connect to APRS-IS server. '{}' ".format(e))
+ time.sleep(backoff)
+ backoff = backoff * 2
+ continue
+ LOG.debug("Logging in to APRS-IS with user '%s'" % user)
+ return aprs_client
+
+ def send_message(self):
+ if request.method == "POST":
+ from_call = request.form["from_call"]
+ to_call = request.form["to_call"]
+ message = request.form["message"]
+ LOG.info(
+ "From: '{}' To: '{}' Send '{}'".format(
+ from_call,
+ to_call,
+ message,
+ ),
+ )
+
+ try:
+ aprsis_client = self.setup_connection()
+ except LoginError as e:
+ result = "Failed to setup Connection {}".format(e)
+
+ msg = messaging.TextMessage(from_call, to_call, message)
+ msg.send_direct(aprsis_client=aprsis_client)
+ result = "Message sent"
+ else:
+ from_call = self.config["aprs"]["login"]
+ result = ""
+
+ return flask.render_template(
+ "send-message.html",
+ from_call=from_call,
+ result=result,
+ )
+
@auth.login_required
def packets(self):
packet_list = packets.PacketList().get()
@@ -224,6 +291,7 @@ def init_flask(config, loglevel, quiet):
flask_app.route("/stats", methods=["GET"])(server.stats)
flask_app.route("/messages", methods=["GET"])(server.messages)
flask_app.route("/packets", methods=["GET"])(server.packets)
+ flask_app.route("/send-message", methods=["GET", "POST"])(server.send_message)
flask_app.route("/save", methods=["GET"])(server.save)
flask_app.route("/plugins", methods=["GET"])(server.plugins)
return flask_app
diff --git a/aprsd/messaging.py b/aprsd/messaging.py
index f53ce0e..3e51f0f 100644
--- a/aprsd/messaging.py
+++ b/aprsd/messaging.py
@@ -299,7 +299,7 @@ class RawMessage(Message):
thread = SendMessageThread(message=self)
thread.start()
- def send_direct(self):
+ def send_direct(self, aprsis_client=None):
"""Send a message without a separate thread."""
cl = self.get_transport()
log_message(
@@ -379,7 +379,7 @@ class TextMessage(Message):
thread = SendMessageThread(message=self)
thread.start()
- def send_direct(self):
+ def send_direct(self, aprsis_client=None):
"""Send a message without a separate thread."""
cl = self.get_transport()
log_message(
@@ -498,7 +498,8 @@ class AckMessage(Message):
thread = SendAckThread(self)
thread.start()
- def send_direct(self):
+
+ def send_direct(self, aprsis_client=None):
"""Send an ack message without a separate thread."""
cl = self.get_transport()
log_message(
diff --git a/aprsd/web/templates/send-message.html b/aprsd/web/templates/send-message.html
new file mode 100644
index 0000000..d0aeb8c
--- /dev/null
+++ b/aprsd/web/templates/send-message.html
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
From 23cbf32814368fa504a1c7ce5fc1012fb899cfa0 Mon Sep 17 00:00:00 2001
From: Hemna
Date: Thu, 26 Aug 2021 20:58:07 -0400
Subject: [PATCH 2/5] New Admin ui send message page working.
---
aprsd/client.py | 8 +-
aprsd/flask.py | 320 +++++++++++++++++++++-----
aprsd/messaging.py | 1 +
aprsd/packets.py | 3 +-
aprsd/web/static/js/send-message.js | 74 ++++++
aprsd/web/templates/send-message.html | 114 ++++++++-
6 files changed, 449 insertions(+), 71 deletions(-)
create mode 100644 aprsd/web/static/js/send-message.js
diff --git a/aprsd/client.py b/aprsd/client.py
index 157f4ef..7b65703 100644
--- a/aprsd/client.py
+++ b/aprsd/client.py
@@ -130,8 +130,12 @@ class Aprsdis(aprslib.IS):
# sock.recv returns empty if the connection drops
if not short_buf:
- self.logger.error("socket.recv(): returned empty")
- raise aprslib.ConnectionDrop("connection dropped")
+ if not blocking:
+ # We could just not be blocking, so empty is expected
+ continue
+ else:
+ self.logger.error("socket.recv(): returned empty")
+ raise aprslib.ConnectionDrop("connection dropped")
except OSError as e:
# self.logger.error("socket error on recv(): %s" % str(e))
if "Resource temporarily unavailable" in str(e):
diff --git a/aprsd/flask.py b/aprsd/flask.py
index e87e695..839d42e 100644
--- a/aprsd/flask.py
+++ b/aprsd/flask.py
@@ -4,8 +4,10 @@ import logging
from logging import NullHandler
from logging.handlers import RotatingFileHandler
import sys
+import threading
import time
+import aprslib
from aprslib.exceptions import LoginError
import flask
from flask import request
@@ -14,7 +16,7 @@ from flask_httpauth import HTTPBasicAuth
from werkzeug.security import check_password_hash, generate_password_hash
import aprsd
-from aprsd import client, kissclient, messaging, packets, plugin, stats, utils
+from aprsd import client, kissclient, messaging, packets, plugin, stats, threads, utils
LOG = logging.getLogger("APRSD")
@@ -22,6 +24,72 @@ LOG = logging.getLogger("APRSD")
auth = HTTPBasicAuth()
users = None
+class SentMessages:
+ _instance = None
+ lock = None
+
+ msgs = {}
+
+ def __new__(cls, *args, **kwargs):
+ """This magic turns this into a singleton."""
+ if cls._instance is None:
+ cls._instance = super().__new__(cls)
+ # Put any initialization here.
+ cls.lock = threading.Lock()
+ return cls._instance
+
+ def add(self, msg):
+ with self.lock:
+ self.msgs[msg.id] = self._create(msg.id)
+ self.msgs[msg.id]["from"] = msg.fromcall
+ self.msgs[msg.id]["to"] = msg.tocall
+ self.msgs[msg.id]["message"] = msg.message.rstrip("\n")
+ self.msgs[msg.id]["raw"] = str(msg).rstrip("\n")
+
+
+ def _create(self, id):
+ return {
+ "id": id,
+ "ts": time.time(),
+ "ack": False,
+ "from": None,
+ "to": None,
+ "raw": None,
+ "message": None,
+ "status": None,
+ "last_update": None,
+ "reply": None,
+ }
+
+ def __len__(self):
+ with self.lock:
+ return len(self.msgs.keys())
+
+ def get(self, id):
+ with self.lock:
+ if id in self.msgs:
+ return self.msgs[id]
+
+ def get_all(self):
+ with self.lock:
+ return self.msgs
+
+ def set_status(self, id, status):
+ with self.lock:
+ self.msgs[id]["last_update"] = str(datetime.datetime.now())
+ self.msgs[id]["status"] = status
+
+ def ack(self, id):
+ """The message got an ack!"""
+ with self.lock:
+ self.msgs[id]["last_update"] = str(datetime.datetime.now())
+ self.msgs[id]["ack"] = True
+
+ def reply(self, id, packet):
+ """We got a packet back from the sent message."""
+ with self.lock:
+ self.msgs[id]["reply"] = packet
+
# HTTPBasicAuth doesn't work on a class method.
# This has to be out here. Rely on the APRSDFlask
@@ -34,6 +102,161 @@ def verify_password(username, password):
return username
+class SendMessageThread(threads.APRSDThread):
+ """Thread for sending a message from web."""
+
+ aprsis_client = None
+ request = None
+ got_ack = False
+
+ def __init__(self, config, info, msg):
+ self.config = config
+ self.request = info
+ self.msg = msg
+ msg = "({} -> {}) : {}".format(
+ info["from"],
+ info["to"],
+ info["message"],
+ )
+ super().__init__(f"WEB_SEND_MSG-{msg}")
+
+ def setup_connection(self):
+ user = self.request["from"]
+ password = self.request["password"]
+ host = self.config["aprs"].get("host", "rotate.aprs.net")
+ port = self.config["aprs"].get("port", 14580)
+ connected = False
+ backoff = 1
+ while not connected:
+ try:
+ LOG.info("Creating aprslib client")
+ aprs_client = client.Aprsdis(
+ user,
+ passwd=password,
+ host=host,
+ port=port,
+ )
+ # Force the logging to be the same
+ aprs_client.logger = LOG
+ aprs_client.connect()
+ connected = True
+ backoff = 1
+ except LoginError as e:
+ LOG.error(f"Failed to login to APRS-IS Server '{e}'")
+ connected = False
+ raise e
+ except Exception as e:
+ LOG.error(f"Unable to connect to APRS-IS server. '{e}' ")
+ time.sleep(backoff)
+ backoff = backoff * 2
+ continue
+ LOG.debug(f"Logging in to APRS-IS with user '{user}'")
+ return aprs_client
+
+ def run(self):
+ LOG.debug("Starting")
+ from_call = self.request["from"]
+ self.request["password"]
+ to_call = self.request["to"]
+ message = self.request["message"]
+ LOG.info(
+ "From: '{}' To: '{}' Send '{}'".format(
+ from_call,
+ to_call,
+ message,
+ ),
+ )
+
+ try:
+ self.aprs_client = self.setup_connection()
+ except LoginError as e:
+ f"Failed to setup Connection {e}"
+
+ self.msg.send_direct(aprsis_client=self.aprs_client)
+ SentMessages().set_status(self.msg.id, "Sent")
+
+ while not self.thread_stop:
+ can_loop = self.loop()
+ if not can_loop:
+ self.stop()
+ threads.APRSDThreadList().remove(self)
+ LOG.debug("Exiting")
+
+ def rx_packet(self, packet):
+ global got_ack, got_response
+ # LOG.debug("Got packet back {}".format(packet))
+ resp = packet.get("response", None)
+ if resp == "ack":
+ ack_num = packet.get("msgNo")
+ LOG.info(f"We got ack for our sent message {ack_num}")
+ messaging.log_packet(packet)
+ SentMessages().ack(self.msg.id)
+ stats.APRSDStats().ack_rx_inc()
+ self.got_ack = True
+ if self.request["wait_reply"] == "0":
+ # We aren't waiting for a reply, so we can bail
+ self.thread_stop = self.aprs_client.thread_stop = True
+ else:
+ packets.PacketList().add(packet)
+ stats.APRSDStats().msgs_rx_inc()
+ message = packet.get("message_text", None)
+ fromcall = packet["from"]
+ msg_number = packet.get("msgNo", "0")
+ messaging.log_message(
+ "Received Message",
+ packet["raw"],
+ message,
+ fromcall=fromcall,
+ ack=msg_number,
+ )
+ got_response = True
+ SentMessages().reply(self.msg.id, packet)
+ SentMessages().set_status(self.msg.id, "Got Reply")
+
+ # Send the ack back?
+ ack = messaging.AckMessage(
+ self.request["from"],
+ fromcall,
+ msg_id=msg_number,
+ )
+ ack.send_direct()
+ SentMessages().set_status(self.msg.id, "Ack Sent")
+
+ # Now we can exit, since we are done.
+ if self.got_ack:
+ self.thread_stop = self.aprs_client.thread_stop = True
+
+ def loop(self):
+ LOG.debug("LOOP Start")
+ try:
+ # This will register a packet consumer with aprslib
+ # When new packets come in the consumer will process
+ # the packet
+ self.aprs_client.consumer(self.rx_packet, raw=False, blocking=False)
+ except aprslib.exceptions.ConnectionDrop:
+ LOG.error("Connection dropped, reconnecting")
+ time.sleep(5)
+ # Force the deletion of the client object connected to aprs
+ # This will cause a reconnect, next time client.get_client()
+ # is called
+ del self.aprs_client
+ connecting = True
+ counter = 0;
+ while connecting:
+ try:
+ self.aprs_client = self.setup_connection()
+ connecting = False
+ except Exception:
+ LOG.error("Couldn't connect")
+ counter += 1
+ if counter >= 3:
+ LOG.error("Reached reconnect limit.")
+ return False
+
+ LOG.debug("LOOP END")
+ return True
+
+
class APRSDFlask(flask_classful.FlaskView):
config = None
@@ -118,69 +341,53 @@ class APRSDFlask(flask_classful.FlaskView):
return flask.render_template("messages.html", messages=json.dumps(msgs))
- def setup_connection(self):
- user = self.config["aprs"]["login"]
- password = self.config["aprs"]["password"]
- host = self.config["aprs"].get("host", "rotate.aprs.net")
- port = self.config["aprs"].get("port", 14580)
- connected = False
- backoff = 1
- while not connected:
- try:
- LOG.info("Creating aprslib client")
- aprs_client = client.Aprsdis(
- user,
- passwd=password,
- host=host,
- port=port,
- )
- # Force the logging to be the same
- aprs_client.logger = LOG
- aprs_client.connect()
- connected = True
- backoff = 1
- except LoginError as e:
- LOG.error("Failed to login to APRS-IS Server '{}'".format(e))
- connected = False
- raise e
- except Exception as e:
- LOG.error("Unable to connect to APRS-IS server. '{}' ".format(e))
- time.sleep(backoff)
- backoff = backoff * 2
- continue
- LOG.debug("Logging in to APRS-IS with user '%s'" % user)
- return aprs_client
+ @auth.login_required
+ def send_message_status(self):
+ LOG.debug(request)
+ msgs = SentMessages()
+ info = msgs.get_all()
+ return json.dumps(info)
+ @auth.login_required
def send_message(self):
+ LOG.debug(request)
if request.method == "POST":
- from_call = request.form["from_call"]
- to_call = request.form["to_call"]
- message = request.form["message"]
- LOG.info(
- "From: '{}' To: '{}' Send '{}'".format(
- from_call,
- to_call,
- message,
- ),
+ info = {
+ "from": request.form["from_call"],
+ "to": request.form["to_call"],
+ "password": request.form["from_call_password"],
+ "message": request.form["message"],
+ "wait_reply": request.form["wait_reply"],
+ }
+ LOG.debug(info)
+ msg = messaging.TextMessage(
+ info["from"], info["to"],
+ info["message"],
)
+ msgs = SentMessages()
+ msgs.add(msg)
+ msgs.set_status(msg.id, "Sending")
- try:
- aprsis_client = self.setup_connection()
- except LoginError as e:
- result = "Failed to setup Connection {}".format(e)
+ send_message_t = SendMessageThread(self.config, info, msg)
+ send_message_t.start()
- msg = messaging.TextMessage(from_call, to_call, message)
- msg.send_direct(aprsis_client=aprsis_client)
- result = "Message sent"
+
+ info["from"]
+ result = "sending"
+ msg_id = msg.id
+ result = {
+ "msg_id": msg_id,
+ "status": "sending",
+ }
+ return json.dumps(result)
else:
- from_call = self.config["aprs"]["login"]
- result = ""
+ result = "fail"
- return flask.render_template(
- "send-message.html",
- from_call=from_call,
- result=result,
- )
+ return flask.render_template(
+ "send-message.html",
+ callsign=self.config["aprs"]["login"],
+ version=aprsd.__version__,
+ )
@auth.login_required
def packets(self):
@@ -292,6 +499,7 @@ def init_flask(config, loglevel, quiet):
flask_app.route("/messages", methods=["GET"])(server.messages)
flask_app.route("/packets", methods=["GET"])(server.packets)
flask_app.route("/send-message", methods=["GET", "POST"])(server.send_message)
+ flask_app.route("/send-message-status", methods=["GET"])(server.send_message_status)
flask_app.route("/save", methods=["GET"])(server.save)
flask_app.route("/plugins", methods=["GET"])(server.plugins)
return flask_app
diff --git a/aprsd/messaging.py b/aprsd/messaging.py
index 3e51f0f..8ae987b 100644
--- a/aprsd/messaging.py
+++ b/aprsd/messaging.py
@@ -391,6 +391,7 @@ class TextMessage(Message):
)
cl.send(self)
stats.APRSDStats().msgs_tx_inc()
+ packets.PacketList().add(self.dict())
class SendMessageThread(threads.APRSDThread):
diff --git a/aprsd/packets.py b/aprsd/packets.py
index ef5e3f5..8e80432 100644
--- a/aprsd/packets.py
+++ b/aprsd/packets.py
@@ -69,6 +69,7 @@ class WatchList:
_instance = None
callsigns = {}
+ config = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
@@ -97,7 +98,7 @@ class WatchList:
}
def is_enabled(self):
- if "watch_list" in self.config["aprsd"]:
+ if self.config and "watch_list" in self.config["aprsd"]:
return self.config["aprsd"]["watch_list"].get("enabled", False)
else:
return False
diff --git a/aprsd/web/static/js/send-message.js b/aprsd/web/static/js/send-message.js
new file mode 100644
index 0000000..92afbd4
--- /dev/null
+++ b/aprsd/web/static/js/send-message.js
@@ -0,0 +1,74 @@
+msgs_list = {};
+
+function size_dict(d){c=0; for (i in d) ++c; return c}
+
+function update_messages(data) {
+ msgs_cnt = size_dict(data);
+ $('#msgs_count').html(msgs_cnt);
+
+ var msgsdiv = $("#msgsDiv");
+ //nuke the contents first, then add to it.
+ if (size_dict(msgs_list) == 0 && size_dict(data) > 0) {
+ msgsdiv.html('')
+ }
+
+ jQuery.each(data, function(i, val) {
+ if ( msgs_list.hasOwnProperty(val["ts"]) == false ) {
+ // Store the packet
+ msgs_list[val["ts"]] = val;
+ ts_str = val["ts"].toString();
+ ts = ts_str.split(".")[0]*1000;
+ var d = new Date(ts).toLocaleDateString("en-US")
+ var t = new Date(ts).toLocaleTimeString("en-US")
+
+ from = val['from']
+ title_id = 'title_tx'
+ var from_to = d + " " + t + " " + from + " > "
+
+ if (val.hasOwnProperty('to')) {
+ from_to = from_to + val['to']
+ }
+ from_to = from_to + " - " + val['raw']
+
+ id = ts_str.split('.')[0]
+ pretty_id = "pretty_" + id
+ loader_id = "loader_" + id
+ reply_id = "reply_" + id
+ json_pretty = Prism.highlight(JSON.stringify(val, null, '\t'), Prism.languages.json, 'json');
+ msg_html = '';
+ msg_html += '
';
+ msg_html += '
' + from_to + '
';
+ msg_html += ''
+ msgsdiv.prepend(msg_html);
+ } else {
+ // We have an existing entry
+ msgs_list[val["ts"]] = val;
+ ts_str = val["ts"].toString();
+ id = ts_str.split('.')[0]
+ pretty_id = "pretty_" + id
+ loader_id = "loader_" + id
+ reply_id = "reply_" + id
+ var pretty_pre = $("#" + pretty_id);
+ if (val['ack'] == true) {
+ var loader_div = $('#' + loader_id);
+ loader_div.removeClass('ui active inline loader');
+ loader_div.addClass('ui disabled loader');
+ loader_div.attr('data-content', 'Got reply');
+ }
+
+ if (val['reply'] !== null) {
+ var reply_div = $('#' + reply_id);
+ reply_div.removeClass("thumbs down outline icon");
+ reply_div.addClass('thumbs up outline icon');
+ reply_div.attr('data-content', 'Got Reply');
+ }
+
+ pretty_pre.html('');
+ json_pretty = Prism.highlight(JSON.stringify(val, null, '\t'), Prism.languages.json, 'json');
+ pretty_pre.html(json_pretty);
+ }
+ });
+
+ $('.ui.accordion').accordion('refresh');
+
+}
diff --git a/aprsd/web/templates/send-message.html b/aprsd/web/templates/send-message.html
index d0aeb8c..1f9f354 100644
--- a/aprsd/web/templates/send-message.html
+++ b/aprsd/web/templates/send-message.html
@@ -1,24 +1,114 @@
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
-
-
From c941379a5c3983568253a09a68613dcb42d68340 Mon Sep 17 00:00:00 2001
From: Hemna
Date: Fri, 27 Aug 2021 15:10:37 -0400
Subject: [PATCH 3/5] Upgraded the send-message POC to use websockets
This patch updates the send message Admi page to use
websockets. It makes updates to the messages list instant.
---
aprsd/client.py | 11 +-
aprsd/flask.py | 162 +++++++++++++--------
aprsd/main.py | 9 +-
aprsd/web/static/js/send-message.js | 193 ++++++++++++++++++--------
aprsd/web/templates/send-message.html | 55 +-------
dev-requirements.in | 4 +-
dev-requirements.txt | 121 +++++++---------
requirements.in | 4 +-
requirements.txt | 63 +++++----
9 files changed, 351 insertions(+), 271 deletions(-)
diff --git a/aprsd/client.py b/aprsd/client.py
index 7b65703..8993d92 100644
--- a/aprsd/client.py
+++ b/aprsd/client.py
@@ -123,7 +123,12 @@ class Aprsdis(aprslib.IS):
self.select_timeout,
)
if not readable:
- continue
+ if not blocking:
+ #self.logger.warning("not fucking readable, not blocking, break!")
+ break
+ else:
+ #self.logger.warning("not fucking readable, continue")
+ continue
try:
short_buf = self.sock.recv(4096)
@@ -224,7 +229,7 @@ class Aprsdis(aprslib.IS):
line = b""
- while True:
+ while True and not self.thread_stop:
try:
for line in self._socket_readlines(blocking):
if line[0:1] != b"#":
@@ -270,7 +275,9 @@ class Aprsdis(aprslib.IS):
raise
if not blocking:
+ #self.logger.error("Not blocking, bail bitch")
break
+ #self.logger.error("Consumer exiting")
def get_client():
diff --git a/aprsd/flask.py b/aprsd/flask.py
index 839d42e..d5aa05f 100644
--- a/aprsd/flask.py
+++ b/aprsd/flask.py
@@ -13,6 +13,7 @@ import flask
from flask import request
import flask_classful
from flask_httpauth import HTTPBasicAuth
+from flask_socketio import Namespace, SocketIO
from werkzeug.security import check_password_hash, generate_password_hash
import aprsd
@@ -108,11 +109,14 @@ class SendMessageThread(threads.APRSDThread):
aprsis_client = None
request = None
got_ack = False
+ got_reply = False
- def __init__(self, config, info, msg):
+ def __init__(self, config, info, msg, namespace):
self.config = config
self.request = info
self.msg = msg
+ self.namespace = namespace
+ self.start_time = datetime.datetime.now()
msg = "({} -> {}) : {}".format(
info["from"],
info["to"],
@@ -183,7 +187,7 @@ class SendMessageThread(threads.APRSDThread):
LOG.debug("Exiting")
def rx_packet(self, packet):
- global got_ack, got_response
+ global socketio
# LOG.debug("Got packet back {}".format(packet))
resp = packet.get("response", None)
if resp == "ack":
@@ -191,10 +195,15 @@ class SendMessageThread(threads.APRSDThread):
LOG.info(f"We got ack for our sent message {ack_num}")
messaging.log_packet(packet)
SentMessages().ack(self.msg.id)
+ socketio.emit(
+ "ack", SentMessages().get(self.msg.id),
+ namespace="/ws",
+ )
stats.APRSDStats().ack_rx_inc()
self.got_ack = True
- if self.request["wait_reply"] == "0":
+ if self.request["wait_reply"] == "0" or self.got_reply:
# We aren't waiting for a reply, so we can bail
+ self.stop()
self.thread_stop = self.aprs_client.thread_stop = True
else:
packets.PacketList().add(packet)
@@ -209,9 +218,12 @@ class SendMessageThread(threads.APRSDThread):
fromcall=fromcall,
ack=msg_number,
)
- got_response = True
SentMessages().reply(self.msg.id, packet)
SentMessages().set_status(self.msg.id, "Got Reply")
+ socketio.emit(
+ "reply", SentMessages().get(self.msg.id),
+ namespace="/ws",
+ )
# Send the ack back?
ack = messaging.AckMessage(
@@ -223,37 +235,37 @@ class SendMessageThread(threads.APRSDThread):
SentMessages().set_status(self.msg.id, "Ack Sent")
# Now we can exit, since we are done.
+ self.got_reply = True
if self.got_ack:
+ self.stop()
self.thread_stop = self.aprs_client.thread_stop = True
def loop(self):
- LOG.debug("LOOP Start")
+ # we have a general time limit expecting results of
+ # around 120 seconds before we exit
+ now = datetime.datetime.now()
+ start_delta = str(now - self.start_time)
+ delta = utils.parse_delta_str(start_delta)
+ d = datetime.timedelta(**delta)
+ max_timeout = {"hours": 0.0, "minutes": 1, "seconds": 0}
+ max_delta = datetime.timedelta(**max_timeout)
+ if d > max_delta:
+ LOG.error("XXXXXX Haven't completed everything in 60 seconds. BAIL!")
+ return False
+
+ if self.got_ack and self.got_reply:
+ LOG.warning("We got everything already. BAIL")
+ return False
+
try:
# This will register a packet consumer with aprslib
# When new packets come in the consumer will process
# the packet
self.aprs_client.consumer(self.rx_packet, raw=False, blocking=False)
except aprslib.exceptions.ConnectionDrop:
- LOG.error("Connection dropped, reconnecting")
- time.sleep(5)
- # Force the deletion of the client object connected to aprs
- # This will cause a reconnect, next time client.get_client()
- # is called
- del self.aprs_client
- connecting = True
- counter = 0;
- while connecting:
- try:
- self.aprs_client = self.setup_connection()
- connecting = False
- except Exception:
- LOG.error("Couldn't connect")
- counter += 1
- if counter >= 3:
- LOG.error("Reached reconnect limit.")
- return False
+ LOG.error("Connection dropped.")
+ return False
- LOG.debug("LOOP END")
return True
@@ -351,38 +363,7 @@ class APRSDFlask(flask_classful.FlaskView):
@auth.login_required
def send_message(self):
LOG.debug(request)
- if request.method == "POST":
- info = {
- "from": request.form["from_call"],
- "to": request.form["to_call"],
- "password": request.form["from_call_password"],
- "message": request.form["message"],
- "wait_reply": request.form["wait_reply"],
- }
- LOG.debug(info)
- msg = messaging.TextMessage(
- info["from"], info["to"],
- info["message"],
- )
- msgs = SentMessages()
- msgs.add(msg)
- msgs.set_status(msg.id, "Sending")
-
- send_message_t = SendMessageThread(self.config, info, msg)
- send_message_t.start()
-
-
- info["from"]
- result = "sending"
- msg_id = msg.id
- result = {
- "msg_id": msg_id,
- "status": "sending",
- }
- return json.dumps(result)
- else:
- result = "fail"
-
+ if request.method == "GET":
return flask.render_template(
"send-message.html",
callsign=self.config["aprs"]["login"],
@@ -451,6 +432,60 @@ class APRSDFlask(flask_classful.FlaskView):
return json.dumps(self._stats())
+class SendMessageNamespace(Namespace):
+ _config = None
+ got_ack = False
+ reply_sent = False
+ msg = None
+ request = None
+
+ def __init__(self, namespace=None, config=None):
+ self._config = config
+ super().__init__(namespace)
+
+ def on_connect(self):
+ global socketio
+ LOG.debug("Web socket connected")
+ socketio.emit(
+ "connected", {"data": "Lets dance"},
+ namespace="/ws",
+ )
+
+ def on_disconnect(self):
+ LOG.debug("WS Disconnected")
+
+ def on_send(self, data):
+ global socketio
+ LOG.debug(f"WS: on_send {data}")
+ self.request = data
+ msg = messaging.TextMessage(
+ data["from"], data["to"],
+ data["message"],
+ )
+ self.msg = msg
+ msgs = SentMessages()
+ msgs.add(msg)
+ msgs.set_status(msg.id, "Sending")
+ socketio.emit(
+ "sent", SentMessages().get(self.msg.id),
+ namespace="/ws",
+ )
+
+ socketio.start_background_task(self._start, self._config, data, msg, self)
+ LOG.warning("WS: on_send: exit")
+
+ def _start(self, config, data, msg, namespace):
+ msg_thread = SendMessageThread(self._config, data, msg, self)
+ msg_thread.start()
+
+ def handle_message(self, data):
+ LOG.debug(f"WS Data {data}")
+
+ def handle_json(self, data):
+ LOG.debug(f"WS json {data}")
+
+
+
def setup_logging(config, flask_app, loglevel, quiet):
flask_log = logging.getLogger("werkzeug")
@@ -485,6 +520,8 @@ def setup_logging(config, flask_app, loglevel, quiet):
def init_flask(config, loglevel, quiet):
+ global socketio
+
flask_app = flask.Flask(
"aprsd",
static_url_path="/static",
@@ -498,8 +535,17 @@ def init_flask(config, loglevel, quiet):
flask_app.route("/stats", methods=["GET"])(server.stats)
flask_app.route("/messages", methods=["GET"])(server.messages)
flask_app.route("/packets", methods=["GET"])(server.packets)
- flask_app.route("/send-message", methods=["GET", "POST"])(server.send_message)
+ flask_app.route("/send-message", methods=["GET"])(server.send_message)
flask_app.route("/send-message-status", methods=["GET"])(server.send_message_status)
flask_app.route("/save", methods=["GET"])(server.save)
flask_app.route("/plugins", methods=["GET"])(server.plugins)
- return flask_app
+
+ socketio = SocketIO(
+ flask_app, logger=False, engineio_logger=False,
+ async_mode="threading",
+ )
+ # import eventlet
+ # eventlet.monkey_patch()
+
+ socketio.on_namespace(SendMessageNamespace("/ws", config=config))
+ return socketio, flask_app
diff --git a/aprsd/main.py b/aprsd/main.py
index bd9a711..8c1fa78 100644
--- a/aprsd/main.py
+++ b/aprsd/main.py
@@ -513,10 +513,11 @@ def server(
if web_enabled:
flask_enabled = True
- app = flask.init_flask(config, loglevel, quiet)
- app.run(
- host=config["aprsd"]["web"]["host"],
- port=config["aprsd"]["web"]["port"],
+ (socketio, app) = flask.init_flask(config, loglevel, quiet)
+ socketio.run(
+ app,
+ host=config["aprsd"]["web"]["host"],
+ port=config["aprsd"]["web"]["port"],
)
# If there are items in the msgTracker, then save them
diff --git a/aprsd/web/static/js/send-message.js b/aprsd/web/static/js/send-message.js
index 92afbd4..812cafc 100644
--- a/aprsd/web/static/js/send-message.js
+++ b/aprsd/web/static/js/send-message.js
@@ -1,74 +1,145 @@
msgs_list = {};
+var cleared = false;
function size_dict(d){c=0; for (i in d) ++c; return c}
-function update_messages(data) {
- msgs_cnt = size_dict(data);
- $('#msgs_count').html(msgs_cnt);
+function init_messages() {
+ const socket = io("/ws");
+ socket.on('connect', function () {
+ console.log("Connected to socketio");
+ });
+ socket.on('connected', function(msg) {
+ console.log("Connected!");
+ console.log(msg);
+ });
- var msgsdiv = $("#msgsDiv");
- //nuke the contents first, then add to it.
- if (size_dict(msgs_list) == 0 && size_dict(data) > 0) {
- msgsdiv.html('')
- }
+ socket.on("sent", function(msg) {
+ if (cleared == false) {
+ var msgsdiv = $("#msgsDiv");
+ msgsdiv.html('')
+ cleared = true
+ }
+ add_msg(msg);
+ });
- jQuery.each(data, function(i, val) {
- if ( msgs_list.hasOwnProperty(val["ts"]) == false ) {
- // Store the packet
- msgs_list[val["ts"]] = val;
- ts_str = val["ts"].toString();
- ts = ts_str.split(".")[0]*1000;
- var d = new Date(ts).toLocaleDateString("en-US")
- var t = new Date(ts).toLocaleTimeString("en-US")
+ socket.on("ack", function(msg) {
+ update_msg(msg);
+ });
+ socket.on("reply", function(msg) {
+ update_msg(msg);
+ });
- from = val['from']
- title_id = 'title_tx'
- var from_to = d + " " + t + " " + from + " > "
+ $("#sendform").submit(function(event) {
+ event.preventDefault();
- if (val.hasOwnProperty('to')) {
- from_to = from_to + val['to']
- }
- from_to = from_to + " - " + val['raw']
+ var $checkboxes = $(this).find('input[type=checkbox]');
- id = ts_str.split('.')[0]
- pretty_id = "pretty_" + id
- loader_id = "loader_" + id
- reply_id = "reply_" + id
- json_pretty = Prism.highlight(JSON.stringify(val, null, '\t'), Prism.languages.json, 'json');
- msg_html = '';
- msg_html += '
';
- msg_html += '
' + from_to + '
';
- msg_html += ''
- msgsdiv.prepend(msg_html);
- } else {
- // We have an existing entry
- msgs_list[val["ts"]] = val;
- ts_str = val["ts"].toString();
- id = ts_str.split('.')[0]
- pretty_id = "pretty_" + id
- loader_id = "loader_" + id
- reply_id = "reply_" + id
- var pretty_pre = $("#" + pretty_id);
- if (val['ack'] == true) {
- var loader_div = $('#' + loader_id);
- loader_div.removeClass('ui active inline loader');
- loader_div.addClass('ui disabled loader');
- loader_div.attr('data-content', 'Got reply');
- }
+ //loop through the checkboxes and change to hidden fields
+ $checkboxes.each(function() {
+ if ($(this)[0].checked) {
+ $(this).attr('type', 'hidden');
+ $(this).val(1);
+ } else {
+ $(this).attr('type', 'hidden');
+ $(this).val(0);
+ }
+ });
- if (val['reply'] !== null) {
- var reply_div = $('#' + reply_id);
- reply_div.removeClass("thumbs down outline icon");
- reply_div.addClass('thumbs up outline icon');
- reply_div.attr('data-content', 'Got Reply');
- }
+ msg = {'from': $('#from').val(),
+ 'password': $('#password').val(),
+ 'to': $('#to').val(),
+ 'message': $('#message').val(),
+ 'wait_reply': $('#wait_reply').val(),
+ }
- pretty_pre.html('');
- json_pretty = Prism.highlight(JSON.stringify(val, null, '\t'), Prism.languages.json, 'json');
- pretty_pre.html(json_pretty);
- }
- });
-
- $('.ui.accordion').accordion('refresh');
+ socket.emit("send", msg);
+ //loop through the checkboxes and change to hidden fields
+ $checkboxes.each(function() {
+ $(this).attr('type', 'checkbox');
+ });
+ });
+}
+
+function add_msg(msg) {
+ var msgsdiv = $("#msgsDiv");
+
+ ts_str = msg["ts"].toString();
+ ts = ts_str.split(".")[0]*1000;
+ var d = new Date(ts).toLocaleDateString("en-US")
+ var t = new Date(ts).toLocaleTimeString("en-US")
+
+ from = msg['from']
+ title_id = 'title_tx'
+ var from_to = d + " " + t + " " + from + " > "
+
+ if (msg.hasOwnProperty('to')) {
+ from_to = from_to + msg['to']
+ }
+ from_to = from_to + " - " + msg['message']
+
+ id = ts_str.split('.')[0]
+ pretty_id = "pretty_" + id
+ loader_id = "loader_" + id
+ ack_id = "ack_" + id
+ reply_id = "reply_" + id
+ span_id = "span_" + id
+ json_pretty = Prism.highlight(JSON.stringify(msg, null, '\t'), Prism.languages.json, 'json');
+ msg_html = '';
+ msg_html += '
';
+ msg_html += '
';
+ msg_html += '
';
+ msg_html += '
' + from_to +' ';
+ msg_html += ''
+ msgsdiv.prepend(msg_html);
+ $('.ui.accordion').accordion('refresh');
+}
+
+function update_msg(msg) {
+ var msgsdiv = $("#msgsDiv");
+ // We have an existing entry
+ ts_str = msg["ts"].toString();
+ id = ts_str.split('.')[0]
+ pretty_id = "pretty_" + id
+ loader_id = "loader_" + id
+ reply_id = "reply_" + id
+ ack_id = "ack_" + id
+ span_id = "span_" + id
+
+
+
+ if (msg['ack'] == true) {
+ var loader_div = $('#' + loader_id);
+ var ack_div = $('#' + ack_id);
+ loader_div.removeClass('ui active inline loader');
+ loader_div.addClass('ui disabled loader');
+ ack_div.removeClass('thumbs up outline icon');
+ ack_div.addClass('thumbs up outline icon');
+ }
+
+ if (msg['reply'] !== null) {
+ var reply_div = $('#' + reply_id);
+ reply_div.removeClass("thumbs down outline icon");
+ reply_div.addClass('reply icon');
+ reply_div.attr('data-content', 'Got Reply');
+
+ var d = new Date(ts).toLocaleDateString("en-US")
+ var t = new Date(ts).toLocaleTimeString("en-US")
+ var from_to = d + " " + t + " " + from + " > "
+
+ if (msg.hasOwnProperty('to')) {
+ from_to = from_to + msg['to']
+ }
+ from_to = from_to + " - " + msg['message']
+ from_to += " ===> " + msg["reply"]["message_text"]
+
+ var span_div = $('#' + span_id);
+ span_div.html(from_to);
+ }
+
+ var pretty_pre = $("#" + pretty_id);
+ pretty_pre.html('');
+ json_pretty = Prism.highlight(JSON.stringify(msg, null, '\t'), Prism.languages.json, 'json');
+ pretty_pre.html(json_pretty);
+ $('.ui.accordion').accordion('refresh');
}
diff --git a/aprsd/web/templates/send-message.html b/aprsd/web/templates/send-message.html
index 1f9f354..566eaba 100644
--- a/aprsd/web/templates/send-message.html
+++ b/aprsd/web/templates/send-message.html
@@ -3,6 +3,9 @@
+
+
+
@@ -17,51 +20,7 @@
@@ -87,12 +46,12 @@
-
+
-
+
diff --git a/dev-requirements.in b/dev-requirements.in
index e12700c..3cce931 100644
--- a/dev-requirements.in
+++ b/dev-requirements.in
@@ -1,12 +1,12 @@
flake8
isort
mypy
-pytest
-pytest-cov
pep8-naming
Sphinx
tox
twine
pre-commit
pip-tools
+#pytest
+#pytest-cov
gray
diff --git a/dev-requirements.txt b/dev-requirements.txt
index e3141af..eba5c04 100644
--- a/dev-requirements.txt
+++ b/dev-requirements.txt
@@ -9,28 +9,26 @@ add-trailing-comma==2.1.0
alabaster==0.7.12
# via sphinx
appdirs==1.4.4
- # via
- # black
- # virtualenv
-attrs==20.3.0
- # via
- # jsonschema
- # pytest
+ # via black
+attrs==21.2.0
+ # via jsonschema
autoflake==1.4
# via gray
babel==2.9.1
# via sphinx
+backports.entry-points-selectable==1.1.0
+ # via virtualenv
black==21.7b0
# via gray
-bleach==3.3.0
+bleach==4.1.0
# via readme-renderer
-certifi==2020.12.5
+certifi==2021.5.30
# via requests
-cfgv==3.2.0
+cfgv==3.3.1
# via pre-commit
-chardet==4.0.0
+charset-normalizer==2.0.4
# via requests
-click==7.1.2
+click==8.0.1
# via
# black
# pip-tools
@@ -40,11 +38,9 @@ colorlog==6.4.1
# via prettylog
configargparse==1.5.2
# via gray
-coverage==5.5
- # via pytest-cov
-distlib==0.3.1
+distlib==0.3.2
# via virtualenv
-docutils==0.16
+docutils==0.17.1
# via
# readme-renderer
# sphinx
@@ -58,40 +54,39 @@ fixit==0.1.4
# via gray
flake8-polyfill==1.0.2
# via pep8-naming
-flake8==3.9.1
+flake8==3.9.2
# via
# -r dev-requirements.in
# fixit
# flake8-polyfill
+ # pep8-naming
gray==0.10.1
# via -r dev-requirements.in
-identify==2.2.4
+identify==2.2.13
# via pre-commit
-idna==2.10
+idna==3.2
# via requests
imagesize==1.2.0
# via sphinx
-importlib-metadata==4.0.1
+importlib-metadata==4.7.1
# via
# keyring
# twine
importlib-resources==5.2.2
# via fixit
-iniconfig==1.1.1
- # via pytest
-isort==5.8.0
+isort==5.9.3
# via
# -r dev-requirements.in
# gray
-jinja2==2.11.3
+jinja2==3.0.1
# via sphinx
jsonschema==3.2.0
# via fixit
-keyring==23.0.1
+keyring==23.1.0
# via twine
libcst==0.3.20
# via fixit
-markupsafe==1.1.1
+markupsafe==2.0.1
# via jinja2
mccabe==0.6.1
# via flake8
@@ -100,45 +95,42 @@ mypy-extensions==0.4.3
# black
# mypy
# typing-inspect
-mypy==0.812
+mypy==0.910
# via -r dev-requirements.in
nodeenv==1.6.0
# via pre-commit
-packaging==20.9
+packaging==21.0
# via
# bleach
- # pytest
# sphinx
# tox
-pathspec==0.8.1
+pathspec==0.9.0
# via black
-pep517==0.10.0
+pep517==0.11.0
# via pip-tools
-pep8-naming==0.11.1
+pep8-naming==0.12.1
# via -r dev-requirements.in
-pip-tools==6.1.0
+pip-tools==6.2.0
# via -r dev-requirements.in
-pkginfo==1.7.0
+pkginfo==1.7.1
# via twine
-pluggy==0.13.1
- # via
- # pytest
- # tox
-pre-commit==2.12.1
+platformdirs==2.2.0
+ # via virtualenv
+pluggy==1.0.0
+ # via tox
+pre-commit==2.14.0
# via -r dev-requirements.in
prettylog==0.3.0
# via gray
py==1.10.0
- # via
- # pytest
- # tox
+ # via tox
pycodestyle==2.7.0
# via flake8
pyflakes==2.3.1
# via
# autoflake
# flake8
-pygments==2.9.0
+pygments==2.10.0
# via
# readme-renderer
# sphinx
@@ -146,12 +138,6 @@ pyparsing==2.4.7
# via packaging
pyrsistent==0.18.0
# via jsonschema
-pytest-cov==2.11.1
- # via -r dev-requirements.in
-pytest==6.2.3
- # via
- # -r dev-requirements.in
- # pytest-cov
pytz==2021.1
# via babel
pyupgrade==2.24.0
@@ -163,18 +149,18 @@ pyyaml==5.4.1
# pre-commit
readme-renderer==29.0
# via twine
-regex==2021.4.4
+regex==2021.8.27
# via black
requests-toolbelt==0.9.1
# via twine
-requests==2.25.1
+requests==2.26.0
# via
# requests-toolbelt
# sphinx
# twine
-rfc3986==1.4.0
+rfc3986==1.5.0
# via twine
-six==1.15.0
+six==1.16.0
# via
# bleach
# jsonschema
@@ -183,19 +169,19 @@ six==1.15.0
# virtualenv
snowballstemmer==2.1.0
# via sphinx
-sphinx==3.5.4
+sphinx==4.1.2
# via -r dev-requirements.in
sphinxcontrib-applehelp==1.0.2
# via sphinx
sphinxcontrib-devhelp==1.0.2
# via sphinx
-sphinxcontrib-htmlhelp==1.0.3
+sphinxcontrib-htmlhelp==2.0.0
# via sphinx
sphinxcontrib-jsmath==1.0.1
# via sphinx
sphinxcontrib-qthelp==1.0.3
# via sphinx
-sphinxcontrib-serializinghtml==1.1.4
+sphinxcontrib-serializinghtml==1.1.5
# via sphinx
tokenize-rt==4.1.0
# via
@@ -203,20 +189,19 @@ tokenize-rt==4.1.0
# pyupgrade
toml==0.10.2
# via
- # pep517
+ # mypy
# pre-commit
- # pytest
# tox
tomli==1.2.1
- # via black
-tox==3.23.0
+ # via
+ # black
+ # pep517
+tox==3.24.3
# via -r dev-requirements.in
-tqdm==4.60.0
+tqdm==4.62.2
# via twine
-twine==3.4.1
+twine==3.4.2
# via -r dev-requirements.in
-typed-ast==1.4.3
- # via mypy
typing-extensions==3.10.0.0
# via
# libcst
@@ -230,15 +215,17 @@ unify==0.5
# via gray
untokenize==0.1.1
# via unify
-urllib3==1.26.5
+urllib3==1.26.6
# via requests
-virtualenv==20.4.4
+virtualenv==20.7.2
# via
# pre-commit
# tox
webencodings==0.5.1
# via bleach
-zipp==3.4.1
+wheel==0.37.0
+ # via pip-tools
+zipp==3.5.0
# via
# importlib-metadata
# importlib-resources
diff --git a/requirements.in b/requirements.in
index 7703126..1e00739 100644
--- a/requirements.in
+++ b/requirements.in
@@ -12,10 +12,12 @@ pbr
pyyaml
# Allowing a newer version can lead to a conflict with
# requests.
-py3-validate-email==0.2.16
+# py3-validate-email
pytz
requests
six
thesmuggler
yfinance
update_checker
+flask-socketio
+eventlet
diff --git a/requirements.txt b/requirements.txt
index f044a69..1f0e6e2 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -8,17 +8,19 @@ aioax25==0.0.10
# via -r requirements.in
aprslib==0.6.47
# via -r requirements.in
-backoff==1.10.0
+backoff==1.11.1
# via opencage
-certifi==2020.12.5
+bidict==0.21.2
+ # via python-socketio
+certifi==2021.5.30
# via requests
-cffi==1.14.5
+cffi==1.14.6
# via cryptography
-chardet==4.0.0
+charset-normalizer==2.0.4
# via requests
click-completion==0.5.2
# via -r requirements.in
-click==7.1.2
+click==8.0.1
# via
# -r requirements.in
# click-completion
@@ -27,50 +29,51 @@ contexter==0.1.4
# via signalslot
cryptography==3.4.7
# via pyopenssl
-dnspython==2.1.0
- # via py3-validate-email
-filelock==3.0.12
- # via py3-validate-email
+dnspython==1.16.0
+ # via eventlet
+eventlet==0.31.1
+ # via -r requirements.in
flask-classful==0.14.2
# via -r requirements.in
-flask-httpauth==4.3.0
+flask-httpauth==4.4.0
# via -r requirements.in
-flask==1.1.2
+flask-socketio==5.1.1
+ # via -r requirements.in
+flask==2.0.1
# via
# -r requirements.in
# flask-classful
# flask-httpauth
-idna==2.10
- # via
- # py3-validate-email
- # requests
+ # flask-socketio
+greenlet==1.1.1
+ # via eventlet
+idna==3.2
+ # via requests
imapclient==2.2.0
# via -r requirements.in
-itsdangerous==1.1.0
+itsdangerous==2.0.1
# via flask
-jinja2==2.11.3
+jinja2==3.0.1
# via
# click-completion
# flask
lxml==4.6.3
# via yfinance
-markupsafe==1.1.1
+markupsafe==2.0.1
# via jinja2
multitasking==0.0.9
# via yfinance
-numpy==1.20.2
+numpy==1.21.2
# via
# pandas
# yfinance
-opencage==1.2.2
+opencage==2.0.0
# via -r requirements.in
-pandas==1.2.4
+pandas==1.3.2
# via yfinance
pbr==5.6.0
# via -r requirements.in
-pluggy==0.13.1
- # via -r requirements.in
-py3-validate-email==0.2.16
+pluggy==1.0.0
# via -r requirements.in
pycparser==2.20
# via cffi
@@ -80,13 +83,17 @@ pyserial==3.5
# via aioax25
python-dateutil==2.8.1
# via pandas
+python-engineio==4.2.1
+ # via python-socketio
+python-socketio==5.4.0
+ # via flask-socketio
pytz==2021.1
# via
# -r requirements.in
# pandas
pyyaml==5.4.1
# via -r requirements.in
-requests==2.25.1
+requests==2.26.0
# via
# -r requirements.in
# opencage
@@ -100,8 +107,8 @@ six==1.15.0
# via
# -r requirements.in
# click-completion
+ # eventlet
# imapclient
- # opencage
# pyopenssl
# python-dateutil
# signalslot
@@ -109,11 +116,11 @@ thesmuggler==1.0.1
# via -r requirements.in
update-checker==0.18.0
# via -r requirements.in
-urllib3==1.26.5
+urllib3==1.26.6
# via requests
weakrefmethod==1.0.3
# via signalslot
werkzeug==1.0.1
# via flask
-yfinance==0.1.59
+yfinance==0.1.63
# via -r requirements.in
From 84ce60bc50a6d3123d9a944e0098660e31d32bf9 Mon Sep 17 00:00:00 2001
From: Hemna
Date: Mon, 30 Aug 2021 12:18:14 -0400
Subject: [PATCH 4/5] Cleaned up some pep8 failures
---
aprsd/client.py | 4 ----
aprsd/flask.py | 3 +--
aprsd/main.py | 4 ++--
aprsd/messaging.py | 1 -
dev-requirements.in | 4 ++--
dev-requirements.txt | 41 ++++++++++++++++++++++++++++++-----------
requirements.txt | 16 +++++++---------
7 files changed, 42 insertions(+), 31 deletions(-)
diff --git a/aprsd/client.py b/aprsd/client.py
index 8993d92..dd1e248 100644
--- a/aprsd/client.py
+++ b/aprsd/client.py
@@ -124,10 +124,8 @@ class Aprsdis(aprslib.IS):
)
if not readable:
if not blocking:
- #self.logger.warning("not fucking readable, not blocking, break!")
break
else:
- #self.logger.warning("not fucking readable, continue")
continue
try:
@@ -275,9 +273,7 @@ class Aprsdis(aprslib.IS):
raise
if not blocking:
- #self.logger.error("Not blocking, bail bitch")
break
- #self.logger.error("Consumer exiting")
def get_client():
diff --git a/aprsd/flask.py b/aprsd/flask.py
index d5aa05f..413f36c 100644
--- a/aprsd/flask.py
+++ b/aprsd/flask.py
@@ -25,6 +25,7 @@ LOG = logging.getLogger("APRSD")
auth = HTTPBasicAuth()
users = None
+
class SentMessages:
_instance = None
lock = None
@@ -47,7 +48,6 @@ class SentMessages:
self.msgs[msg.id]["message"] = msg.message.rstrip("\n")
self.msgs[msg.id]["raw"] = str(msg).rstrip("\n")
-
def _create(self, id):
return {
"id": id,
@@ -485,7 +485,6 @@ class SendMessageNamespace(Namespace):
LOG.debug(f"WS json {data}")
-
def setup_logging(config, flask_app, loglevel, quiet):
flask_log = logging.getLogger("werkzeug")
diff --git a/aprsd/main.py b/aprsd/main.py
index 8c1fa78..30b2d84 100644
--- a/aprsd/main.py
+++ b/aprsd/main.py
@@ -516,8 +516,8 @@ def server(
(socketio, app) = flask.init_flask(config, loglevel, quiet)
socketio.run(
app,
- host=config["aprsd"]["web"]["host"],
- port=config["aprsd"]["web"]["port"],
+ host=config["aprsd"]["web"]["host"],
+ port=config["aprsd"]["web"]["port"],
)
# If there are items in the msgTracker, then save them
diff --git a/aprsd/messaging.py b/aprsd/messaging.py
index 8ae987b..0fe54f6 100644
--- a/aprsd/messaging.py
+++ b/aprsd/messaging.py
@@ -499,7 +499,6 @@ class AckMessage(Message):
thread = SendAckThread(self)
thread.start()
-
def send_direct(self, aprsis_client=None):
"""Send an ack message without a separate thread."""
cl = self.get_transport()
diff --git a/dev-requirements.in b/dev-requirements.in
index 3cce931..e55ac9e 100644
--- a/dev-requirements.in
+++ b/dev-requirements.in
@@ -7,6 +7,6 @@ tox
twine
pre-commit
pip-tools
-#pytest
-#pytest-cov
+pytest
+pytest-cov
gray
diff --git a/dev-requirements.txt b/dev-requirements.txt
index eba5c04..6f7ad18 100644
--- a/dev-requirements.txt
+++ b/dev-requirements.txt
@@ -1,5 +1,5 @@
#
-# This file is autogenerated by pip-compile
+# This file is autogenerated by pip-compile with python 3.8
# To update, run:
#
# pip-compile dev-requirements.in
@@ -11,7 +11,9 @@ alabaster==0.7.12
appdirs==1.4.4
# via black
attrs==21.2.0
- # via jsonschema
+ # via
+ # jsonschema
+ # pytest
autoflake==1.4
# via gray
babel==2.9.1
@@ -38,6 +40,8 @@ colorlog==6.4.1
# via prettylog
configargparse==1.5.2
# via gray
+coverage==5.5
+ # via pytest-cov
distlib==0.3.2
# via virtualenv
docutils==0.17.1
@@ -52,14 +56,14 @@ filelock==3.0.12
# virtualenv
fixit==0.1.4
# via gray
-flake8-polyfill==1.0.2
- # via pep8-naming
flake8==3.9.2
# via
# -r dev-requirements.in
# fixit
# flake8-polyfill
# pep8-naming
+flake8-polyfill==1.0.2
+ # via pep8-naming
gray==0.10.1
# via -r dev-requirements.in
identify==2.2.13
@@ -74,6 +78,8 @@ importlib-metadata==4.7.1
# twine
importlib-resources==5.2.2
# via fixit
+iniconfig==1.1.1
+ # via pytest
isort==5.9.3
# via
# -r dev-requirements.in
@@ -90,18 +96,19 @@ markupsafe==2.0.1
# via jinja2
mccabe==0.6.1
# via flake8
+mypy==0.910
+ # via -r dev-requirements.in
mypy-extensions==0.4.3
# via
# black
# mypy
# typing-inspect
-mypy==0.910
- # via -r dev-requirements.in
nodeenv==1.6.0
# via pre-commit
packaging==21.0
# via
# bleach
+ # pytest
# sphinx
# tox
pathspec==0.9.0
@@ -116,14 +123,18 @@ pkginfo==1.7.1
# via twine
platformdirs==2.2.0
# via virtualenv
-pluggy==1.0.0
- # via tox
+pluggy==0.13.1
+ # via
+ # pytest
+ # tox
pre-commit==2.14.0
# via -r dev-requirements.in
prettylog==0.3.0
# via gray
py==1.10.0
- # via tox
+ # via
+ # pytest
+ # tox
pycodestyle==2.7.0
# via flake8
pyflakes==2.3.1
@@ -138,6 +149,12 @@ pyparsing==2.4.7
# via packaging
pyrsistent==0.18.0
# via jsonschema
+pytest==6.2.4
+ # via
+ # -r dev-requirements.in
+ # pytest-cov
+pytest-cov==2.12.1
+ # via -r dev-requirements.in
pytz==2021.1
# via babel
pyupgrade==2.24.0
@@ -151,13 +168,13 @@ readme-renderer==29.0
# via twine
regex==2021.8.27
# via black
-requests-toolbelt==0.9.1
- # via twine
requests==2.26.0
# via
# requests-toolbelt
# sphinx
# twine
+requests-toolbelt==0.9.1
+ # via twine
rfc3986==1.5.0
# via twine
six==1.16.0
@@ -191,6 +208,8 @@ toml==0.10.2
# via
# mypy
# pre-commit
+ # pytest
+ # pytest-cov
# tox
tomli==1.2.1
# via
diff --git a/requirements.txt b/requirements.txt
index 1f0e6e2..645eb47 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,5 +1,5 @@
#
-# This file is autogenerated by pip-compile
+# This file is autogenerated by pip-compile with python 3.8
# To update, run:
#
# pip-compile requirements.in
@@ -18,8 +18,6 @@ cffi==1.14.6
# via cryptography
charset-normalizer==2.0.4
# via requests
-click-completion==0.5.2
- # via -r requirements.in
click==8.0.1
# via
# -r requirements.in
@@ -33,18 +31,18 @@ dnspython==1.16.0
# via eventlet
eventlet==0.31.1
# via -r requirements.in
-flask-classful==0.14.2
- # via -r requirements.in
-flask-httpauth==4.4.0
- # via -r requirements.in
-flask-socketio==5.1.1
- # via -r requirements.in
flask==2.0.1
# via
# -r requirements.in
# flask-classful
# flask-httpauth
# flask-socketio
+flask-classful==0.14.2
+ # via -r requirements.in
+flask-httpauth==4.4.0
+ # via -r requirements.in
+flask-socketio==5.1.1
+ # via -r requirements.in
greenlet==1.1.1
# via eventlet
idna==3.2
From c537b54df64c9a47dd60764969d54afda9d766c8 Mon Sep 17 00:00:00 2001
From: Hemna
Date: Wed, 1 Sep 2021 17:26:10 -0400
Subject: [PATCH 5/5] Updated requirements
---
aprsd/flask.py | 4 +++-
dev-requirements.txt | 4 ++--
requirements.in | 2 +-
requirements.txt | 22 ++++++++++++++++------
4 files changed, 22 insertions(+), 10 deletions(-)
diff --git a/aprsd/flask.py b/aprsd/flask.py
index 413f36c..bf4a81c 100644
--- a/aprsd/flask.py
+++ b/aprsd/flask.py
@@ -17,7 +17,9 @@ from flask_socketio import Namespace, SocketIO
from werkzeug.security import check_password_hash, generate_password_hash
import aprsd
-from aprsd import client, kissclient, messaging, packets, plugin, stats, threads, utils
+from aprsd import (
+ client, kissclient, messaging, packets, plugin, stats, threads, utils,
+)
LOG = logging.getLogger("APRSD")
diff --git a/dev-requirements.txt b/dev-requirements.txt
index 6f7ad18..c709889 100644
--- a/dev-requirements.txt
+++ b/dev-requirements.txt
@@ -123,7 +123,7 @@ pkginfo==1.7.1
# via twine
platformdirs==2.2.0
# via virtualenv
-pluggy==0.13.1
+pluggy==1.0.0
# via
# pytest
# tox
@@ -149,7 +149,7 @@ pyparsing==2.4.7
# via packaging
pyrsistent==0.18.0
# via jsonschema
-pytest==6.2.4
+pytest==6.2.5
# via
# -r dev-requirements.in
# pytest-cov
diff --git a/requirements.in b/requirements.in
index 1e00739..b55cb43 100644
--- a/requirements.in
+++ b/requirements.in
@@ -12,7 +12,7 @@ pbr
pyyaml
# Allowing a newer version can lead to a conflict with
# requests.
-# py3-validate-email
+py3-validate-email
pytz
requests
six
diff --git a/requirements.txt b/requirements.txt
index 645eb47..727141a 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -23,14 +23,20 @@ click==8.0.1
# -r requirements.in
# click-completion
# flask
+click-completion==0.5.2
+ # via -r requirements.in
contexter==0.1.4
# via signalslot
cryptography==3.4.7
# via pyopenssl
-dnspython==1.16.0
- # via eventlet
-eventlet==0.31.1
+dnspython==2.1.0
+ # via
+ # eventlet
+ # py3-validate-email
+eventlet==0.32.0
# via -r requirements.in
+filelock==3.0.12
+ # via py3-validate-email
flask==2.0.1
# via
# -r requirements.in
@@ -46,7 +52,9 @@ flask-socketio==5.1.1
greenlet==1.1.1
# via eventlet
idna==3.2
- # via requests
+ # via
+ # py3-validate-email
+ # requests
imapclient==2.2.0
# via -r requirements.in
itsdangerous==2.0.1
@@ -73,6 +81,8 @@ pbr==5.6.0
# via -r requirements.in
pluggy==1.0.0
# via -r requirements.in
+py3-validate-email==1.0.1
+ # via -r requirements.in
pycparser==2.20
# via cffi
pyopenssl==20.0.1
@@ -101,7 +111,7 @@ shellingham==1.4.0
# via click-completion
signalslot==0.1.2
# via aioax25
-six==1.15.0
+six==1.16.0
# via
# -r requirements.in
# click-completion
@@ -118,7 +128,7 @@ urllib3==1.26.6
# via requests
weakrefmethod==1.0.3
# via signalslot
-werkzeug==1.0.1
+werkzeug==2.0.0
# via flask
yfinance==0.1.63
# via -r requirements.in