2026-05-12 23:50:49 +00:00
|
|
|
import threading
|
|
|
|
|
import time
|
|
|
|
|
from datetime import datetime, timedelta, timezone
|
|
|
|
|
|
|
|
|
|
from models.state import (
|
|
|
|
|
messages,
|
|
|
|
|
room_users,
|
2026-10-06 02:36:54 +00:00
|
|
|
room_passwords,
|
|
|
|
|
authorized_room_users,
|
|
|
|
|
room_owners,
|
2026-05-12 23:50:49 +00:00
|
|
|
kicked_users,
|
|
|
|
|
user_sessions,
|
2026-10-06 02:36:54 +00:00
|
|
|
user_ips,
|
2026-05-12 23:50:49 +00:00
|
|
|
video_state,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
2026-10-06 02:36:54 +00:00
|
|
|
def cleanup_loop() -> None:
|
|
|
|
|
"""Background loop that periodically cleans expired messages, inactive users, and empty rooms."""
|
2026-05-12 23:50:49 +00:00
|
|
|
while True:
|
2026-10-06 02:36:54 +00:00
|
|
|
now = datetime.now(timezone.utc)
|
|
|
|
|
|
|
|
|
|
# 1. Remove chat messages older than 1 hour
|
|
|
|
|
message_cutoff = now - timedelta(hours=1)
|
2026-05-12 23:50:49 +00:00
|
|
|
for room, room_messages in list(messages.items()):
|
|
|
|
|
messages[room] = [
|
|
|
|
|
msg
|
|
|
|
|
for msg in room_messages
|
|
|
|
|
if datetime.fromisoformat(msg["timestamp"]) > message_cutoff
|
|
|
|
|
]
|
|
|
|
|
|
2026-10-06 02:36:54 +00:00
|
|
|
# 2. Remove inactive users from rooms (after 1 min of silence)
|
|
|
|
|
inactive_cutoff = now - timedelta(minutes=1)
|
2026-05-12 23:50:49 +00:00
|
|
|
for room in list(room_users.keys()):
|
|
|
|
|
room_users[room] = {
|
|
|
|
|
user: ts
|
|
|
|
|
for user, ts in room_users[room].items()
|
|
|
|
|
if datetime.fromisoformat(ts) > inactive_cutoff
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-06 02:36:54 +00:00
|
|
|
# 3. Clean up on-demand rooms that have become empty
|
|
|
|
|
# Rooms exist strictly while active; once empty for over 1 minute, everything vanishes.
|
|
|
|
|
for room in list(room_users.keys()):
|
|
|
|
|
if not room_users[room]:
|
|
|
|
|
messages.pop(room, None)
|
|
|
|
|
room_users.pop(room, None)
|
|
|
|
|
room_passwords.pop(room, None)
|
|
|
|
|
authorized_room_users.pop(room, None)
|
|
|
|
|
room_owners.pop(room, None)
|
|
|
|
|
kicked_users.pop(room, None)
|
|
|
|
|
video_state.pop(room, None)
|
|
|
|
|
|
|
|
|
|
# Also purge any dangling rooms in room_passwords that have no users
|
|
|
|
|
for room in list(room_passwords.keys()):
|
|
|
|
|
if room not in room_users or not room_users[room]:
|
2026-05-12 23:50:49 +00:00
|
|
|
messages.pop(room, None)
|
|
|
|
|
room_users.pop(room, None)
|
2026-10-06 02:36:54 +00:00
|
|
|
room_passwords.pop(room, None)
|
|
|
|
|
authorized_room_users.pop(room, None)
|
|
|
|
|
room_owners.pop(room, None)
|
2026-05-12 23:50:49 +00:00
|
|
|
kicked_users.pop(room, None)
|
|
|
|
|
video_state.pop(room, None)
|
|
|
|
|
|
2026-10-06 02:36:54 +00:00
|
|
|
# 4. Remove stale user sessions (after 3 days)
|
|
|
|
|
user_cutoff = now - timedelta(days=3)
|
2026-05-12 23:50:49 +00:00
|
|
|
for user, session in list(user_sessions.items()):
|
|
|
|
|
if datetime.fromisoformat(session["last_active"]) < user_cutoff:
|
|
|
|
|
user_sessions.pop(user, None)
|
2026-10-06 02:36:54 +00:00
|
|
|
user_ips.pop(user, None)
|
2026-05-12 23:50:49 +00:00
|
|
|
|
2026-10-06 02:36:54 +00:00
|
|
|
time.sleep(60)
|
2026-05-12 23:50:49 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
_cleanup_thread_started = False
|
|
|
|
|
|
2026-10-06 02:36:54 +00:00
|
|
|
|
|
|
|
|
def start_cleanup_thread() -> None:
|
|
|
|
|
"""Starts daemon thread for ephemeral resource cleanup."""
|
2026-05-12 23:50:49 +00:00
|
|
|
global _cleanup_thread_started
|
|
|
|
|
if _cleanup_thread_started:
|
|
|
|
|
return
|
|
|
|
|
_cleanup_thread_started = True
|
|
|
|
|
thread = threading.Thread(target=cleanup_loop, daemon=True)
|
|
|
|
|
thread.start()
|