mirror of
https://github.com/basicmachines-co/basic-memory
synced 2026-06-21 13:47:35 +00:00
finish watch_service.py
This commit is contained in:
@@ -2,14 +2,14 @@
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
from typing import Dict, Sequence
|
||||
from typing import Dict, Sequence, Optional
|
||||
|
||||
from loguru import logger
|
||||
|
||||
from basic_memory.file_utils import compute_checksum
|
||||
from basic_memory.models import Entity
|
||||
from basic_memory.repository.entity_repository import EntityRepository
|
||||
from basic_memory.sync.utils import SyncReport
|
||||
from basic_memory.sync.utils import SyncReport, FileChange
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -87,13 +87,19 @@ class FileChangeScanner:
|
||||
return result
|
||||
|
||||
async def find_changes(
|
||||
self, directory: Path, db_file_state: Dict[str, FileState]
|
||||
self,
|
||||
db_file_state: Dict[str, FileState],
|
||||
directory: Optional[Path] = None,
|
||||
) -> SyncReport:
|
||||
"""Find changes between filesystem and database."""
|
||||
# Get current files and checksums
|
||||
scan_result = await self.scan_directory(directory)
|
||||
current_files = scan_result.files
|
||||
|
||||
# scan the directory provided
|
||||
scan_result = await self.scan_directory(directory)
|
||||
|
||||
# the set of all of the current files and their checksums
|
||||
current_files = scan_result.files
|
||||
|
||||
# Build report
|
||||
report = SyncReport()
|
||||
|
||||
@@ -157,5 +163,6 @@ class FileChangeScanner:
|
||||
|
||||
async def find_knowledge_changes(self, directory: Path) -> SyncReport:
|
||||
"""Find changes in knowledge directory."""
|
||||
|
||||
db_file_state = await self.get_db_file_state(await self.entity_repository.find_all())
|
||||
return await self.find_changes(directory=directory, db_file_state=db_file_state)
|
||||
|
||||
@@ -61,28 +61,10 @@ class SyncService:
|
||||
else:
|
||||
logger.debug(f"No entity found to delete: {file_path}")
|
||||
|
||||
async def sync(self, directory: Optional[Path] = None, file_changes: Optional[dict[str, FileChange]] = None) -> SyncReport:
|
||||
"""Sync knowledge files with database."""
|
||||
if file_changes is not None:
|
||||
changes = SyncReport()
|
||||
for path, file_change in file_changes.items():
|
||||
logger.debug(f"path {path} file_change {file_change}")
|
||||
match file_change.change_type:
|
||||
case Change.added:
|
||||
changes.new.add(path)
|
||||
changes.checksums[path] = file_change.checksum
|
||||
case Change.modified:
|
||||
changes.modified.add(path)
|
||||
changes.checksums[path] = file_change.checksum
|
||||
case Change.deleted:
|
||||
changes.deleted.add(path)
|
||||
else:
|
||||
# Traditional directory scan mode
|
||||
if directory is None:
|
||||
raise ValueError("Must provide either directory or file_changes")
|
||||
|
||||
changes = await self.scanner.find_knowledge_changes(directory)
|
||||
logger.info(f"Found {changes.total_changes} knowledge changes")
|
||||
async def sync(self, directory: Path) -> SyncReport:
|
||||
"""Sync knowledge files with database."""
|
||||
changes = await self.scanner.find_knowledge_changes(directory)
|
||||
logger.info(f"Found {changes.total_changes} knowledge changes")
|
||||
|
||||
# Handle moves first
|
||||
for old_path, new_path in changes.moves.items():
|
||||
|
||||
@@ -55,5 +55,10 @@ class SyncReport:
|
||||
|
||||
@property
|
||||
def total_changes(self) -> int:
|
||||
"""Total number of files that need attention."""
|
||||
"""Total number of changes."""
|
||||
return len(self.new) + len(self.modified) + len(self.deleted) + len(self.moves)
|
||||
|
||||
@property
|
||||
def total_files(self) -> int:
|
||||
"""Total number of files synced."""
|
||||
return len(self.new) + len(self.modified) + len(self.moves)
|
||||
|
||||
@@ -20,7 +20,7 @@ from basic_memory.sync.utils import FileChange
|
||||
class WatchEvent(BaseModel):
|
||||
timestamp: datetime
|
||||
path: str
|
||||
action: str # sync, delete, etc
|
||||
action: str # new, delete, etc
|
||||
status: str # success, error
|
||||
error: Optional[str] = None
|
||||
|
||||
@@ -32,8 +32,6 @@ class WatchServiceState(BaseModel):
|
||||
pid: int = dataclasses.field(default_factory=os.getpid)
|
||||
|
||||
# Stats
|
||||
files_synced: int = 0
|
||||
bytes_processed: int = 0
|
||||
error_count: int = 0
|
||||
last_error: Optional[datetime] = None
|
||||
last_scan: Optional[datetime] = None
|
||||
@@ -54,6 +52,7 @@ class WatchServiceState(BaseModel):
|
||||
|
||||
def record_error(self, error: str):
|
||||
self.error_count += 1
|
||||
self.add_event(path="", action="sync", status="error", error=error)
|
||||
self.last_error = datetime.now()
|
||||
|
||||
|
||||
@@ -79,7 +78,7 @@ class WatchService:
|
||||
debounce=self.config.sync_delay,
|
||||
recursive=True,
|
||||
):
|
||||
await self.handle_changes(changes)
|
||||
await self.handle_changes(self.config.home)
|
||||
|
||||
except Exception as e:
|
||||
self.state.record_error(str(e))
|
||||
@@ -97,35 +96,28 @@ class WatchService:
|
||||
"""Filter to only watch markdown files"""
|
||||
return path.endswith(".md") and not Path(path).name.startswith(".")
|
||||
|
||||
async def handle_changes(self, changes: set[tuple[Change, str]]):
|
||||
async def handle_changes(self, directory: Path):
|
||||
"""Process a batch of file changes"""
|
||||
|
||||
# Group changes by file path
|
||||
changes_by_file = {}
|
||||
try:
|
||||
for change_type, path in changes:
|
||||
file_change = await FileChange.from_path(path, change_type, self.file_service)
|
||||
|
||||
# store changes by relative path
|
||||
changes_by_file[str(file_change.path)] = file_change
|
||||
|
||||
# Process changes with timeout
|
||||
await self.sync_service.sync(file_changes=changes_by_file)
|
||||
report = await self.sync_service.sync(directory)
|
||||
self.state.last_scan = datetime.now()
|
||||
self.state.total_files = report.total_files
|
||||
|
||||
# Update stats
|
||||
self.state.files_synced += len(changes_by_file)
|
||||
for path, change in changes_by_file.items():
|
||||
if change.change_type != Change.deleted:
|
||||
size = self.file_service.path(path,absolute=True).stat().st_size
|
||||
self.state.bytes_processed += size
|
||||
|
||||
self.state.add_event(path=path, action="sync", status="success")
|
||||
for path in report.new:
|
||||
self.state.add_event(path=path, action="new", status="success")
|
||||
for path in report.modified:
|
||||
self.state.add_event(path=path, action="modified", status="success")
|
||||
for path in report.moves:
|
||||
self.state.add_event(path=path, action="moved", status="success")
|
||||
for path in report.deleted:
|
||||
self.state.add_event(path=path, action="deleted", status="success")
|
||||
|
||||
await self.write_status()
|
||||
|
||||
except Exception as e:
|
||||
self.state.record_error(str(e))
|
||||
for path in changes_by_file:
|
||||
self.state.add_event(path=path, action="sync", status="error", error=str(e))
|
||||
await self.write_status()
|
||||
raise
|
||||
|
||||
Reference in New Issue
Block a user