1
0
mirror of https://github.com/craigerl/aprsd.git synced 2025-06-18 14:22:32 -04:00

Lots of fixes

This commit is contained in:
Hemna 2021-01-08 15:47:30 -05:00
parent 4c0150dd97
commit 231c15b1af
17 changed files with 258 additions and 155 deletions

@ -1,29 +1,48 @@
repos: repos:
- repo: https://github.com/pre-commit/pre-commit-hooks - repo: https://github.com/pre-commit/pre-commit-hooks
rev: v3.2.0 rev: v3.4.0
hooks: hooks:
- id: trailing-whitespace - id: trailing-whitespace
- id: end-of-file-fixer - id: end-of-file-fixer
- id: check-yaml - id: check-yaml
- id: check-added-large-files - id: check-added-large-files
- id: fix-encoding-pragma - id: detect-private-key
- id: detect-private-key - id: check-merge-conflict
- id: check-merge-conflict - id: check-case-conflict
- id: check-case-conflict - id: check-docstring-first
- id: check-docstring-first - id: check-builtin-literals
- id: check-builtin-literals - id: double-quote-string-fixer
- repo: https://github.com/psf/black
rev: 19.3b0
hooks:
- id: black
- repo: https://github.com/pre-commit/mirrors-isort - repo: https://github.com/asottile/setup-cfg-fmt
rev: v5.7.0 rev: v1.16.0
hooks: hooks:
- id: isort - id: setup-cfg-fmt
- repo: https://gitlab.com/pycqa/flake8 - repo: https://github.com/asottile/add-trailing-comma
rev: 3.8.1 rev: v2.0.2
hooks: hooks:
- id: flake8 - id: add-trailing-comma
additional_dependencies: [flake8-bugbear] args: [--py36-plus]
- repo: https://github.com/asottile/pyupgrade
rev: v2.7.4
hooks:
- id: pyupgrade
args:
- --py3-plus
- repo: https://github.com/pre-commit/mirrors-isort
rev: v5.7.0
hooks:
- id: isort
- repo: https://github.com/psf/black
rev: 20.8b1
hooks:
- id: black
- repo: https://gitlab.com/pycqa/flake8
rev: 3.8.4
hooks:
- id: flake8
additional_dependencies: [flake8-bugbear]

@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
# Licensed under the Apache License, Version 2.0 (the "License"); you may # Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain # not use this file except in compliance with the License. You may obtain
# a copy of the License at # a copy of the License at

@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
import logging import logging
import select import select
import socket
import time import time
import aprslib import aprslib
@ -9,7 +7,7 @@ import aprslib
LOG = logging.getLogger("APRSD") LOG = logging.getLogger("APRSD")
class Client(object): class Client:
"""Singleton client class that constructs the aprslib connection.""" """Singleton client class that constructs the aprslib connection."""
_instance = None _instance = None
@ -19,7 +17,7 @@ class Client(object):
def __new__(cls, *args, **kwargs): def __new__(cls, *args, **kwargs):
"""This magic turns this into a singleton.""" """This magic turns this into a singleton."""
if cls._instance is None: if cls._instance is None:
cls._instance = super(Client, cls).__new__(cls) cls._instance = super().__new__(cls)
# Put any initialization here. # Put any initialization here.
return cls._instance return cls._instance
@ -82,7 +80,7 @@ class Aprsdis(aprslib.IS):
""" """
try: try:
self.sock.setblocking(0) self.sock.setblocking(0)
except socket.error as e: except OSError as e:
self.logger.error("socket error when setblocking(0): %s" % str(e)) self.logger.error("socket error when setblocking(0): %s" % str(e))
raise aprslib.ConnectionDrop("connection dropped") raise aprslib.ConnectionDrop("connection dropped")
@ -93,7 +91,10 @@ class Aprsdis(aprslib.IS):
# set a select timeout, so we get a chance to exit # set a select timeout, so we get a chance to exit
# when user hits CTRL-C # when user hits CTRL-C
readable, writable, exceptional = select.select( readable, writable, exceptional = select.select(
[self.sock], [], [], self.select_timeout [self.sock],
[],
[],
self.select_timeout,
) )
if not readable: if not readable:
continue continue
@ -105,7 +106,7 @@ class Aprsdis(aprslib.IS):
if not short_buf: if not short_buf:
self.logger.error("socket.recv(): returned empty") self.logger.error("socket.recv(): returned empty")
raise aprslib.ConnectionDrop("connection dropped") raise aprslib.ConnectionDrop("connection dropped")
except socket.error as e: except OSError as e:
# self.logger.error("socket error on recv(): %s" % str(e)) # self.logger.error("socket error on recv(): %s" % str(e))
if "Resource temporarily unavailable" in str(e): if "Resource temporarily unavailable" in str(e):
if not blocking: if not blocking:

@ -1,18 +1,15 @@
# -*- coding: utf-8 -*-
import datetime import datetime
import email import email
from email.mime.text import MIMEText
import imaplib import imaplib
import logging import logging
import re import re
import smtplib import smtplib
import time import time
from email.mime.text import MIMEText
import imapclient
import six
from validate_email import validate_email
from aprsd import messaging, threads from aprsd import messaging, threads
import imapclient
from validate_email import validate_email
LOG = logging.getLogger("APRSD") LOG = logging.getLogger("APRSD")
@ -30,7 +27,10 @@ def _imap_connect():
try: try:
server = imapclient.IMAPClient( server = imapclient.IMAPClient(
CONFIG["imap"]["host"], port=imap_port, use_uid=True, ssl=use_ssl CONFIG["imap"]["host"],
port=imap_port,
use_uid=True,
ssl=use_ssl,
) )
except Exception: except Exception:
LOG.error("Failed to connect IMAP server") LOG.error("Failed to connect IMAP server")
@ -53,7 +53,7 @@ def _smtp_connect():
use_ssl = CONFIG["smtp"].get("use_ssl", False) use_ssl = CONFIG["smtp"].get("use_ssl", False)
msg = "{}{}:{}".format("SSL " if use_ssl else "", host, smtp_port) msg = "{}{}:{}".format("SSL " if use_ssl else "", host, smtp_port)
LOG.debug( LOG.debug(
"Connect to SMTP host {} with user '{}'".format(msg, CONFIG["imap"]["login"]) "Connect to SMTP host {} with user '{}'".format(msg, CONFIG["imap"]["login"]),
) )
try: try:
@ -84,7 +84,7 @@ def validate_shortcuts(config):
LOG.info( LOG.info(
"Validating {} Email shortcuts. This can take up to 10 seconds" "Validating {} Email shortcuts. This can take up to 10 seconds"
" per shortcut".format(len(shortcuts)) " per shortcut".format(len(shortcuts)),
) )
delete_keys = [] delete_keys = []
for key in shortcuts: for key in shortcuts:
@ -102,8 +102,8 @@ def validate_shortcuts(config):
if not is_valid: if not is_valid:
LOG.error( LOG.error(
"'{}' is an invalid email address. Removing shortcut".format( "'{}' is an invalid email address. Removing shortcut".format(
shortcuts[key] shortcuts[key],
) ),
) )
delete_keys.append(key) delete_keys.append(key)
@ -173,14 +173,18 @@ def parse_email(msgid, data, server):
if part.get_content_type() == "text/plain": if part.get_content_type() == "text/plain":
LOG.debug("Email got text/plain") LOG.debug("Email got text/plain")
text = six.text_type( text = str(
part.get_payload(decode=True), str(charset), "ignore" part.get_payload(decode=True),
str(charset),
"ignore",
).encode("utf8", "replace") ).encode("utf8", "replace")
if part.get_content_type() == "text/html": if part.get_content_type() == "text/html":
LOG.debug("Email got text/html") LOG.debug("Email got text/html")
html = six.text_type( html = str(
part.get_payload(decode=True), str(charset), "ignore" part.get_payload(decode=True),
str(charset),
"ignore",
).encode("utf8", "replace") ).encode("utf8", "replace")
if text is not None: if text is not None:
@ -192,12 +196,15 @@ def parse_email(msgid, data, server):
# email.uscc.net sends no charset, blows up unicode function below # email.uscc.net sends no charset, blows up unicode function below
LOG.debug("Email is not multipart") LOG.debug("Email is not multipart")
if msg.get_content_charset() is None: if msg.get_content_charset() is None:
text = six.text_type( text = str(msg.get_payload(decode=True), "US-ASCII", "ignore").encode(
msg.get_payload(decode=True), "US-ASCII", "ignore" "utf8",
).encode("utf8", "replace") "replace",
)
else: else:
text = six.text_type( text = str(
msg.get_payload(decode=True), msg.get_content_charset(), "ignore" msg.get_payload(decode=True),
msg.get_content_charset(),
"ignore",
).encode("utf8", "replace") ).encode("utf8", "replace")
body = text.strip() body = text.strip()
@ -266,11 +273,11 @@ def resend_email(count, fromcall):
month = date.strftime("%B")[:3] # Nov, Mar, Apr month = date.strftime("%B")[:3] # Nov, Mar, Apr
day = date.day day = date.day
year = date.year year = date.year
today = "%s-%s-%s" % (day, month, year) today = "{}-{}-{}".format(day, month, year)
shortcuts = CONFIG["shortcuts"] shortcuts = CONFIG["shortcuts"]
# swap key/value # swap key/value
shortcuts_inverted = dict([[v, k] for k, v in shortcuts.items()]) shortcuts_inverted = {v: k for k, v in shortcuts.items()}
try: try:
server = _imap_connect() server = _imap_connect()
@ -310,7 +317,7 @@ def resend_email(count, fromcall):
# thinking this is a duplicate message. # thinking this is a duplicate message.
# The FT1XDR pretty much ignores the aprs message number in this # The FT1XDR pretty much ignores the aprs message number in this
# regard. The FTM400 gets it right. # regard. The FTM400 gets it right.
reply = "No new msg %s:%s:%s" % ( reply = "No new msg {}:{}:{}".format(
str(h).zfill(2), str(h).zfill(2),
str(m).zfill(2), str(m).zfill(2),
str(s).zfill(2), str(s).zfill(2),
@ -328,7 +335,7 @@ def resend_email(count, fromcall):
class APRSDEmailThread(threads.APRSDThread): class APRSDEmailThread(threads.APRSDThread):
def __init__(self, msg_queues, config): def __init__(self, msg_queues, config):
super(APRSDEmailThread, self).__init__("EmailThread") super().__init__("EmailThread")
self.msg_queues = msg_queues self.msg_queues = msg_queues
self.config = config self.config = config
@ -354,13 +361,13 @@ class APRSDEmailThread(threads.APRSDThread):
shortcuts = CONFIG["shortcuts"] shortcuts = CONFIG["shortcuts"]
# swap key/value # swap key/value
shortcuts_inverted = dict([[v, k] for k, v in shortcuts.items()]) shortcuts_inverted = {v: k for k, v in shortcuts.items()}
date = datetime.datetime.now() date = datetime.datetime.now()
month = date.strftime("%B")[:3] # Nov, Mar, Apr month = date.strftime("%B")[:3] # Nov, Mar, Apr
day = date.day day = date.day
year = date.year year = date.year
today = "%s-%s-%s" % (day, month, year) today = "{}-{}-{}".format(day, month, year)
server = None server = None
try: try:
@ -378,7 +385,8 @@ class APRSDEmailThread(threads.APRSDThread):
envelope = data[b"ENVELOPE"] envelope = data[b"ENVELOPE"]
# LOG.debug('ID:%d "%s" (%s)' % (msgid, envelope.subject.decode(), envelope.date)) # LOG.debug('ID:%d "%s" (%s)' % (msgid, envelope.subject.decode(), envelope.date))
f = re.search( f = re.search(
r"'([[A-a][0-9]_-]+@[[A-a][0-9]_-\.]+)", str(envelope.from_[0]) r"'([[A-a][0-9]_-]+@[[A-a][0-9]_-\.]+)",
str(envelope.from_[0]),
) )
if f is not None: if f is not None:
from_addr = f.group(1) from_addr = f.group(1)

@ -1,10 +1,9 @@
# -*- coding: utf-8 -*-
import argparse import argparse
import logging import logging
from logging.handlers import RotatingFileHandler
import socketserver import socketserver
import sys import sys
import time import time
from logging.handlers import RotatingFileHandler
from aprsd import utils from aprsd import utils
@ -74,7 +73,7 @@ def main():
ip = CONFIG["aprs"]["host"] ip = CONFIG["aprs"]["host"]
port = CONFIG["aprs"]["port"] port = CONFIG["aprs"]["port"]
LOG.info("Start server listening on %s:%s" % (args.ip, args.port)) LOG.info("Start server listening on {}:{}".format(args.ip, args.port))
with socketserver.TCPServer((ip, port), MyAPRSTCPHandler) as server: with socketserver.TCPServer((ip, port), MyAPRSTCPHandler) as server:
server.serve_forever() server.serve_forever()

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# #
# @author Sinu John # @author Sinu John
# sinuvian at gmail dot com # sinuvian at gmail dot com

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# #
# Listen on amateur radio aprs-is network for messages and respond to them. # Listen on amateur radio aprs-is network for messages and respond to them.
# You must have an amateur radio callsign to use this software. You must # You must have an amateur radio callsign to use this software. You must
@ -22,23 +21,22 @@
# python included libs # python included libs
import logging import logging
from logging import NullHandler
from logging.handlers import RotatingFileHandler
import os import os
import queue import queue
import signal import signal
import sys import sys
import threading import threading
import time import time
from logging import NullHandler
from logging.handlers import RotatingFileHandler
import aprslib
import click
import click_completion
import yaml
# local imports here # local imports here
import aprsd import aprsd
from aprsd import client, email, messaging, plugin, threads, utils from aprsd import client, email, messaging, plugin, threads, utils
import aprslib
import click
import click_completion
import yaml
# setup the global logger # setup the global logger
# logging.basicConfig(level=logging.DEBUG) # level=10 # logging.basicConfig(level=logging.DEBUG) # level=10
@ -99,7 +97,9 @@ def main():
@main.command() @main.command()
@click.option( @click.option(
"-i", "--case-insensitive/--no-case-insensitive", help="Case insensitive completion" "-i",
"--case-insensitive/--no-case-insensitive",
help="Case insensitive completion",
) )
@click.argument( @click.argument(
"shell", "shell",
@ -118,10 +118,14 @@ def show(shell, case_insensitive):
@main.command() @main.command()
@click.option( @click.option(
"--append/--overwrite", help="Append the completion code to the file", default=None "--append/--overwrite",
help="Append the completion code to the file",
default=None,
) )
@click.option( @click.option(
"-i", "--case-insensitive/--no-case-insensitive", help="Case insensitive completion" "-i",
"--case-insensitive/--no-case-insensitive",
help="Case insensitive completion",
) )
@click.argument( @click.argument(
"shell", "shell",
@ -137,16 +141,19 @@ def install(append, case_insensitive, shell, path):
else {} else {}
) )
shell, path = click_completion.core.install( shell, path = click_completion.core.install(
shell=shell, path=path, append=append, extra_env=extra_env shell=shell,
path=path,
append=append,
extra_env=extra_env,
) )
click.echo("%s completion installed in %s" % (shell, path)) click.echo("{} completion installed in {}".format(shell, path))
def signal_handler(signal, frame): def signal_handler(signal, frame):
global server_vent global server_vent
LOG.info( LOG.info(
"Ctrl+C, Sending all threads exit! Can take up to 10 seconds to exit all threads" "Ctrl+C, Sending all threads exit! Can take up to 10 seconds to exit all threads",
) )
threads.APRSDThreadList().stop_all() threads.APRSDThreadList().stop_all()
server_event.set() server_event.set()
@ -191,7 +198,8 @@ def sample_config():
default="DEBUG", default="DEBUG",
show_default=True, show_default=True,
type=click.Choice( type=click.Choice(
["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG"], case_sensitive=False ["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG"],
case_sensitive=False,
), ),
show_choices=True, show_choices=True,
help="The log level to use for aprsd.log", help="The log level to use for aprsd.log",
@ -220,7 +228,13 @@ def sample_config():
@click.argument("tocallsign") @click.argument("tocallsign")
@click.argument("command", nargs=-1) @click.argument("command", nargs=-1)
def send_message( def send_message(
loglevel, quiet, config_file, aprs_login, aprs_password, tocallsign, command loglevel,
quiet,
config_file,
aprs_login,
aprs_password,
tocallsign,
command,
): ):
"""Send a message to a callsign via APRS_IS.""" """Send a message to a callsign via APRS_IS."""
global got_ack, got_response global got_ack, got_response
@ -273,7 +287,9 @@ def send_message(
got_response = True got_response = True
# Send the ack back? # Send the ack back?
ack = messaging.AckMessage( ack = messaging.AckMessage(
config["aprs"]["login"], fromcall, msg_id=msg_number config["aprs"]["login"],
fromcall,
msg_id=msg_number,
) )
ack.send_direct() ack.send_direct()
@ -312,7 +328,8 @@ def send_message(
default="DEBUG", default="DEBUG",
show_default=True, show_default=True,
type=click.Choice( type=click.Choice(
["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG"], case_sensitive=False ["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG"],
case_sensitive=False,
), ),
show_choices=True, show_choices=True,
help="The log level to use for aprsd.log", help="The log level to use for aprsd.log",

@ -1,14 +1,13 @@
# -*- coding: utf-8 -*-
import abc import abc
import datetime import datetime
import logging import logging
from multiprocessing import RawValue
import os import os
import pathlib import pathlib
import pickle import pickle
import re import re
import threading import threading
import time import time
from multiprocessing import RawValue
from aprsd import client, threads, utils from aprsd import client, threads, utils
@ -19,7 +18,7 @@ LOG = logging.getLogger("APRSD")
NULL_MESSAGE = -1 NULL_MESSAGE = -1
class MsgTrack(object): class MsgTrack:
"""Class to keep track of outstanding text messages. """Class to keep track of outstanding text messages.
This is a thread safe class that keeps track of active This is a thread safe class that keeps track of active
@ -47,7 +46,7 @@ class MsgTrack(object):
def __new__(cls, *args, **kwargs): def __new__(cls, *args, **kwargs):
if cls._instance is None: if cls._instance is None:
cls._instance = super(MsgTrack, cls).__new__(cls) cls._instance = super().__new__(cls)
cls._instance.track = {} cls._instance.track = {}
cls._instance.lock = threading.Lock() cls._instance.lock = threading.Lock()
return cls._instance return cls._instance
@ -129,7 +128,7 @@ class MsgTrack(object):
self.track = {} self.track = {}
class MessageCounter(object): class MessageCounter:
""" """
Global message id counter class. Global message id counter class.
@ -147,7 +146,7 @@ class MessageCounter(object):
def __new__(cls, *args, **kwargs): def __new__(cls, *args, **kwargs):
"""Make this a singleton class.""" """Make this a singleton class."""
if cls._instance is None: if cls._instance is None:
cls._instance = super(MessageCounter, cls).__new__(cls) cls._instance = super().__new__(cls)
cls._instance.val = RawValue("i", 1) cls._instance.val = RawValue("i", 1)
cls._instance.lock = threading.Lock() cls._instance.lock = threading.Lock()
return cls._instance return cls._instance
@ -173,7 +172,7 @@ class MessageCounter(object):
return str(self.val.value) return str(self.val.value)
class Message(object, metaclass=abc.ABCMeta): class Message(metaclass=abc.ABCMeta):
"""Base Message Class.""" """Base Message Class."""
# The message id to send over the air # The message id to send over the air
@ -204,7 +203,7 @@ class TextMessage(Message):
message = None message = None
def __init__(self, fromcall, tocall, message, msg_id=None, allow_delay=True): def __init__(self, fromcall, tocall, message, msg_id=None, allow_delay=True):
super(TextMessage, self).__init__(fromcall, tocall, msg_id) super().__init__(fromcall, tocall, msg_id)
self.message = message self.message = message
# do we try and save this message for later if we don't get # do we try and save this message for later if we don't get
# an ack? Some messages we don't want to do this ever. # an ack? Some messages we don't want to do this ever.
@ -213,7 +212,10 @@ class TextMessage(Message):
def __repr__(self): def __repr__(self):
"""Build raw string to send over the air.""" """Build raw string to send over the air."""
return "{}>APRS::{}:{}{{{}\n".format( return "{}>APRS::{}:{}{{{}\n".format(
self.fromcall, self.tocall.ljust(9), self._filter_for_send(), str(self.id) self.fromcall,
self.tocall.ljust(9),
self._filter_for_send(),
str(self.id),
) )
def __str__(self): def __str__(self):
@ -222,7 +224,11 @@ class TextMessage(Message):
now = datetime.datetime.now() now = datetime.datetime.now()
delta = now - self.last_send_time delta = now - self.last_send_time
return "{}>{} Msg({})({}): '{}'".format( return "{}>{} Msg({})({}): '{}'".format(
self.fromcall, self.tocall, self.id, delta, self.message self.fromcall,
self.tocall,
self.id,
delta,
self.message,
) )
def _filter_for_send(self): def _filter_for_send(self):
@ -259,9 +265,7 @@ class SendMessageThread(threads.APRSDThread):
def __init__(self, message): def __init__(self, message):
self.msg = message self.msg = message
name = self.msg.message[:5] name = self.msg.message[:5]
super(SendMessageThread, self).__init__( super().__init__("SendMessage-{}-{}".format(self.msg.id, name))
"SendMessage-{}-{}".format(self.msg.id, name)
)
def loop(self): def loop(self):
"""Loop until a message is acked or it gets delayed. """Loop until a message is acked or it gets delayed.
@ -326,11 +330,13 @@ class AckMessage(Message):
"""Class for building Acks and sending them.""" """Class for building Acks and sending them."""
def __init__(self, fromcall, tocall, msg_id): def __init__(self, fromcall, tocall, msg_id):
super(AckMessage, self).__init__(fromcall, tocall, msg_id=msg_id) super().__init__(fromcall, tocall, msg_id=msg_id)
def __repr__(self): def __repr__(self):
return "{}>APRS::{}:ack{}\n".format( return "{}>APRS::{}:ack{}\n".format(
self.fromcall, self.tocall.ljust(9), self.id self.fromcall,
self.tocall.ljust(9),
self.id,
) )
def __str__(self): def __str__(self):
@ -378,7 +384,7 @@ class AckMessage(Message):
class SendAckThread(threads.APRSDThread): class SendAckThread(threads.APRSDThread):
def __init__(self, ack): def __init__(self, ack):
self.ack = ack self.ack = ack
super(SendAckThread, self).__init__("SendAck-{}".format(self.ack.id)) super().__init__("SendAck-{}".format(self.ack.id))
def loop(self): def loop(self):
"""Separate thread to send acks with retries.""" """Separate thread to send acks with retries."""

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# The base plugin class # The base plugin class
import abc import abc
import fnmatch import fnmatch
@ -12,14 +11,12 @@ import shutil
import subprocess import subprocess
import time import time
import pluggy
import requests
import six
from thesmuggler import smuggle
import aprsd import aprsd
from aprsd import email, messaging from aprsd import email, messaging
from aprsd.fuzzyclock import fuzzy from aprsd.fuzzyclock import fuzzy
import pluggy
import requests
from thesmuggler import smuggle
# setup the global logger # setup the global logger
LOG = logging.getLogger("APRSD") LOG = logging.getLogger("APRSD")
@ -39,7 +36,7 @@ CORE_PLUGINS = [
] ]
class PluginManager(object): class PluginManager:
# The singleton instance object for this class # The singleton instance object for this class
_instance = None _instance = None
@ -52,7 +49,7 @@ class PluginManager(object):
def __new__(cls, *args, **kwargs): def __new__(cls, *args, **kwargs):
"""This magic turns this into a singleton.""" """This magic turns this into a singleton."""
if cls._instance is None: if cls._instance is None:
cls._instance = super(PluginManager, cls).__new__(cls) cls._instance = super().__new__(cls)
# Put any initialization here. # Put any initialization here.
return cls._instance return cls._instance
@ -79,7 +76,7 @@ class PluginManager(object):
for mem_name, obj in inspect.getmembers(module): for mem_name, obj in inspect.getmembers(module):
if inspect.isclass(obj) and self.is_plugin(obj): if inspect.isclass(obj) and self.is_plugin(obj):
self.obj_list.append( self.obj_list.append(
{"name": mem_name, "obj": obj(self.config)} {"name": mem_name, "obj": obj(self.config)},
) )
return self.obj_list return self.obj_list
@ -108,14 +105,16 @@ class PluginManager(object):
return return
assert hasattr(module, class_name), "class {} is not in {}".format( assert hasattr(module, class_name), "class {} is not in {}".format(
class_name, module_name class_name,
module_name,
) )
# click.echo('reading class {} from module {}'.format( # click.echo('reading class {} from module {}'.format(
# class_name, module_name)) # class_name, module_name))
cls = getattr(module, class_name) cls = getattr(module, class_name)
if super_cls is not None: if super_cls is not None:
assert issubclass(cls, super_cls), "class {} should inherit from {}".format( assert issubclass(cls, super_cls), "class {} should inherit from {}".format(
class_name, super_cls.__name__ class_name,
super_cls.__name__,
) )
# click.echo('initialising {} with params {}'.format(class_name, kwargs)) # click.echo('initialising {} with params {}'.format(class_name, kwargs))
obj = cls(**kwargs) obj = cls(**kwargs)
@ -131,13 +130,17 @@ class PluginManager(object):
plugin_obj = None plugin_obj = None
try: try:
plugin_obj = self._create_class( plugin_obj = self._create_class(
plugin_name, APRSDPluginBase, config=self.config plugin_name,
APRSDPluginBase,
config=self.config,
) )
if plugin_obj: if plugin_obj:
LOG.info( LOG.info(
"Registering Command plugin '{}'({}) '{}'".format( "Registering Command plugin '{}'({}) '{}'".format(
plugin_name, plugin_obj.version, plugin_obj.command_regex plugin_name,
) plugin_obj.version,
plugin_obj.command_regex,
),
) )
self._pluggy_pm.register(plugin_obj) self._pluggy_pm.register(plugin_obj)
except Exception as ex: except Exception as ex:
@ -173,8 +176,10 @@ class PluginManager(object):
if plugin_obj: if plugin_obj:
LOG.info( LOG.info(
"Registering Command plugin '{}'({}) '{}'".format( "Registering Command plugin '{}'({}) '{}'".format(
o["name"], o["obj"].version, o["obj"].command_regex o["name"],
) o["obj"].version,
o["obj"].command_regex,
),
) )
self._pluggy_pm.register(o["obj"]) self._pluggy_pm.register(o["obj"])
@ -203,8 +208,7 @@ class APRSDCommandSpec:
pass pass
@six.add_metaclass(abc.ABCMeta) class APRSDPluginBase(metaclass=abc.ABCMeta):
class APRSDPluginBase(object):
def __init__(self, config): def __init__(self, config):
"""The aprsd config object is stored.""" """The aprsd config object is stored."""
self.config = config self.config = config
@ -257,7 +261,8 @@ class FortunePlugin(APRSDPluginBase):
try: try:
process = subprocess.Popen( process = subprocess.Popen(
[fortune_path, "-s", "-n 60"], stdout=subprocess.PIPE [fortune_path, "-s", "-n 60"],
stdout=subprocess.PIPE,
) )
reply = process.communicate()[0] reply = process.communicate()[0]
reply = reply.decode(errors="ignore").rstrip() reply = reply.decode(errors="ignore").rstrip()
@ -406,7 +411,10 @@ class TimePlugin(APRSDPluginBase):
m = stm.tm_min m = stm.tm_min
cur_time = fuzzy(h, m, 1) cur_time = fuzzy(h, m, 1)
reply = "{} ({}:{} PDT) ({})".format( reply = "{} ({}:{} PDT) ({})".format(
cur_time, str(h), str(m).rjust(2, "0"), message.rstrip() cur_time,
str(h),
str(m).rjust(2, "0"),
message.rstrip(),
) )
return reply return reply
@ -497,7 +505,7 @@ class EmailPlugin(APRSDPluginBase):
# send recipient link to aprs.fi map # send recipient link to aprs.fi map
if content == "mapme": if content == "mapme":
content = "Click for my location: http://aprs.fi/{}".format( content = "Click for my location: http://aprs.fi/{}".format(
self.config["ham"]["callsign"] self.config["ham"]["callsign"],
) )
too_soon = 0 too_soon = 0
now = time.time() now = time.time()
@ -521,7 +529,7 @@ class EmailPlugin(APRSDPluginBase):
LOG.debug( LOG.debug(
"DEBUG: email_sent_dict is big (" "DEBUG: email_sent_dict is big ("
+ str(len(self.email_sent_dict)) + str(len(self.email_sent_dict))
+ ") clearing out." + ") clearing out.",
) )
self.email_sent_dict.clear() self.email_sent_dict.clear()
self.email_sent_dict[ack] = now self.email_sent_dict[ack] = now
@ -529,7 +537,7 @@ class EmailPlugin(APRSDPluginBase):
LOG.info( LOG.info(
"Email for message number " "Email for message number "
+ ack + ack
+ " recently sent, not sending again." + " recently sent, not sending again.",
) )
else: else:
reply = "Bad email address" reply = "Bad email address"

@ -1,13 +1,11 @@
# -*- coding: utf-8 -*-
import abc import abc
import logging import logging
import queue import queue
import threading import threading
import time import time
import aprslib
from aprsd import client, messaging, plugin from aprsd import client, messaging, plugin
import aprslib
LOG = logging.getLogger("APRSD") LOG = logging.getLogger("APRSD")
@ -16,7 +14,7 @@ TX_THREAD = "TX"
EMAIL_THREAD = "Email" EMAIL_THREAD = "Email"
class APRSDThreadList(object): class APRSDThreadList:
"""Singleton class that keeps track of application wide threads.""" """Singleton class that keeps track of application wide threads."""
_instance = None _instance = None
@ -26,7 +24,7 @@ class APRSDThreadList(object):
def __new__(cls, *args, **kwargs): def __new__(cls, *args, **kwargs):
if cls._instance is None: if cls._instance is None:
cls._instance = super(APRSDThreadList, cls).__new__(cls) cls._instance = super().__new__(cls)
cls.lock = threading.Lock() cls.lock = threading.Lock()
cls.threads_list = [] cls.threads_list = []
return cls._instance return cls._instance
@ -48,7 +46,7 @@ class APRSDThreadList(object):
class APRSDThread(threading.Thread, metaclass=abc.ABCMeta): class APRSDThread(threading.Thread, metaclass=abc.ABCMeta):
def __init__(self, name): def __init__(self, name):
super(APRSDThread, self).__init__(name=name) super().__init__(name=name)
self.thread_stop = False self.thread_stop = False
APRSDThreadList().add(self) APRSDThreadList().add(self)
@ -67,7 +65,7 @@ class APRSDThread(threading.Thread, metaclass=abc.ABCMeta):
class APRSDRXThread(APRSDThread): class APRSDRXThread(APRSDThread):
def __init__(self, msg_queues, config): def __init__(self, msg_queues, config):
super(APRSDRXThread, self).__init__("RX_MSG") super().__init__("RX_MSG")
self.msg_queues = msg_queues self.msg_queues = msg_queues
self.config = config self.config = config
@ -112,7 +110,11 @@ class APRSDRXThread(APRSDThread):
ack_num = packet.get("msgNo") ack_num = packet.get("msgNo")
LOG.info("Got ack for message {}".format(ack_num)) LOG.info("Got ack for message {}".format(ack_num))
messaging.log_message( messaging.log_message(
"ACK", packet["raw"], None, ack=ack_num, fromcall=packet["from"] "ACK",
packet["raw"],
None,
ack=ack_num,
fromcall=packet["from"],
) )
tracker = messaging.MsgTrack() tracker = messaging.MsgTrack()
tracker.remove(ack_num) tracker.remove(ack_num)
@ -153,7 +155,9 @@ class APRSDRXThread(APRSDThread):
LOG.debug("Sending '{}'".format(reply)) LOG.debug("Sending '{}'".format(reply))
msg = messaging.TextMessage( msg = messaging.TextMessage(
self.config["aprs"]["login"], fromcall, reply self.config["aprs"]["login"],
fromcall,
reply,
) )
self.msg_queues["tx"].put(msg) self.msg_queues["tx"].put(msg)
else: else:
@ -166,7 +170,9 @@ class APRSDRXThread(APRSDThread):
reply = "Usage: {}".format(", ".join(names)) reply = "Usage: {}".format(", ".join(names))
msg = messaging.TextMessage( msg = messaging.TextMessage(
self.config["aprs"]["login"], fromcall, reply self.config["aprs"]["login"],
fromcall,
reply,
) )
self.msg_queues["tx"].put(msg) self.msg_queues["tx"].put(msg)
except Exception as ex: except Exception as ex:
@ -178,7 +184,9 @@ class APRSDRXThread(APRSDThread):
# let any threads do their thing, then ack # let any threads do their thing, then ack
# send an ack last # send an ack last
ack = messaging.AckMessage( ack = messaging.AckMessage(
self.config["aprs"]["login"], fromcall, msg_id=msg_id self.config["aprs"]["login"],
fromcall,
msg_id=msg_id,
) )
self.msg_queues["tx"].put(ack) self.msg_queues["tx"].put(ack)
LOG.debug("Packet processing complete") LOG.debug("Packet processing complete")
@ -213,7 +221,7 @@ class APRSDRXThread(APRSDThread):
class APRSDTXThread(APRSDThread): class APRSDTXThread(APRSDThread):
def __init__(self, msg_queues, config): def __init__(self, msg_queues, config):
super(APRSDTXThread, self).__init__("TX_MSG") super().__init__("TX_MSG")
self.msg_queues = msg_queues self.msg_queues = msg_queues
self.config = config self.config = config

@ -1,17 +1,15 @@
# -*- coding: utf-8 -*-
"""Utilities and helper functions.""" """Utilities and helper functions."""
import errno import errno
import functools import functools
import os import os
from pathlib import Path
import sys import sys
import threading import threading
from pathlib import Path
import click
import yaml
from aprsd import plugin from aprsd import plugin
import click
import yaml
# an example of what should be in the ~/.aprsd/config.yml # an example of what should be in the ~/.aprsd/config.yml
DEFAULT_CONFIG_DICT = { DEFAULT_CONFIG_DICT = {
@ -103,13 +101,13 @@ def get_config(config_file):
"""This tries to read the yaml config from <config_file>.""" """This tries to read the yaml config from <config_file>."""
config_file_expanded = os.path.expanduser(config_file) config_file_expanded = os.path.expanduser(config_file)
if os.path.exists(config_file_expanded): if os.path.exists(config_file_expanded):
with open(config_file_expanded, "r") as stream: with open(config_file_expanded) as stream:
config = yaml.load(stream, Loader=yaml.FullLoader) config = yaml.load(stream, Loader=yaml.FullLoader)
return config return config
else: else:
if config_file == DEFAULT_CONFIG_FILE: if config_file == DEFAULT_CONFIG_FILE:
click.echo( click.echo(
"{} is missing, creating config file".format(config_file_expanded) "{} is missing, creating config file".format(config_file_expanded),
) )
create_default_config() create_default_config()
msg = ( msg = (
@ -144,7 +142,10 @@ def parse_config(config_file):
if name and name not in config[section]: if name and name not in config[section]:
if not default: if not default:
fail( fail(
"'%s' was not in '%s' section of config file" % (name, section) "'{}' was not in '{}' section of config file".format(
name,
section,
),
) )
else: else:
config[section][name] = default config[section][name] = default
@ -166,7 +167,10 @@ def parse_config(config_file):
# special check here to make sure user has edited the config file # special check here to make sure user has edited the config file
# and changed the ham callsign # and changed the ham callsign
check_option( check_option(
config, "ham", "callsign", default_fail=DEFAULT_CONFIG_DICT["ham"]["callsign"] config,
"ham",
"callsign",
default_fail=DEFAULT_CONFIG_DICT["ham"]["callsign"],
) )
check_option(config, "aprs", "login") check_option(config, "aprs", "login")
check_option(config, "aprs", "password") check_option(config, "aprs", "password")

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
import logging import logging
from aprsd import plugin from aprsd import plugin

36
pyproject.toml Normal file

@ -0,0 +1,36 @@
[build-system]
requires = ["setuptools>=46.0", "wheel"]
build-backend = "setuptools.build_meta"
[tool.black]
# Use the more relaxed max line length permitted in PEP8.
line-length = 88
target-version = ["py36", "py37", "py38"]
# black will automatically exclude all files listed in .gitignore
include = '\.pyi?$'
exclude = '''
/(
\.git
| \.hg
| \.mypy_cache
| \.tox
| \.venv
| _build
| buck-out
| build
| dist
)/
'''
[tool.isort]
profile = "black"
line_length = 88
force_sort_within_sections = true
# Inform isort of paths to import names that should be considered part of the "First Party" group.
src_paths = ["src/openstack_loadtest"]
skip_gitignore = true
# If you need to skip/exclude folders, consider using skip_glob as that will allow the
# isort defaults for skip to remain without the need to duplicate them.
[tool.coverage.run]
branch = true

@ -1,15 +1,16 @@
[metadata] [metadata]
name = aprsd name = aprsd
summary = Amateur radio APRS daemon which listens for messages and responds long_description = file: README.rst
description-file = long_description_content_type = text/x-rst
README.rst
long-description-content-type = text/x-rst; charset=UTF-8
author = Craig Lamparter author = Craig Lamparter
author-email = something@somewhere.com author_email = something@somewhere.com
classifier = classifier =
Topic :: Communications :: Ham Radio Topic :: Communications :: Ham Radio
Operating System :: POSIX :: Linux Operating System :: POSIX :: Linux
Programming Language :: Python Programming Language :: Python
description_file =
README.rst
summary = Amateur radio APRS daemon which listens for messages and responds
[global] [global]
setup-hooks = setup-hooks =

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. # Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
import sys import sys
import unittest import unittest
@ -7,7 +6,7 @@ from aprsd import email
if sys.version_info >= (3, 2): if sys.version_info >= (3, 2):
from unittest import mock from unittest import mock
else: else:
import mock from unittest import mock
class TestMain(unittest.TestCase): class TestMain(unittest.TestCase):

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
import unittest import unittest
from unittest import mock from unittest import mock
@ -57,7 +56,10 @@ class TestPlugin(unittest.TestCase):
message = "time" message = "time"
expected = "{} ({}:{} PDT) ({})".format( expected = "{} ({}:{} PDT) ({})".format(
cur_time, str(h), str(m).rjust(2, "0"), message.rstrip() cur_time,
str(h),
str(m).rjust(2, "0"),
message.rstrip(),
) )
actual = time_plugin.run(fromcall, message, ack) actual = time_plugin.run(fromcall, message, ack)
self.assertEqual(expected, actual) self.assertEqual(expected, actual)