Spaces:
Sleeping
Sleeping
| """Per-project upload storage. | |
| Each project gets its OWN folder -> no shared, rescanned "current uploads" | |
| directory. A project's document set is exactly what's in its own folder, | |
| nothing else, so a new project can never see another project's (or a prior | |
| project's leftover) files. Duplication across projects is an accepted | |
| tradeoff for that isolation. | |
| """ | |
| import os | |
| import shutil | |
| from typing import List | |
| PROJECT_UPLOADS_ROOT = os.path.expanduser("~/.studybuddy/projects/uploads") | |
| _SUPPORTED_EXTS = {".pdf", ".docx", ".txt"} | |
| def project_upload_dir(project_id: str) -> str: | |
| d = os.path.join(PROJECT_UPLOADS_ROOT, project_id) | |
| os.makedirs(d, exist_ok=True) | |
| return d | |
| def delete_project_uploads(project_id: str) -> None: | |
| folder = os.path.join(PROJECT_UPLOADS_ROOT, project_id) | |
| shutil.rmtree(folder, ignore_errors=True) | |
| def list_project_files(project_id: str) -> List[str]: | |
| folder = project_upload_dir(project_id) | |
| return sorted( | |
| os.path.join(folder, f) | |
| for f in os.listdir(folder) | |
| if os.path.isfile(os.path.join(folder, f)) | |
| and os.path.splitext(f)[1].lower() in _SUPPORTED_EXTS | |
| ) | |
| def add_file_to_project(project_id: str, file_path: str) -> str: | |
| folder = project_upload_dir(project_id) | |
| filename = os.path.basename(file_path) | |
| dest = os.path.join(folder, filename) | |
| shutil.copy2(file_path, dest) | |
| return dest | |
| async def remove_file_from_project(project_id: str, filename: str) -> bool: | |
| from app.rag.chromadb_client import ChromaDBClient | |
| from app.rag.evidence_ingestion import EvidenceIngestionService | |
| folder = project_upload_dir(project_id) | |
| file_path = os.path.join(folder, filename) | |
| if not os.path.exists(file_path): | |
| return False | |
| try: | |
| with open(file_path, "rb") as f: | |
| content = f.read() | |
| document_id = ChromaDBClient.file_hash(content) | |
| except Exception: | |
| document_id = None | |
| # Remove derived state first. If this fails, keep the irreplaceable source | |
| # PDF so the operation can be retried safely. | |
| if document_id: | |
| EvidenceIngestionService().remove_document(project_id, document_id) | |
| from app.services.paper_metadata import PaperMetadataService | |
| PaperMetadataService().forget_file_metadata(document_id) | |
| os.remove(file_path) | |
| # Cognee is cross-project student memory, not a mirror of project papers; | |
| # paper deletion therefore does not mutate the durable learner profile. | |
| return True | |