mirror of https://github.com/craigerl/aprsd.git
Lots of fixes
This commit is contained in:
parent
4c0150dd97
commit
231c15b1af
|
@ -1,29 +1,48 @@
|
|||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v3.2.0
|
||||
hooks:
|
||||
- id: trailing-whitespace
|
||||
- id: end-of-file-fixer
|
||||
- id: check-yaml
|
||||
- id: check-added-large-files
|
||||
- id: fix-encoding-pragma
|
||||
- id: detect-private-key
|
||||
- id: check-merge-conflict
|
||||
- id: check-case-conflict
|
||||
- id: check-docstring-first
|
||||
- id: check-builtin-literals
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 19.3b0
|
||||
hooks:
|
||||
- id: black
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v3.4.0
|
||||
hooks:
|
||||
- id: trailing-whitespace
|
||||
- id: end-of-file-fixer
|
||||
- id: check-yaml
|
||||
- id: check-added-large-files
|
||||
- id: detect-private-key
|
||||
- id: check-merge-conflict
|
||||
- id: check-case-conflict
|
||||
- id: check-docstring-first
|
||||
- id: check-builtin-literals
|
||||
- id: double-quote-string-fixer
|
||||
|
||||
- repo: https://github.com/pre-commit/mirrors-isort
|
||||
rev: v5.7.0
|
||||
hooks:
|
||||
- id: isort
|
||||
- repo: https://github.com/asottile/setup-cfg-fmt
|
||||
rev: v1.16.0
|
||||
hooks:
|
||||
- id: setup-cfg-fmt
|
||||
|
||||
- repo: https://gitlab.com/pycqa/flake8
|
||||
rev: 3.8.1
|
||||
hooks:
|
||||
- id: flake8
|
||||
additional_dependencies: [flake8-bugbear]
|
||||
- repo: https://github.com/asottile/add-trailing-comma
|
||||
rev: v2.0.2
|
||||
hooks:
|
||||
- id: add-trailing-comma
|
||||
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
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import logging
|
||||
import select
|
||||
import socket
|
||||
import time
|
||||
|
||||
import aprslib
|
||||
|
@ -9,7 +7,7 @@ import aprslib
|
|||
LOG = logging.getLogger("APRSD")
|
||||
|
||||
|
||||
class Client(object):
|
||||
class Client:
|
||||
"""Singleton client class that constructs the aprslib connection."""
|
||||
|
||||
_instance = None
|
||||
|
@ -19,7 +17,7 @@ class Client(object):
|
|||
def __new__(cls, *args, **kwargs):
|
||||
"""This magic turns this into a singleton."""
|
||||
if cls._instance is None:
|
||||
cls._instance = super(Client, cls).__new__(cls)
|
||||
cls._instance = super().__new__(cls)
|
||||
# Put any initialization here.
|
||||
return cls._instance
|
||||
|
||||
|
@ -82,7 +80,7 @@ class Aprsdis(aprslib.IS):
|
|||
"""
|
||||
try:
|
||||
self.sock.setblocking(0)
|
||||
except socket.error as e:
|
||||
except OSError as e:
|
||||
self.logger.error("socket error when setblocking(0): %s" % str(e))
|
||||
raise aprslib.ConnectionDrop("connection dropped")
|
||||
|
||||
|
@ -93,7 +91,10 @@ class Aprsdis(aprslib.IS):
|
|||
# set a select timeout, so we get a chance to exit
|
||||
# when user hits CTRL-C
|
||||
readable, writable, exceptional = select.select(
|
||||
[self.sock], [], [], self.select_timeout
|
||||
[self.sock],
|
||||
[],
|
||||
[],
|
||||
self.select_timeout,
|
||||
)
|
||||
if not readable:
|
||||
continue
|
||||
|
@ -105,7 +106,7 @@ class Aprsdis(aprslib.IS):
|
|||
if not short_buf:
|
||||
self.logger.error("socket.recv(): returned empty")
|
||||
raise aprslib.ConnectionDrop("connection dropped")
|
||||
except socket.error as e:
|
||||
except OSError as e:
|
||||
# self.logger.error("socket error on recv(): %s" % str(e))
|
||||
if "Resource temporarily unavailable" in str(e):
|
||||
if not blocking:
|
||||
|
|
|
@ -1,18 +1,15 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import datetime
|
||||
import email
|
||||
from email.mime.text import MIMEText
|
||||
import imaplib
|
||||
import logging
|
||||
import re
|
||||
import smtplib
|
||||
import time
|
||||
from email.mime.text import MIMEText
|
||||
|
||||
import imapclient
|
||||
import six
|
||||
from validate_email import validate_email
|
||||
|
||||
from aprsd import messaging, threads
|
||||
import imapclient
|
||||
from validate_email import validate_email
|
||||
|
||||
LOG = logging.getLogger("APRSD")
|
||||
|
||||
|
@ -30,7 +27,10 @@ def _imap_connect():
|
|||
|
||||
try:
|
||||
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:
|
||||
LOG.error("Failed to connect IMAP server")
|
||||
|
@ -53,7 +53,7 @@ def _smtp_connect():
|
|||
use_ssl = CONFIG["smtp"].get("use_ssl", False)
|
||||
msg = "{}{}:{}".format("SSL " if use_ssl else "", host, smtp_port)
|
||||
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:
|
||||
|
@ -84,7 +84,7 @@ def validate_shortcuts(config):
|
|||
|
||||
LOG.info(
|
||||
"Validating {} Email shortcuts. This can take up to 10 seconds"
|
||||
" per shortcut".format(len(shortcuts))
|
||||
" per shortcut".format(len(shortcuts)),
|
||||
)
|
||||
delete_keys = []
|
||||
for key in shortcuts:
|
||||
|
@ -102,8 +102,8 @@ def validate_shortcuts(config):
|
|||
if not is_valid:
|
||||
LOG.error(
|
||||
"'{}' is an invalid email address. Removing shortcut".format(
|
||||
shortcuts[key]
|
||||
)
|
||||
shortcuts[key],
|
||||
),
|
||||
)
|
||||
delete_keys.append(key)
|
||||
|
||||
|
@ -173,14 +173,18 @@ def parse_email(msgid, data, server):
|
|||
|
||||
if part.get_content_type() == "text/plain":
|
||||
LOG.debug("Email got text/plain")
|
||||
text = six.text_type(
|
||||
part.get_payload(decode=True), str(charset), "ignore"
|
||||
text = str(
|
||||
part.get_payload(decode=True),
|
||||
str(charset),
|
||||
"ignore",
|
||||
).encode("utf8", "replace")
|
||||
|
||||
if part.get_content_type() == "text/html":
|
||||
LOG.debug("Email got text/html")
|
||||
html = six.text_type(
|
||||
part.get_payload(decode=True), str(charset), "ignore"
|
||||
html = str(
|
||||
part.get_payload(decode=True),
|
||||
str(charset),
|
||||
"ignore",
|
||||
).encode("utf8", "replace")
|
||||
|
||||
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
|
||||
LOG.debug("Email is not multipart")
|
||||
if msg.get_content_charset() is None:
|
||||
text = six.text_type(
|
||||
msg.get_payload(decode=True), "US-ASCII", "ignore"
|
||||
).encode("utf8", "replace")
|
||||
text = str(msg.get_payload(decode=True), "US-ASCII", "ignore").encode(
|
||||
"utf8",
|
||||
"replace",
|
||||
)
|
||||
else:
|
||||
text = six.text_type(
|
||||
msg.get_payload(decode=True), msg.get_content_charset(), "ignore"
|
||||
text = str(
|
||||
msg.get_payload(decode=True),
|
||||
msg.get_content_charset(),
|
||||
"ignore",
|
||||
).encode("utf8", "replace")
|
||||
body = text.strip()
|
||||
|
||||
|
@ -266,11 +273,11 @@ def resend_email(count, fromcall):
|
|||
month = date.strftime("%B")[:3] # Nov, Mar, Apr
|
||||
day = date.day
|
||||
year = date.year
|
||||
today = "%s-%s-%s" % (day, month, year)
|
||||
today = "{}-{}-{}".format(day, month, year)
|
||||
|
||||
shortcuts = CONFIG["shortcuts"]
|
||||
# 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:
|
||||
server = _imap_connect()
|
||||
|
@ -310,7 +317,7 @@ def resend_email(count, fromcall):
|
|||
# thinking this is a duplicate message.
|
||||
# The FT1XDR pretty much ignores the aprs message number in this
|
||||
# regard. The FTM400 gets it right.
|
||||
reply = "No new msg %s:%s:%s" % (
|
||||
reply = "No new msg {}:{}:{}".format(
|
||||
str(h).zfill(2),
|
||||
str(m).zfill(2),
|
||||
str(s).zfill(2),
|
||||
|
@ -328,7 +335,7 @@ def resend_email(count, fromcall):
|
|||
|
||||
class APRSDEmailThread(threads.APRSDThread):
|
||||
def __init__(self, msg_queues, config):
|
||||
super(APRSDEmailThread, self).__init__("EmailThread")
|
||||
super().__init__("EmailThread")
|
||||
self.msg_queues = msg_queues
|
||||
self.config = config
|
||||
|
||||
|
@ -354,13 +361,13 @@ class APRSDEmailThread(threads.APRSDThread):
|
|||
|
||||
shortcuts = CONFIG["shortcuts"]
|
||||
# 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()
|
||||
month = date.strftime("%B")[:3] # Nov, Mar, Apr
|
||||
day = date.day
|
||||
year = date.year
|
||||
today = "%s-%s-%s" % (day, month, year)
|
||||
today = "{}-{}-{}".format(day, month, year)
|
||||
|
||||
server = None
|
||||
try:
|
||||
|
@ -378,7 +385,8 @@ class APRSDEmailThread(threads.APRSDThread):
|
|||
envelope = data[b"ENVELOPE"]
|
||||
# LOG.debug('ID:%d "%s" (%s)' % (msgid, envelope.subject.decode(), envelope.date))
|
||||
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:
|
||||
from_addr = f.group(1)
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import argparse
|
||||
import logging
|
||||
from logging.handlers import RotatingFileHandler
|
||||
import socketserver
|
||||
import sys
|
||||
import time
|
||||
from logging.handlers import RotatingFileHandler
|
||||
|
||||
from aprsd import utils
|
||||
|
||||
|
@ -74,7 +73,7 @@ def main():
|
|||
|
||||
ip = CONFIG["aprs"]["host"]
|
||||
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:
|
||||
server.serve_forever()
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# @author Sinu John
|
||||
# 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.
|
||||
# You must have an amateur radio callsign to use this software. You must
|
||||
|
@ -22,23 +21,22 @@
|
|||
|
||||
# python included libs
|
||||
import logging
|
||||
from logging import NullHandler
|
||||
from logging.handlers import RotatingFileHandler
|
||||
import os
|
||||
import queue
|
||||
import signal
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
from logging import NullHandler
|
||||
from logging.handlers import RotatingFileHandler
|
||||
|
||||
import aprslib
|
||||
import click
|
||||
import click_completion
|
||||
import yaml
|
||||
|
||||
# local imports here
|
||||
import aprsd
|
||||
from aprsd import client, email, messaging, plugin, threads, utils
|
||||
import aprslib
|
||||
import click
|
||||
import click_completion
|
||||
import yaml
|
||||
|
||||
# setup the global logger
|
||||
# logging.basicConfig(level=logging.DEBUG) # level=10
|
||||
|
@ -99,7 +97,9 @@ def main():
|
|||
|
||||
@main.command()
|
||||
@click.option(
|
||||
"-i", "--case-insensitive/--no-case-insensitive", help="Case insensitive completion"
|
||||
"-i",
|
||||
"--case-insensitive/--no-case-insensitive",
|
||||
help="Case insensitive completion",
|
||||
)
|
||||
@click.argument(
|
||||
"shell",
|
||||
|
@ -118,10 +118,14 @@ def show(shell, case_insensitive):
|
|||
|
||||
@main.command()
|
||||
@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(
|
||||
"-i", "--case-insensitive/--no-case-insensitive", help="Case insensitive completion"
|
||||
"-i",
|
||||
"--case-insensitive/--no-case-insensitive",
|
||||
help="Case insensitive completion",
|
||||
)
|
||||
@click.argument(
|
||||
"shell",
|
||||
|
@ -137,16 +141,19 @@ def install(append, case_insensitive, shell, path):
|
|||
else {}
|
||||
)
|
||||
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):
|
||||
global server_vent
|
||||
|
||||
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()
|
||||
server_event.set()
|
||||
|
@ -191,7 +198,8 @@ def sample_config():
|
|||
default="DEBUG",
|
||||
show_default=True,
|
||||
type=click.Choice(
|
||||
["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG"], case_sensitive=False
|
||||
["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG"],
|
||||
case_sensitive=False,
|
||||
),
|
||||
show_choices=True,
|
||||
help="The log level to use for aprsd.log",
|
||||
|
@ -220,7 +228,13 @@ def sample_config():
|
|||
@click.argument("tocallsign")
|
||||
@click.argument("command", nargs=-1)
|
||||
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."""
|
||||
global got_ack, got_response
|
||||
|
@ -273,7 +287,9 @@ def send_message(
|
|||
got_response = True
|
||||
# Send the ack back?
|
||||
ack = messaging.AckMessage(
|
||||
config["aprs"]["login"], fromcall, msg_id=msg_number
|
||||
config["aprs"]["login"],
|
||||
fromcall,
|
||||
msg_id=msg_number,
|
||||
)
|
||||
ack.send_direct()
|
||||
|
||||
|
@ -312,7 +328,8 @@ def send_message(
|
|||
default="DEBUG",
|
||||
show_default=True,
|
||||
type=click.Choice(
|
||||
["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG"], case_sensitive=False
|
||||
["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG"],
|
||||
case_sensitive=False,
|
||||
),
|
||||
show_choices=True,
|
||||
help="The log level to use for aprsd.log",
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import abc
|
||||
import datetime
|
||||
import logging
|
||||
from multiprocessing import RawValue
|
||||
import os
|
||||
import pathlib
|
||||
import pickle
|
||||
import re
|
||||
import threading
|
||||
import time
|
||||
from multiprocessing import RawValue
|
||||
|
||||
from aprsd import client, threads, utils
|
||||
|
||||
|
@ -19,7 +18,7 @@ LOG = logging.getLogger("APRSD")
|
|||
NULL_MESSAGE = -1
|
||||
|
||||
|
||||
class MsgTrack(object):
|
||||
class MsgTrack:
|
||||
"""Class to keep track of outstanding text messages.
|
||||
|
||||
This is a thread safe class that keeps track of active
|
||||
|
@ -47,7 +46,7 @@ class MsgTrack(object):
|
|||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
if cls._instance is None:
|
||||
cls._instance = super(MsgTrack, cls).__new__(cls)
|
||||
cls._instance = super().__new__(cls)
|
||||
cls._instance.track = {}
|
||||
cls._instance.lock = threading.Lock()
|
||||
return cls._instance
|
||||
|
@ -129,7 +128,7 @@ class MsgTrack(object):
|
|||
self.track = {}
|
||||
|
||||
|
||||
class MessageCounter(object):
|
||||
class MessageCounter:
|
||||
"""
|
||||
Global message id counter class.
|
||||
|
||||
|
@ -147,7 +146,7 @@ class MessageCounter(object):
|
|||
def __new__(cls, *args, **kwargs):
|
||||
"""Make this a singleton class."""
|
||||
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.lock = threading.Lock()
|
||||
return cls._instance
|
||||
|
@ -173,7 +172,7 @@ class MessageCounter(object):
|
|||
return str(self.val.value)
|
||||
|
||||
|
||||
class Message(object, metaclass=abc.ABCMeta):
|
||||
class Message(metaclass=abc.ABCMeta):
|
||||
"""Base Message Class."""
|
||||
|
||||
# The message id to send over the air
|
||||
|
@ -204,7 +203,7 @@ class TextMessage(Message):
|
|||
message = None
|
||||
|
||||
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
|
||||
# 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.
|
||||
|
@ -213,7 +212,10 @@ class TextMessage(Message):
|
|||
def __repr__(self):
|
||||
"""Build raw string to send over the air."""
|
||||
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):
|
||||
|
@ -222,7 +224,11 @@ class TextMessage(Message):
|
|||
now = datetime.datetime.now()
|
||||
delta = now - self.last_send_time
|
||||
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):
|
||||
|
@ -259,9 +265,7 @@ class SendMessageThread(threads.APRSDThread):
|
|||
def __init__(self, message):
|
||||
self.msg = message
|
||||
name = self.msg.message[:5]
|
||||
super(SendMessageThread, self).__init__(
|
||||
"SendMessage-{}-{}".format(self.msg.id, name)
|
||||
)
|
||||
super().__init__("SendMessage-{}-{}".format(self.msg.id, name))
|
||||
|
||||
def loop(self):
|
||||
"""Loop until a message is acked or it gets delayed.
|
||||
|
@ -326,11 +330,13 @@ class AckMessage(Message):
|
|||
"""Class for building Acks and sending them."""
|
||||
|
||||
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):
|
||||
return "{}>APRS::{}:ack{}\n".format(
|
||||
self.fromcall, self.tocall.ljust(9), self.id
|
||||
self.fromcall,
|
||||
self.tocall.ljust(9),
|
||||
self.id,
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
|
@ -378,7 +384,7 @@ class AckMessage(Message):
|
|||
class SendAckThread(threads.APRSDThread):
|
||||
def __init__(self, ack):
|
||||
self.ack = ack
|
||||
super(SendAckThread, self).__init__("SendAck-{}".format(self.ack.id))
|
||||
super().__init__("SendAck-{}".format(self.ack.id))
|
||||
|
||||
def loop(self):
|
||||
"""Separate thread to send acks with retries."""
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# The base plugin class
|
||||
import abc
|
||||
import fnmatch
|
||||
|
@ -12,14 +11,12 @@ import shutil
|
|||
import subprocess
|
||||
import time
|
||||
|
||||
import pluggy
|
||||
import requests
|
||||
import six
|
||||
from thesmuggler import smuggle
|
||||
|
||||
import aprsd
|
||||
from aprsd import email, messaging
|
||||
from aprsd.fuzzyclock import fuzzy
|
||||
import pluggy
|
||||
import requests
|
||||
from thesmuggler import smuggle
|
||||
|
||||
# setup the global logger
|
||||
LOG = logging.getLogger("APRSD")
|
||||
|
@ -39,7 +36,7 @@ CORE_PLUGINS = [
|
|||
]
|
||||
|
||||
|
||||
class PluginManager(object):
|
||||
class PluginManager:
|
||||
# The singleton instance object for this class
|
||||
_instance = None
|
||||
|
||||
|
@ -52,7 +49,7 @@ class PluginManager(object):
|
|||
def __new__(cls, *args, **kwargs):
|
||||
"""This magic turns this into a singleton."""
|
||||
if cls._instance is None:
|
||||
cls._instance = super(PluginManager, cls).__new__(cls)
|
||||
cls._instance = super().__new__(cls)
|
||||
# Put any initialization here.
|
||||
return cls._instance
|
||||
|
||||
|
@ -79,7 +76,7 @@ class PluginManager(object):
|
|||
for mem_name, obj in inspect.getmembers(module):
|
||||
if inspect.isclass(obj) and self.is_plugin(obj):
|
||||
self.obj_list.append(
|
||||
{"name": mem_name, "obj": obj(self.config)}
|
||||
{"name": mem_name, "obj": obj(self.config)},
|
||||
)
|
||||
|
||||
return self.obj_list
|
||||
|
@ -108,14 +105,16 @@ class PluginManager(object):
|
|||
return
|
||||
|
||||
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(
|
||||
# class_name, module_name))
|
||||
cls = getattr(module, class_name)
|
||||
if super_cls is not None:
|
||||
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))
|
||||
obj = cls(**kwargs)
|
||||
|
@ -131,13 +130,17 @@ class PluginManager(object):
|
|||
plugin_obj = None
|
||||
try:
|
||||
plugin_obj = self._create_class(
|
||||
plugin_name, APRSDPluginBase, config=self.config
|
||||
plugin_name,
|
||||
APRSDPluginBase,
|
||||
config=self.config,
|
||||
)
|
||||
if plugin_obj:
|
||||
LOG.info(
|
||||
"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)
|
||||
except Exception as ex:
|
||||
|
@ -173,8 +176,10 @@ class PluginManager(object):
|
|||
if plugin_obj:
|
||||
LOG.info(
|
||||
"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"])
|
||||
|
||||
|
@ -203,8 +208,7 @@ class APRSDCommandSpec:
|
|||
pass
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class APRSDPluginBase(object):
|
||||
class APRSDPluginBase(metaclass=abc.ABCMeta):
|
||||
def __init__(self, config):
|
||||
"""The aprsd config object is stored."""
|
||||
self.config = config
|
||||
|
@ -257,7 +261,8 @@ class FortunePlugin(APRSDPluginBase):
|
|||
|
||||
try:
|
||||
process = subprocess.Popen(
|
||||
[fortune_path, "-s", "-n 60"], stdout=subprocess.PIPE
|
||||
[fortune_path, "-s", "-n 60"],
|
||||
stdout=subprocess.PIPE,
|
||||
)
|
||||
reply = process.communicate()[0]
|
||||
reply = reply.decode(errors="ignore").rstrip()
|
||||
|
@ -406,7 +411,10 @@ class TimePlugin(APRSDPluginBase):
|
|||
m = stm.tm_min
|
||||
cur_time = fuzzy(h, m, 1)
|
||||
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
|
||||
|
||||
|
@ -497,7 +505,7 @@ class EmailPlugin(APRSDPluginBase):
|
|||
# send recipient link to aprs.fi map
|
||||
if content == "mapme":
|
||||
content = "Click for my location: http://aprs.fi/{}".format(
|
||||
self.config["ham"]["callsign"]
|
||||
self.config["ham"]["callsign"],
|
||||
)
|
||||
too_soon = 0
|
||||
now = time.time()
|
||||
|
@ -521,7 +529,7 @@ class EmailPlugin(APRSDPluginBase):
|
|||
LOG.debug(
|
||||
"DEBUG: email_sent_dict is big ("
|
||||
+ str(len(self.email_sent_dict))
|
||||
+ ") clearing out."
|
||||
+ ") clearing out.",
|
||||
)
|
||||
self.email_sent_dict.clear()
|
||||
self.email_sent_dict[ack] = now
|
||||
|
@ -529,7 +537,7 @@ class EmailPlugin(APRSDPluginBase):
|
|||
LOG.info(
|
||||
"Email for message number "
|
||||
+ ack
|
||||
+ " recently sent, not sending again."
|
||||
+ " recently sent, not sending again.",
|
||||
)
|
||||
else:
|
||||
reply = "Bad email address"
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import abc
|
||||
import logging
|
||||
import queue
|
||||
import threading
|
||||
import time
|
||||
|
||||
import aprslib
|
||||
|
||||
from aprsd import client, messaging, plugin
|
||||
import aprslib
|
||||
|
||||
LOG = logging.getLogger("APRSD")
|
||||
|
||||
|
@ -16,7 +14,7 @@ TX_THREAD = "TX"
|
|||
EMAIL_THREAD = "Email"
|
||||
|
||||
|
||||
class APRSDThreadList(object):
|
||||
class APRSDThreadList:
|
||||
"""Singleton class that keeps track of application wide threads."""
|
||||
|
||||
_instance = None
|
||||
|
@ -26,7 +24,7 @@ class APRSDThreadList(object):
|
|||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
if cls._instance is None:
|
||||
cls._instance = super(APRSDThreadList, cls).__new__(cls)
|
||||
cls._instance = super().__new__(cls)
|
||||
cls.lock = threading.Lock()
|
||||
cls.threads_list = []
|
||||
return cls._instance
|
||||
|
@ -48,7 +46,7 @@ class APRSDThreadList(object):
|
|||
|
||||
class APRSDThread(threading.Thread, metaclass=abc.ABCMeta):
|
||||
def __init__(self, name):
|
||||
super(APRSDThread, self).__init__(name=name)
|
||||
super().__init__(name=name)
|
||||
self.thread_stop = False
|
||||
APRSDThreadList().add(self)
|
||||
|
||||
|
@ -67,7 +65,7 @@ class APRSDThread(threading.Thread, metaclass=abc.ABCMeta):
|
|||
|
||||
class APRSDRXThread(APRSDThread):
|
||||
def __init__(self, msg_queues, config):
|
||||
super(APRSDRXThread, self).__init__("RX_MSG")
|
||||
super().__init__("RX_MSG")
|
||||
self.msg_queues = msg_queues
|
||||
self.config = config
|
||||
|
||||
|
@ -112,7 +110,11 @@ class APRSDRXThread(APRSDThread):
|
|||
ack_num = packet.get("msgNo")
|
||||
LOG.info("Got ack for message {}".format(ack_num))
|
||||
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.remove(ack_num)
|
||||
|
@ -153,7 +155,9 @@ class APRSDRXThread(APRSDThread):
|
|||
LOG.debug("Sending '{}'".format(reply))
|
||||
|
||||
msg = messaging.TextMessage(
|
||||
self.config["aprs"]["login"], fromcall, reply
|
||||
self.config["aprs"]["login"],
|
||||
fromcall,
|
||||
reply,
|
||||
)
|
||||
self.msg_queues["tx"].put(msg)
|
||||
else:
|
||||
|
@ -166,7 +170,9 @@ class APRSDRXThread(APRSDThread):
|
|||
|
||||
reply = "Usage: {}".format(", ".join(names))
|
||||
msg = messaging.TextMessage(
|
||||
self.config["aprs"]["login"], fromcall, reply
|
||||
self.config["aprs"]["login"],
|
||||
fromcall,
|
||||
reply,
|
||||
)
|
||||
self.msg_queues["tx"].put(msg)
|
||||
except Exception as ex:
|
||||
|
@ -178,7 +184,9 @@ class APRSDRXThread(APRSDThread):
|
|||
# let any threads do their thing, then ack
|
||||
# send an ack last
|
||||
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)
|
||||
LOG.debug("Packet processing complete")
|
||||
|
@ -213,7 +221,7 @@ class APRSDRXThread(APRSDThread):
|
|||
|
||||
class APRSDTXThread(APRSDThread):
|
||||
def __init__(self, msg_queues, config):
|
||||
super(APRSDTXThread, self).__init__("TX_MSG")
|
||||
super().__init__("TX_MSG")
|
||||
self.msg_queues = msg_queues
|
||||
self.config = config
|
||||
|
||||
|
|
|
@ -1,17 +1,15 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Utilities and helper functions."""
|
||||
|
||||
import errno
|
||||
import functools
|
||||
import os
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import threading
|
||||
from pathlib import Path
|
||||
|
||||
import click
|
||||
import yaml
|
||||
|
||||
from aprsd import plugin
|
||||
import click
|
||||
import yaml
|
||||
|
||||
# an example of what should be in the ~/.aprsd/config.yml
|
||||
DEFAULT_CONFIG_DICT = {
|
||||
|
@ -103,13 +101,13 @@ def get_config(config_file):
|
|||
"""This tries to read the yaml config from <config_file>."""
|
||||
config_file_expanded = os.path.expanduser(config_file)
|
||||
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)
|
||||
return config
|
||||
else:
|
||||
if config_file == DEFAULT_CONFIG_FILE:
|
||||
click.echo(
|
||||
"{} is missing, creating config file".format(config_file_expanded)
|
||||
"{} is missing, creating config file".format(config_file_expanded),
|
||||
)
|
||||
create_default_config()
|
||||
msg = (
|
||||
|
@ -144,7 +142,10 @@ def parse_config(config_file):
|
|||
if name and name not in config[section]:
|
||||
if not default:
|
||||
fail(
|
||||
"'%s' was not in '%s' section of config file" % (name, section)
|
||||
"'{}' was not in '{}' section of config file".format(
|
||||
name,
|
||||
section,
|
||||
),
|
||||
)
|
||||
else:
|
||||
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
|
||||
# and changed the ham callsign
|
||||
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", "password")
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import logging
|
||||
|
||||
from aprsd import plugin
|
||||
|
|
|
@ -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
|
11
setup.cfg
11
setup.cfg
|
@ -1,15 +1,16 @@
|
|||
[metadata]
|
||||
name = aprsd
|
||||
summary = Amateur radio APRS daemon which listens for messages and responds
|
||||
description-file =
|
||||
README.rst
|
||||
long-description-content-type = text/x-rst; charset=UTF-8
|
||||
long_description = file: README.rst
|
||||
long_description_content_type = text/x-rst
|
||||
author = Craig Lamparter
|
||||
author-email = something@somewhere.com
|
||||
author_email = something@somewhere.com
|
||||
classifier =
|
||||
Topic :: Communications :: Ham Radio
|
||||
Operating System :: POSIX :: Linux
|
||||
Programming Language :: Python
|
||||
description_file =
|
||||
README.rst
|
||||
summary = Amateur radio APRS daemon which listens for messages and responds
|
||||
|
||||
[global]
|
||||
setup-hooks =
|
||||
|
|
1
setup.py
1
setup.py
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
|
@ -7,7 +6,7 @@ from aprsd import email
|
|||
if sys.version_info >= (3, 2):
|
||||
from unittest import mock
|
||||
else:
|
||||
import mock
|
||||
from unittest import mock
|
||||
|
||||
|
||||
class TestMain(unittest.TestCase):
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import unittest
|
||||
from unittest import mock
|
||||
|
||||
|
@ -57,7 +56,10 @@ class TestPlugin(unittest.TestCase):
|
|||
|
||||
message = "time"
|
||||
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)
|
||||
self.assertEqual(expected, actual)
|
||||
|
|
Loading…
Reference in New Issue