mirror of
https://github.com/craigerl/aprsd.git
synced 2025-09-03 21:57:47 -04:00
Fixed email plugin's use of globals
The email plugin was still using globals for tracking the check_email_delay as well as the config. This patch creates a new singleton thread safe mechanism for check_email_delay with the EmailInfo class.
This commit is contained in:
parent
270be947b5
commit
a6ed7b894b
@ -5,6 +5,7 @@ import imaplib
|
|||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
import smtplib
|
import smtplib
|
||||||
|
import threading
|
||||||
import time
|
import time
|
||||||
|
|
||||||
import imapclient
|
import imapclient
|
||||||
@ -15,9 +16,45 @@ from aprsd import messaging, plugin, stats, threads, trace
|
|||||||
|
|
||||||
LOG = logging.getLogger("APRSD")
|
LOG = logging.getLogger("APRSD")
|
||||||
|
|
||||||
# This gets forced set from main.py prior to being used internally
|
|
||||||
CONFIG = {}
|
class EmailInfo:
|
||||||
check_email_delay = 60
|
"""A singleton thread safe mechanism for the global check_email_delay.
|
||||||
|
|
||||||
|
This has to be done because we have 2 separate threads that access
|
||||||
|
the delay value.
|
||||||
|
1) when EmailPlugin runs from a user message and
|
||||||
|
2) when the background EmailThread runs to check email.
|
||||||
|
|
||||||
|
Access the check email delay with
|
||||||
|
EmailInfo().delay
|
||||||
|
|
||||||
|
Set it with
|
||||||
|
EmailInfo().delay = 100
|
||||||
|
or
|
||||||
|
EmailInfo().delay += 10
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
_instance = None
|
||||||
|
|
||||||
|
def __new__(cls, *args, **kwargs):
|
||||||
|
"""This magic turns this into a singleton."""
|
||||||
|
if cls._instance is None:
|
||||||
|
cls._instance = super().__new__(cls)
|
||||||
|
cls._instance.lock = threading.Lock()
|
||||||
|
cls._instance._delay = 60
|
||||||
|
return cls._instance
|
||||||
|
|
||||||
|
@property
|
||||||
|
def delay(self):
|
||||||
|
with self.lock:
|
||||||
|
return self._delay
|
||||||
|
|
||||||
|
@delay.setter
|
||||||
|
def delay(self, val):
|
||||||
|
with self.lock:
|
||||||
|
self._delay = val
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class EmailPlugin(plugin.APRSDRegexCommandPluginBase):
|
class EmailPlugin(plugin.APRSDRegexCommandPluginBase):
|
||||||
@ -34,8 +71,6 @@ class EmailPlugin(plugin.APRSDRegexCommandPluginBase):
|
|||||||
|
|
||||||
def setup(self):
|
def setup(self):
|
||||||
"""Ensure that email is enabled and start the thread."""
|
"""Ensure that email is enabled and start the thread."""
|
||||||
global CONFIG
|
|
||||||
CONFIG = self.config
|
|
||||||
|
|
||||||
email_enabled = self.config["aprsd"]["email"].get("enabled", False)
|
email_enabled = self.config["aprsd"]["email"].get("enabled", False)
|
||||||
validation = self.config["aprsd"]["email"].get("validate", False)
|
validation = self.config["aprsd"]["email"].get("validate", False)
|
||||||
@ -81,7 +116,7 @@ class EmailPlugin(plugin.APRSDRegexCommandPluginBase):
|
|||||||
r = re.search("^-([0-9])[0-9]*$", message)
|
r = re.search("^-([0-9])[0-9]*$", message)
|
||||||
if r is not None:
|
if r is not None:
|
||||||
LOG.debug("RESEND EMAIL")
|
LOG.debug("RESEND EMAIL")
|
||||||
resend_email(r.group(1), fromcall)
|
resend_email(self.config, r.group(1), fromcall)
|
||||||
reply = messaging.NULL_MESSAGE
|
reply = messaging.NULL_MESSAGE
|
||||||
# -user@address.com body of email
|
# -user@address.com body of email
|
||||||
elif re.search(r"^-([A-Za-z0-9_\-\.@]+) (.*)", message):
|
elif re.search(r"^-([A-Za-z0-9_\-\.@]+) (.*)", message):
|
||||||
@ -91,7 +126,7 @@ class EmailPlugin(plugin.APRSDRegexCommandPluginBase):
|
|||||||
to_addr = a.group(1)
|
to_addr = a.group(1)
|
||||||
content = a.group(2)
|
content = a.group(2)
|
||||||
|
|
||||||
email_address = get_email_from_shortcut(to_addr)
|
email_address = get_email_from_shortcut(self.config, to_addr)
|
||||||
if not email_address:
|
if not email_address:
|
||||||
reply = "Bad email address"
|
reply = "Bad email address"
|
||||||
return reply
|
return reply
|
||||||
@ -114,7 +149,7 @@ class EmailPlugin(plugin.APRSDRegexCommandPluginBase):
|
|||||||
too_soon = 1
|
too_soon = 1
|
||||||
if not too_soon or ack == 0:
|
if not too_soon or ack == 0:
|
||||||
LOG.info(f"Send email '{content}'")
|
LOG.info(f"Send email '{content}'")
|
||||||
send_result = email.send_email(to_addr, content)
|
send_result = send_email(self.config, to_addr, content)
|
||||||
reply = messaging.NULL_MESSAGE
|
reply = messaging.NULL_MESSAGE
|
||||||
if send_result != 0:
|
if send_result != 0:
|
||||||
reply = f"-{to_addr} failed"
|
reply = f"-{to_addr} failed"
|
||||||
@ -143,10 +178,9 @@ class EmailPlugin(plugin.APRSDRegexCommandPluginBase):
|
|||||||
return reply
|
return reply
|
||||||
|
|
||||||
|
|
||||||
def _imap_connect():
|
def _imap_connect(config):
|
||||||
global CONFIG
|
imap_port = config["aprsd"]["email"]["imap"].get("port", 143)
|
||||||
imap_port = CONFIG["aprsd"]["email"]["imap"].get("port", 143)
|
use_ssl = config["aprsd"]["email"]["imap"].get("use_ssl", False)
|
||||||
use_ssl = CONFIG["aprsd"]["email"]["imap"].get("use_ssl", False)
|
|
||||||
# host = CONFIG["aprsd"]["email"]["imap"]["host"]
|
# host = CONFIG["aprsd"]["email"]["imap"]["host"]
|
||||||
# msg = "{}{}:{}".format("TLS " if use_ssl else "", host, imap_port)
|
# msg = "{}{}:{}".format("TLS " if use_ssl else "", host, imap_port)
|
||||||
# LOG.debug("Connect to IMAP host {} with user '{}'".
|
# LOG.debug("Connect to IMAP host {} with user '{}'".
|
||||||
@ -154,7 +188,7 @@ def _imap_connect():
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
server = imapclient.IMAPClient(
|
server = imapclient.IMAPClient(
|
||||||
CONFIG["aprsd"]["email"]["imap"]["host"],
|
config["aprsd"]["email"]["imap"]["host"],
|
||||||
port=imap_port,
|
port=imap_port,
|
||||||
use_uid=True,
|
use_uid=True,
|
||||||
ssl=use_ssl,
|
ssl=use_ssl,
|
||||||
@ -166,8 +200,8 @@ def _imap_connect():
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
server.login(
|
server.login(
|
||||||
CONFIG["aprsd"]["email"]["imap"]["login"],
|
config["aprsd"]["email"]["imap"]["login"],
|
||||||
CONFIG["aprsd"]["email"]["imap"]["password"],
|
config["aprsd"]["email"]["imap"]["password"],
|
||||||
)
|
)
|
||||||
except (imaplib.IMAP4.error, Exception) as e:
|
except (imaplib.IMAP4.error, Exception) as e:
|
||||||
msg = getattr(e, "message", repr(e))
|
msg = getattr(e, "message", repr(e))
|
||||||
@ -183,15 +217,15 @@ def _imap_connect():
|
|||||||
return server
|
return server
|
||||||
|
|
||||||
|
|
||||||
def _smtp_connect():
|
def _smtp_connect(config):
|
||||||
host = CONFIG["aprsd"]["email"]["smtp"]["host"]
|
host = config["aprsd"]["email"]["smtp"]["host"]
|
||||||
smtp_port = CONFIG["aprsd"]["email"]["smtp"]["port"]
|
smtp_port = config["aprsd"]["email"]["smtp"]["port"]
|
||||||
use_ssl = CONFIG["aprsd"]["email"]["smtp"].get("use_ssl", False)
|
use_ssl = config["aprsd"]["email"]["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(
|
"Connect to SMTP host {} with user '{}'".format(
|
||||||
msg,
|
msg,
|
||||||
CONFIG["aprsd"]["email"]["imap"]["login"],
|
config["aprsd"]["email"]["imap"]["login"],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -214,15 +248,15 @@ def _smtp_connect():
|
|||||||
|
|
||||||
LOG.debug(f"Connected to smtp host {msg}")
|
LOG.debug(f"Connected to smtp host {msg}")
|
||||||
|
|
||||||
debug = CONFIG["aprsd"]["email"]["smtp"].get("debug", False)
|
debug = config["aprsd"]["email"]["smtp"].get("debug", False)
|
||||||
if debug:
|
if debug:
|
||||||
server.set_debuglevel(5)
|
server.set_debuglevel(5)
|
||||||
server.sendmail = trace.trace(server.sendmail)
|
server.sendmail = trace.trace(server.sendmail)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
server.login(
|
server.login(
|
||||||
CONFIG["aprsd"]["email"]["smtp"]["login"],
|
config["aprsd"]["email"]["smtp"]["login"],
|
||||||
CONFIG["aprsd"]["email"]["smtp"]["password"],
|
config["aprsd"]["email"]["smtp"]["password"],
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
LOG.error("Couldn't connect to SMTP Server")
|
LOG.error("Couldn't connect to SMTP Server")
|
||||||
@ -273,9 +307,9 @@ def validate_shortcuts(config):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_email_from_shortcut(addr):
|
def get_email_from_shortcut(config, addr):
|
||||||
if CONFIG["aprsd"]["email"].get("shortcuts", False):
|
if config["aprsd"]["email"].get("shortcuts", False):
|
||||||
return CONFIG["aprsd"]["email"]["shortcuts"].get(addr, addr)
|
return config["aprsd"]["email"]["shortcuts"].get(addr, addr)
|
||||||
else:
|
else:
|
||||||
return addr
|
return addr
|
||||||
|
|
||||||
@ -286,9 +320,9 @@ def validate_email_config(config, disable_validation=False):
|
|||||||
This helps with failing early during startup.
|
This helps with failing early during startup.
|
||||||
"""
|
"""
|
||||||
LOG.info("Checking IMAP configuration")
|
LOG.info("Checking IMAP configuration")
|
||||||
imap_server = _imap_connect()
|
imap_server = _imap_connect(config)
|
||||||
LOG.info("Checking SMTP configuration")
|
LOG.info("Checking SMTP configuration")
|
||||||
smtp_server = _smtp_connect()
|
smtp_server = _smtp_connect(config)
|
||||||
|
|
||||||
# Now validate and flag any shortcuts as invalid
|
# Now validate and flag any shortcuts as invalid
|
||||||
if not disable_validation:
|
if not disable_validation:
|
||||||
@ -398,34 +432,32 @@ def parse_email(msgid, data, server):
|
|||||||
|
|
||||||
|
|
||||||
@trace.trace
|
@trace.trace
|
||||||
def send_email(to_addr, content):
|
def send_email(config, to_addr, content):
|
||||||
global check_email_delay
|
shortcuts = config["aprsd"]["email"]["shortcuts"]
|
||||||
|
email_address = get_email_from_shortcut(config, to_addr)
|
||||||
shortcuts = CONFIG["aprsd"]["email"]["shortcuts"]
|
|
||||||
email_address = get_email_from_shortcut(to_addr)
|
|
||||||
LOG.info("Sending Email_________________")
|
LOG.info("Sending Email_________________")
|
||||||
|
|
||||||
if to_addr in shortcuts:
|
if to_addr in shortcuts:
|
||||||
LOG.info("To : " + to_addr)
|
LOG.info("To : " + to_addr)
|
||||||
to_addr = email_address
|
to_addr = email_address
|
||||||
LOG.info(" (" + to_addr + ")")
|
LOG.info(" (" + to_addr + ")")
|
||||||
subject = CONFIG["ham"]["callsign"]
|
subject = config["ham"]["callsign"]
|
||||||
# content = content + "\n\n(NOTE: reply with one line)"
|
# content = content + "\n\n(NOTE: reply with one line)"
|
||||||
LOG.info("Subject : " + subject)
|
LOG.info("Subject : " + subject)
|
||||||
LOG.info("Body : " + content)
|
LOG.info("Body : " + content)
|
||||||
|
|
||||||
# check email more often since there's activity right now
|
# check email more often since there's activity right now
|
||||||
check_email_delay = 60
|
EmailInfo().delay = 60
|
||||||
|
|
||||||
msg = MIMEText(content)
|
msg = MIMEText(content)
|
||||||
msg["Subject"] = subject
|
msg["Subject"] = subject
|
||||||
msg["From"] = CONFIG["aprsd"]["email"]["smtp"]["login"]
|
msg["From"] = config["aprsd"]["email"]["smtp"]["login"]
|
||||||
msg["To"] = to_addr
|
msg["To"] = to_addr
|
||||||
server = _smtp_connect()
|
server = _smtp_connect()
|
||||||
if server:
|
if server:
|
||||||
try:
|
try:
|
||||||
server.sendmail(
|
server.sendmail(
|
||||||
CONFIG["aprsd"]["email"]["smtp"]["login"],
|
config["aprsd"]["email"]["smtp"]["login"],
|
||||||
[to_addr],
|
[to_addr],
|
||||||
msg.as_string(),
|
msg.as_string(),
|
||||||
)
|
)
|
||||||
@ -440,20 +472,19 @@ def send_email(to_addr, content):
|
|||||||
|
|
||||||
|
|
||||||
@trace.trace
|
@trace.trace
|
||||||
def resend_email(count, fromcall):
|
def resend_email(config, count, fromcall):
|
||||||
global check_email_delay
|
|
||||||
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 = f"{day}-{month}-{year}"
|
today = f"{day}-{month}-{year}"
|
||||||
|
|
||||||
shortcuts = CONFIG["aprsd"]["email"]["shortcuts"]
|
shortcuts = config["aprsd"]["email"]["shortcuts"]
|
||||||
# swap key/value
|
# swap key/value
|
||||||
shortcuts_inverted = {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(config)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.exception("Failed to Connect to IMAP. Cannot resend email ", e)
|
LOG.exception("Failed to Connect to IMAP. Cannot resend email ", e)
|
||||||
return
|
return
|
||||||
@ -493,7 +524,7 @@ def resend_email(count, fromcall):
|
|||||||
reply = "-" + from_addr + " * " + body.decode(errors="ignore")
|
reply = "-" + from_addr + " * " + body.decode(errors="ignore")
|
||||||
# messaging.send_message(fromcall, reply)
|
# messaging.send_message(fromcall, reply)
|
||||||
msg = messaging.TextMessage(
|
msg = messaging.TextMessage(
|
||||||
CONFIG["aprs"]["login"],
|
config["aprs"]["login"],
|
||||||
fromcall,
|
fromcall,
|
||||||
reply,
|
reply,
|
||||||
)
|
)
|
||||||
@ -515,11 +546,11 @@ def resend_email(count, fromcall):
|
|||||||
str(s).zfill(2),
|
str(s).zfill(2),
|
||||||
)
|
)
|
||||||
# messaging.send_message(fromcall, reply)
|
# messaging.send_message(fromcall, reply)
|
||||||
msg = messaging.TextMessage(CONFIG["aprs"]["login"], fromcall, reply)
|
msg = messaging.TextMessage(config["aprs"]["login"], fromcall, reply)
|
||||||
msg.send()
|
msg.send()
|
||||||
|
|
||||||
# check email more often since we're resending one now
|
# check email more often since we're resending one now
|
||||||
check_email_delay = 60
|
EmailInfo().delay = 60
|
||||||
|
|
||||||
server.logout()
|
server.logout()
|
||||||
# end resend_email()
|
# end resend_email()
|
||||||
@ -533,27 +564,24 @@ class APRSDEmailThread(threads.APRSDThread):
|
|||||||
self.past = datetime.datetime.now()
|
self.past = datetime.datetime.now()
|
||||||
|
|
||||||
def loop(self):
|
def loop(self):
|
||||||
global check_email_delay
|
|
||||||
|
|
||||||
check_email_delay = 60
|
|
||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
stats.APRSDStats().email_thread_update()
|
stats.APRSDStats().email_thread_update()
|
||||||
# always sleep for 5 seconds and see if we need to check email
|
# always sleep for 5 seconds and see if we need to check email
|
||||||
# This allows CTRL-C to stop the execution of this loop sooner
|
# This allows CTRL-C to stop the execution of this loop sooner
|
||||||
# than check_email_delay time
|
# than check_email_delay time
|
||||||
now = datetime.datetime.now()
|
now = datetime.datetime.now()
|
||||||
if now - self.past > datetime.timedelta(seconds=check_email_delay):
|
if now - self.past > datetime.timedelta(seconds=EmailInfo().delay):
|
||||||
# It's time to check email
|
# It's time to check email
|
||||||
|
|
||||||
# slowly increase delay every iteration, max out at 300 seconds
|
# slowly increase delay every iteration, max out at 300 seconds
|
||||||
# any send/receive/resend activity will reset this to 60 seconds
|
# any send/receive/resend activity will reset this to 60 seconds
|
||||||
if check_email_delay < 300:
|
if EmailInfo().delay < 300:
|
||||||
check_email_delay += 1
|
EmailInfo().delay += 1
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
"check_email_delay is " + str(check_email_delay) + " seconds",
|
f"check_email_delay is {EmailInfo().delay} seconds ",
|
||||||
)
|
)
|
||||||
|
|
||||||
shortcuts = CONFIG["aprsd"]["email"]["shortcuts"]
|
shortcuts = self.config["aprsd"]["email"]["shortcuts"]
|
||||||
# swap key/value
|
# swap key/value
|
||||||
shortcuts_inverted = {v: k for k, v in shortcuts.items()}
|
shortcuts_inverted = {v: k for k, v in shortcuts.items()}
|
||||||
|
|
||||||
@ -564,7 +592,7 @@ class APRSDEmailThread(threads.APRSDThread):
|
|||||||
today = f"{day}-{month}-{year}"
|
today = f"{day}-{month}-{year}"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
server = _imap_connect()
|
server = _imap_connect(self.config)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.exception("IMAP failed to connect.", e)
|
LOG.exception("IMAP failed to connect.", e)
|
||||||
return True
|
return True
|
||||||
@ -658,7 +686,7 @@ class APRSDEmailThread(threads.APRSDThread):
|
|||||||
LOG.exception("Couldn't remove seen flag from email", e)
|
LOG.exception("Couldn't remove seen flag from email", e)
|
||||||
|
|
||||||
# check email more often since we just received an email
|
# check email more often since we just received an email
|
||||||
check_email_delay = 60
|
EmailInfo().delay = 60
|
||||||
|
|
||||||
# reset clock
|
# reset clock
|
||||||
LOG.debug("Done looping over Server.fetch, logging out.")
|
LOG.debug("Done looping over Server.fetch, logging out.")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user