diff --git a/server/src/wled_controller/api/routes/picture_targets.py b/server/src/wled_controller/api/routes/picture_targets.py index c32db81..1f5cae2 100644 --- a/server/src/wled_controller/api/routes/picture_targets.py +++ b/server/src/wled_controller/api/routes/picture_targets.py @@ -191,25 +191,10 @@ async def create_target( ) # Register in processor manager - if isinstance(target, WledPictureTarget) and target.device_id: - try: - manager.add_target( - target_id=target.id, - device_id=target.device_id, - settings=target.settings, - picture_source_id=target.picture_source_id, - ) - except ValueError as e: - logger.warning(f"Could not register target {target.id} in processor manager: {e}") - elif isinstance(target, KeyColorsPictureTarget): - try: - manager.add_kc_target( - target_id=target.id, - picture_source_id=target.picture_source_id, - settings=target.settings, - ) - except ValueError as e: - logger.warning(f"Could not register KC target {target.id}: {e}") + try: + target.register_with_manager(manager) + except ValueError as e: + logger.warning(f"Could not register target {target.id} in processor manager: {e}") return _target_to_response(target) @@ -279,14 +264,14 @@ async def update_target( description=data.description, ) - # Sync processor manager (unified API handles both target types) + # Sync processor manager try: - if data.settings is not None or data.key_colors_settings is not None: - manager.update_target_settings(target_id, target.settings) - if data.picture_source_id is not None: - manager.update_target_source(target_id, target.picture_source_id) - if data.device_id is not None and isinstance(target, WledPictureTarget): - manager.update_target_device(target_id, target.device_id) + target.sync_with_manager( + manager, + settings_changed=data.settings is not None or data.key_colors_settings is not None, + source_changed=data.picture_source_id is not None, + device_changed=data.device_id is not None, + ) except ValueError: pass diff --git a/server/src/wled_controller/main.py b/server/src/wled_controller/main.py index d5ee020..b26b1b4 100644 --- a/server/src/wled_controller/main.py +++ b/server/src/wled_controller/main.py @@ -23,8 +23,6 @@ from wled_controller.storage.postprocessing_template_store import Postprocessing from wled_controller.storage.pattern_template_store import PatternTemplateStore from wled_controller.storage.picture_source_store import PictureSourceStore from wled_controller.storage.picture_target_store import PictureTargetStore -from wled_controller.storage.wled_picture_target import WledPictureTarget -from wled_controller.storage.key_colors_picture_target import KeyColorsPictureTarget from wled_controller.storage.profile_store import ProfileStore from wled_controller.core.profiles.profile_engine import ProfileEngine from wled_controller.utils import setup_logging, get_logger @@ -182,29 +180,12 @@ async def lifespan(app: FastAPI): targets = picture_target_store.get_all_targets() registered_targets = 0 for target in targets: - if isinstance(target, WledPictureTarget) and target.device_id: - try: - processor_manager.add_target( - target_id=target.id, - device_id=target.device_id, - settings=target.settings, - picture_source_id=target.picture_source_id, - ) - registered_targets += 1 - logger.info(f"Registered target: {target.name} ({target.id})") - except Exception as e: - logger.error(f"Failed to register target {target.id}: {e}") - elif isinstance(target, KeyColorsPictureTarget): - try: - processor_manager.add_kc_target( - target_id=target.id, - picture_source_id=target.picture_source_id, - settings=target.settings, - ) - registered_targets += 1 - logger.info(f"Registered KC target: {target.name} ({target.id})") - except Exception as e: - logger.error(f"Failed to register KC target {target.id}: {e}") + try: + target.register_with_manager(processor_manager) + registered_targets += 1 + logger.info(f"Registered target: {target.name} ({target.id})") + except Exception as e: + logger.error(f"Failed to register target {target.id}: {e}") logger.info(f"Registered {registered_targets} picture target(s)") diff --git a/server/src/wled_controller/storage/key_colors_picture_target.py b/server/src/wled_controller/storage/key_colors_picture_target.py index 203d9b5..36ee8f0 100644 --- a/server/src/wled_controller/storage/key_colors_picture_target.py +++ b/server/src/wled_controller/storage/key_colors_picture_target.py @@ -74,6 +74,34 @@ class KeyColorsPictureTarget(PictureTarget): picture_source_id: str = "" settings: KeyColorsSettings = field(default_factory=KeyColorsSettings) + def register_with_manager(self, manager) -> None: + """Register this KC target with the processor manager.""" + manager.add_kc_target( + target_id=self.id, + picture_source_id=self.picture_source_id, + settings=self.settings, + ) + + def sync_with_manager(self, manager, *, settings_changed: bool, source_changed: bool, device_changed: bool) -> None: + """Push changed fields to the processor manager.""" + if settings_changed: + manager.update_target_settings(self.id, self.settings) + if source_changed: + manager.update_target_source(self.id, self.picture_source_id) + + def update_fields(self, *, name=None, device_id=None, picture_source_id=None, + settings=None, key_colors_settings=None, description=None) -> None: + """Apply mutable field updates for KC targets.""" + super().update_fields(name=name, description=description) + if picture_source_id is not None: + self.picture_source_id = picture_source_id + if key_colors_settings is not None: + self.settings = key_colors_settings + + @property + def has_picture_source(self) -> bool: + return True + def to_dict(self) -> dict: d = super().to_dict() d["picture_source_id"] = self.picture_source_id diff --git a/server/src/wled_controller/storage/picture_target.py b/server/src/wled_controller/storage/picture_target.py index 43598f8..4c24002 100644 --- a/server/src/wled_controller/storage/picture_target.py +++ b/server/src/wled_controller/storage/picture_target.py @@ -16,6 +16,27 @@ class PictureTarget: updated_at: datetime description: Optional[str] = None + def register_with_manager(self, manager) -> None: + """Register this target with the processor manager. Subclasses override.""" + pass + + def sync_with_manager(self, manager, *, settings_changed: bool, source_changed: bool, device_changed: bool) -> None: + """Push changed fields to a running processor. Subclasses override.""" + pass + + def update_fields(self, *, name=None, device_id=None, picture_source_id=None, + settings=None, key_colors_settings=None, description=None) -> None: + """Apply mutable field updates. Base handles common fields; subclasses handle type-specific ones.""" + if name is not None: + self.name = name + if description is not None: + self.description = description + + @property + def has_picture_source(self) -> bool: + """Whether this target type uses a picture source.""" + return False + def to_dict(self) -> dict: """Convert to dictionary.""" return { diff --git a/server/src/wled_controller/storage/picture_target_store.py b/server/src/wled_controller/storage/picture_target_store.py index 10d8271..4eb7af9 100644 --- a/server/src/wled_controller/storage/picture_target_store.py +++ b/server/src/wled_controller/storage/picture_target_store.py @@ -190,24 +190,15 @@ class PictureTargetStore: for other in self._targets.values(): if other.id != target_id and other.name == name: raise ValueError(f"Picture target with name '{name}' already exists") - target.name = name - if description is not None: - target.description = description - - if isinstance(target, WledPictureTarget): - if device_id is not None: - target.device_id = device_id - if picture_source_id is not None: - target.picture_source_id = picture_source_id - if settings is not None: - target.settings = settings - - if isinstance(target, KeyColorsPictureTarget): - if picture_source_id is not None: - target.picture_source_id = picture_source_id - if key_colors_settings is not None: - target.settings = key_colors_settings + target.update_fields( + name=name, + device_id=device_id, + picture_source_id=picture_source_id, + settings=settings, + key_colors_settings=key_colors_settings, + description=description, + ) target.updated_at = datetime.utcnow() self._save() @@ -239,7 +230,7 @@ class PictureTargetStore: def is_referenced_by_source(self, source_id: str) -> bool: """Check if any target references a picture source.""" for target in self._targets.values(): - if isinstance(target, (WledPictureTarget, KeyColorsPictureTarget)) and target.picture_source_id == source_id: + if target.has_picture_source and target.picture_source_id == source_id: return True return False diff --git a/server/src/wled_controller/storage/wled_picture_target.py b/server/src/wled_controller/storage/wled_picture_target.py index 2c9dcbb..f33cb0e 100644 --- a/server/src/wled_controller/storage/wled_picture_target.py +++ b/server/src/wled_controller/storage/wled_picture_target.py @@ -16,6 +16,40 @@ class WledPictureTarget(PictureTarget): picture_source_id: str = "" settings: ProcessingSettings = field(default_factory=ProcessingSettings) + def register_with_manager(self, manager) -> None: + """Register this WLED target with the processor manager.""" + if self.device_id: + manager.add_target( + target_id=self.id, + device_id=self.device_id, + settings=self.settings, + picture_source_id=self.picture_source_id, + ) + + def sync_with_manager(self, manager, *, settings_changed: bool, source_changed: bool, device_changed: bool) -> None: + """Push changed fields to the processor manager.""" + if settings_changed: + manager.update_target_settings(self.id, self.settings) + if source_changed: + manager.update_target_source(self.id, self.picture_source_id) + if device_changed: + manager.update_target_device(self.id, self.device_id) + + def update_fields(self, *, name=None, device_id=None, picture_source_id=None, + settings=None, key_colors_settings=None, description=None) -> None: + """Apply mutable field updates for WLED targets.""" + super().update_fields(name=name, description=description) + if device_id is not None: + self.device_id = device_id + if picture_source_id is not None: + self.picture_source_id = picture_source_id + if settings is not None: + self.settings = settings + + @property + def has_picture_source(self) -> bool: + return True + def to_dict(self) -> dict: """Convert to dictionary.""" d = super().to_dict()