Cluster/config/settings.py
2025-07-17 17:04:56 +08:00

321 lines
10 KiB
Python

"""
Application settings and configuration management.
This module handles application-wide settings, preferences, and configuration
data. It provides a centralized location for managing user preferences,
default values, and application state.
Main Components:
- Settings class for configuration management
- Default configuration values
- Settings persistence and loading
- Configuration validation
Usage:
from cluster4npu_ui.config.settings import Settings
settings = Settings()
recent_files = settings.get_recent_files()
settings.add_recent_file('/path/to/pipeline.mflow')
"""
import json
import os
from typing import Dict, Any, List, Optional
from pathlib import Path
class Settings:
"""
Application settings and configuration management.
Handles loading, saving, and managing application settings including
user preferences, recent files, and default configurations.
"""
def __init__(self, config_file: Optional[str] = None):
"""
Initialize settings manager.
Args:
config_file: Optional path to configuration file
"""
self.config_file = config_file or self._get_default_config_path()
self._settings = self._load_default_settings()
self.load()
def _get_default_config_path(self) -> str:
"""Get the default configuration file path."""
home_dir = Path.home()
config_dir = home_dir / '.cluster4npu'
config_dir.mkdir(exist_ok=True)
return str(config_dir / 'settings.json')
def _load_default_settings(self) -> Dict[str, Any]:
"""Load default application settings."""
return {
'general': {
'auto_save': True,
'auto_save_interval': 300, # seconds
'check_for_updates': True,
'theme': 'harmonious_dark',
'language': 'en'
},
'recent_files': [],
'window': {
'main_window_geometry': None,
'main_window_state': None,
'splitter_sizes': None,
'recent_window_size': [1200, 800]
},
'pipeline': {
'default_project_location': str(Path.home() / 'Documents' / 'Cluster4NPU'),
'auto_layout': True,
'show_grid': True,
'snap_to_grid': False,
'grid_size': 20,
'auto_connect': True,
'validate_on_save': True
},
'performance': {
'max_undo_steps': 50,
'render_quality': 'high',
'enable_animations': True,
'cache_size_mb': 100
},
'hardware': {
'auto_detect_dongles': True,
'preferred_dongle_series': '720',
'max_dongles_per_stage': 4,
'power_management': 'balanced'
},
'export': {
'default_format': 'JSON',
'include_metadata': True,
'compress_exports': False,
'export_location': str(Path.home() / 'Downloads')
},
'debugging': {
'log_level': 'INFO',
'enable_profiling': False,
'save_debug_logs': False,
'max_log_files': 10
}
}
def load(self) -> bool:
"""
Load settings from file.
Returns:
True if settings were loaded successfully, False otherwise
"""
try:
if os.path.exists(self.config_file):
with open(self.config_file, 'r', encoding='utf-8') as f:
saved_settings = json.load(f)
self._merge_settings(saved_settings)
return True
except Exception as e:
print(f"Error loading settings: {e}")
return False
def save(self) -> bool:
"""
Save current settings to file.
Returns:
True if settings were saved successfully, False otherwise
"""
try:
os.makedirs(os.path.dirname(self.config_file), exist_ok=True)
with open(self.config_file, 'w', encoding='utf-8') as f:
json.dump(self._settings, f, indent=2, ensure_ascii=False)
return True
except Exception as e:
print(f"Error saving settings: {e}")
return False
def _merge_settings(self, saved_settings: Dict[str, Any]):
"""Merge saved settings with defaults."""
def merge_dict(default: dict, saved: dict) -> dict:
result = default.copy()
for key, value in saved.items():
if key in result and isinstance(result[key], dict) and isinstance(value, dict):
result[key] = merge_dict(result[key], value)
else:
result[key] = value
return result
self._settings = merge_dict(self._settings, saved_settings)
def get(self, key: str, default: Any = None) -> Any:
"""
Get a setting value using dot notation.
Args:
key: Setting key (e.g., 'general.auto_save')
default: Default value if key not found
Returns:
Setting value or default
"""
keys = key.split('.')
value = self._settings
try:
for k in keys:
value = value[k]
return value
except (KeyError, TypeError):
return default
def set(self, key: str, value: Any):
"""
Set a setting value using dot notation.
Args:
key: Setting key (e.g., 'general.auto_save')
value: Value to set
"""
keys = key.split('.')
setting = self._settings
# Navigate to the parent dictionary
for k in keys[:-1]:
if k not in setting:
setting[k] = {}
setting = setting[k]
# Set the final value
setting[keys[-1]] = value
def get_recent_files(self) -> List[str]:
"""Get list of recent files."""
return self.get('recent_files', [])
def add_recent_file(self, file_path: str, max_files: int = 10):
"""
Add a file to recent files list.
Args:
file_path: Path to the file
max_files: Maximum number of recent files to keep
"""
recent_files = self.get_recent_files()
# Remove if already exists
if file_path in recent_files:
recent_files.remove(file_path)
# Add to beginning
recent_files.insert(0, file_path)
# Limit list size
recent_files = recent_files[:max_files]
self.set('recent_files', recent_files)
self.save()
def remove_recent_file(self, file_path: str):
"""Remove a file from recent files list."""
recent_files = self.get_recent_files()
if file_path in recent_files:
recent_files.remove(file_path)
self.set('recent_files', recent_files)
self.save()
def clear_recent_files(self):
"""Clear all recent files."""
self.set('recent_files', [])
self.save()
def get_default_project_location(self) -> str:
"""Get default project location."""
return self.get('pipeline.default_project_location', str(Path.home() / 'Documents' / 'Cluster4NPU'))
def set_window_geometry(self, geometry: bytes):
"""Save window geometry."""
# Convert bytes to base64 string for JSON serialization
import base64
geometry_str = base64.b64encode(geometry).decode('utf-8')
self.set('window.main_window_geometry', geometry_str)
self.save()
def get_window_geometry(self) -> Optional[bytes]:
"""Get saved window geometry."""
geometry_str = self.get('window.main_window_geometry')
if geometry_str:
import base64
return base64.b64decode(geometry_str.encode('utf-8'))
return None
def set_window_state(self, state: bytes):
"""Save window state."""
import base64
state_str = base64.b64encode(state).decode('utf-8')
self.set('window.main_window_state', state_str)
self.save()
def get_window_state(self) -> Optional[bytes]:
"""Get saved window state."""
state_str = self.get('window.main_window_state')
if state_str:
import base64
return base64.b64decode(state_str.encode('utf-8'))
return None
def reset_to_defaults(self):
"""Reset all settings to default values."""
self._settings = self._load_default_settings()
self.save()
def export_settings(self, file_path: str) -> bool:
"""
Export settings to a file.
Args:
file_path: Path to export file
Returns:
True if export was successful, False otherwise
"""
try:
with open(file_path, 'w', encoding='utf-8') as f:
json.dump(self._settings, f, indent=2, ensure_ascii=False)
return True
except Exception as e:
print(f"Error exporting settings: {e}")
return False
def import_settings(self, file_path: str) -> bool:
"""
Import settings from a file.
Args:
file_path: Path to import file
Returns:
True if import was successful, False otherwise
"""
try:
with open(file_path, 'r', encoding='utf-8') as f:
imported_settings = json.load(f)
self._merge_settings(imported_settings)
self.save()
return True
except Exception as e:
print(f"Error importing settings: {e}")
return False
# Global settings instance
_settings_instance = None
def get_settings() -> Settings:
"""Get the global settings instance."""
global _settings_instance
if _settings_instance is None:
_settings_instance = Settings()
return _settings_instance