diff --git a/application/workflows/co4e_workflow_service.py b/application/workflows/co4e_workflow_service.py index 549c18e..3b2d3cb 100644 --- a/application/workflows/co4e_workflow_service.py +++ b/application/workflows/co4e_workflow_service.py @@ -35,6 +35,8 @@ service này. """ from __future__ import annotations +from ...infrastructure.persistence.json.atomic_json_file import AtomicJsonFile + import json import os from datetime import datetime @@ -174,10 +176,10 @@ class Co4EWorkflowService: payload = {"runs": [r.to_dict() for r in runs]} try: self._history_path_value.parent.mkdir(parents=True, exist_ok=True) - tmp = self._history_path_value.with_suffix(".json.tmp") - tmp.write_text(json.dumps(payload, ensure_ascii=False, indent=2), - encoding="utf-8") - tmp.replace(self._history_path_value) # atomic — khong bao gio de lai file ghi do dang + # AtomicJsonFile thay cho tmp+replace tự viết: bản cũ thiếu fsync + # (dữ liệu có thể còn trong bộ đệm khi mất điện) và dùng thẳng + # Path.replace, vốn thỉnh thoảng bị Defender từ chối trên Windows. + AtomicJsonFile(self._history_path_value).write(payload) except OSError: # Giu dung hanh vi cu (core/co4e_run_manager.py::_save_history): # mot lan luu that bai (day dia, mat quyen...) KHONG duoc phep diff --git a/core/co4e.py b/core/co4e.py index e0337c4..46e3682 100644 --- a/core/co4e.py +++ b/core/co4e.py @@ -18,6 +18,8 @@ existing ``core/skills.py`` registry. """ from __future__ import annotations +from ..infrastructure.persistence.json.atomic_json_file import AtomicJsonFile + import json from dataclasses import asdict, dataclass, field from pathlib import Path @@ -233,7 +235,9 @@ def save_workflow(wf: Workflow, directory: Optional[Path] = None) -> Path: directory = directory or WORKFLOWS_DIR directory.mkdir(parents=True, exist_ok=True) path = directory / f"{wf.id}.json" - path.write_text(json.dumps(workflow_to_dict(wf), ensure_ascii=False, indent=2), encoding="utf-8") + # Tiêu chí nghiệm thu A: mọi thao tác ghi tệp đi qua AtomicJsonFile. Trước + # đây ghi thẳng, nên tắt máy giữa lúc lưu là mất luôn workflow. + AtomicJsonFile(path).write(workflow_to_dict(wf)) return path @@ -307,7 +311,7 @@ def save_custom_agent(agent: CustomAgent, directory: Optional[Path] = None) -> P directory = directory or AGENTS_DIR directory.mkdir(parents=True, exist_ok=True) path = directory / f"{agent.id}.json" - path.write_text(json.dumps(agent_to_dict(agent), ensure_ascii=False, indent=2), encoding="utf-8") + AtomicJsonFile(path).write(agent_to_dict(agent)) return path diff --git a/core/co4e_run_manager.py b/core/co4e_run_manager.py index c92662f..1695401 100644 --- a/core/co4e_run_manager.py +++ b/core/co4e_run_manager.py @@ -13,6 +13,8 @@ the run that is currently open. """ from __future__ import annotations +from ..infrastructure.persistence.json.atomic_json_file import AtomicJsonFile + import json from pathlib import Path from typing import Dict, List, Optional @@ -152,10 +154,10 @@ class Co4ERunManager(QObject): payload = {"runs": [h.to_record() for h in runs]} try: path.parent.mkdir(parents=True, exist_ok=True) - tmp = path.with_suffix(".json.tmp") - tmp.write_text(json.dumps(payload, ensure_ascii=False, indent=2), - encoding="utf-8") - tmp.replace(path) # atomic — never leaves a half-written file + # AtomicJsonFile thay cho tmp+replace tự viết: bản cũ thiếu fsync + # (dữ liệu có thể còn trong bộ đệm khi mất điện) và dùng thẳng + # Path.replace, vốn thỉnh thoảng bị Defender từ chối trên Windows. + AtomicJsonFile(path).write(payload) except OSError: pass