# Copyright (c) 2026 Krzysztof Rudnicki. MIT licence. """The one mutable box the pack lives in, so a new photo is visible at once. ``pack_command.pack_handler`` used to close over a frozen ``Pack``. That was right while the only writer was ``tool/tagger.py`` and a restart followed every change; with ``!add_photo`` it means a photo somebody just added stays invisible to their own command until the unit is restarted. Every pack handler therefore reads ``holder.pack`` at call time instead of capturing it. What a swap may and may not change is the load-bearing part. Custom tags never become commands, so a swap only ever replaces *Pack data* -- it never re-registers a trigger and never recomputes ``Command.groups``. That is what makes immediate availability cheap, and it is the property to preserve: the moment a swap could add a command, it could also add one to the wrong group. ``reload`` never raises. A handler that raised here would take the exception to the dispatcher, which logs and moves on -- but the album would already be half-swapped, and ``Restart=always`` turns a startup-time ConfigError into a loop. Failing back onto the pack that was already working is the only behaviour that degrades safely. """ from __future__ import annotations import logging from dataclasses import dataclass from typing import TYPE_CHECKING from signal_bot.groups import ConfigError from signal_bot.pack_load import load if TYPE_CHECKING: from signal_bot.pack import Pack logger = logging.getLogger(__name__) @dataclass(slots=True) class PackHolder: """A mutable reference to the album every handler reads through.""" pack: Pack def reload(self) -> bool: """Re-read the album from disk and publish it if it is servable. Returns: True when the new pack is live, False when the old one is still being served because the directory no longer loads. """ directory = self.pack.directory try: fresh = load(directory) except ConfigError: # The path that keeps the bot alive after a bad write: the files # are rolled back by the caller and the album in memory is the # one that was already answering. logger.exception("photo pack %s no longer loads; keeping the previous one", directory) return False self.pack = fresh return True