"""
Storage configuration for the LucidLink Python client.
This module provides two operational modes for managing client files:
- ``SANDBOXED``: Files in temporary directory, always cleaned up on exit
- ``PHYSICAL``: Files in ``.lucid`` subfolder, optional persistence
IMPORTANT: Directory Structure
===============================
The client automatically manages the directory structure:
1. Python passes the BASE directory (e.g., C:/work/script/).
2. The ``.lucid`` subdirectory is appended automatically.
3. Per-filespace UUID subdirectories are created for isolation.
Example with PHYSICAL mode running from ``C:/work/script/``::
1. StorageConfig.get_root_path() returns: C:/work/script/
2. Python passes the base path: "C:/work/script" (forward slashes)
3. The following directory structure is created:
- C:/work/script/.lucid/{filespace1-uuid}/node.cfg
- C:/work/script/.lucid/{filespace1-uuid}/metadb/
- C:/work/script/.lucid/{filespace1-uuid}/cache/
4. If you link to a second filespace in the same script:
- C:/work/script/.lucid/{filespace2-uuid}/node.cfg
- C:/work/script/.lucid/{filespace2-uuid}/metadb/
- C:/work/script/.lucid/{filespace2-uuid}/cache/
This prevents file clashes when multiple clients link to different
filespaces from the same script.
**Note**: Paths are passed using forward slashes (generic format) for
cross-platform compatibility.
"""
from enum import Enum
from pathlib import Path
import tempfile
import atexit
import shutil
from typing import Optional
[docs]
class StorageMode(Enum):
"""Storage mode for client operational files."""
PHYSICAL = "physical"
"""Files in ``.lucid`` subfolder of script directory."""
SANDBOXED = "sandboxed"
"""Files in temp directory, always cleaned up."""
[docs]
class StorageConfig:
"""
Configuration for client storage mode and file locations.
Args:
mode: Storage mode (``PHYSICAL`` or ``SANDBOXED``)
persist_on_exit: If ``False``, clean up files when the client closes.
Only applies to ``PHYSICAL`` mode; ``SANDBOXED`` always cleans up.
root_path: Override root path for files (only for ``PHYSICAL`` mode).
If ``None``, uses current working directory.
Example:
.. code-block:: python
# Sandboxed mode (default) - temp directory, always cleaned up
config = StorageConfig()
# Physical mode with cleanup
config = StorageConfig(mode=StorageMode.PHYSICAL)
# Physical mode with persistence
config = StorageConfig(
mode=StorageMode.PHYSICAL,
persist_on_exit=True
)
# Custom root path
config = StorageConfig(
mode=StorageMode.PHYSICAL,
root_path=Path("D:/lucid_data")
)
"""
def __init__(
self,
mode: StorageMode = StorageMode.SANDBOXED,
persist_on_exit: bool = False,
root_path: Optional[Path] = None,
):
self.mode = mode
self.persist_on_exit = persist_on_exit
self._root_path = root_path
self._temp_dir: Optional[Path] = None
self._cleanup_registered = False
[docs]
def get_root_path(self) -> Path:
"""
Get the root path for client files.
Returns:
Path to .lucid directory where per-filespace UUID subdirectories
will be created for isolation.
"""
if self.mode == StorageMode.PHYSICAL:
if self._root_path:
return self._root_path / ".lucid"
# Use script's execution directory
return Path.cwd() / ".lucid"
else: # SANDBOXED
if self._temp_dir is None:
self._temp_dir = Path(tempfile.mkdtemp(prefix="lucid_"))
if not self._cleanup_registered:
atexit.register(self._cleanup)
self._cleanup_registered = True
# For sandboxed mode, use .lucid subdirectory
return self._temp_dir / ".lucid"
def _cleanup(self):
"""Clean up sandbox directory on exit."""
if self._temp_dir and self._temp_dir.exists():
try:
shutil.rmtree(self._temp_dir, ignore_errors=True)
except Exception:
# Ignore cleanup errors during exit
pass
[docs]
def should_cleanup(self) -> bool:
"""
Check if files should be cleaned up when the client closes.
Returns:
True if files should be cleaned up
"""
if self.mode == StorageMode.SANDBOXED:
return True # Always cleanup in sandboxed mode
else:
return not self.persist_on_exit