Files
intercept/utils/cleanup.py
Smittix 0d5310eb4b Fix DataStore subscript access for ADS-B tracking
Add __getitem__, __setitem__, and __delitem__ methods to DataStore
class to support dict-style subscript notation (store[key]).

Fixes TypeError: 'DataStore' object is not subscriptable

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-06 20:35:51 +00:00

259 lines
7.4 KiB
Python

"""Data cleanup utilities for stale entries."""
from __future__ import annotations
import logging
import threading
import time
from typing import Any
logger = logging.getLogger('intercept.cleanup')
class DataStore:
"""Thread-safe data store with automatic cleanup of stale entries."""
def __init__(self, max_age_seconds: float = 300.0, name: str = 'data'):
"""
Initialize data store.
Args:
max_age_seconds: Maximum age of entries before cleanup (default 5 minutes)
name: Name for logging purposes
"""
self.data: dict[str, Any] = {}
self.timestamps: dict[str, float] = {}
self.max_age = max_age_seconds
self.name = name
self._lock = threading.Lock()
def set(self, key: str, value: Any) -> None:
"""Add or update an entry."""
with self._lock:
self.data[key] = value
self.timestamps[key] = time.time()
def get(self, key: str, default: Any = None) -> Any:
"""Get an entry."""
with self._lock:
return self.data.get(key, default)
def update(self, key: str, updates: dict) -> None:
"""Update an existing entry with new values."""
with self._lock:
if key in self.data:
if isinstance(self.data[key], dict):
self.data[key].update(updates)
else:
self.data[key] = updates
else:
self.data[key] = updates
self.timestamps[key] = time.time()
def touch(self, key: str) -> None:
"""Update timestamp for an entry without changing data."""
with self._lock:
if key in self.data:
self.timestamps[key] = time.time()
def delete(self, key: str) -> bool:
"""Delete an entry."""
with self._lock:
if key in self.data:
del self.data[key]
del self.timestamps[key]
return True
return False
def clear(self) -> None:
"""Clear all entries."""
with self._lock:
self.data.clear()
self.timestamps.clear()
def all(self) -> dict[str, Any]:
"""Get a copy of all data."""
with self._lock:
return dict(self.data)
def keys(self) -> list[str]:
"""Get all keys."""
with self._lock:
return list(self.data.keys())
def values(self) -> list[Any]:
"""Get all values."""
with self._lock:
return list(self.data.values())
def items(self) -> list[tuple[str, Any]]:
"""Get all items."""
with self._lock:
return list(self.data.items())
def __len__(self) -> int:
with self._lock:
return len(self.data)
def __contains__(self, key: str) -> bool:
with self._lock:
return key in self.data
def __getitem__(self, key: str) -> Any:
"""Get an entry using subscript notation."""
with self._lock:
return self.data[key]
def __setitem__(self, key: str, value: Any) -> None:
"""Set an entry using subscript notation."""
with self._lock:
self.data[key] = value
self.timestamps[key] = time.time()
def __delitem__(self, key: str) -> None:
"""Delete an entry using subscript notation."""
with self._lock:
del self.data[key]
del self.timestamps[key]
def cleanup(self) -> int:
"""
Remove entries older than max_age.
Returns:
Number of entries removed
"""
now = time.time()
expired = []
with self._lock:
for key, timestamp in self.timestamps.items():
if now - timestamp > self.max_age:
expired.append(key)
for key in expired:
del self.data[key]
del self.timestamps[key]
if expired:
logger.debug(f"{self.name}: Cleaned up {len(expired)} stale entries")
return len(expired)
class CleanupManager:
"""Manages periodic cleanup of multiple data stores."""
def __init__(self, interval: float = 60.0):
"""
Initialize cleanup manager.
Args:
interval: Cleanup interval in seconds
"""
self.stores: list[DataStore] = []
self.interval = interval
self._timer: threading.Timer | None = None
self._running = False
self._lock = threading.Lock()
def register(self, store: DataStore) -> None:
"""Register a data store for cleanup."""
with self._lock:
if store not in self.stores:
self.stores.append(store)
def unregister(self, store: DataStore) -> None:
"""Unregister a data store."""
with self._lock:
if store in self.stores:
self.stores.remove(store)
def start(self) -> None:
"""Start the cleanup timer."""
with self._lock:
if self._running:
return
self._running = True
self._schedule_cleanup()
def stop(self) -> None:
"""Stop the cleanup timer."""
with self._lock:
self._running = False
if self._timer:
self._timer.cancel()
self._timer = None
def _schedule_cleanup(self) -> None:
"""Schedule the next cleanup."""
if not self._running:
return
self._timer = threading.Timer(self.interval, self._run_cleanup)
self._timer.daemon = True
self._timer.start()
def _run_cleanup(self) -> None:
"""Run cleanup on all registered stores."""
total_cleaned = 0
with self._lock:
stores = list(self.stores)
for store in stores:
try:
total_cleaned += store.cleanup()
except Exception as e:
logger.error(f"Error cleaning up {store.name}: {e}")
if total_cleaned > 0:
logger.info(f"Cleanup complete: removed {total_cleaned} stale entries")
self._schedule_cleanup()
def cleanup_now(self) -> int:
"""Run cleanup immediately."""
total = 0
with self._lock:
stores = list(self.stores)
for store in stores:
try:
total += store.cleanup()
except Exception as e:
logger.error(f"Error cleaning up {store.name}: {e}")
return total
# Global cleanup manager
cleanup_manager = CleanupManager(interval=60.0)
def cleanup_dict(
data: dict[str, Any],
timestamps: dict[str, float],
max_age_seconds: float = 300.0
) -> list[str]:
"""
Clean up stale entries from a dictionary.
Args:
data: Dictionary to clean
timestamps: Dictionary of key -> last_seen timestamp
max_age_seconds: Maximum age in seconds
Returns:
List of removed keys
"""
now = time.time()
expired = []
for key, timestamp in list(timestamps.items()):
if now - timestamp > max_age_seconds:
expired.append(key)
for key in expired:
data.pop(key, None)
timestamps.pop(key, None)
return expired