04/23 Fixed bugs

This commit is contained in:
2026-04-23 15:53:29 -04:00
parent eab4207e1f
commit 2ab0408cbe
5 changed files with 123 additions and 15 deletions
+41 -2
View File
@@ -175,10 +175,38 @@ def _send_report(cfg: dict):
logger.error(f"Failed to send daily report email: {e}")
def _get_last_sent_date() -> "datetime.date | None":
"""Read the last-sent date from config.ini [email] last_sent_date key."""
cfg = configparser.ConfigParser()
if not os.path.exists(CONFIG_FILE):
return None
cfg.read(CONFIG_FILE, encoding="utf-8")
raw = cfg.get("email", "last_sent_date", fallback="")
if not raw:
return None
try:
return datetime.date.fromisoformat(raw)
except ValueError:
return None
def _set_last_sent_date(d: "datetime.date"):
"""Persist the last-sent date to config.ini [email] last_sent_date key."""
cfg = configparser.ConfigParser()
if os.path.exists(CONFIG_FILE):
cfg.read(CONFIG_FILE, encoding="utf-8")
if "email" not in cfg:
cfg["email"] = {}
cfg["email"]["last_sent_date"] = d.isoformat()
with open(CONFIG_FILE, "w", encoding="utf-8") as fh:
cfg.write(fh)
# ─── Scheduler loop ───────────────────────────────────────────────────────────
def _scheduler_loop():
last_sent_date = None
# Seed from persisted value so a restart after send_time does not re-send.
last_sent_date = _get_last_sent_date()
while not _stop_event.is_set():
_stop_event.wait(60) # sleep 60 seconds between checks
@@ -200,6 +228,7 @@ def _scheduler_loop():
if (now.hour > send_h or (now.hour == send_h and now.minute >= send_m)):
if last_sent_date != today:
last_sent_date = today
_set_last_sent_date(today)
logger.info(f"Scheduler: sending daily report at {now.strftime('%H:%M')}.")
_send_report(cfg)
@@ -207,7 +236,17 @@ def _scheduler_loop():
def start():
"""Start the background scheduler thread. Call once after successful login."""
global _scheduler_thread, _stop_event
_stop_event.clear()
# Guard against double-start: if a thread is already alive (e.g. admin
# logs out and back in), stop it cleanly before spawning a new one.
# Without this guard, _stop_event.clear() would unblock the sleeping
# thread while a second thread also starts, resulting in two scheduler
# threads firing simultaneously and potentially sending duplicate emails.
if _scheduler_thread is not None and _scheduler_thread.is_alive():
logger.info("Email scheduler already running - stopping before restart.")
_stop_event.set()
_scheduler_thread.join(timeout=5)
# Create a fresh Event so there is no residual set-state from a prior stop()
_stop_event = threading.Event()
_scheduler_thread = threading.Thread(target=_scheduler_loop,
name="EmailScheduler", daemon=True)
_scheduler_thread.start()