"""Unified file manager for Research and Docs sources in the dashboard. Provides a unified API over both ``research/`` and ``docs/`` directories with proper MIME type detection, binary file serving, and directory trees. """ from __future__ import annotations import json import re import subprocess from http import HTTPStatus from io import BytesIO from pathlib import Path from typing import Any, Iterator, cast from urllib.parse import unquote from .docs_source import DocumentationSource, create_docs_source from .research_source import ResearchSource # --------------------------------------------------------------------------- # MIME type map # --------------------------------------------------------------------------- _MEDIA_TYPES: dict[str, str] = { ".html": "text/html; charset=utf-8", ".css": "text/css; charset=utf-8", ".js": "text/javascript; charset=utf-8", ".svg": "image/svg+xml", ".ico": "image/x-icon", ".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".gif": "image/gif", ".webp": "image/webp", ".bmp": "image/bmp", ".mp4": "video/mp4", ".webm": "video/webm", ".ogg": "video/ogg", ".mov": "video/quicktime", ".avi": "video/x-msvideo", ".mp3": "audio/mpeg", ".wav": "audio/wav", ".flac": "audio/flac", ".aac": "audio/aac", ".m4a": "audio/mp4", ".opus": "audio/opus", ".pdf": "application/pdf", ".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document", ".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", } _BINARY_EXTENSIONS = frozenset( { ".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg", ".mp4", ".webm", ".ogg", ".mov", ".avi", ".mp3", ".wav", ".flac", ".aac", ".m4a", ".opus", ".pdf", ".docx", ".xlsx", ".xls", ".ico", ".bmp", } ) def _mime_type(ext: str) -> str: return _MEDIA_TYPES.get(ext.lower(), "application/octet-stream") # --------------------------------------------------------------------------- # Exceptions # --------------------------------------------------------------------------- class FileManagerError(Exception): """Base error for file manager operations.""" class SourceNotAvailableError(FileManagerError): """Raised when a requested source is not configured.""" class InvalidSourceError(FileManagerError): """Raised when an unknown source name is given.""" class PathTraversalError(FileManagerError): """Raised on path traversal attempts.""" # --------------------------------------------------------------------------- # Markdown export helpers # --------------------------------------------------------------------------- def _escape_html(value: str) -> str: return ( value.replace("&", "&") .replace("<", "<") .replace(">", ">") .replace('"', """) ) def _markdown_to_html(content: str, title: str = "Document") -> str: """Convert a simple Markdown document to a standalone HTML page.""" lines = content.splitlines() html_parts: list[str] = [] in_code = False code_lines: list[str] = [] for raw_line in lines: line = raw_line.rstrip() if line.startswith("```"): if in_code: html_parts.append( "
"
                    + _escape_html("\n".join(code_lines))
                    + "
" ) code_lines = [] in_code = not in_code continue if in_code: code_lines.append(raw_line) continue # Heading match = re.match(r"^(#{1,6})\s+(.*)$", line) if match: level = len(match.group(1)) html_parts.append(f"{_escape_html(match.group(2))}") continue # Unordered list if re.match(r"^\s*[-*+]\s+", line): text = re.sub(r"^\s*[-*+]\s+", "", line) html_parts.append(f"
  • {_escape_html(text)}
  • ") continue # Ordered list match = re.match(r"^(\s*)\d+\.\s+(.*)$", line) if match: html_parts.append(f"
  • {_escape_html(match.group(2))}
  • ") continue # Empty line if not line.strip(): html_parts.append("
    ") continue # Paragraph with inline formatting para = _inline_markdown(line) html_parts.append(f"

    {para}

    ") # Wrap loose list items in