|
|
@@ -9,6 +9,7 @@ from __future__ import annotations
|
|
|
|
|
|
import json
|
|
|
import threading
|
|
|
+import time
|
|
|
import tkinter as tk
|
|
|
from tkinter import messagebox
|
|
|
from pathlib import Path
|
|
|
@@ -114,6 +115,12 @@ class ProtectorGUI(tk.Tk):
|
|
|
self.countdown_label = tk.Label(self, textvariable=self.countdown_var)
|
|
|
self.countdown_label.pack(anchor="ne", padx=8, pady=4)
|
|
|
|
|
|
+ # Pause/Resume button
|
|
|
+ self.pause_btn = None
|
|
|
+ if self.protector_runner is not None:
|
|
|
+ self.pause_btn = tk.Button(self, text="Pause", command=self._toggle_pause)
|
|
|
+ self.pause_btn.pack(anchor="ne", padx=8)
|
|
|
+
|
|
|
# start initial delayed run countdown (10 seconds) if runner provided
|
|
|
if self.protector_runner is not None:
|
|
|
self._initial_delay = 10
|
|
|
@@ -247,24 +254,70 @@ class ProtectorGUI(tk.Tk):
|
|
|
if not self.protector_runner:
|
|
|
return
|
|
|
# Update label
|
|
|
+ # compute seconds until next run based on protector_runner's schedule
|
|
|
try:
|
|
|
- self.countdown_var.set(f"下一次检测:{self._countdown_seconds}s")
|
|
|
+ if self.protector_runner.is_paused():
|
|
|
+ self.countdown_var.set("Protector: paused")
|
|
|
+ else:
|
|
|
+ next_ts = self.protector_runner.get_next_run_time()
|
|
|
+ if next_ts is None:
|
|
|
+ # fallback to internal counter
|
|
|
+ secs = max(0, int(self._countdown_seconds))
|
|
|
+ else:
|
|
|
+ secs = max(0, int(round(next_ts - time.time())))
|
|
|
+ self.countdown_var.set(f"下一次检测:{secs}s")
|
|
|
except Exception:
|
|
|
self.countdown_var.set("Protector: running")
|
|
|
|
|
|
- if self._countdown_seconds <= 0:
|
|
|
- # trigger a run in background
|
|
|
+ # if protector hasn't been started yet, use internal countdown
|
|
|
+ try:
|
|
|
+ started = getattr(self.protector_runner, "_started", False)
|
|
|
+ except Exception:
|
|
|
+ started = False
|
|
|
+
|
|
|
+ if not started:
|
|
|
+ # use internal countdown to start
|
|
|
+ if self._countdown_seconds <= 0:
|
|
|
+ do_start = True
|
|
|
+ else:
|
|
|
+ do_start = False
|
|
|
+ else:
|
|
|
+ # protector already started; rely on its internal schedule
|
|
|
+ do_start = False
|
|
|
+
|
|
|
+ if do_start:
|
|
|
+ # If protector runner not started yet, start its loop first
|
|
|
try:
|
|
|
- threading.Thread(target=self.protector_runner.run_once, daemon=True).start()
|
|
|
- except Exception as e:
|
|
|
- print("触发 protector 运行失败:", e)
|
|
|
+ if not getattr(self.protector_runner, "_started", False):
|
|
|
+ # start the background loop
|
|
|
+ self.protector_runner._started = True
|
|
|
+ threading.Thread(target=self.protector_runner.start, daemon=True).start()
|
|
|
+ except Exception:
|
|
|
+ pass
|
|
|
+
|
|
|
+ # ProtectorRunner.start() will perform an initial run, so do not
|
|
|
+ # explicitly call run_once() here to avoid double execution.
|
|
|
+ # reset internal countdown to interval after start
|
|
|
+ try:
|
|
|
+ self._countdown_seconds = int(self.protector_runner.get_interval())
|
|
|
+ except Exception:
|
|
|
+ self._countdown_seconds = self._initial_delay
|
|
|
# reset countdown to configured interval
|
|
|
try:
|
|
|
self._countdown_seconds = int(self.protector_runner.get_interval())
|
|
|
except Exception:
|
|
|
self._countdown_seconds = self._initial_delay
|
|
|
+ # update pause button text
|
|
|
+ try:
|
|
|
+ if self.pause_btn:
|
|
|
+ self.pause_btn.config(text=("Resume" if self.protector_runner.is_paused() else "Pause"))
|
|
|
+ except Exception:
|
|
|
+ pass
|
|
|
else:
|
|
|
- self._countdown_seconds -= 1
|
|
|
+ # when protector already started, we won't decrement internal counter
|
|
|
+ # keep internal counter but keep ticking for display purposes
|
|
|
+ if not started:
|
|
|
+ self._countdown_seconds -= 1
|
|
|
|
|
|
except Exception as e:
|
|
|
# keep ticking even on error
|
|
|
@@ -276,6 +329,21 @@ class ProtectorGUI(tk.Tk):
|
|
|
except Exception:
|
|
|
pass
|
|
|
|
|
|
+ def _toggle_pause(self):
|
|
|
+ if not self.protector_runner:
|
|
|
+ return
|
|
|
+ try:
|
|
|
+ if self.protector_runner.is_paused():
|
|
|
+ self.protector_runner.resume()
|
|
|
+ if self.pause_btn:
|
|
|
+ self.pause_btn.config(text="Pause")
|
|
|
+ else:
|
|
|
+ self.protector_runner.pause()
|
|
|
+ if self.pause_btn:
|
|
|
+ self.pause_btn.config(text="Resume")
|
|
|
+ except Exception as e:
|
|
|
+ messagebox.showerror("错误", f"切换暂停/恢复失败: {e}")
|
|
|
+
|
|
|
def run_gui(protector_runner=None):
|
|
|
app = ProtectorGUI(protector_runner=protector_runner)
|
|
|
app.mainloop()
|