|
6 | 6 | import shutil |
7 | 7 | from datetime import datetime |
8 | 8 | from pathlib import Path |
9 | | -from typing import Dict, Optional, Sequence |
| 9 | +from typing import TYPE_CHECKING, Dict, Optional, Sequence |
10 | 10 |
|
11 | 11 |
|
12 | 12 | from loguru import logger |
|
30 | 30 | ) |
31 | 31 | from basic_memory.utils import generate_permalink |
32 | 32 |
|
| 33 | +if TYPE_CHECKING: # pragma: no cover |
| 34 | + from basic_memory.services.file_service import FileService |
| 35 | + |
33 | 36 |
|
34 | 37 | class ProjectService: |
35 | 38 | """Service for managing Basic Memory projects.""" |
36 | 39 |
|
37 | 40 | repository: ProjectRepository |
38 | 41 |
|
39 | | - def __init__(self, repository: ProjectRepository): |
| 42 | + def __init__( |
| 43 | + self, repository: ProjectRepository, file_service: Optional["FileService"] = None |
| 44 | + ): |
40 | 45 | """Initialize the project service.""" |
41 | 46 | super().__init__() |
42 | 47 | self.repository = repository |
| 48 | + self.file_service = file_service |
43 | 49 |
|
44 | 50 | @property |
45 | 51 | def config_manager(self) -> ConfigManager: |
@@ -205,6 +211,16 @@ async def add_project(self, name: str, path: str, set_default: bool = False) -> |
205 | 211 | f"Projects cannot share directory trees." |
206 | 212 | ) |
207 | 213 |
|
| 214 | + # Ensure the project directory exists on disk. |
| 215 | + # Trigger: project_root not set means local filesystem mode (not S3/cloud) |
| 216 | + # Why: FileService (or future S3FileService) provides cloud-compatible directory creation; |
| 217 | + # direct Path.mkdir() bypasses this abstraction |
| 218 | + # Outcome: directory exists before config/DB entries are written |
| 219 | + if not self.config_manager.config.project_root: |
| 220 | + if self.file_service is None: |
| 221 | + raise ValueError("file_service is required for local project directory creation") |
| 222 | + await self.file_service.ensure_directory(Path(resolved_path)) |
| 223 | + |
208 | 224 | # First add to config file (this validates project uniqueness and keeps |
209 | 225 | # config + database aligned for all backends). |
210 | 226 | self.config_manager.add_project(name, resolved_path) |
@@ -460,8 +476,13 @@ async def move_project(self, name: str, new_path: str) -> None: |
460 | 476 | raise ValueError(f"Project '{name}' not found in configuration") |
461 | 477 |
|
462 | 478 | # Create the new directory if it doesn't exist (skip in cloud mode where storage is S3) |
| 479 | + # Trigger: project_root not set means local filesystem mode |
| 480 | + # Why: FileService (or future S3FileService) provides cloud-compatible directory creation |
| 481 | + # Outcome: destination directory exists before config/DB are updated |
463 | 482 | if not self.config_manager.config.project_root: |
464 | | - Path(resolved_path).mkdir(parents=True, exist_ok=True) |
| 483 | + if self.file_service is None: |
| 484 | + raise ValueError("file_service is required for local project directory creation") |
| 485 | + await self.file_service.ensure_directory(Path(resolved_path)) |
465 | 486 |
|
466 | 487 | # Update in configuration |
467 | 488 | config = self.config_manager.load_config() |
|
0 commit comments