feat: email debt timer
This commit is contained in:
18
flake.nix
18
flake.nix
@@ -131,6 +131,21 @@
|
||||
default = "emacsclient";
|
||||
description = "Path to the emacsclient binary";
|
||||
};
|
||||
debt_query = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "tag:unread and date:..1w";
|
||||
description = "notmuch query whose count drives the mail-debt severity color on the bar widget";
|
||||
};
|
||||
debt_warn_at = lib.mkOption {
|
||||
type = lib.types.int;
|
||||
default = 1;
|
||||
description = "Debt count at which the widget switches to the warn (orange) color";
|
||||
};
|
||||
debt_alarm_at = lib.mkOption {
|
||||
type = lib.types.int;
|
||||
default = 6;
|
||||
description = "Debt count at which the widget switches to the alarm (red) color";
|
||||
};
|
||||
};
|
||||
screenrec = {
|
||||
enable = lib.mkOption {
|
||||
@@ -210,6 +225,9 @@
|
||||
enable = true;
|
||||
notmuch_path = "notmuch";
|
||||
emacsclient_command = "emacsclient";
|
||||
debt_query = "tag:unread and date:..1w";
|
||||
debt_warn_at = 1;
|
||||
debt_alarm_at = 6;
|
||||
};
|
||||
screenrec = {
|
||||
enable = false;
|
||||
|
||||
@@ -131,7 +131,7 @@ def open_screenshot_menu():
|
||||
@Application.action()
|
||||
def refresh_notmuch():
|
||||
if notmuch_widget is not None:
|
||||
notmuch_widget.service.update_unread_count()
|
||||
notmuch_widget.service.update_counts()
|
||||
|
||||
|
||||
@Application.action()
|
||||
|
||||
@@ -12,27 +12,33 @@ from loguru import logger
|
||||
from sims.config import NOTMUCH
|
||||
|
||||
|
||||
DEFAULT_DEBT_QUERY = "tag:unread and date:..1w"
|
||||
DEFAULT_DEBT_WARN_AT = 1
|
||||
DEFAULT_DEBT_ALARM_AT = 6
|
||||
|
||||
|
||||
class NotmuchService:
|
||||
def __init__(self, update_interval=60000): # 1 minute default
|
||||
self.unread_count = 0
|
||||
self.debt_count = 0
|
||||
self.callbacks = []
|
||||
self._update_interval = update_interval
|
||||
self._timer_id = None
|
||||
|
||||
# Initial load
|
||||
self.update_unread_count()
|
||||
self.update_counts()
|
||||
# Start periodic updates
|
||||
self.start_monitoring()
|
||||
|
||||
def connect(self, signal_name, callback):
|
||||
"""Simple callback system to replace signals"""
|
||||
if signal_name == "unread-changed":
|
||||
if signal_name == "counts-changed":
|
||||
self.callbacks.append(callback)
|
||||
|
||||
def emit_unread_changed(self, count):
|
||||
"""Emit unread changed to all callbacks"""
|
||||
def emit_counts_changed(self):
|
||||
"""Emit counts changed to all callbacks"""
|
||||
for callback in self.callbacks:
|
||||
callback(self, count)
|
||||
callback(self, self.unread_count, self.debt_count)
|
||||
|
||||
def start_monitoring(self):
|
||||
"""Start periodic unread count updates"""
|
||||
@@ -57,21 +63,38 @@ class NotmuchService:
|
||||
|
||||
def _periodic_update(self):
|
||||
"""Periodic update callback"""
|
||||
logger.info("[Notmuch] Performing periodic unread count update")
|
||||
self.update_unread_count()
|
||||
logger.info("[Notmuch] Performing periodic count update")
|
||||
self.update_counts()
|
||||
return True # Keep the timer running
|
||||
|
||||
def get_cached_count(self):
|
||||
"""Get cached unread count without triggering update"""
|
||||
return self.unread_count
|
||||
|
||||
def update_unread_count(self):
|
||||
"""Fetch unread email count from notmuch"""
|
||||
def get_cached_debt_count(self):
|
||||
"""Get cached debt count without triggering update"""
|
||||
return self.debt_count
|
||||
|
||||
def _run_count(self, notmuch_path, query):
|
||||
cmd = [notmuch_path, "count", query]
|
||||
logger.info(f"[Notmuch] Running command: {' '.join(cmd)}")
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True,
|
||||
)
|
||||
out = result.stdout.strip()
|
||||
return int(out) if out else 0
|
||||
|
||||
def update_counts(self):
|
||||
"""Fetch unread + debt counts from notmuch"""
|
||||
# Check if notmuch is enabled
|
||||
if not NOTMUCH.get("enable", True):
|
||||
logger.info("[Notmuch] Notmuch is disabled in config")
|
||||
self.unread_count = 0
|
||||
self.emit_unread_changed(self.unread_count)
|
||||
self.debt_count = 0
|
||||
self.emit_counts_changed()
|
||||
return
|
||||
|
||||
# Get notmuch path from config
|
||||
@@ -81,42 +104,34 @@ class NotmuchService:
|
||||
if not shutil.which(notmuch_path):
|
||||
logger.warning(f"[Notmuch] notmuch not found at '{notmuch_path}'. Please install notmuch or configure the correct path.")
|
||||
self.unread_count = 0
|
||||
self.emit_unread_changed(self.unread_count)
|
||||
self.debt_count = 0
|
||||
self.emit_counts_changed()
|
||||
return
|
||||
|
||||
debt_query = NOTMUCH.get("debt_query", DEFAULT_DEBT_QUERY)
|
||||
|
||||
try:
|
||||
# Get unread email count
|
||||
cmd = [notmuch_path, "count", "tag:unread"]
|
||||
logger.info(f"[Notmuch] Running command: {' '.join(cmd)}")
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True,
|
||||
self.unread_count = self._run_count(notmuch_path, "tag:unread")
|
||||
self.debt_count = self._run_count(notmuch_path, debt_query)
|
||||
logger.info(
|
||||
f"[Notmuch] {self.unread_count} unread, {self.debt_count} aging (debt query: {debt_query!r})"
|
||||
)
|
||||
logger.info(f"[Notmuch] Command stdout: '{result.stdout.strip()}'")
|
||||
logger.info(f"[Notmuch] Command stderr: '{result.stderr.strip()}'")
|
||||
|
||||
if result.stdout.strip():
|
||||
self.unread_count = int(result.stdout.strip())
|
||||
logger.info(f"[Notmuch] Found {self.unread_count} unread emails")
|
||||
self.emit_unread_changed(self.unread_count)
|
||||
else:
|
||||
self.unread_count = 0
|
||||
self.emit_unread_changed(self.unread_count)
|
||||
|
||||
self.emit_counts_changed()
|
||||
except subprocess.CalledProcessError as e:
|
||||
logger.error(f"[Notmuch] Failed to fetch unread count: {e}")
|
||||
logger.error(f"[Notmuch] Failed to fetch counts: {e}")
|
||||
self.unread_count = 0
|
||||
self.emit_unread_changed(self.unread_count)
|
||||
self.debt_count = 0
|
||||
self.emit_counts_changed()
|
||||
except ValueError as e:
|
||||
logger.error(f"[Notmuch] Error parsing unread count: {e}")
|
||||
logger.error(f"[Notmuch] Error parsing count: {e}")
|
||||
self.unread_count = 0
|
||||
self.emit_unread_changed(self.unread_count)
|
||||
self.debt_count = 0
|
||||
self.emit_counts_changed()
|
||||
except Exception as e:
|
||||
logger.error(f"[Notmuch] Error getting unread count: {e}")
|
||||
logger.error(f"[Notmuch] Error getting counts: {e}")
|
||||
self.unread_count = 0
|
||||
self.emit_unread_changed(self.unread_count)
|
||||
self.debt_count = 0
|
||||
self.emit_counts_changed()
|
||||
|
||||
|
||||
class NotmuchWidget(Button):
|
||||
@@ -141,12 +156,14 @@ class NotmuchWidget(Button):
|
||||
|
||||
# Initialize the service
|
||||
self.service = NotmuchService()
|
||||
self.service.connect("unread-changed", self.update_display)
|
||||
self.service.connect("counts-changed", self.update_display)
|
||||
|
||||
logger.info("[Notmuch] Notmuch widget initialized")
|
||||
|
||||
# Initial update
|
||||
self.update_display(self.service, self.service.unread_count)
|
||||
self.update_display(
|
||||
self.service, self.service.unread_count, self.service.debt_count
|
||||
)
|
||||
|
||||
def open_email_client(self, button=None):
|
||||
"""Open notmuch in emacsclient"""
|
||||
@@ -161,18 +178,31 @@ class NotmuchWidget(Button):
|
||||
except Exception as e:
|
||||
logger.error(f"[Notmuch] Failed to open notmuch in emacsclient '{emacsclient_command}': {e}")
|
||||
|
||||
def update_display(self, service, count):
|
||||
"""Update the widget display with unread count"""
|
||||
# Only show count if there are unread emails
|
||||
if count > 0:
|
||||
self.label.set_text(str(count))
|
||||
def update_display(self, service, unread, debt):
|
||||
"""Update the widget display with unread + debt counts"""
|
||||
warn_at = NOTMUCH.get("debt_warn_at", DEFAULT_DEBT_WARN_AT)
|
||||
alarm_at = NOTMUCH.get("debt_alarm_at", DEFAULT_DEBT_ALARM_AT)
|
||||
|
||||
classes = ["notmuch-widget"]
|
||||
if unread > 0:
|
||||
self.label.set_text(str(unread))
|
||||
self.label.set_visible(True)
|
||||
self.icon.set_property("icon-name", "mail-unread-symbolic")
|
||||
self.set_style_classes(["notmuch-widget", "has-unread"])
|
||||
classes.append("has-unread")
|
||||
else:
|
||||
self.label.set_text("")
|
||||
self.label.set_visible(False)
|
||||
self.icon.set_property("icon-name", "mail-read-symbolic")
|
||||
self.set_style_classes(["notmuch-widget", "no-unread"])
|
||||
classes.append("no-unread")
|
||||
|
||||
logger.info(f"[Notmuch] Updated display: {count} unread emails")
|
||||
if debt >= alarm_at:
|
||||
classes.append("debt-alarm")
|
||||
elif debt >= warn_at:
|
||||
classes.append("debt-warn")
|
||||
|
||||
self.set_style_classes(classes)
|
||||
self.set_tooltip_text(f"{unread} unread · {debt} aging")
|
||||
|
||||
logger.info(
|
||||
f"[Notmuch] Updated display: {unread} unread, {debt} aging — classes={classes}"
|
||||
)
|
||||
@@ -23,6 +23,22 @@
|
||||
background-color: var(--module-bg);
|
||||
}
|
||||
|
||||
#notmuch-widget.debt-warn {
|
||||
background-color: var(--orange);
|
||||
}
|
||||
|
||||
#notmuch-widget.debt-warn:hover {
|
||||
background-color: var(--gold);
|
||||
}
|
||||
|
||||
#notmuch-widget.debt-alarm {
|
||||
background-color: var(--red);
|
||||
}
|
||||
|
||||
#notmuch-widget.debt-alarm:hover {
|
||||
background-color: var(--pink);
|
||||
}
|
||||
|
||||
#unread-count {
|
||||
color: var(--foreground);
|
||||
font-size: 14px;
|
||||
@@ -30,6 +46,8 @@
|
||||
min-width: 16px;
|
||||
}
|
||||
|
||||
#notmuch-widget.has-unread #unread-count {
|
||||
#notmuch-widget.has-unread #unread-count,
|
||||
#notmuch-widget.debt-warn #unread-count,
|
||||
#notmuch-widget.debt-alarm #unread-count {
|
||||
color: var(--background);
|
||||
}
|
||||
Reference in New Issue
Block a user