local_store.py 3.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
  1. import json
  2. import logging
  3. import os
  4. import time
  5. import threading
  6. from typing import Any, Optional
  7. from app.config import settings
  8. from app.repository.record_interface import RecordInterface
  9. from app.repository.task_record import TaskRecord
  10. logger = logging.getLogger(__name__)
  11. class LocalRecordStore(RecordInterface):
  12. def __init__(self):
  13. self._storage: dict[str, TaskRecord] = {}
  14. self._lock = threading.Lock()
  15. self._data_dir = settings.task_store_data_dir
  16. os.makedirs(self._data_dir, exist_ok=True)
  17. self._load_from_file()
  18. def _load_from_file(self):
  19. filepath = os.path.join(self._data_dir, "tasks.json")
  20. if os.path.isfile(filepath) and os.path.getsize(filepath) > 0:
  21. try:
  22. with open(filepath, "r", encoding="utf-8") as f:
  23. data = json.load(f)
  24. for tid, rec in data.items():
  25. self._storage[tid] = TaskRecord.from_dict(rec)
  26. logger.info("Loaded %d tasks from file system", len(data))
  27. except Exception as e:
  28. logger.error("Failed to load tasks from file: %s", e)
  29. def _save_to_file(self):
  30. try:
  31. tmp = os.path.join(self._data_dir, "tasks.tmp")
  32. filepath = os.path.join(self._data_dir, "tasks.json")
  33. with open(tmp, "w", encoding="utf-8") as f:
  34. json.dump(
  35. {tid: r.to_dict() for tid, r in self._storage.items()},
  36. f,
  37. indent=2,
  38. ensure_ascii=False,
  39. )
  40. os.replace(tmp, filepath)
  41. except Exception as e:
  42. logger.error("Failed to save tasks to file: %s", e)
  43. def _on_save(self, record: TaskRecord):
  44. with self._lock:
  45. self._storage[record.task_id] = record
  46. self._save_to_file()
  47. def _on_get(self, task_id: str) -> Optional[TaskRecord]:
  48. return self._storage.get(task_id)
  49. def _on_get_all(self) -> dict[str, TaskRecord]:
  50. return dict(self._storage)
  51. def _on_delete(self, max_age_hours: int):
  52. cutoff = int(time.time() * 1000) - max_age_hours * 3600 * 1000
  53. with self._lock:
  54. to_delete = [
  55. tid
  56. for tid, r in self._storage.items()
  57. if r.status in ("successes", "failed") and r.update_time < cutoff
  58. ]
  59. for tid in to_delete:
  60. del self._storage[tid]
  61. self._save_to_file()
  62. def _on_update_status(
  63. self, task_id: str, status: str, message: str, data: Optional[dict]
  64. ):
  65. try:
  66. record = self._on_get(task_id)
  67. if record:
  68. record.status = status
  69. record.message = message
  70. record.touch()
  71. if data is not None:
  72. record.data = data
  73. self._on_save(record)
  74. except Exception as e:
  75. logger.error("Failed to update task status: %s", e)