259 Zeilen
9.6 KiB
Python
259 Zeilen
9.6 KiB
Python
"""
|
|
Fingerprint Persistence Service - Handles fingerprint storage and retrieval.
|
|
"""
|
|
|
|
import logging
|
|
from typing import Optional, List, Dict, Any
|
|
from datetime import datetime, timedelta
|
|
|
|
from domain.entities.browser_fingerprint import BrowserFingerprint
|
|
from domain.repositories.fingerprint_repository import IFingerprintRepository
|
|
|
|
logger = logging.getLogger("fingerprint_persistence_service")
|
|
|
|
|
|
class FingerprintPersistenceService:
|
|
"""Service for fingerprint persistence operations."""
|
|
|
|
def __init__(self, repository: IFingerprintRepository):
|
|
self.repository = repository
|
|
self._cache = {} # Simple in-memory cache
|
|
self._cache_ttl = 300 # 5 minutes
|
|
|
|
def save_fingerprint(self, fingerprint: BrowserFingerprint) -> str:
|
|
"""Save a fingerprint and return its ID."""
|
|
try:
|
|
fingerprint_id = self.repository.save(fingerprint)
|
|
|
|
# Update cache
|
|
self._cache[fingerprint_id] = {
|
|
'fingerprint': fingerprint,
|
|
'timestamp': datetime.now()
|
|
}
|
|
|
|
logger.info(f"Saved fingerprint: {fingerprint_id}")
|
|
return fingerprint_id
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to save fingerprint: {e}")
|
|
raise
|
|
|
|
def load_fingerprint(self, fingerprint_id: str) -> Optional[BrowserFingerprint]:
|
|
"""Load a fingerprint by ID."""
|
|
# Check cache first
|
|
if fingerprint_id in self._cache:
|
|
cache_entry = self._cache[fingerprint_id]
|
|
if (datetime.now() - cache_entry['timestamp']).seconds < self._cache_ttl:
|
|
logger.debug(f"Loaded fingerprint from cache: {fingerprint_id}")
|
|
return cache_entry['fingerprint']
|
|
|
|
# Load from repository
|
|
try:
|
|
fingerprint = self.repository.find_by_id(fingerprint_id)
|
|
|
|
if fingerprint:
|
|
# Update cache
|
|
self._cache[fingerprint_id] = {
|
|
'fingerprint': fingerprint,
|
|
'timestamp': datetime.now()
|
|
}
|
|
logger.info(f"Loaded fingerprint from repository: {fingerprint_id}")
|
|
else:
|
|
logger.warning(f"Fingerprint not found: {fingerprint_id}")
|
|
|
|
return fingerprint
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to load fingerprint {fingerprint_id}: {e}")
|
|
return None
|
|
|
|
def load_fingerprint_for_account(self, account_id: str) -> Optional[BrowserFingerprint]:
|
|
"""Load fingerprint associated with an account."""
|
|
try:
|
|
fingerprint = self.repository.find_by_account_id(account_id)
|
|
|
|
if fingerprint:
|
|
# Update cache
|
|
self._cache[fingerprint.fingerprint_id] = {
|
|
'fingerprint': fingerprint,
|
|
'timestamp': datetime.now()
|
|
}
|
|
logger.info(f"Loaded fingerprint for account {account_id}")
|
|
else:
|
|
logger.warning(f"No fingerprint found for account {account_id}")
|
|
|
|
return fingerprint
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to load fingerprint for account {account_id}: {e}")
|
|
return None
|
|
|
|
def update_fingerprint(self, fingerprint: BrowserFingerprint) -> bool:
|
|
"""Update an existing fingerprint."""
|
|
try:
|
|
success = self.repository.update(fingerprint)
|
|
|
|
if success:
|
|
# Update cache
|
|
self._cache[fingerprint.fingerprint_id] = {
|
|
'fingerprint': fingerprint,
|
|
'timestamp': datetime.now()
|
|
}
|
|
logger.info(f"Updated fingerprint: {fingerprint.fingerprint_id}")
|
|
else:
|
|
logger.warning(f"Failed to update fingerprint: {fingerprint.fingerprint_id}")
|
|
|
|
return success
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to update fingerprint {fingerprint.fingerprint_id}: {e}")
|
|
return False
|
|
|
|
def delete_fingerprint(self, fingerprint_id: str) -> bool:
|
|
"""Delete a fingerprint."""
|
|
try:
|
|
success = self.repository.delete(fingerprint_id)
|
|
|
|
if success:
|
|
# Remove from cache
|
|
self._cache.pop(fingerprint_id, None)
|
|
logger.info(f"Deleted fingerprint: {fingerprint_id}")
|
|
else:
|
|
logger.warning(f"Failed to delete fingerprint: {fingerprint_id}")
|
|
|
|
return success
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to delete fingerprint {fingerprint_id}: {e}")
|
|
return False
|
|
|
|
def list_fingerprints(self, limit: int = 100) -> List[BrowserFingerprint]:
|
|
"""List all fingerprints."""
|
|
try:
|
|
fingerprints = self.repository.find_all()
|
|
|
|
# Limit results
|
|
if len(fingerprints) > limit:
|
|
fingerprints = fingerprints[:limit]
|
|
|
|
logger.info(f"Listed {len(fingerprints)} fingerprints")
|
|
return fingerprints
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to list fingerprints: {e}")
|
|
return []
|
|
|
|
def list_recent_fingerprints(self, limit: int = 10) -> List[BrowserFingerprint]:
|
|
"""List recently created fingerprints."""
|
|
try:
|
|
fingerprints = self.repository.find_recent(limit)
|
|
logger.info(f"Listed {len(fingerprints)} recent fingerprints")
|
|
return fingerprints
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to list recent fingerprints: {e}")
|
|
return []
|
|
|
|
def list_fingerprints_by_platform(self, platform: str) -> List[BrowserFingerprint]:
|
|
"""List fingerprints for a specific platform."""
|
|
try:
|
|
fingerprints = self.repository.find_by_platform(platform)
|
|
logger.info(f"Listed {len(fingerprints)} fingerprints for platform {platform}")
|
|
return fingerprints
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to list fingerprints for platform {platform}: {e}")
|
|
return []
|
|
|
|
def get_fingerprint_pool(self, size: int = 10) -> List[BrowserFingerprint]:
|
|
"""Get a pool of random fingerprints."""
|
|
try:
|
|
# Get more than needed to filter
|
|
candidates = self.repository.find_recent(size * 3)
|
|
|
|
# Filter for quality
|
|
quality_fingerprints = []
|
|
for fp in candidates:
|
|
# Skip if too old
|
|
if fp.created_at:
|
|
age_days = (datetime.now() - fp.created_at).days
|
|
if age_days > 30:
|
|
continue
|
|
|
|
# Skip if account-bound
|
|
if fp.account_bound:
|
|
continue
|
|
|
|
quality_fingerprints.append(fp)
|
|
|
|
if len(quality_fingerprints) >= size:
|
|
break
|
|
|
|
logger.info(f"Created fingerprint pool of size {len(quality_fingerprints)}")
|
|
return quality_fingerprints
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to create fingerprint pool: {e}")
|
|
return []
|
|
|
|
def cleanup_old_fingerprints(self, days_to_keep: int = 90) -> int:
|
|
"""Clean up fingerprints older than specified days."""
|
|
try:
|
|
# Calculate cutoff date
|
|
cutoff = datetime.now() - timedelta(days=days_to_keep)
|
|
|
|
# Get all fingerprints to check
|
|
all_fingerprints = self.repository.find_all()
|
|
deleted_count = 0
|
|
|
|
for fp in all_fingerprints:
|
|
if fp.created_at and fp.created_at < cutoff:
|
|
# Skip if account-bound
|
|
if fp.account_bound:
|
|
continue
|
|
|
|
if self.repository.delete(fp.fingerprint_id):
|
|
deleted_count += 1
|
|
# Remove from cache
|
|
self._cache.pop(fp.fingerprint_id, None)
|
|
|
|
logger.info(f"Cleaned up {deleted_count} old fingerprints")
|
|
return deleted_count
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to cleanup old fingerprints: {e}")
|
|
return 0
|
|
|
|
def clear_cache(self) -> None:
|
|
"""Clear the in-memory cache."""
|
|
self._cache.clear()
|
|
logger.info("Cleared fingerprint cache")
|
|
|
|
def get_statistics(self) -> Dict[str, Any]:
|
|
"""Get fingerprint statistics."""
|
|
try:
|
|
total = self.repository.count()
|
|
recent = len(self.repository.find_recent(100))
|
|
|
|
# Platform breakdown
|
|
platforms = {}
|
|
for platform in ['instagram', 'facebook', 'tiktok', 'twitter']:
|
|
count = len(self.repository.find_by_platform(platform))
|
|
if count > 0:
|
|
platforms[platform] = count
|
|
|
|
return {
|
|
'total_fingerprints': total,
|
|
'recent_fingerprints': recent,
|
|
'platforms': platforms,
|
|
'cache_size': len(self._cache)
|
|
}
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to get statistics: {e}")
|
|
return {
|
|
'total_fingerprints': 0,
|
|
'recent_fingerprints': 0,
|
|
'platforms': {},
|
|
'cache_size': len(self._cache)
|
|
} |