Files
2025-07-16 11:53:31 -05:00

120 lines
4.4 KiB
Python

"""History management for file edits with disk-based storage and memory constraints."""
import logging
import tempfile
from pathlib import Path
from typing import List, Optional
from .file_cache import FileCache
class FileHistoryManager:
"""Manages file edit history with disk-based storage and memory constraints."""
def __init__(
self, max_history_per_file: int = 5, history_dir: Optional[Path] = None
):
"""Initialize the history manager.
Args:
max_history_per_file: Maximum number of history entries to keep per file (default: 5)
history_dir: Directory to store history files. If None, uses a temp directory
Notes:
- Each file's history is limited to the last N entries to conserve memory
- The file cache is limited to prevent excessive disk usage
- Older entries are automatically removed when limits are exceeded
"""
self.max_history_per_file = max_history_per_file
if history_dir is None:
history_dir = Path(tempfile.mkdtemp(prefix='oh_editor_history_'))
self.cache = FileCache(str(history_dir))
self.logger = logging.getLogger(__name__)
def _get_metadata_key(self, file_path: Path) -> str:
return f'{file_path}.metadata'
def _get_history_key(self, file_path: Path, counter: int) -> str:
return f'{file_path}.{counter}'
def add_history(self, file_path: Path, content: str):
"""Add a new history entry for a file."""
metadata_key = self._get_metadata_key(file_path)
metadata = self.cache.get(metadata_key, {'entries': [], 'counter': 0})
counter = metadata['counter']
# Add new entry
history_key = self._get_history_key(file_path, counter)
self.cache.set(history_key, content)
metadata['entries'].append(counter)
metadata['counter'] += 1
# Keep only last N entries
while len(metadata['entries']) > self.max_history_per_file:
old_counter = metadata['entries'].pop(0)
old_history_key = self._get_history_key(file_path, old_counter)
self.cache.delete(old_history_key)
self.cache.set(metadata_key, metadata)
def pop_last_history(self, file_path: Path) -> Optional[str]:
"""Pop and return the most recent history entry for a file."""
metadata_key = self._get_metadata_key(file_path)
metadata = self.cache.get(metadata_key, {'entries': [], 'counter': 0})
entries = metadata['entries']
if not entries:
return None
# Pop and remove the last entry
last_counter = entries.pop()
history_key = self._get_history_key(file_path, last_counter)
content = self.cache.get(history_key)
if content is None:
self.logger.warning(f'History entry not found for {file_path}')
else:
# Remove the entry from the cache
self.cache.delete(history_key)
# Update metadata
metadata['entries'] = entries
self.cache.set(metadata_key, metadata)
return content
def get_metadata(self, file_path: Path):
"""Get metadata for a file (for testing purposes)."""
metadata_key = self._get_metadata_key(file_path)
metadata = self.cache.get(metadata_key, {'entries': [], 'counter': 0})
return metadata # Return the actual metadata, not a copy
def clear_history(self, file_path: Path):
"""Clear history for a given file."""
metadata_key = self._get_metadata_key(file_path)
metadata = self.cache.get(metadata_key, {'entries': [], 'counter': 0})
# Delete all history entries
for counter in metadata['entries']:
history_key = self._get_history_key(file_path, counter)
self.cache.delete(history_key)
# Clear metadata
self.cache.set(metadata_key, {'entries': [], 'counter': 0})
def get_all_history(self, file_path: Path) -> List[str]:
"""Get all history entries for a file."""
metadata_key = self._get_metadata_key(file_path)
metadata = self.cache.get(metadata_key, {'entries': [], 'counter': 0})
entries = metadata['entries']
history = []
for counter in entries:
history_key = self._get_history_key(file_path, counter)
content = self.cache.get(history_key)
if content is not None:
history.append(content)
return history