Files
wled-screen-controller-mixed/server/src/wled_controller/storage/template_store.py
alexei.dolgolyov 9cfe628cc5 Codebase review fixes: stability, performance, quality improvements
Stability: Add outer try/except/finally with _running=False cleanup to all 6
processing loop methods (live, color_strip, effect, audio, composite, mapped).
Add exponential backoff on consecutive capture errors in live_stream. Move
audio stream.stop() outside lock scope.

Performance: Replace per-pixel Python loop with np.array().tobytes() in
ddp_client. Vectorize pixelate filter with cv2.resize down+up. Vectorize
gradient rendering with np.searchsorted.

Frontend: Add lockBody/unlockBody re-entrancy counter. Add {once:true} to
fetchWithAuth abort listener. Null ws.onclose before ws.close() in LED preview.

Backend: Remove auth token prefix from log messages. Add atomic_write_json
helper (tempfile + os.replace) and update all 10 stores. Add name uniqueness
checks to all update methods. Fix DELETE status codes to 204 in audio_sources
and value_sources. Fix get_source() silent bug in color_strip_sources.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 18:23:04 +03:00

248 lines
7.5 KiB
Python

"""Template storage using JSON files."""
import json
import uuid
from datetime import datetime
from pathlib import Path
from typing import Dict, List, Optional
from wled_controller.core.capture_engines.factory import EngineRegistry
from wled_controller.storage.template import CaptureTemplate
from wled_controller.utils import atomic_write_json, get_logger
logger = get_logger(__name__)
class TemplateStore:
"""Storage for capture templates.
All templates are persisted to the JSON file.
On startup, if no templates exist, one is auto-created using the
highest-priority available engine.
"""
def __init__(self, file_path: str):
"""Initialize template store.
Args:
file_path: Path to templates JSON file
"""
self.file_path = Path(file_path)
self._templates: Dict[str, CaptureTemplate] = {}
self._load()
self._ensure_initial_template()
def _ensure_initial_template(self) -> None:
"""Auto-create a template if none exist, using the best available engine."""
if self._templates:
return
best_engine = EngineRegistry.get_best_available_engine()
if not best_engine:
logger.warning("No capture engines available, cannot create initial template")
return
engine_class = EngineRegistry.get_engine(best_engine)
default_config = engine_class.get_default_config()
now = datetime.utcnow()
template_id = f"tpl_{uuid.uuid4().hex[:8]}"
template = CaptureTemplate(
id=template_id,
name="Default",
engine_type=best_engine,
engine_config=default_config,
created_at=now,
updated_at=now,
description=f"Default capture template using {best_engine.upper()} engine",
)
self._templates[template_id] = template
self._save()
logger.info(f"Auto-created initial template: {template.name} ({template_id}, engine={best_engine})")
def _load(self) -> None:
"""Load templates from file."""
if not self.file_path.exists():
return
try:
with open(self.file_path, "r", encoding="utf-8") as f:
data = json.load(f)
templates_data = data.get("templates", {})
loaded = 0
for template_id, template_dict in templates_data.items():
try:
template = CaptureTemplate.from_dict(template_dict)
self._templates[template_id] = template
loaded += 1
except Exception as e:
logger.error(
f"Failed to load template {template_id}: {e}",
exc_info=True
)
if loaded > 0:
logger.info(f"Loaded {loaded} templates from storage")
except Exception as e:
logger.error(f"Failed to load templates from {self.file_path}: {e}")
raise
logger.info(f"Template store initialized with {len(self._templates)} templates")
def _save(self) -> None:
"""Save all templates to file."""
try:
data = {
"version": "1.0.0",
"templates": {
template_id: template.to_dict()
for template_id, template in self._templates.items()
},
}
atomic_write_json(self.file_path, data)
except Exception as e:
logger.error(f"Failed to save templates to {self.file_path}: {e}")
raise
def get_all_templates(self) -> List[CaptureTemplate]:
"""Get all templates.
Returns:
List of all templates
"""
return list(self._templates.values())
def get_template(self, template_id: str) -> CaptureTemplate:
"""Get template by ID.
Args:
template_id: Template ID
Returns:
Template instance
Raises:
ValueError: If template not found
"""
if template_id not in self._templates:
raise ValueError(f"Template not found: {template_id}")
return self._templates[template_id]
def create_template(
self,
name: str,
engine_type: str,
engine_config: Dict[str, any],
description: Optional[str] = None,
) -> CaptureTemplate:
"""Create a new template.
Args:
name: Template name
engine_type: Engine type (mss, dxcam, wgc)
engine_config: Engine-specific configuration
description: Optional description
Returns:
Created template
Raises:
ValueError: If template with same name exists
"""
# Check for duplicate name
for template in self._templates.values():
if template.name == name:
raise ValueError(f"Template with name '{name}' already exists")
# Generate new ID
template_id = f"tpl_{uuid.uuid4().hex[:8]}"
# Create template
now = datetime.utcnow()
template = CaptureTemplate(
id=template_id,
name=name,
engine_type=engine_type,
engine_config=engine_config,
created_at=now,
updated_at=now,
description=description,
)
# Store and save
self._templates[template_id] = template
self._save()
logger.info(f"Created template: {name} ({template_id})")
return template
def update_template(
self,
template_id: str,
name: Optional[str] = None,
engine_type: Optional[str] = None,
engine_config: Optional[Dict[str, any]] = None,
description: Optional[str] = None,
) -> CaptureTemplate:
"""Update an existing template.
Args:
template_id: Template ID
name: New name (optional)
engine_type: New engine type (optional)
engine_config: New engine config (optional)
description: New description (optional)
Returns:
Updated template
Raises:
ValueError: If template not found
"""
if template_id not in self._templates:
raise ValueError(f"Template not found: {template_id}")
template = self._templates[template_id]
# Update fields
if name is not None:
for tid, t in self._templates.items():
if tid != template_id and t.name == name:
raise ValueError(f"Template with name '{name}' already exists")
template.name = name
if engine_type is not None:
template.engine_type = engine_type
if engine_config is not None:
template.engine_config = engine_config
if description is not None:
template.description = description
template.updated_at = datetime.utcnow()
# Save
self._save()
logger.info(f"Updated template: {template_id}")
return template
def delete_template(self, template_id: str) -> None:
"""Delete a template.
Args:
template_id: Template ID
Raises:
ValueError: If template not found
"""
if template_id not in self._templates:
raise ValueError(f"Template not found: {template_id}")
# Remove and save
del self._templates[template_id]
self._save()
logger.info(f"Deleted template: {template_id}")