from __future__ import annotations import os import sys import tempfile from datetime import datetime, timezone from pathlib import Path def _bridge_app(): return sys.modules["services.asterisk_bridge_service.app"] def fetch_recording_file(remote_path: str, file_name: str | None) -> tuple[Path, bool]: bridge = _bridge_app() source = Path(remote_path) if source.is_absolute() and source.exists(): return source.resolve(), False if not bridge._sftp_enabled(): raise RuntimeError("SFTP is not configured and remote_path is not a local file") try: import paramiko # type: ignore except ImportError as exc: # pragma: no cover - depends on runtime package set raise RuntimeError("paramiko is required for SFTP recording transfer") from exc safe_name = file_name or Path(remote_path).name or "asterisk-call.wav" suffix = Path(safe_name).suffix or ".wav" handle = tempfile.NamedTemporaryFile(prefix="mvpcc-ast-", suffix=suffix, delete=False) handle.close() target = Path(handle.name) transport = paramiko.Transport((bridge._sftp_host(), bridge._sftp_port())) try: transport.connect(username=bridge._sftp_username(), password=bridge._sftp_password()) sftp = paramiko.SFTPClient.from_transport(transport) try: sftp.get(remote_path, str(target)) finally: sftp.close() finally: transport.close() return target.resolve(), True def recording_search_directories() -> list[str]: base = os.getenv("ASTERISK_SFTP_BASE_PATH", "/var/spool/asterisk/monitor").rstrip("/") return [f"{base}/mvpcc", base] def guess_mime_type(file_name: str) -> str: lower = file_name.lower() if lower.endswith(".mp3"): return "audio/mpeg" if lower.endswith(".ogg"): return "audio/ogg" return "audio/wav" def find_remote_recording_for_call(call_id: str) -> tuple[str, str, str, datetime] | None: bridge = _bridge_app() suffixes = (".wav", ".mp3", ".ogg") candidates: list[tuple[float, str, str]] = [] if bridge._sftp_enabled(): try: import paramiko # type: ignore except ImportError: return None transport = paramiko.Transport((bridge._sftp_host(), bridge._sftp_port())) try: transport.connect(username=bridge._sftp_username(), password=bridge._sftp_password()) sftp = paramiko.SFTPClient.from_transport(transport) try: for directory in recording_search_directories(): try: names = sftp.listdir(directory) except Exception: # noqa: BLE001 continue for name in names: lower = name.lower() if call_id not in name or not lower.endswith(suffixes): continue full_path = f"{directory}/{name}" try: st = sftp.stat(full_path) except Exception: # noqa: BLE001 continue candidates.append((float(st.st_mtime), full_path, name)) finally: sftp.close() finally: transport.close() else: for directory in recording_search_directories(): root = Path(directory) if not root.exists(): continue for file_path in root.glob(f"*{call_id}*"): if not file_path.is_file(): continue lower = file_path.name.lower() if not lower.endswith(suffixes): continue candidates.append((file_path.stat().st_mtime, str(file_path), file_path.name)) if not candidates: return None candidates.sort(key=lambda item: item[0], reverse=True) mtime, full_path, file_name = candidates[0] mtime_dt = datetime.fromtimestamp(mtime, tz=timezone.utc) return full_path, file_name, guess_mime_type(file_name), mtime_dt