diff --git a/src/basic_memory/cli/commands/sync.py b/src/basic_memory/cli/commands/sync.py index 801148cc..3b2a35cf 100644 --- a/src/basic_memory/cli/commands/sync.py +++ b/src/basic_memory/cli/commands/sync.py @@ -209,7 +209,6 @@ async def run_sync(verbose: bool = False, watch: bool = False): # Start watching if requested if watch: - console.print("\n[cyan]Starting watch service...[/cyan]") watch_service = WatchService( sync_service=sync_service, file_service=sync_service.entity_service.file_service, diff --git a/src/basic_memory/sync/utils.py b/src/basic_memory/sync/utils.py index e0d5f94c..eedf13aa 100644 --- a/src/basic_memory/sync/utils.py +++ b/src/basic_memory/sync/utils.py @@ -53,7 +53,7 @@ class SyncReport: modified: Set[str] = field(default_factory=set) deleted: Set[str] = field(default_factory=set) moves: Dict[str, str] = field(default_factory=dict) # old_path -> new_path - checksums: Dict[str, str] = field(default_factory=dict) + checksums: Dict[str, str] = field(default_factory=dict) # path -> checksum @property def total_changes(self) -> int: diff --git a/src/basic_memory/sync/watch_service.py b/src/basic_memory/sync/watch_service.py index 09b6d294..fd84c741 100644 --- a/src/basic_memory/sync/watch_service.py +++ b/src/basic_memory/sync/watch_service.py @@ -1,21 +1,22 @@ """Watch service for Basic Memory.""" -import json import dataclasses from loguru import logger from pydantic import BaseModel -from pydantic.dataclasses import dataclass from datetime import datetime from pathlib import Path from typing import List, Optional + +from rich.console import Console from watchfiles import awatch, Change import os from basic_memory.config import ProjectConfig from basic_memory.sync.sync_service import SyncService from basic_memory.services.file_service import FileService -from basic_memory.sync.utils import FileChange + +console = Console() class WatchEvent(BaseModel): @@ -23,6 +24,7 @@ class WatchEvent(BaseModel): path: str action: str # new, delete, etc status: str # success, error + checksum: Optional[str] error: Optional[str] = None @@ -43,12 +45,25 @@ class WatchServiceState(BaseModel): # Recent activity recent_events: List[WatchEvent] = dataclasses.field(default_factory=list) - def add_event(self, path: str, action: str, status: str, error: Optional[str] = None): + def add_event( + self, + path: str, + action: str, + status: str, + checksum: Optional[str] = None, + error: Optional[str] = None, + ) -> WatchEvent: event = WatchEvent( - timestamp=datetime.now(), path=path, action=action, status=status, error=error + timestamp=datetime.now(), + path=path, + action=action, + status=status, + checksum=checksum, + error=error, ) self.recent_events.insert(0, event) self.recent_events = self.recent_events[:100] # Keep last 100 + return event def record_error(self, error: str): self.error_count += 1 @@ -71,6 +86,7 @@ class WatchService: self.state.start_time = datetime.now() await self.write_status() + console.print("\n[cyan]Watching for changes...[/cyan]") try: async for changes in awatch( self.config.home, @@ -100,26 +116,39 @@ class WatchService: async def handle_changes(self, directory: Path): """Process a batch of file changes""" - try: - logger.debug(f"handling change in directory: {directory} ...") - # Process changes with timeout - report = await self.sync_service.sync(directory) - self.state.last_scan = datetime.now() - self.state.synced_files = report.total + logger.debug(f"handling change in directory: {directory} ...") + # Process changes with timeout + report = await self.sync_service.sync(directory) + self.state.last_scan = datetime.now() + self.state.synced_files = report.total - # Update stats - 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") + # Update stats + for path in report.new: + event = self.state.add_event( + path=path, action="new", status="success", checksum=report.checksums[path] + ) + console.print( + f"{event.timestamp.isoformat(timespec='minutes')} New:\t\t [green]{path}[/green] ({event.checksum[:8]})" + ) + for path in report.modified: + event = self.state.add_event( + path=path, action="modified", status="success", checksum=report.checksums[path] + ) + console.print( + f"{event.timestamp.isoformat(timespec='minutes')} Modified:\t [yellow]{path}[/yellow] ({event.checksum[:8]})" + ) + for old_path, new_path in report.moves.items(): + event = self.state.add_event( + path=f"{old_path} -> {new_path}", + action="moved", + status="success", + checksum=report.checksums[new_path], + ) + console.print( + f"{event.timestamp.isoformat(timespec='minutes')} Moved:\t\t [blue]{old_path} -> {new_path}[/blue] ({event.checksum[:8]})" + ) + for path in report.deleted: + event = self.state.add_event(path=path, action="deleted", status="success") + console.print(f"{event.timestamp.isoformat(timespec='minutes')} Deleted:\t [red]{path}[/red]") - await self.write_status() - - except Exception as e: - self.state.record_error(str(e)) - await self.write_status() - raise + await self.write_status()