Files
agessaman a0d3c1497c refactor(web_viewer): move the cleanup scheduler into CleanupSchedulerMixin
_start_cleanup_scheduler and _cleanup_old_data move verbatim to modules/web_viewer/cleanup.py (AST-identical; the other 23 BotDataViewer methods are unchanged). app.py keeps importing delete_timestamp_rows_in_chunks and retention_delete_settings so modules.web_viewer.app still exposes them; as with the earlier mixins, patching those names on app no longer reaches the moved code. app.py is 4,808 lines.
2026-10-03 21:19:40 -07:00

83 lines
3.1 KiB
Python

"""Background cleanup for the web viewer: the hourly scheduler that drops
stale Socket.IO clients and prunes old packet_stream rows."""
from __future__ import annotations
import time
from contextlib import suppress
from typing import Any
from modules.db_retention import (
delete_timestamp_rows_in_chunks,
retention_delete_settings,
)
class CleanupSchedulerMixin:
"""Mixed into BotDataViewer."""
_cleanup_stale_clients: Any
_with_db_connection: Any
config: Any
logger: Any
def _start_cleanup_scheduler(self):
"""Start background thread for periodic database cleanup"""
import threading
def cleanup_scheduler():
import time
while True:
try:
# Clean up stale clients every 5 minutes
for _ in range(12): # 12 x 5 minutes = 1 hour
time.sleep(300) # 5 minutes
self._cleanup_stale_clients()
# Clean up old data every hour (after 12 stale client cleanups)
self._cleanup_old_data()
except Exception as e:
self.logger.error(f"Error in cleanup scheduler: {e}", exc_info=True)
time.sleep(60) # Sleep on error
# Start the cleanup thread
cleanup_thread = threading.Thread(target=cleanup_scheduler, daemon=True)
cleanup_thread.start()
self.logger.info("Cleanup scheduler started")
def _cleanup_old_data(self, days_to_keep: int | None = None):
"""Clean up old packet stream data to prevent database bloat.
Uses [Data_Retention] packet_stream_retention_days when days_to_keep is not provided."""
try:
import sqlite3
if days_to_keep is None:
days_to_keep = 3
if self.config.has_section('Data_Retention') and self.config.has_option('Data_Retention', 'packet_stream_retention_days'):
with suppress(ValueError, TypeError):
days_to_keep = self.config.getint('Data_Retention', 'packet_stream_retention_days')
cutoff_time = time.time() - (days_to_keep * 24 * 60 * 60)
batch_size, pause_seconds = retention_delete_settings(self.config)
total_deleted = delete_timestamp_rows_in_chunks(
self._with_db_connection,
'packet_stream',
'timestamp',
cutoff_time,
batch_size=batch_size,
pause_seconds=pause_seconds,
logger=self.logger,
progress_label='packet stream',
)
if total_deleted > 0:
self.logger.info(
f"Cleaned up {total_deleted} old packet stream entries "
f"(older than {days_to_keep} days)"
)
except sqlite3.OperationalError as e:
self.logger.warning(f"Database busy during cleanup (will retry next cycle): {e}")
except Exception as e:
self.logger.error(f"Error cleaning up old packet stream data: {e}", exc_info=True)