Add spinner/countdown-bar to stats panel; backfill on widened days_back
Stats panel: a persistent spinner marks whichever target is currently being scanned/reacted to, and jittered waits show a live-ticking countdown bar instead of a static "waiting Xs" line. Both the status line and countdown row are always rendered (blank when idle) so their appearing/disappearing no longer shifts the table's height. Backlog: channel_scan_state now tracks oldest_covered_at alongside newest_id_seen. If days_back is widened after a channel already has a resume checkpoint, the forward-only diff would never look back far enough to notice — now the newly-exposed gap gets backfilled first.
This commit is contained in:
@@ -27,9 +27,10 @@ CREATE TABLE IF NOT EXISTS reactions_log (
|
||||
CREATE INDEX IF NOT EXISTS idx_reactions_log_target ON reactions_log(target_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS channel_scan_state (
|
||||
channel_id TEXT PRIMARY KEY,
|
||||
target_id TEXT NOT NULL,
|
||||
channel_id TEXT PRIMARY KEY,
|
||||
target_id TEXT NOT NULL,
|
||||
newest_id_seen TEXT,
|
||||
oldest_covered_at TEXT,
|
||||
backlog_complete INTEGER NOT NULL DEFAULT 0,
|
||||
last_scanned_at TEXT
|
||||
);
|
||||
@@ -64,6 +65,12 @@ class Storage:
|
||||
if "message_created_at" not in columns:
|
||||
self._conn.execute("ALTER TABLE reactions_log ADD COLUMN message_created_at TEXT")
|
||||
|
||||
# Older DBs created before oldest_covered_at existed (widening days_back
|
||||
# couldn't be detected/backfilled without it — see scan_channel_backlog).
|
||||
scan_columns = {row["name"] for row in self._conn.execute("PRAGMA table_info(channel_scan_state)")}
|
||||
if "oldest_covered_at" not in scan_columns:
|
||||
self._conn.execute("ALTER TABLE channel_scan_state ADD COLUMN oldest_covered_at TEXT")
|
||||
|
||||
# Older DBs created before 'dm' targets (1:1 DMs) existed have a CHECK
|
||||
# constraint that only allows 'guild'/'group_dm' — rebuild the table
|
||||
# with the wider constraint, preserving all existing rows.
|
||||
@@ -185,11 +192,10 @@ class Storage:
|
||||
return inserted
|
||||
|
||||
def clear_scan_state(self) -> int:
|
||||
"""Wipe all per-channel resume points. The backlog scan only ever
|
||||
consults days_back on a channel's very first scan and otherwise just
|
||||
diffs forward from the last-seen message, so this must be cleared
|
||||
whenever reactions are undone (or days_back is widened) to force a
|
||||
fresh full backlog scan instead of silently skipping everything."""
|
||||
"""Wipe all per-channel resume points, forcing a full fresh backlog
|
||||
scan on the next run (widening days_back alone no longer requires
|
||||
this — see oldest_covered_at in scan_channel_backlog — but --undo
|
||||
still wants a clean slate since it also removes the reactions log)."""
|
||||
with self._lock, self._conn:
|
||||
cur = self._conn.execute("DELETE FROM channel_scan_state")
|
||||
return cur.rowcount
|
||||
@@ -203,19 +209,25 @@ class Storage:
|
||||
row = cur.fetchone()
|
||||
return dict(row) if row else None
|
||||
|
||||
def set_scan_state(self, channel_id, target_id, newest_id_seen, backlog_complete: bool) -> None:
|
||||
def set_scan_state(self, channel_id, target_id, newest_id_seen, oldest_covered_at, backlog_complete: bool) -> None:
|
||||
with self._lock, self._conn:
|
||||
self._conn.execute(
|
||||
"""
|
||||
INSERT INTO channel_scan_state
|
||||
(channel_id, target_id, newest_id_seen, backlog_complete, last_scanned_at)
|
||||
VALUES (?, ?, ?, ?, strftime('%Y-%m-%dT%H:%M:%fZ','now'))
|
||||
(channel_id, target_id, newest_id_seen, oldest_covered_at, backlog_complete, last_scanned_at)
|
||||
VALUES (?, ?, ?, ?, ?, strftime('%Y-%m-%dT%H:%M:%fZ','now'))
|
||||
ON CONFLICT(channel_id) DO UPDATE SET
|
||||
newest_id_seen = excluded.newest_id_seen,
|
||||
oldest_covered_at = excluded.oldest_covered_at,
|
||||
backlog_complete = excluded.backlog_complete,
|
||||
last_scanned_at = excluded.last_scanned_at
|
||||
""",
|
||||
(str(channel_id), str(target_id), str(newest_id_seen) if newest_id_seen else None, int(backlog_complete)),
|
||||
(
|
||||
str(channel_id), str(target_id),
|
||||
str(newest_id_seen) if newest_id_seen else None,
|
||||
oldest_covered_at.isoformat() if oldest_covered_at else None,
|
||||
int(backlog_complete),
|
||||
),
|
||||
)
|
||||
|
||||
def get_all_reactions(self) -> list:
|
||||
|
||||
Reference in New Issue
Block a user