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