Spaces:
Sleeping
Sleeping
| """Per-browser user sessions and idle cleanup.""" | |
| import os | |
| import secrets | |
| import shutil | |
| import threading | |
| import time | |
| from fastapi import Request | |
| from torrent2drive.config import BASE_TEMP | |
| from torrent2drive.constants import DEFAULT_ENC | |
| class UserSession: | |
| """Isolated state for a single browser session / user.""" | |
| def __init__(self, sid: str): | |
| self.sid = sid | |
| self.lock = threading.Lock() | |
| self.last_seen = time.time() | |
| self.dl_dir = os.path.join(BASE_TEMP, sid, "downloads") | |
| os.makedirs(self.dl_dir, exist_ok=True) | |
| self.handle = None | |
| self.ti = None | |
| self.selected_file_indices = [] | |
| self.download_started = False | |
| self.download_finished = False | |
| self.is_transferring = False | |
| self.is_completed = False | |
| self.transfer_status = "" | |
| self.use_ffmpeg = False | |
| self.ff_progress = 0.0 | |
| self.ff_eta = "" | |
| self.ff_speed = "" | |
| self.ff_size_done = "" | |
| self.ff_file_label = "" | |
| self.ff_running = False | |
| self.ff_stage = "" | |
| self.ff_duration_s = 0.0 | |
| self.ff_time_done_s = 0.0 | |
| self.pipeline_total = 0 | |
| self.pipeline_current = 0 | |
| self.pipeline_current_name = "" | |
| self.pipeline_failed: list[str] = [] | |
| self.pipeline_skipped: list[str] = [] | |
| self.pipeline_done: list[str] = [] | |
| self.pipeline_overall_pct = 0.0 | |
| self.pipeline_error = "" | |
| self.enc = dict(DEFAULT_ENC) | |
| self.stream_watch_only = False | |
| self.stream_watch_active = False | |
| self.stream_watch_index = -1 | |
| def touch(self): | |
| self.last_seen = time.time() | |
| _sessions: dict[str, UserSession] = {} | |
| _sessions_lock = threading.Lock() | |
| def get_or_create_session(request: Request) -> UserSession: | |
| sid = request.session.get("sid") | |
| if not sid: | |
| sid = secrets.token_urlsafe(16) | |
| request.session["sid"] = sid | |
| with _sessions_lock: | |
| us = _sessions.get(sid) | |
| if us is None: | |
| us = UserSession(sid) | |
| _sessions[sid] = us | |
| us.touch() | |
| return us | |
| def get_session_by_sid(sid: str) -> UserSession | None: | |
| with _sessions_lock: | |
| return _sessions.get(sid) | |
| def _gc_sessions(idle_seconds=3600 * 6): | |
| now = time.time() | |
| drop = [] | |
| with _sessions_lock: | |
| for sid, us in _sessions.items(): | |
| if ( | |
| us.is_transferring | |
| or us.ff_running | |
| or us.download_started | |
| or us.stream_watch_active | |
| ): | |
| continue | |
| if now - us.last_seen > idle_seconds: | |
| drop.append(sid) | |
| for sid in drop: | |
| us = _sessions.pop(sid, None) | |
| if not us: | |
| continue | |
| try: | |
| from torrent2drive.torrent_engine import remove_handle | |
| remove_handle(us.handle) | |
| except Exception: | |
| pass | |
| try: | |
| shutil.rmtree(os.path.join(BASE_TEMP, sid), ignore_errors=True) | |
| except Exception: | |
| pass | |
| def _gc_loop(): | |
| while True: | |
| try: | |
| _gc_sessions() | |
| except Exception as e: | |
| print(f"[GC] {e}") | |
| time.sleep(900) | |
| def start_gc_loop(): | |
| threading.Thread(target=_gc_loop, daemon=True).start() | |