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:
@@ -24,7 +24,7 @@ async def scan_channel_backlog(
|
||||
react_fn: ReactFn,
|
||||
display: Optional[StatsDisplay] = None,
|
||||
) -> None:
|
||||
"""Scan a channel's history once, resumably, in two phases.
|
||||
"""Scan a channel's history, resumably, in two phases.
|
||||
|
||||
Phase 1 finds every message from the friend in the window first (so the
|
||||
"found" count settles before any reacting starts, rather than climbing
|
||||
@@ -38,6 +38,13 @@ async def scan_channel_backlog(
|
||||
up again — this is independent of the newest_id_seen checkpoint below,
|
||||
so nothing found is ever silently abandoned even if the checkpoint has
|
||||
already moved past it.
|
||||
|
||||
The channel's resume state tracks not just newest_id_seen (for the
|
||||
forward diff) but also oldest_covered_at — the earliest point in time
|
||||
already fully scanned. If days_back has been widened since the last run
|
||||
(cutoff is now older than oldest_covered_at), the newly-exposed gap
|
||||
[cutoff, oldest_covered_at) is backfilled first — otherwise the forward-
|
||||
only diff would silently never look back far enough to notice.
|
||||
"""
|
||||
channel_id = getattr(channel, "id", None)
|
||||
if channel_id is None:
|
||||
@@ -59,13 +66,16 @@ async def scan_channel_backlog(
|
||||
|
||||
state = await asyncio.to_thread(storage.get_scan_state, channel_id)
|
||||
newest_id_seen = int(state["newest_id_seen"]) if state and state.get("newest_id_seen") else None
|
||||
after = discord.Object(id=newest_id_seen) if newest_id_seen else cutoff
|
||||
oldest_covered_at = (
|
||||
datetime.datetime.fromisoformat(state["oldest_covered_at"])
|
||||
if state and state.get("oldest_covered_at") else None
|
||||
)
|
||||
|
||||
if display is not None:
|
||||
display.set_status(f"Fetching message history for {chan_label}...")
|
||||
|
||||
try:
|
||||
async for message in channel.history(after=after, oldest_first=True, limit=None):
|
||||
async def scan_range(after, before, status_verb: str) -> None:
|
||||
nonlocal newest_id_seen
|
||||
if display is not None:
|
||||
display.set_status(f"{status_verb} {chan_label}...")
|
||||
async for message in channel.history(after=after, before=before, oldest_first=True, limit=None):
|
||||
if newest_id_seen is None or message.id > newest_id_seen:
|
||||
newest_id_seen = message.id
|
||||
|
||||
@@ -75,10 +85,27 @@ async def scan_channel_backlog(
|
||||
storage.record_message_found, message.id, channel_id, target.target_id, friend_id
|
||||
)
|
||||
if display is not None:
|
||||
display.set_status(f"Scanning {chan_label} — {len(found_messages)} found so far...")
|
||||
display.set_status(f"{status_verb} {chan_label} — {len(found_messages)} found so far...")
|
||||
await display.refresh()
|
||||
|
||||
await asyncio.to_thread(storage.set_scan_state, channel_id, target.target_id, newest_id_seen, True)
|
||||
try:
|
||||
if oldest_covered_at is None:
|
||||
# Never scanned before: one pass across the whole configured window.
|
||||
await scan_range(cutoff, None, "Fetching message history for")
|
||||
oldest_covered_at = cutoff
|
||||
else:
|
||||
if cutoff < oldest_covered_at:
|
||||
# days_back was widened since the last run — the forward-only
|
||||
# diff below would never notice the newly-exposed older range,
|
||||
# so backfill exactly that gap first.
|
||||
await scan_range(cutoff, oldest_covered_at, "Backfilling older history in")
|
||||
oldest_covered_at = cutoff
|
||||
after = discord.Object(id=newest_id_seen) if newest_id_seen else cutoff
|
||||
await scan_range(after, None, "Fetching new messages in")
|
||||
|
||||
await asyncio.to_thread(
|
||||
storage.set_scan_state, channel_id, target.target_id, newest_id_seen, oldest_covered_at, True
|
||||
)
|
||||
except discord.Forbidden:
|
||||
log.warning("No access to channel %s (%s), skipping", channel_id, getattr(channel, "name", ""))
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user