Add --debug flag, dual-mode logging, and auto-run on startup

- logger.py: root-level handlers; normal mode shows only user-facing INFO
  (sync, export, push, MBOX) plus WARNING/ERROR; --debug shows all DEBUG
  with full context (module.func:line). Third-party loggers silenced to WARNING.
- main.py: add --debug CLI flag, call configure_logging() at startup,
  auto-trigger sync on first run or when last sync is overdue by the interval
- database.py: add metadata table with record_sync_time() / get_last_sync_time()
  so startup knows whether a sync is due; sync time recorded on success
- forwarder.py: INFO at push start and push complete with counts
- packager.py: INFO before MBOX conversion begins
- exporter.py: INFO when Proton Mail export starts

https://claude.ai/code/session_01KjaNo9RXevw6x1DjJD8mj6
This commit is contained in:
Claude
2026-03-24 21:54:25 +00:00
parent f021410622
commit 69c685798c
6 changed files with 114 additions and 24 deletions
+30 -1
View File
@@ -2,8 +2,9 @@
import sqlite3
from contextlib import contextmanager
from datetime import datetime, timezone
from pathlib import Path
from typing import Generator, Iterable
from typing import Generator, Iterable, Optional
DB_PATH = Path(__file__).parent.parent / "data" / "mailrelay.db"
@@ -48,6 +49,12 @@ def init_db() -> None:
conn.execute("""
CREATE INDEX IF NOT EXISTS idx_state ON messages(state)
""")
conn.execute("""
CREATE TABLE IF NOT EXISTS metadata (
key TEXT PRIMARY KEY,
value TEXT NOT NULL
)
""")
def is_known(message_id: str) -> bool:
@@ -127,6 +134,28 @@ def get_pending_mboxes() -> list[dict]:
return [{"mbox_path": path, "message_ids": ids} for path, ids in by_path.items()]
def get_last_sync_time() -> Optional[datetime]:
"""Return the UTC timestamp of the last completed sync, or None."""
with _db() as conn:
row = conn.execute(
"SELECT value FROM metadata WHERE key = 'last_sync_at'"
).fetchone()
if not row:
return None
return datetime.fromisoformat(row["value"]).replace(tzinfo=timezone.utc)
def record_sync_time() -> None:
"""Record that a sync cycle just completed."""
now = datetime.now(timezone.utc).isoformat()
with _db() as conn:
conn.execute(
"INSERT INTO metadata (key, value) VALUES ('last_sync_at', ?) "
"ON CONFLICT(key) DO UPDATE SET value = excluded.value",
(now,),
)
def clear_pending_for_mbox(mbox_path: str) -> list[str]:
"""Remove pending state for a given MBOX (used on cleanup/re-process).