feat(collect): show per-item collection activity timer

This commit is contained in:
chengma
2026-07-14 11:10:36 +08:00
parent da1da13fdf
commit 0abd7ffdff
4 changed files with 494 additions and 9 deletions
+153 -6
View File
@@ -65,6 +65,7 @@ from app.gui import (
)
from app.gui import file_manager
import app.gui.workers as gui_workers
from app.gui.tabs.collect import _format_collect_elapsed
from app.gui.tabs.generate import CoverGalleryDialog, OriginalImageDialog
from app.gui.main_window import _fit_and_center_window
@@ -7391,6 +7392,130 @@ class GuiTests(TempDirMixin, unittest.TestCase):
self.assert_removed(temp_dir)
def test_collect_activity_tracks_step_resets_each_task_and_freezes_on_stop(self):
with self.make_temp_dir() as temp_dir:
tab = CollectTab(config=self.make_config(temp_dir))
self.addCleanup(tab.close)
self.assertEqual("collectActivityLabel", tab.collect_activity_label.objectName())
self.assertTrue(tab.collect_activity_label.isHidden())
self.assertEqual("1:01:01", _format_collect_elapsed(3661))
with mock.patch("app.gui.tabs.collect.time.monotonic", return_value=100.0):
tab._start_collect_activity()
self.assertTrue(tab._collect_elapsed_timer.isActive())
self.assertIn("正在检查账号", tab.collect_activity_label.text())
self.assertIn("00:00", tab.collect_activity_label.text())
with mock.patch("app.gui.tabs.collect.time.monotonic", return_value=105.0):
tab._on_collect_activity(
{
"state": "preflight_started",
"total": 2,
"step": "preflight",
}
)
self.assertIn("正在检查账号", tab.collect_activity_label.text())
self.assertIn("00:05", tab.collect_activity_label.text())
with mock.patch("app.gui.tabs.collect.time.monotonic", return_value=110.0):
tab._on_collect_activity(
{
"state": "task_started",
"index": 1,
"total": 2,
"task_id": 1,
"item_id": "51100639510",
"alias": "alias-a",
"step": "match_account",
}
)
self.assertEqual(
"正在采集 1/2 · 匹配账号 · 本条 00:00",
tab.collect_activity_label.text(),
)
with mock.patch("app.gui.tabs.collect.time.monotonic", return_value=117.0):
tab._on_collect_activity(
{
"state": "task_step",
"index": 1,
"total": 2,
"task_id": 1,
"item_id": "51100639510",
"alias": "alias-a",
"step": "wait_ready",
}
)
self.assertEqual(
"正在采集 1/2 · 等待商品页加载 · 本条 00:07",
tab.collect_activity_label.text(),
)
self.assertIn("商品ID:51100639510", tab.collect_activity_label.toolTip())
self.assertIn("当前阶段:等待商品页加载", tab.collect_activity_label.toolTip())
with mock.patch("app.gui.tabs.collect.time.monotonic", return_value=120.0):
tab._on_collect_activity(
{
"state": "task_started",
"index": 2,
"total": 2,
"task_id": 2,
"item_id": "51100639511",
"alias": "alias-a",
"step": "check_login",
}
)
self.assertEqual(
"正在采集 2/2 · 检查账号登录 · 本条 00:00",
tab.collect_activity_label.text(),
)
with mock.patch("app.gui.tabs.collect.time.monotonic", return_value=125.0):
tab._collect_stop_requested = True
tab._refresh_collect_activity()
self.assertEqual("正在停止 · 本条 00:05", tab.collect_activity_label.text())
with mock.patch("app.gui.tabs.collect.time.monotonic", return_value=130.0):
tab._finish_collect_activity("cancelled")
self.assertEqual("采集已停止 · 总用时 00:30", tab.collect_activity_label.text())
self.assertFalse(tab._collect_elapsed_timer.isActive())
frozen_text = tab.collect_activity_label.text()
with mock.patch("app.gui.tabs.collect.time.monotonic", return_value=999.0):
tab._refresh_collect_activity()
self.assertEqual(frozen_text, tab.collect_activity_label.text())
self.assert_removed(temp_dir)
def test_collect_activity_terminal_states_use_chinese_semantics(self):
with self.make_temp_dir() as temp_dir:
tab = CollectTab(config=self.make_config(temp_dir))
self.addCleanup(tab.close)
with mock.patch("app.gui.tabs.collect.time.monotonic", return_value=10.0):
tab._start_collect_activity()
with mock.patch("app.gui.tabs.collect.time.monotonic", return_value=15.0):
tab._finish_collect_activity("blocked")
self.assertEqual("采集未开始 · 检查未通过", tab.collect_activity_label.text())
self.assertFalse(tab._collect_elapsed_timer.isActive())
with mock.patch("app.gui.tabs.collect.time.monotonic", return_value=20.0):
tab._start_collect_activity()
with mock.patch("app.gui.tabs.collect.time.monotonic", return_value=25.0):
tab._finish_collect_activity("error", {"error": "后台线程异常"})
self.assertEqual("采集已结束 · 请查看运行日志", tab.collect_activity_label.text())
self.assertEqual("采集异常结束,请查看下方采集运行日志", tab.collect_activity_label.toolTip())
with mock.patch("app.gui.tabs.collect.time.monotonic", return_value=30.0):
tab._start_collect_activity()
with mock.patch("app.gui.tabs.collect.time.monotonic", return_value=40.0):
tab._finish_collect_activity("finished")
self.assertEqual("采集完成 · 总用时 00:10", tab.collect_activity_label.text())
self.assertFalse(tab._collect_elapsed_timer.isActive())
self.assert_removed(temp_dir)
def test_collect_tab_soft_deletes_batch_and_refreshes_workflow_tabs(self):
with self.make_temp_dir() as temp_dir:
cfg = self.make_config(temp_dir)
@@ -7706,14 +7831,17 @@ class GuiTests(TempDirMixin, unittest.TestCase):
"old_cover_path": task["old_cover_path"],
}
worker = CollectWorker(
tasks,
db_path=cfg["db_path"],
config=cfg,
preflight=False,
)
activity_events = []
worker.activity.connect(activity_events.append)
with mock.patch("app.gui.accounts.detect_login", side_effect=fake_login), \
mock.patch("app.gui.editor.collect", side_effect=fake_collect) as collect:
summary = CollectWorker(
tasks,
db_path=cfg["db_path"],
config=cfg,
preflight=False,
).execute()
summary = worker.execute()
self.assertTrue(summary["ok"])
self.assertEqual(3, summary["total"])
@@ -7728,6 +7856,25 @@ class GuiTests(TempDirMixin, unittest.TestCase):
self.assertEqual([batch_id], summary["batch_ids"])
self.assertIsInstance(summary["run_id"], int)
collect.assert_called_once()
self.assertEqual("preflight_started", activity_events[0]["state"])
self.assertEqual(3, activity_events[0]["total"])
task_starts = [event for event in activity_events if event["state"] == "task_started"]
self.assertEqual([1, 2, 3], [event["index"] for event in task_starts])
self.assertEqual(
["51100639510", "51100639511", "51100639512"],
[event["item_id"] for event in task_starts],
)
self.assertTrue(
any(
event["state"] == "task_step" and event.get("step") == "download_cover"
for event in activity_events
)
)
task_finishes = [event for event in activity_events if event["state"] == "task_finished"]
self.assertEqual(
["success", "skipped", "skipped"],
[event["result"] for event in task_finishes],
)
updated = db.list_tasks(batch_id=batch_id, path=cfg["db_path"])
by_alias = {task.alias: task for task in updated}
self.assertEqual("collected", by_alias["alias-a"].stage)