Files
Atomizer/atomizer-dashboard/backend/api/routes/files.py

156 lines
4.5 KiB
Python
Raw Normal View History

"""
Files API Routes
Provides file browsing capabilities for the Canvas Builder.
"""
from fastapi import APIRouter, Query
from pathlib import Path
from typing import List, Optional
import os
router = APIRouter()
# Path to studies root (go up 5 levels from this file)
_file_path = os.path.abspath(__file__)
ATOMIZER_ROOT = Path(os.path.normpath(os.path.dirname(os.path.dirname(os.path.dirname(
os.path.dirname(os.path.dirname(_file_path))
)))))
STUDIES_ROOT = ATOMIZER_ROOT / "studies"
@router.get("/list")
async def list_files(
path: str = "",
types: str = ".sim,.prt,.fem,.afem"
):
"""
List files in a directory, filtered by type.
Args:
path: Relative path from studies root (empty for root)
types: Comma-separated list of file extensions to include
Returns:
List of files and directories with their paths
"""
allowed_types = [t.strip().lower() for t in types.split(',') if t.strip()]
base_path = STUDIES_ROOT / path if path else STUDIES_ROOT
if not base_path.exists():
return {"files": [], "path": path, "error": "Directory not found"}
files = []
try:
for entry in sorted(base_path.iterdir(), key=lambda x: (not x.is_dir(), x.name.lower())):
# Skip hidden files and directories
if entry.name.startswith('.'):
continue
if entry.is_dir():
# Include directories
files.append({
"name": entry.name,
"path": str(entry.relative_to(STUDIES_ROOT)).replace("\\", "/"),
"isDirectory": True,
})
else:
# Include files matching type filter
suffix = entry.suffix.lower()
if suffix in allowed_types:
files.append({
"name": entry.name,
"path": str(entry.relative_to(STUDIES_ROOT)).replace("\\", "/"),
"isDirectory": False,
"size": entry.stat().st_size,
})
except PermissionError:
return {"files": [], "path": path, "error": "Permission denied"}
except Exception as e:
return {"files": [], "path": path, "error": str(e)}
return {"files": files, "path": path}
@router.get("/search")
async def search_files(
query: str,
types: str = ".sim,.prt,.fem,.afem",
max_results: int = 50
):
"""
Search for files by name pattern.
Args:
query: Search pattern (partial name match)
types: Comma-separated list of file extensions to include
max_results: Maximum number of results to return
Returns:
List of matching files with their paths
"""
allowed_types = [t.strip().lower() for t in types.split(',') if t.strip()]
query_lower = query.lower()
files = []
def search_recursive(directory: Path, depth: int = 0):
"""Recursively search for matching files"""
if depth > 10 or len(files) >= max_results: # Limit depth and results
return
try:
for entry in directory.iterdir():
if len(files) >= max_results:
return
if entry.name.startswith('.'):
continue
if entry.is_dir():
search_recursive(entry, depth + 1)
elif entry.suffix.lower() in allowed_types:
if query_lower in entry.name.lower():
files.append({
"name": entry.name,
"path": str(entry.relative_to(STUDIES_ROOT)).replace("\\", "/"),
"isDirectory": False,
"size": entry.stat().st_size,
})
except (PermissionError, OSError):
pass
search_recursive(STUDIES_ROOT)
return {"files": files, "query": query, "total": len(files)}
@router.get("/exists")
async def check_file_exists(path: str):
"""
Check if a file exists.
Args:
path: Relative path from studies root
Returns:
Boolean indicating if file exists and file info
"""
file_path = STUDIES_ROOT / path
exists = file_path.exists()
result = {
"exists": exists,
"path": path,
}
if exists:
result["isDirectory"] = file_path.is_dir()
if file_path.is_file():
result["size"] = file_path.stat().st_size
result["name"] = file_path.name
return result