Make Stop stop: no replay, no restart, immediate feedback
Build — Windows, Linux & macOS / GDPRScanner / linux (push) Has been cancelled
Build — Windows, Linux & macOS / GDPRScanner / macos (push) Has been cancelled
Build — Windows, Linux & macOS / GDPRScanner / windows (push) Has been cancelled
Build — Windows, Linux & macOS / Create GitHub Release (push) Has been cancelled

Pressing Stop could look like the scan restarting, and for scheduled jobs
it really did restart the Google stage.

- Scheduler: check the abort flag between stages and never clear the
  Google abort after a Stop (that started a fresh all-users Google scan).
  A stopped job is recorded as "cancelled" and skips email/retention;
  scheduler_done carries cancelled=true.
- SSE: scan_cancelled and file_scan_done now end the current scan like
  scan_done, and sse.replay_messages() never replays a finished scan.
  Previously a stopped M365 scan (and every file scan) stayed "current",
  so each reconnect replayed it and re-showed Stop + progress.
- Frontend: scan_cancelled clears the M365 running flag and only tears
  down the UI when no Google/file scan is still running; scheduler_done /
  scheduler_error clear all running flags.
- run_scan: Stop during collection now ends the scan (checkpoint, finish,
  scan_cancelled) instead of announcing scan_start / "Scanning…".
- Stop button: switches to a disabled "Stopping…" state on click; all
  show/hide goes through _setStopBtnDisplay(), which resets it when hidden
  or when a new scan starts.

Adds tests/test_stop_scan.py (10 tests).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit was merged in pull request #2.
This commit is contained in:
StyxX65andClaude Opus 5.5 committed 2026-09-28 10:03:12 +02:00
1 parent 9cf23da907
commit 606d8e944b
12 files changed
+372 -64

No files matched your search

+22 -17
View File
@@ -553,12 +553,6 @@ def run_scan(options: dict):
if rem < 3600: return f"{int(rem/60)}m"
return f"{int(rem/3600)}h"
def _check_abort():
if _state._scan_abort.is_set():
broadcast("scan_cancelled", {"completed": completed})
return True
return False
def _broadcast_card(item_meta: dict, cprs: list, pii_counts: dict | None = None):
card = {
"id": item_meta.get("id", ""),
@@ -1089,6 +1083,26 @@ def run_scan(options: dict):
if scanned_ids:
work_items = [(k, m, f) for k, m, f in work_items if m.get("id", "") not in scanned_ids]
def _finish_aborted(done: int) -> None:
"""Stop requested: checkpoint, finalise the DB scan, announce it."""
# Save checkpoint so scan can be resumed later
_save_checkpoint(ck_key, scanned_ids, _state.flagged_items, _state.scan_meta)
# Finalise the DB scan record so items found before the stop stay
# visible — the early return otherwise skips finish_scan below,
# stranding them (invisible to get_session_items / get_open_items).
if _db and _db_scan_id:
try:
_db.finish_scan(_db_scan_id, done)
except Exception as _e:
logger.error("[db] finish_scan (aborted) failed: %s", _e)
broadcast("scan_cancelled", {"completed": done - resumed_count})
# Stop pressed during collection: the per-user loops just break out, so
# stop here — announcing scan_start/"Scanning…" made Stop look like a restart.
if _state._scan_abort.is_set():
_finish_aborted(resumed_count)
return
total = len(work_items)
broadcast("scan_start", {
"total": total + resumed_count,
@@ -1107,17 +1121,8 @@ def run_scan(options: dict):
_items_since_save = 0
idx = -1
while _work_q:
if _check_abort():
# Save checkpoint so scan can be resumed later
_save_checkpoint(ck_key, scanned_ids, _state.flagged_items, _state.scan_meta)
# Finalise the DB scan record so items found before the stop stay
# visible — this early return otherwise skips finish_scan below,
# stranding them (invisible to get_session_items / get_open_items).
if _db and _db_scan_id:
try:
_db.finish_scan(_db_scan_id, resumed_count + idx + 1)
except Exception as _e:
logger.error("[db] finish_scan (aborted) failed: %s", _e)
if _state._scan_abort.is_set():
_finish_aborted(resumed_count + idx + 1)
return
idx += 1
kind, meta, _ = _work_q.popleft() # releases this item from the deque immediately