From 87d2a365a2620eae4eb32cb71bc81d26c64eb628 Mon Sep 17 00:00:00 2001 From: Sassan Haradji Date: Tue, 3 Sep 2024 18:32:08 +0400 Subject: [PATCH] refactor(housekeeping): improve store imports --- CHANGELOG.md | 1 + tests/flows/test_wireless.py | 32 ++++++------- ubo_app/load_services.py | 4 +- ubo_app/main.py | 4 +- ubo_app/menu_app/home_page.py | 6 ++- ubo_app/menu_app/menu_central.py | 34 ++++++++----- ubo_app/menu_app/menu_footer.py | 10 ++-- ubo_app/menu_app/menu_header.py | 4 +- ubo_app/menu_app/menu_notification_handler.py | 22 +++++---- ubo_app/service.py | 4 +- ubo_app/services/000-audio/audio_manager.py | 4 +- ubo_app/services/000-audio/setup.py | 14 +++--- ubo_app/services/000-keypad/setup.py | 8 ++-- ubo_app/services/010-voice/setup.py | 28 +++++------ ubo_app/services/020-keyboard/setup.py | 30 ++++++------ ubo_app/services/030-ethernet/setup.py | 4 +- ubo_app/services/030-ip/setup.py | 12 ++--- .../pages/create_wireless_connection.py | 12 ++--- ubo_app/services/030-wifi/pages/main.py | 10 ++-- ubo_app/services/030-wifi/setup.py | 14 +++--- ubo_app/services/030-wifi/wifi_manager.py | 4 +- ubo_app/services/040-camera/setup.py | 12 ++--- .../services/040-rgb-ring/rgb_ring_client.py | 6 +-- ubo_app/services/040-rgb-ring/setup.py | 6 +-- ubo_app/services/040-sensors/setup.py | 6 +-- ubo_app/services/050-lightdm/setup.py | 24 +++++----- ubo_app/services/050-rpi-connect/commands.py | 26 +++++----- ubo_app/services/050-rpi-connect/setup.py | 8 ++-- .../services/050-rpi-connect/sign_in_page.py | 10 ++-- ubo_app/services/050-ssh/setup.py | 32 ++++++------- ubo_app/services/050-vscode/commands.py | 20 ++++---- ubo_app/services/050-vscode/login_page.py | 10 ++-- ubo_app/services/050-vscode/setup.py | 14 +++--- ubo_app/services/080-docker/image_.py | 48 ++++++++++--------- ubo_app/services/080-docker/setup.py | 34 ++++++------- ubo_app/setup.py | 8 ++-- ubo_app/side_effects.py | 26 ++++------ ubo_app/store/core/_menus.py | 14 +++--- ubo_app/store/main.py | 46 ++++++++---------- ubo_app/store/update_manager/utils.py | 34 ++++++------- ubo_app/utils/bus_provider.py | 4 +- ubo_app/utils/persistent_store.py | 6 +-- ubo_app/utils/qrcode.py | 12 ++--- 43 files changed, 338 insertions(+), 329 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b814c72..1b729cc0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - fix(lightdm): install raspberrypi-ui-mods instead of lightdm to activate wayland and enable rpi-connect screen sharing - test: fix an issue in tests which caused minor random store state changes, ruining snapshot assertions - test: add vscode and rpi-connect services to `test_all_services` test +- refactor(housekeeping): improve store imports ## Version 0.15.11 diff --git a/tests/flows/test_wireless.py b/tests/flows/test_wireless.py index 01f035fe..11e496cf 100644 --- a/tests/flows/test_wireless.py +++ b/tests/flows/test_wireless.py @@ -53,7 +53,7 @@ async def strength() -> int: from ubo_app.menu_app.menu import MenuApp from ubo_app.store.core import ChooseMenuItemByIconEvent, ChooseMenuItemByLabelEvent - from ubo_app.store.main import dispatch, store + from ubo_app.store.main import store from ubo_app.store.services.keypad import Key, KeypadKeyPressAction def store_snapshot_selector(state: RootState) -> WiFiState: @@ -83,32 +83,32 @@ def check_icon(expected_icon: str) -> None: store_snapshot.take(selector=store_snapshot_selector) # Select the main menu - dispatch(ChooseMenuItemByIconEvent(icon='󰍜')) + store.dispatch(ChooseMenuItemByIconEvent(icon='󰍜')) await stability() # Select the settings menu - dispatch(ChooseMenuItemByLabelEvent(label='Settings')) + store.dispatch(ChooseMenuItemByLabelEvent(label='Settings')) await stability() # Go to network category - dispatch(ChooseMenuItemByLabelEvent(label='Network')) + store.dispatch(ChooseMenuItemByLabelEvent(label='Network')) await stability() # Open the wireless menu - dispatch(ChooseMenuItemByLabelEvent(label='WiFi')) + store.dispatch(ChooseMenuItemByLabelEvent(label='WiFi')) await stability() window_snapshot.take() # Select "Select" to open the wireless connection list - dispatch(ChooseMenuItemByLabelEvent(label='Select')) + store.dispatch(ChooseMenuItemByLabelEvent(label='Select')) await stability() # Back to the wireless menu - dispatch(KeypadKeyPressAction(key=Key.BACK)) + store.dispatch(KeypadKeyPressAction(key=Key.BACK)) await stability() # Select "Add" to add a new connection - dispatch(ChooseMenuItemByLabelEvent(label='Add')) + store.dispatch(ChooseMenuItemByLabelEvent(label='Add')) await stability() window_snapshot.take() @@ -116,7 +116,7 @@ def check_icon(expected_icon: str) -> None: camera.set_image('qrcode/wifi') # Select "QR code" to scan a QR code for credentials - dispatch(ChooseMenuItemByIconEvent(icon='󰄀')) + store.dispatch(ChooseMenuItemByIconEvent(icon='󰄀')) # Success notification should be shown window_snapshot.take() @@ -124,11 +124,11 @@ def check_icon(expected_icon: str) -> None: # Dismiss the notification informing the user that the connection was added await check_icon('󰤨') await wait_for_menu_item(label='', icon='󰆴') - dispatch(ChooseMenuItemByIconEvent(icon='󰆴')) + store.dispatch(ChooseMenuItemByIconEvent(icon='󰆴')) await stability() # Select "Select" to open the wireless connection list and see the new connection - dispatch(ChooseMenuItemByLabelEvent(label='Select')) + store.dispatch(ChooseMenuItemByLabelEvent(label='Select')) @wait_for(wait=wait_fixed(1), run_async=True) def check_connections() -> None: @@ -143,13 +143,13 @@ def check_connections() -> None: window_snapshot.take() # Select the connection - dispatch(ChooseMenuItemByLabelEvent(label='ubo-test-ssid')) + store.dispatch(ChooseMenuItemByLabelEvent(label='ubo-test-ssid')) # Wait for the "Disconnect" item to show up await wait_for_menu_item(label='Disconnect') await stability() window_snapshot.take() - dispatch(ChooseMenuItemByLabelEvent(label='Disconnect')) + store.dispatch(ChooseMenuItemByLabelEvent(label='Disconnect')) # Wait for the "Connect" item to show up await wait_for_menu_item(label='Connect') @@ -157,14 +157,14 @@ def check_connections() -> None: await stability() store_snapshot.take(selector=store_snapshot_selector) window_snapshot.take() - dispatch(ChooseMenuItemByLabelEvent(label='Connect')) + store.dispatch(ChooseMenuItemByLabelEvent(label='Connect')) await wait_for_menu_item(label='Disconnect') await check_icon('󰤨') await stability() store_snapshot.take(selector=store_snapshot_selector) window_snapshot.take() - dispatch(ChooseMenuItemByLabelEvent(label='Delete')) + store.dispatch(ChooseMenuItemByLabelEvent(label='Delete')) @wait_for(wait=wait_fixed(1), run_async=True) def check_no_connections() -> None: @@ -179,7 +179,7 @@ def check_no_connections() -> None: # Dismiss the notification informing the user that the connection was deleted await wait_for_menu_item(label='', icon='󰆴') window_snapshot.take() - dispatch(ChooseMenuItemByIconEvent(icon='󰆴')) + store.dispatch(ChooseMenuItemByIconEvent(icon='󰆴')) await wait_for_empty_menu(placeholder='No Wi-Fi connections found') window_snapshot.take() diff --git a/ubo_app/load_services.py b/ubo_app/load_services.py index 3fdcd73b..a5255626 100644 --- a/ubo_app/load_services.py +++ b/ubo_app/load_services.py @@ -176,7 +176,7 @@ def __init__( self.module = None def register_reducer(self: UboServiceThread, reducer: ReducerType) -> None: - from ubo_app.store.main import dispatch, root_reducer_id + from ubo_app.store.main import root_reducer_id, store logger.info( 'Registering ubo service reducer', @@ -187,7 +187,7 @@ def register_reducer(self: UboServiceThread, reducer: ReducerType) -> None: }, ) - dispatch( + store.dispatch( CombineReducerRegisterAction( _id=root_reducer_id, key=self.service_id, diff --git a/ubo_app/main.py b/ubo_app/main.py index 1db6fe90..ae858134 100644 --- a/ubo_app/main.py +++ b/ubo_app/main.py @@ -70,9 +70,9 @@ def main() -> None: logger.exception('An error occurred while running the app.') from redux import FinishAction - from ubo_app.store.main import dispatch + from ubo_app.store.main import store - dispatch(FinishAction()) + store.dispatch(FinishAction()) if __name__ == '__main__': diff --git a/ubo_app/menu_app/home_page.py b/ubo_app/menu_app/home_page.py index de8343fb..33563e52 100644 --- a/ubo_app/menu_app/home_page.py +++ b/ubo_app/menu_app/home_page.py @@ -12,7 +12,7 @@ from ubo_gui.page import PageWidget from ubo_gui.volume import VolumeWidget -from ubo_app.store.main import autorun +from ubo_app.store.main import store if TYPE_CHECKING: from collections.abc import Sequence @@ -41,7 +41,9 @@ def __init__( self.volume_widget = VolumeWidget() self.ids.right_column.add_widget(self.volume_widget) - autorun(lambda state: state.audio.playback_volume)(self._sync_output_volume) + store.autorun(lambda state: state.audio.playback_volume)( + self._sync_output_volume, + ) def _sync_output_volume(self: HomePage, selector_result: float) -> None: self.volume_widget.value = selector_result * 100 diff --git a/ubo_app/menu_app/menu_central.py b/ubo_app/menu_app/menu_central.py index 85496405..c85fa73c 100644 --- a/ubo_app/menu_app/menu_central.py +++ b/ubo_app/menu_app/menu_central.py @@ -22,7 +22,7 @@ OpenApplicationEvent, SetMenuPathAction, ) -from ubo_app.store.main import autorun, dispatch, subscribe_event +from ubo_app.store.main import store from ubo_app.store.services.keypad import Key, KeypadKeyPressEvent from ubo_app.store.services.notifications import NotificationsDisplayEvent from ubo_app.store.update_manager import UpdateManagerSetUpdateServiceStatusAction @@ -54,7 +54,7 @@ def _render_menu(self: MenuWidgetWithHomePage, menu: Menu) -> PageWidget | None: def set_path(_: MenuWidget, stack: list[StackItem]) -> None: - dispatch( + store.dispatch( SetMenuPathAction( path=[ stack_item.selection.key @@ -76,7 +76,7 @@ def __init__(self: MenuAppCentral, **kwargs: object) -> None: _self = weakref.ref(self) - @autorun(lambda state: state.main.menu) + @store.autorun(lambda state: state.main.menu) @debounce(0.1, DebounceOptions(leading=True, trailing=True, time_window=0.1)) @mainthread def _(menu: Menu | None) -> None: @@ -91,7 +91,7 @@ def build(self: UboApp) -> Widget | None: self.menu_widget.padding_bottom = root.ids.footer_layout.height def check_update(status: str) -> None: - dispatch( + store.dispatch( UpdateManagerSetUpdateServiceStatusAction( is_active=status in ('active', 'activating', 'reloading'), ), @@ -129,27 +129,39 @@ def central(self: MenuAppCentral) -> Widget | None: menu_representation = 'Menu:\n' + repr(self.menu_widget) self.menu_widget.bind(stack=lambda *_: logger.info(menu_representation)) - subscribe_event( + store.subscribe_event( KeypadKeyPressEvent, self.handle_key_press_event, keep_ref=False, ) - subscribe_event( + store.subscribe_event( NotificationsDisplayEvent, self.display_notification, keep_ref=False, ) - subscribe_event(OpenApplicationEvent, self.open_application, keep_ref=False) - subscribe_event(CloseApplicationEvent, self.close_application, keep_ref=False) - subscribe_event(ChooseMenuItemByIconEvent, self.select_by_icon, keep_ref=False) - subscribe_event( + store.subscribe_event( + OpenApplicationEvent, + self.open_application, + keep_ref=False, + ) + store.subscribe_event( + CloseApplicationEvent, + self.close_application, + keep_ref=False, + ) + store.subscribe_event( + ChooseMenuItemByIconEvent, + self.select_by_icon, + keep_ref=False, + ) + store.subscribe_event( ChooseMenuItemByLabelEvent, self.select_by_label, keep_ref=False, ) - subscribe_event( + store.subscribe_event( ChooseMenuItemByIndexEvent, self.select_by_index, keep_ref=False, diff --git a/ubo_app/menu_app/menu_footer.py b/ubo_app/menu_app/menu_footer.py index 7e9a74de..8f996b48 100644 --- a/ubo_app/menu_app/menu_footer.py +++ b/ubo_app/menu_app/menu_footer.py @@ -14,7 +14,7 @@ from redux import AutorunOptions from ubo_gui.app import UboApp -from ubo_app.store.main import autorun +from ubo_app.store.main import store if TYPE_CHECKING: from collections.abc import Sequence @@ -48,7 +48,7 @@ def temperature_widget(self: MenuAppFooter) -> BoxLayout: or setattr(layout, 'width', temperature.width + dp(12)), ) - autorun( + store.autorun( lambda state: state.sensors.temperature.value, options=AutorunOptions(keep_ref=False), )(self.set_temperature_value) @@ -91,7 +91,7 @@ def light_widget(self: MenuAppFooter) -> Label: ), ) - autorun( + store.autorun( lambda state: state.sensors.light.value, options=AutorunOptions(keep_ref=False), )(self.set_light_value) @@ -216,12 +216,12 @@ def footer(self: MenuAppFooter) -> Widget | None: x=self.set_icons_layout_x, ) - autorun( + store.autorun( lambda state: state.status_icons.icons, options=AutorunOptions(keep_ref=False), )(self.render_icons) - autorun( + store.autorun( lambda state: state.main.path, options=AutorunOptions(keep_ref=False), )(self.handle_depth_change) diff --git a/ubo_app/menu_app/menu_header.py b/ubo_app/menu_app/menu_header.py index 905caef1..661fb37a 100644 --- a/ubo_app/menu_app/menu_header.py +++ b/ubo_app/menu_app/menu_header.py @@ -10,7 +10,7 @@ from ubo_gui.app import UboApp from ubo_gui.progress_ring import ProgressRingWidget -from ubo_app.store.main import autorun +from ubo_app.store.main import store if TYPE_CHECKING: from kivy.uix.widget import Widget @@ -37,7 +37,7 @@ def header(self: MenuAppHeader) -> Widget | None: ) self.header_layout.add_widget(progress_layout) - @autorun( + @store.autorun( lambda state: [ notification for notification in state.notifications.notifications diff --git a/ubo_app/menu_app/menu_notification_handler.py b/ubo_app/menu_app/menu_notification_handler.py index efad3d5f..2f09ee38 100644 --- a/ubo_app/menu_app/menu_notification_handler.py +++ b/ubo_app/menu_app/menu_notification_handler.py @@ -15,7 +15,7 @@ from ubo_app.menu_app.notification_info import NotificationInfo from ubo_app.store.core import CloseApplicationEvent, OpenApplicationEvent -from ubo_app.store.main import dispatch, subscribe_event +from ubo_app.store.main import store from ubo_app.store.services.notifications import ( Notification, NotificationActionItem, @@ -74,9 +74,11 @@ def close(_: object = None) -> None: for unsubscribe in subscriptions: unsubscribe() notification_application.unbind(on_close=close) - dispatch(CloseApplicationEvent(application=notification_application)) + store.dispatch(CloseApplicationEvent(application=notification_application)) if notification.value.dismiss_on_close: - dispatch(NotificationsClearAction(notification=notification.value)) + store.dispatch( + NotificationsClearAction(notification=notification.value), + ) if notification.value.on_close: notification.value.on_close() @@ -108,7 +110,7 @@ def renew_notification(event: NotificationsDisplayEvent) -> None: != notification.value.extra_information ): notification.is_initialized = True - dispatch( + store.dispatch( VoiceReadTextAction( text=event.notification.extra_information.text, piper_text=event.notification.extra_information.piper_text, @@ -128,14 +130,14 @@ def renew_notification(event: NotificationsDisplayEvent) -> None: notification_application.bind(on_close=close) subscriptions.append( - subscribe_event( + store.subscribe_event( NotificationsClearEvent, clear_notification, ), ) if notification.value.id is not None: subscriptions.append( - subscribe_event( + store.subscribe_event( NotificationsDisplayEvent, renew_notification, ), @@ -143,7 +145,7 @@ def renew_notification(event: NotificationsDisplayEvent) -> None: renew_notification(event) - dispatch(OpenApplicationEvent(application=notification_application)) + store.dispatch(OpenApplicationEvent(application=notification_application)) def _notification_items( self: MenuNotificationHandler, @@ -153,7 +155,9 @@ def _notification_items( def dismiss(_: object = None) -> None: close() if not notification.value.dismiss_on_close: - dispatch(NotificationsClearAction(notification=notification.value)) + store.dispatch( + NotificationsClearAction(notification=notification.value), + ) def run_notification_action(action: NotificationActionItem) -> None: result = action.action() @@ -171,7 +175,7 @@ def run_notification_action(action: NotificationActionItem) -> None: def open_info() -> None: info_application = NotificationInfo(text=text) - dispatch(OpenApplicationEvent(application=info_application)) + store.dispatch(OpenApplicationEvent(application=info_application)) items.append( NotificationActionItem( diff --git a/ubo_app/service.py b/ubo_app/service.py index 6fe369e8..84454c01 100644 --- a/ubo_app/service.py +++ b/ubo_app/service.py @@ -114,13 +114,13 @@ def start_event_loop_thread(loop: asyncio.AbstractEventLoop) -> None: from redux.basic_types import FinishEvent - from ubo_app.store.main import subscribe_event + from ubo_app.store.main import store def stop() -> None: unsubscribe() worker_thread.stop() - unsubscribe = subscribe_event(FinishEvent, stop) + unsubscribe = store.subscribe_event(FinishEvent, stop) def _create_task( diff --git a/ubo_app/services/000-audio/audio_manager.py b/ubo_app/services/000-audio/audio_manager.py index 97820f97..3b0170ec 100644 --- a/ubo_app/services/000-audio/audio_manager.py +++ b/ubo_app/services/000-audio/audio_manager.py @@ -11,7 +11,7 @@ from simpleaudio import _simpleaudio # pyright: ignore [reportAttributeAccessIssue] from ubo_app.logging import logger -from ubo_app.store.main import dispatch +from ubo_app.store.main import store from ubo_app.store.services.audio import AudioPlaybackDoneEvent from ubo_app.utils.async_ import create_task from ubo_app.utils.server import send_command @@ -135,7 +135,7 @@ async def play_sequence( extra={'tried_times': TRIALS}, ) if id is not None: - dispatch(AudioPlaybackDoneEvent(id=id)) + store.dispatch(AudioPlaybackDoneEvent(id=id)) def set_playback_mute(self: AudioManager, *, mute: bool = False) -> None: """Set the playback mute of the audio output. diff --git a/ubo_app/services/000-audio/setup.py b/ubo_app/services/000-audio/setup.py index 5fa6e4a7..f627004c 100644 --- a/ubo_app/services/000-audio/setup.py +++ b/ubo_app/services/000-audio/setup.py @@ -8,7 +8,7 @@ from audio_manager import AudioManager from constants import AUDIO_MIC_STATE_ICON_ID, AUDIO_MIC_STATE_ICON_PRIORITY -from ubo_app.store.main import autorun, dispatch, subscribe_event +from ubo_app.store.main import store from ubo_app.store.services.audio import AudioPlayAudioEvent, AudioPlayChimeEvent from ubo_app.store.status_icons import StatusIconsRegisterAction from ubo_app.utils.async_ import to_thread @@ -34,7 +34,7 @@ def _run_async_in_thread( def init_service() -> None: audio_manager = AudioManager() - dispatch( + store.dispatch( StatusIconsRegisterAction( icon='󰍭', priority=AUDIO_MIC_STATE_ICON_PRIORITY, @@ -42,26 +42,26 @@ def init_service() -> None: ), ) - @autorun(lambda state: state.audio.playback_volume) + @store.autorun(lambda state: state.audio.playback_volume) def _(volume: float) -> None: audio_manager.set_playback_volume(volume) - @autorun(lambda state: state.audio.capture_volume) + @store.autorun(lambda state: state.audio.capture_volume) def _(volume: float) -> None: audio_manager.set_capture_volume(volume) - @autorun(lambda state: state.audio.is_playback_mute) + @store.autorun(lambda state: state.audio.is_playback_mute) def _(is_mute: bool) -> None: # noqa: FBT001 audio_manager.set_playback_mute(mute=is_mute) - subscribe_event( + store.subscribe_event( AudioPlayChimeEvent, lambda event: audio_manager.play_file( Path(__file__).parent.joinpath(f'sounds/{event.name}.wav').as_posix(), ), ) - subscribe_event( + store.subscribe_event( AudioPlayAudioEvent, lambda event: to_thread( _run_async_in_thread, diff --git a/ubo_app/services/000-keypad/setup.py b/ubo_app/services/000-keypad/setup.py index d621cddf..89faff6f 100644 --- a/ubo_app/services/000-keypad/setup.py +++ b/ubo_app/services/000-keypad/setup.py @@ -206,15 +206,15 @@ def key_press_cb(self: Keypad, _: object) -> None: @staticmethod def on_button_event(*, index: int, status: ButtonStatus) -> None: - from ubo_app.store.main import dispatch + from ubo_app.store.main import store if index in KEY_INDEX: if status == 'pressed': - dispatch(KeypadKeyPressAction(key=KEY_INDEX[index])) + store.dispatch(KeypadKeyPressAction(key=KEY_INDEX[index])) elif status == 'released': - dispatch(KeypadKeyReleaseAction(key=KEY_INDEX[index])) + store.dispatch(KeypadKeyReleaseAction(key=KEY_INDEX[index])) if index == MIC_INDEX: - dispatch( + store.dispatch( AudioSetMuteStatusAction( device=AudioDevice.INPUT, is_mute=status == 'pressed', diff --git a/ubo_app/services/010-voice/setup.py b/ubo_app/services/010-voice/setup.py index e5d296e6..ad380248 100644 --- a/ubo_app/services/010-voice/setup.py +++ b/ubo_app/services/010-voice/setup.py @@ -17,7 +17,7 @@ from ubo_app.constants import PICOVOICE_ACCESS_KEY from ubo_app.store.core import RegisterSettingAppAction, SettingsCategory -from ubo_app.store.main import autorun, dispatch, subscribe_event, view +from ubo_app.store.main import store from ubo_app.store.services.audio import AudioPlayAudioAction, AudioPlaybackDoneEvent from ubo_app.store.services.voice import ( VoiceEngine, @@ -41,14 +41,14 @@ class _Context: picovoice_lock = fasteners.ReaderWriterLock() def cleanup(self: _Context) -> None: - dispatch(VoiceUpdateAccessKeyStatus(is_access_key_set=False)) + store.dispatch(VoiceUpdateAccessKeyStatus(is_access_key_set=False)) with self.picovoice_lock.write_lock(): if self.picovoice_instance: self.picovoice_instance.delete() self.picovoice_instance = None def set_access_key(self: _Context, access_key: str) -> None: - dispatch(VoiceUpdateAccessKeyStatus(is_access_key_set=True)) + store.dispatch(VoiceUpdateAccessKeyStatus(is_access_key_set=True)) with self.picovoice_lock.write_lock(): if access_key: if self.picovoice_instance: @@ -93,7 +93,7 @@ def clear_access_key() -> None: to_thread(_context.cleanup) -@view(lambda state: state.voice.selected_engine) +@store.view(lambda state: state.voice.selected_engine) def _engine(engine: VoiceEngine) -> VoiceEngine: return engine @@ -119,7 +119,7 @@ def synthesize_and_play(event: VoiceSynthesizeTextEvent) -> None: piper_cache[text] = [] is_first_time = True - unsubscribe = subscribe_event( + unsubscribe = store.subscribe_event( AudioPlaybackDoneEvent, lambda event: event.id == id and queue.get(), ) @@ -128,7 +128,7 @@ def synthesize_and_play(event: VoiceSynthesizeTextEvent) -> None: if is_first_time: piper_cache[text].append(sample) queue.put(None) - dispatch( + store.dispatch( AudioPlayAudioAction( id=id, sample=sample, @@ -149,7 +149,7 @@ def synthesize_and_play(event: VoiceSynthesizeTextEvent) -> None: speech_rate=event.speech_rate, ) sample = b''.join(struct.pack('h', sample) for sample in audio_sequence[0]) - dispatch( + store.dispatch( AudioPlayAudioAction( sample=sample, channels=1, @@ -159,7 +159,7 @@ def synthesize_and_play(event: VoiceSynthesizeTextEvent) -> None: ) -@autorun(lambda state: state.voice.is_access_key_set) +@store.autorun(lambda state: state.voice.is_access_key_set) def _menu_items(is_access_key_set: bool | None) -> Sequence[ActionItem]: if is_access_key_set: return [ @@ -178,7 +178,7 @@ def _menu_items(is_access_key_set: bool | None) -> Sequence[ActionItem]: ] -@autorun(lambda state: state.voice.is_access_key_set) +@store.autorun(lambda state: state.voice.is_access_key_set) def _menu_sub_heading(_: bool | None) -> str: return f"""Set the access key Current value: {secrets.read_covered_secret(PICOVOICE_ACCESS_KEY)}""" @@ -190,7 +190,7 @@ def _menu_sub_heading(_: bool | None) -> str: } -@autorun(lambda state: state.voice.selected_engine) +@store.autorun(lambda state: state.voice.selected_engine) def _voice_engine_items(selected_engine: VoiceEngine) -> Sequence[ActionItem]: selected_engine_parameters = { 'background_color': SUCCESS_COLOR, @@ -216,7 +216,7 @@ def create_engine_selector(engine: VoiceEngine) -> Callable[[], None]: """Select the voice engine.""" def _engine_selector() -> None: - dispatch( + store.dispatch( VoiceSetEngineAction(engine=engine), VoiceReadTextAction( text={ @@ -245,12 +245,12 @@ def init_service() -> None: to_thread(_context.load_piper) - subscribe_event( + store.subscribe_event( VoiceSynthesizeTextEvent, lambda event: to_thread(synthesize_and_play, event), ) - dispatch( + store.dispatch( RegisterSettingAppAction( category=SettingsCategory.ACCESSIBILITY, priority=0, @@ -284,4 +284,4 @@ def init_service() -> None: ), ) - subscribe_event(FinishEvent, _context.cleanup) + store.subscribe_event(FinishEvent, _context.cleanup) diff --git a/ubo_app/services/020-keyboard/setup.py b/ubo_app/services/020-keyboard/setup.py index a5a7dad7..16055243 100644 --- a/ubo_app/services/020-keyboard/setup.py +++ b/ubo_app/services/020-keyboard/setup.py @@ -6,7 +6,7 @@ from kivy.core.window import Keyboard, Window, WindowBase from redux import FinishAction, FinishEvent -from ubo_app.store.main import ScreenshotEvent, SnapshotEvent, subscribe_event +from ubo_app.store.main import ScreenshotEvent, SnapshotEvent, store from ubo_app.store.services.audio import AudioDevice, AudioToggleMuteStatusAction from ubo_app.store.services.keypad import Key, KeypadKeyPressAction @@ -23,44 +23,44 @@ def on_keyboard( # noqa: C901 ) -> None: """Handle keyboard events.""" _ = window, scancode, codepoint - from ubo_app.store.main import dispatch + from ubo_app.store.main import store if modifier == []: if key in (Keyboard.keycodes['up'], Keyboard.keycodes['k']): - dispatch(KeypadKeyPressAction(key=Key.UP)) + store.dispatch(KeypadKeyPressAction(key=Key.UP)) elif key in (Keyboard.keycodes['down'], Keyboard.keycodes['j']): - dispatch(KeypadKeyPressAction(key=Key.DOWN)) + store.dispatch(KeypadKeyPressAction(key=Key.DOWN)) elif key == Keyboard.keycodes['1']: - dispatch(KeypadKeyPressAction(key=Key.L1)) + store.dispatch(KeypadKeyPressAction(key=Key.L1)) elif key == Keyboard.keycodes['2']: - dispatch(KeypadKeyPressAction(key=Key.L2)) + store.dispatch(KeypadKeyPressAction(key=Key.L2)) elif key == Keyboard.keycodes['3']: - dispatch(KeypadKeyPressAction(key=Key.L3)) + store.dispatch(KeypadKeyPressAction(key=Key.L3)) elif key in ( Keyboard.keycodes['left'], Keyboard.keycodes['escape'], Keyboard.keycodes['h'], ): - dispatch(KeypadKeyPressAction(key=Key.BACK)) + store.dispatch(KeypadKeyPressAction(key=Key.BACK)) elif key == Keyboard.keycodes['backspace']: - dispatch(KeypadKeyPressAction(key=Key.HOME)) + store.dispatch(KeypadKeyPressAction(key=Key.HOME)) elif key == Keyboard.keycodes['m']: - from ubo_app.store.main import dispatch + from ubo_app.store.main import store - dispatch( + store.dispatch( AudioToggleMuteStatusAction( device=AudioDevice.INPUT, ), ) elif key == Keyboard.keycodes['p']: - dispatch(ScreenshotEvent()) + store.dispatch(ScreenshotEvent()) elif key == Keyboard.keycodes['s']: - dispatch(SnapshotEvent()) + store.dispatch(SnapshotEvent()) elif key == Keyboard.keycodes['q']: - dispatch(FinishAction()) + store.dispatch(FinishAction()) def init_service() -> None: Window.bind(on_keyboard=on_keyboard) - subscribe_event(FinishEvent, lambda: Window.unbind(on_keyboard=on_keyboard)) + store.subscribe_event(FinishEvent, lambda: Window.unbind(on_keyboard=on_keyboard)) diff --git a/ubo_app/services/030-ethernet/setup.py b/ubo_app/services/030-ethernet/setup.py index 2a887021..5dd2fe0e 100644 --- a/ubo_app/services/030-ethernet/setup.py +++ b/ubo_app/services/030-ethernet/setup.py @@ -5,7 +5,7 @@ from debouncer import DebounceOptions, debounce from ethernet_manager import get_ethernet_device, get_ethernet_device_state -from ubo_app.store.main import dispatch +from ubo_app.store.main import store from ubo_app.store.services.ethernet import GlobalEthernetState from ubo_app.store.status_icons import StatusIconsRegisterAction from ubo_app.utils.async_ import create_task @@ -17,7 +17,7 @@ ) async def update_ethernet_icon() -> None: state = await get_ethernet_device_state() - dispatch( + store.dispatch( StatusIconsRegisterAction( icon={ GlobalEthernetState.CONNECTED: '󱊪', diff --git a/ubo_app/services/030-ip/setup.py b/ubo_app/services/030-ip/setup.py index 41ed5c73..6303509d 100644 --- a/ubo_app/services/030-ip/setup.py +++ b/ubo_app/services/030-ip/setup.py @@ -12,7 +12,7 @@ from ubo_gui.menu.types import HeadlessMenu, Item, SubMenuItem from ubo_app.store.core import RegisterSettingAppAction, SettingsCategory -from ubo_app.store.main import autorun, dispatch +from ubo_app.store.main import store from ubo_app.store.services.ip import ( IpNetworkInterface, IpSetIsConnectedAction, @@ -24,7 +24,7 @@ from collections.abc import Sequence -@autorun(lambda state: state.ip.interfaces) +@store.autorun(lambda state: state.ip.interfaces) def get_ip_addresses(interfaces: Sequence[IpNetworkInterface]) -> list[SubMenuItem]: if not interfaces: return [] @@ -61,7 +61,7 @@ def load_ip_addresses() -> None: if address.family == socket.AddressFamily.AF_INET: ip_addresses_by_interface[interface_name].append(address.address) - dispatch( + store.dispatch( IpUpdateInterfacesAction( interfaces=[ IpNetworkInterface(name=interface_name, ip_addresses=ip_addresses) @@ -96,7 +96,7 @@ async def check_connection() -> bool: while True: load_ip_addresses() if await is_connected(): - dispatch( + store.dispatch( StatusIconsRegisterAction( icon='󰖟', priority=INTERNET_STATE_ICON_PRIORITY, @@ -105,7 +105,7 @@ async def check_connection() -> bool: IpSetIsConnectedAction(is_connected=True), ) else: - dispatch( + store.dispatch( StatusIconsRegisterAction( icon=f'[color={DANGER_COLOR}]󰪎[/color]', priority=INTERNET_STATE_ICON_PRIORITY, @@ -128,7 +128,7 @@ async def check_connection() -> bool: async def init_service() -> None: - dispatch( + store.dispatch( RegisterSettingAppAction( priority=0, category=SettingsCategory.NETWORK, diff --git a/ubo_app/services/030-wifi/pages/create_wireless_connection.py b/ubo_app/services/030-wifi/pages/create_wireless_connection.py index 5b843940..18cd8419 100644 --- a/ubo_app/services/030-wifi/pages/create_wireless_connection.py +++ b/ubo_app/services/030-wifi/pages/create_wireless_connection.py @@ -14,7 +14,7 @@ from ubo_app.logging import logger from ubo_app.store.core import CloseApplicationEvent -from ubo_app.store.main import dispatch +from ubo_app.store.main import store from ubo_app.store.services.notifications import ( Chime, Notification, @@ -66,15 +66,15 @@ async def create_wireless_connection(self: CreateWirelessConnectionPage) -> None ), ) except asyncio.CancelledError: - dispatch(CloseApplicationEvent(application=self)) + store.dispatch(CloseApplicationEvent(application=self)) return if not match: - dispatch(CloseApplicationEvent(application=self)) + store.dispatch(CloseApplicationEvent(application=self)) return ssid = match.get('SSID') or match.get('SSID_') if ssid is None: - dispatch(CloseApplicationEvent(application=self)) + store.dispatch(CloseApplicationEvent(application=self)) return password = match.get('Password') or match.get('Password_') @@ -85,7 +85,7 @@ async def create_wireless_connection(self: CreateWirelessConnectionPage) -> None if not password: logger.warning('Password is required') - dispatch(CloseApplicationEvent(application=self)) + store.dispatch(CloseApplicationEvent(application=self)) return self.creating = True @@ -106,7 +106,7 @@ async def create_wireless_connection(self: CreateWirelessConnectionPage) -> None }, ) - dispatch( + store.dispatch( WiFiUpdateRequestAction(reset=True), NotificationsAddAction( notification=Notification( diff --git a/ubo_app/services/030-wifi/pages/main.py b/ubo_app/services/030-wifi/pages/main.py index 07b7b3ce..ac55af52 100644 --- a/ubo_app/services/030-wifi/pages/main.py +++ b/ubo_app/services/030-wifi/pages/main.py @@ -24,7 +24,7 @@ ) from ubo_app.store.core import CloseApplicationEvent -from ubo_app.store.main import autorun, dispatch +from ubo_app.store.main import store from ubo_app.store.services.wifi import ( ConnectionState, WiFiConnection, @@ -47,11 +47,11 @@ def first_option_callback(self: WiFiConnectionPage) -> None: create_task(disconnect_wireless_connection()) elif self.state is ConnectionState.DISCONNECTED: create_task(connect_wireless_connection(self.ssid)) - dispatch(WiFiUpdateRequestAction(reset=True)) + store.dispatch(WiFiUpdateRequestAction(reset=True)) def second_option_callback(self: WiFiConnectionPage) -> None: create_task(forget_wireless_connection(self.ssid)) - dispatch( + store.dispatch( CloseApplicationEvent(application=self), WiFiUpdateRequestAction(reset=True), ) @@ -121,7 +121,7 @@ async def listener() -> None: create_task(listener()) -@autorun(lambda state: state.wifi.connections) +@store.autorun(lambda state: state.wifi.connections) def wireless_connections_menu( connections: Sequence[WiFiConnection] | None, ) -> HeadlessMenu: @@ -172,7 +172,7 @@ def __init__(self: WiFiNetworkPageWithSSID, **kwargs: object) -> None: def list_connections() -> Callable[[], HeadlessMenu]: - dispatch(WiFiUpdateRequestAction()) + store.dispatch(WiFiUpdateRequestAction()) return wireless_connections_menu diff --git a/ubo_app/services/030-wifi/setup.py b/ubo_app/services/030-wifi/setup.py index ee7195ad..0013bad1 100644 --- a/ubo_app/services/030-wifi/setup.py +++ b/ubo_app/services/030-wifi/setup.py @@ -17,7 +17,7 @@ RegisterSettingAppAction, SettingsCategory, ) -from ubo_app.store.main import autorun, dispatch, subscribe_event +from ubo_app.store.main import store from ubo_app.store.services.notifications import ( Importance, Notification, @@ -46,7 +46,7 @@ async def update_wifi_list(_: WiFiUpdateRequestEvent | None = None) -> None: connections = await get_connections() - dispatch( + store.dispatch( WiFiUpdateAction( connections=connections, state=await get_wifi_device_state(), @@ -102,7 +102,7 @@ async def setup_listeners() -> None: def show_onboarding_notification() -> None: - dispatch( + store.dispatch( NotificationsAddAction( notification=ONBOARDING_NOTIFICATION, ), @@ -118,7 +118,7 @@ async def init_service() -> None: lambda state: state.wifi.has_visited_onboarding, ) - dispatch( + store.dispatch( RegisterSettingAppAction( priority=2, category=SettingsCategory.NETWORK, @@ -126,9 +126,9 @@ async def init_service() -> None: ), ) - subscribe_event(WiFiUpdateRequestEvent, request_scan) + store.subscribe_event(WiFiUpdateRequestEvent, request_scan) - @autorun( + @store.autorun( lambda state: state.ip.is_connected, options=AutorunOptions(default_value=None), ) @@ -144,7 +144,7 @@ def check_onboarding(is_connected: bool | None) -> None: ): logger.info('No internet connection, showing WiFi onboarding.') show_onboarding_notification() - dispatch(WiFiSetHasVisitedOnboardingAction(has_visited_onboarding=True)) + store.dispatch(WiFiSetHasVisitedOnboardingAction(has_visited_onboarding=True)) if is_connected is not None: check_onboarding.unsubscribe() diff --git a/ubo_app/services/030-wifi/wifi_manager.py b/ubo_app/services/030-wifi/wifi_manager.py index fcb2843e..e5278465 100644 --- a/ubo_app/services/030-wifi/wifi_manager.py +++ b/ubo_app/services/030-wifi/wifi_manager.py @@ -10,7 +10,7 @@ from debouncer import DebounceOptions, debounce from ubo_gui.constants import DANGER_COLOR -from ubo_app.store.main import dispatch +from ubo_app.store.main import store from ubo_app.store.services.notifications import ( Chime, Notification, @@ -377,7 +377,7 @@ async def forget_wireless_connection(ssid: str) -> None: and settings['802-11-wireless']['ssid'][1].decode('utf-8') == ssid ): await wait_for(network_connection_settings.delete()) - dispatch( + store.dispatch( NotificationsAddAction( notification=Notification( title=f'"{ssid}" Deleted', diff --git a/ubo_app/services/040-camera/setup.py b/ubo_app/services/040-camera/setup.py index f70e43c7..b6fcf0ad 100644 --- a/ubo_app/services/040-camera/setup.py +++ b/ubo_app/services/040-camera/setup.py @@ -18,7 +18,7 @@ from ubo_app import display from ubo_app.store.core import CloseApplicationEvent, OpenApplicationEvent -from ubo_app.store.main import dispatch, subscribe_event +from ubo_app.store.main import store from ubo_app.store.services.camera import ( CameraReportBarcodeAction, CameraStartViewfinderEvent, @@ -55,7 +55,7 @@ def resize_image( options=DebounceOptions(leading=True, trailing=False, time_window=THROTTL_TIME), ) def check_codes(codes: list[str]) -> None: - dispatch(CameraReportBarcodeAction(codes=codes)) + store.dispatch(CameraReportBarcodeAction(codes=codes)) class CameraApplication(PageWidget): @@ -177,7 +177,7 @@ def start_camera_viewfinder() -> None: fs_lock = Lock() application = CameraApplication() - dispatch(OpenApplicationEvent(application=application)) + store.dispatch(OpenApplicationEvent(application=application)) def feed_viewfinder_locked(_: object) -> None: with fs_lock: @@ -194,21 +194,21 @@ def handle_stop_viewfinder() -> None: nonlocal is_running is_running = False feed_viewfinder_scheduler.cancel() - dispatch(CloseApplicationEvent(application=application)) + store.dispatch(CloseApplicationEvent(application=application)) display.state.resume() cancel_subscription() if picamera2: picamera2.stop() picamera2.close() - cancel_subscription = subscribe_event( + cancel_subscription = store.subscribe_event( CameraStopViewfinderEvent, handle_stop_viewfinder, ) def init_service() -> None: - subscribe_event( + store.subscribe_event( CameraStartViewfinderEvent, start_camera_viewfinder, ) diff --git a/ubo_app/services/040-rgb-ring/rgb_ring_client.py b/ubo_app/services/040-rgb-ring/rgb_ring_client.py index 6ac5f8d7..45f88275 100644 --- a/ubo_app/services/040-rgb-ring/rgb_ring_client.py +++ b/ubo_app/services/040-rgb-ring/rgb_ring_client.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING from ubo_app.logging import logger -from ubo_app.store.main import dispatch +from ubo_app.store.main import store from ubo_app.store.services.rgb_ring import RgbRingSetIsConnectedAction from ubo_app.utils.server import send_command @@ -30,7 +30,7 @@ class RgbRingClient: async def send(self: RgbRingClient, cmd: Sequence[str]) -> None: try: await send_command('led', *cmd) - dispatch(RgbRingSetIsConnectedAction(is_connected=True)) + store.dispatch(RgbRingSetIsConnectedAction(is_connected=True)) except Exception: - dispatch(RgbRingSetIsConnectedAction(is_connected=False)) + store.dispatch(RgbRingSetIsConnectedAction(is_connected=False)) logger.exception('Unable to connect to the socket') diff --git a/ubo_app/services/040-rgb-ring/setup.py b/ubo_app/services/040-rgb-ring/setup.py index ab2ea1b0..66fc58f4 100644 --- a/ubo_app/services/040-rgb-ring/setup.py +++ b/ubo_app/services/040-rgb-ring/setup.py @@ -1,5 +1,5 @@ # ruff: noqa: D100, D101, D102, D103, D104, D107, N999 -from ubo_app.store.main import dispatch, subscribe_event +from ubo_app.store.main import store from ubo_app.store.services.rgb_ring import RgbRingCommandEvent, RgbRingPulseAction @@ -11,6 +11,6 @@ def init_service() -> None: async def handle_rgb_ring_command(event: RgbRingCommandEvent) -> None: await rgb_ring_client.send(event.command) - subscribe_event(RgbRingCommandEvent, handle_rgb_ring_command) + store.subscribe_event(RgbRingCommandEvent, handle_rgb_ring_command) - dispatch(RgbRingPulseAction(repetitions=2, wait=180)) + store.dispatch(RgbRingPulseAction(repetitions=2, wait=180)) diff --git a/ubo_app/services/040-sensors/setup.py b/ubo_app/services/040-sensors/setup.py index 8eaada50..63bd73e8 100644 --- a/ubo_app/services/040-sensors/setup.py +++ b/ubo_app/services/040-sensors/setup.py @@ -9,7 +9,7 @@ import board from redux import FinishEvent -from ubo_app.store.main import dispatch, subscribe_event +from ubo_app.store.main import store from ubo_app.store.services.sensors import Sensor, SensorsReportReadingAction temperature_sensor: adafruit_pct2075.PCT2075 @@ -20,7 +20,7 @@ def read_sensors(_: float | None = None) -> None: """Read the sensor.""" temperature = temperature_sensor.temperature light = light_sensor.lux - dispatch( + store.dispatch( SensorsReportReadingAction( sensor=Sensor.TEMPERATURE, reading=temperature, @@ -45,5 +45,5 @@ def init_service() -> None: light_sensor = adafruit_veml7700.VEML7700(i2c, address=0x10) clock_event = Clock.schedule_interval(read_sensors, 1) - subscribe_event(FinishEvent, clock_event.cancel) + store.subscribe_event(FinishEvent, clock_event.cancel) read_sensors() diff --git a/ubo_app/services/050-lightdm/setup.py b/ubo_app/services/050-lightdm/setup.py index a4489613..1b8597e3 100644 --- a/ubo_app/services/050-lightdm/setup.py +++ b/ubo_app/services/050-lightdm/setup.py @@ -9,7 +9,7 @@ from ubo_gui.menu.types import ActionItem, HeadedMenu, HeadlessMenu, Item, Menu from ubo_app.store.core import RegisterSettingAppAction, SettingsCategory -from ubo_app.store.main import autorun, dispatch +from ubo_app.store.main import store from ubo_app.store.services.lightdm import ( LightDMClearEnabledStateAction, LightDMUpdateStateAction, @@ -35,16 +35,16 @@ def install_lightdm() -> None: """Install LightDM.""" async def act() -> None: - dispatch(LightDMUpdateStateAction(is_installing=True)) + store.dispatch(LightDMUpdateStateAction(is_installing=True)) result = await send_command( 'package', 'install', 'lightdm', has_output=True, ) - dispatch(LightDMUpdateStateAction(is_installing=False)) + store.dispatch(LightDMUpdateStateAction(is_installing=False)) if result != 'installed': - dispatch( + store.dispatch( NotificationsAddAction( notification=Notification( title='LightDM', @@ -75,7 +75,7 @@ def enable_lightdm_service() -> None: """Enable the LightDM service.""" async def act() -> None: - dispatch(LightDMClearEnabledStateAction()) + store.dispatch(LightDMClearEnabledStateAction()) await send_command('service', 'lightdm', 'enable') await asyncio.sleep(5) await check_lightdm() @@ -87,7 +87,7 @@ def disable_lightdm_service() -> None: """Disable the LightDM service.""" async def act() -> None: - dispatch(LightDMClearEnabledStateAction()) + store.dispatch(LightDMClearEnabledStateAction()) await send_command('service', 'lightdm', 'disable') await asyncio.sleep(5) await check_lightdm() @@ -95,7 +95,7 @@ async def act() -> None: create_task(act()) -@autorun(lambda state: state.lightdm) +@store.autorun(lambda state: state.lightdm) def lightdm_menu(state: LightDMState) -> Menu: """Get the LightDM menu items.""" if state.is_installing: @@ -148,13 +148,13 @@ def lightdm_menu(state: LightDMState) -> Menu: ) -@autorun(lambda state: state.lightdm) +@store.autorun(lambda state: state.lightdm) def lightdm_icon(state: LightDMState) -> str: """Get the LightDM icon.""" return '[color=#008000]󰪥[/color]' if state.is_active else '[color=#ffff00]󰝦[/color]' -@autorun(lambda state: state.lightdm) +@store.autorun(lambda state: state.lightdm) def lightdm_title(_: LightDMState) -> str: """Get the LightDM title.""" return lightdm_icon() + ' LightDM' @@ -168,7 +168,7 @@ async def check_lightdm() -> None: is_package_installed('raspberrypi-ui-mods'), ) - dispatch( + store.dispatch( LightDMUpdateStateAction( is_active=is_installed and is_active, is_enabled=is_installed and is_enabled, @@ -186,7 +186,7 @@ def open_lightdm_menu() -> Callable[[], Menu]: def init_service() -> None: """Initialize the LightDM service.""" - dispatch( + store.dispatch( RegisterSettingAppAction( priority=0, category=SettingsCategory.DESKTOP, @@ -202,7 +202,7 @@ def init_service() -> None: create_task( monitor_unit( 'lightdm.service', - lambda status: dispatch( + lambda status: store.dispatch( LightDMUpdateStateAction( is_active=status in ('active', 'activating', 'reloading'), ), diff --git a/ubo_app/services/050-rpi-connect/commands.py b/ubo_app/services/050-rpi-connect/commands.py index 2b22650d..a4c9fd43 100644 --- a/ubo_app/services/050-rpi-connect/commands.py +++ b/ubo_app/services/050-rpi-connect/commands.py @@ -10,7 +10,7 @@ from ubo_gui.constants import DANGER_COLOR from ubo_app.logging import logger -from ubo_app.store.main import dispatch, view +from ubo_app.store.main import store from ubo_app.store.services.notifications import ( Chime, Importance, @@ -79,7 +79,7 @@ async def _check_status() -> None: else None, } except (subprocess.CalledProcessError, TimeoutError): - dispatch( + store.dispatch( NotificationsAddAction( notification=Notification( title='RPi-Connect', @@ -95,7 +95,7 @@ async def _check_status() -> None: 'Checked VSCode Tunnel Status', extra=status_data, ) - dispatch( + store.dispatch( RPiConnectSetStatusAction( is_installed=is_installed, is_signed_in=is_signed_in, @@ -110,7 +110,7 @@ async def _check_status() -> None: def install_rpi_connect() -> None: - dispatch(RPiConnectStartDownloadingAction()) + store.dispatch(RPiConnectStartDownloadingAction()) async def act() -> None: result = await send_command( @@ -120,9 +120,9 @@ async def act() -> None: has_output=True, ) - dispatch(RPiConnectDoneDownloadingAction()) + store.dispatch(RPiConnectDoneDownloadingAction()) if result != 'installed': - dispatch( + store.dispatch( NotificationsAddAction( notification=Notification( title='RPi-Connect', @@ -140,7 +140,7 @@ async def act() -> None: def uninstall_rpi_connect() -> None: - dispatch(RPiConnectSetPendingAction()) + store.dispatch(RPiConnectSetPendingAction()) async def act() -> None: result = await send_command( @@ -151,7 +151,7 @@ async def act() -> None: ) if result != 'uninstalled': - dispatch( + store.dispatch( NotificationsAddAction( notification=Notification( title='RPi-Connect', @@ -181,7 +181,7 @@ async def act() -> None: await process.wait() await check_status() except subprocess.CalledProcessError: - dispatch( + store.dispatch( NotificationsAddAction( notification=Notification( title='RPi-Connect', @@ -197,13 +197,13 @@ async def act() -> None: create_task(act()) -@view(lambda state: state.lightdm.is_active) +@store.view(lambda state: state.lightdm.is_active) def start_service(is_lightdm_active: bool) -> None: # noqa: FBT001 """Start the RPi Connect service.""" async def act() -> None: if not is_lightdm_active: - dispatch( + store.dispatch( NotificationsAddAction( notification=Notification( title='RPi-Connect', @@ -263,9 +263,9 @@ async def act() -> None: async def check_is_active() -> None: """Check if the SSH service is active.""" if await is_unit_active('rpi-connect', is_user_service=True): - dispatch(RPiConnectUpdateServiceStateAction(is_active=True)) + store.dispatch(RPiConnectUpdateServiceStateAction(is_active=True)) else: - dispatch(RPiConnectUpdateServiceStateAction(is_active=False)) + store.dispatch(RPiConnectUpdateServiceStateAction(is_active=False)) async def check_status() -> None: diff --git a/ubo_app/services/050-rpi-connect/setup.py b/ubo_app/services/050-rpi-connect/setup.py index b4705dd2..19c09273 100644 --- a/ubo_app/services/050-rpi-connect/setup.py +++ b/ubo_app/services/050-rpi-connect/setup.py @@ -19,7 +19,7 @@ from ubo_gui.page import PageWidget from ubo_app.store.core import RegisterSettingAppAction, SettingsCategory -from ubo_app.store.main import autorun, dispatch +from ubo_app.store.main import store from ubo_app.utils.async_ import create_task if TYPE_CHECKING: @@ -65,7 +65,7 @@ def login_actions(*, is_signed_in: bool | None) -> list[ActionItem | Application return actions -@autorun(lambda state: state.rpi_connect) +@store.autorun(lambda state: state.rpi_connect) def actions(state: RPiConnectState) -> list[ActionItem | ApplicationItem]: actions = [] if not state.is_downloading: @@ -96,7 +96,7 @@ def actions(state: RPiConnectState) -> list[ActionItem | ApplicationItem]: return actions -@autorun(lambda state: state.rpi_connect) +@store.autorun(lambda state: state.rpi_connect) def status(state: RPiConnectState) -> str: if state.status: status = 'Screen sharing: ' @@ -139,7 +139,7 @@ def generate_rpi_connect_menu() -> HeadedMenu: def init_service() -> None: - dispatch( + store.dispatch( RegisterSettingAppAction( menu_item=ActionItem( label='RPi Connect', diff --git a/ubo_app/services/050-rpi-connect/sign_in_page.py b/ubo_app/services/050-rpi-connect/sign_in_page.py index cf1b97d5..dcf8fa4e 100644 --- a/ubo_app/services/050-rpi-connect/sign_in_page.py +++ b/ubo_app/services/050-rpi-connect/sign_in_page.py @@ -15,7 +15,7 @@ from ubo_app.logging import logger from ubo_app.store.core import CloseApplicationEvent -from ubo_app.store.main import dispatch, subscribe_event +from ubo_app.store.main import store from ubo_app.store.services.notifications import ( Chime, Notification, @@ -36,9 +36,9 @@ def __init__( **kwargs: object, ) -> None: super().__init__(*args, **kwargs, items=[]) - subscribe_event( + store.subscribe_event( RPiConnectLoginEvent, - lambda: dispatch(CloseApplicationEvent(application=self)), + lambda: store.dispatch(CloseApplicationEvent(application=self)), ) create_task(self.login()) @@ -65,7 +65,7 @@ def set_properties() -> None: mainthread(set_properties)() await self.process.wait() else: - dispatch( + store.dispatch( NotificationsAddAction( notification=Notification( title='RPi-Connect', @@ -79,7 +79,7 @@ def set_properties() -> None: ) except subprocess.CalledProcessError: logger.exception('Failed to login') - dispatch( + store.dispatch( NotificationsAddAction( notification=Notification( title='RPi-Connect', diff --git a/ubo_app/services/050-ssh/setup.py b/ubo_app/services/050-ssh/setup.py index cdaef47f..436125d2 100644 --- a/ubo_app/services/050-ssh/setup.py +++ b/ubo_app/services/050-ssh/setup.py @@ -23,7 +23,7 @@ RegisterSettingAppAction, SettingsCategory, ) -from ubo_app.store.main import autorun, dispatch +from ubo_app.store.main import store from ubo_app.store.services.notifications import ( Importance, Notification, @@ -48,14 +48,14 @@ class ClearTemporaryUsersPrompt(PromptWidget): def first_option_callback(self: ClearTemporaryUsersPrompt) -> None: """Clear all temporary users.""" - dispatch(CloseApplicationEvent(application=self)) + store.dispatch(CloseApplicationEvent(application=self)) def second_option_callback(self: ClearTemporaryUsersPrompt) -> None: """Close the prompt.""" async def act() -> None: await send_command('service', 'ssh', 'clear_all_temporary_accounts') - dispatch( + store.dispatch( CloseApplicationEvent(application=self), NotificationsAddAction( notification=Notification( @@ -95,7 +95,7 @@ async def act() -> None: else: result = 'username:password' if not result: - dispatch( + store.dispatch( NotificationsAddAction( notification=Notification( title='Failed to create temporary SSH account', @@ -112,7 +112,7 @@ async def act() -> None: return username, password = result.split(':') hostname = socket.gethostname() - dispatch( + store.dispatch( NotificationsAddAction( notification=Notification( title='Account Info', @@ -153,7 +153,7 @@ def enable_ssh_service() -> None: """Enable the SSH service.""" async def act() -> None: - dispatch(SSHClearEnabledStateAction()) + store.dispatch(SSHClearEnabledStateAction()) await send_command('service', 'ssh', 'enable') await asyncio.sleep(5) await check_is_ssh_enabled() @@ -165,7 +165,7 @@ def disable_ssh_service() -> None: """Disable the SSH service.""" async def act() -> None: - dispatch(SSHClearEnabledStateAction()) + store.dispatch(SSHClearEnabledStateAction()) await send_command('service', 'ssh', 'disable') await asyncio.sleep(5) await check_is_ssh_enabled() @@ -173,7 +173,7 @@ async def act() -> None: create_task(act()) -@autorun(lambda state: state.ssh) +@store.autorun(lambda state: state.ssh) def ssh_items(state: SSHState) -> Sequence[Item]: """Get the SSH menu items.""" return [ @@ -223,13 +223,13 @@ def ssh_items(state: SSHState) -> Sequence[Item]: ] -@autorun(lambda state: state.ssh) +@store.autorun(lambda state: state.ssh) def ssh_icon(state: SSHState) -> str: """Get the SSH icon.""" return '[color=#008000]󰪥[/color]' if state.is_active else '[color=#ffff00]󰝦[/color]' -@autorun(lambda state: state.ssh) +@store.autorun(lambda state: state.ssh) def ssh_title(_: SSHState) -> str: """Get the SSH title.""" return ssh_icon() + ' SSH' @@ -238,17 +238,17 @@ def ssh_title(_: SSHState) -> str: async def check_is_ssh_active() -> None: """Check if the SSH service is active.""" if await is_unit_active('ssh'): - dispatch(SSHUpdateStateAction(is_active=True)) + store.dispatch(SSHUpdateStateAction(is_active=True)) else: - dispatch(SSHUpdateStateAction(is_active=False)) + store.dispatch(SSHUpdateStateAction(is_active=False)) async def check_is_ssh_enabled() -> None: """Check if the SSH service is enabled.""" if await is_unit_enabled('ssh'): - dispatch(SSHUpdateStateAction(is_enabled=True)) + store.dispatch(SSHUpdateStateAction(is_enabled=True)) else: - dispatch(SSHUpdateStateAction(is_enabled=False)) + store.dispatch(SSHUpdateStateAction(is_enabled=False)) def open_ssh_menu() -> Menu: @@ -263,7 +263,7 @@ def open_ssh_menu() -> Menu: def init_service() -> None: """Initialize the SSH service.""" - dispatch( + store.dispatch( RegisterSettingAppAction( priority=1, category=SettingsCategory.REMOTE, @@ -279,7 +279,7 @@ def init_service() -> None: create_task( monitor_unit( 'ssh.service', - lambda status: dispatch( + lambda status: store.dispatch( SSHUpdateStateAction( is_active=status in ('active', 'activating', 'reloading'), ), diff --git a/ubo_app/services/050-vscode/commands.py b/ubo_app/services/050-vscode/commands.py index c899e9d2..0fcdbebe 100644 --- a/ubo_app/services/050-vscode/commands.py +++ b/ubo_app/services/050-vscode/commands.py @@ -12,7 +12,7 @@ from ubo_gui.constants import DANGER_COLOR from ubo_app.logging import logger -from ubo_app.store.main import dispatch +from ubo_app.store.main import store from ubo_app.store.services.notifications import ( Chime, Notification, @@ -61,7 +61,7 @@ async def _check_status() -> None: output = await process.stdout.read() status_data = json.loads(output) except (subprocess.CalledProcessError, TimeoutError): - dispatch( + store.dispatch( NotificationsAddAction( notification=Notification( title='VSCode', @@ -90,7 +90,7 @@ async def _check_status() -> None: process.kill() is_logged_in = process.returncode == 0 except (subprocess.CalledProcessError, TimeoutError): - dispatch( + store.dispatch( NotificationsAddAction( notification=Notification( title='VSCode', @@ -110,7 +110,7 @@ async def _check_status() -> None: 'is_binary_installed': is_binary_installed, }, ) - dispatch( + store.dispatch( VSCodeSetStatusAction( is_binary_installed=is_binary_installed, is_logged_in=is_logged_in, @@ -137,7 +137,7 @@ async def check_status() -> None: async def set_name() -> None: - dispatch(VSCodeSetPendingAction()) + store.dispatch(VSCodeSetPendingAction()) try: hostname = socket.gethostname() process = await asyncio.create_subprocess_exec( @@ -153,7 +153,7 @@ async def set_name() -> None: if process.returncode is None: process.kill() except (subprocess.CalledProcessError, TimeoutError): - dispatch( + store.dispatch( NotificationsAddAction( notification=Notification( title='VSCode', @@ -170,7 +170,7 @@ async def set_name() -> None: async def install_service() -> None: - dispatch(VSCodeSetPendingAction()) + store.dispatch(VSCodeSetPendingAction()) try: process = await asyncio.create_subprocess_exec( CODE_BINARY_PATH, @@ -183,7 +183,7 @@ async def install_service() -> None: if process.returncode is None: process.kill() except (subprocess.CalledProcessError, TimeoutError): - dispatch( + store.dispatch( NotificationsAddAction( notification=Notification( title='VSCode', @@ -200,7 +200,7 @@ async def install_service() -> None: async def uninstall_service() -> None: - dispatch(VSCodeSetPendingAction()) + store.dispatch(VSCodeSetPendingAction()) try: process = await asyncio.create_subprocess_exec( CODE_BINARY_PATH, @@ -215,7 +215,7 @@ async def uninstall_service() -> None: if process.returncode is None: process.kill() except (subprocess.CalledProcessError, TimeoutError): - dispatch( + store.dispatch( NotificationsAddAction( notification=Notification( title='VSCode', diff --git a/ubo_app/services/050-vscode/login_page.py b/ubo_app/services/050-vscode/login_page.py index 00868e13..56df3228 100644 --- a/ubo_app/services/050-vscode/login_page.py +++ b/ubo_app/services/050-vscode/login_page.py @@ -16,7 +16,7 @@ from ubo_app.logging import logger from ubo_app.store.core import CloseApplicationEvent -from ubo_app.store.main import dispatch, subscribe_event +from ubo_app.store.main import store from ubo_app.store.services.notifications import ( Chime, Notification, @@ -38,9 +38,9 @@ def __init__( **kwargs: object, ) -> None: super().__init__(*args, **kwargs, items=[]) - subscribe_event( + store.subscribe_event( VSCodeLoginEvent, - lambda: dispatch(CloseApplicationEvent(application=self)), + lambda: store.dispatch(CloseApplicationEvent(application=self)), ) create_task(self.login()) @@ -75,7 +75,7 @@ def set_properties() -> None: mainthread(set_properties)() await self.process.wait() else: - dispatch( + store.dispatch( NotificationsAddAction( notification=Notification( title='VSCode', @@ -89,7 +89,7 @@ def set_properties() -> None: ) except subprocess.CalledProcessError: logger.exception('Failed to login') - dispatch( + store.dispatch( NotificationsAddAction( notification=Notification( title='VSCode', diff --git a/ubo_app/services/050-vscode/setup.py b/ubo_app/services/050-vscode/setup.py index b2c8b692..d091e06e 100644 --- a/ubo_app/services/050-vscode/setup.py +++ b/ubo_app/services/050-vscode/setup.py @@ -16,7 +16,7 @@ from ubo_app.constants import INSTALLATION_PATH from ubo_app.store.core import RegisterSettingAppAction, SettingsCategory -from ubo_app.store.main import autorun, dispatch +from ubo_app.store.main import store from ubo_app.store.services.notifications import ( Chime, Notification, @@ -39,7 +39,7 @@ def download_code() -> None: CODE_BINARY_PATH.unlink(missing_ok=True) - dispatch(VSCodeStartDownloadingAction()) + store.dispatch(VSCodeStartDownloadingAction()) async def act() -> None: try: @@ -66,7 +66,7 @@ async def act() -> None: ) await process.wait() except subprocess.CalledProcessError: - dispatch( + store.dispatch( NotificationsAddAction( notification=Notification( title='VSCode', @@ -80,7 +80,7 @@ async def act() -> None: ) CODE_BINARY_PATH.unlink(missing_ok=True) raise - dispatch(VSCodeDoneDownloadingAction()) + store.dispatch(VSCodeDoneDownloadingAction()) await check_status() create_task(act()) @@ -101,7 +101,7 @@ async def act() -> None: await process.wait() await check_status() except subprocess.CalledProcessError: - dispatch( + store.dispatch( NotificationsAddAction( notification=Notification( title='VSCode', @@ -185,7 +185,7 @@ def generate_actions(state: VSCodeState) -> list[ActionItem | ApplicationItem]: return actions -@autorun(lambda state: state.vscode) +@store.autorun(lambda state: state.vscode) def vscode_menu(state: VSCodeState) -> HeadedMenu: actions = generate_actions(state) @@ -228,7 +228,7 @@ def generate_vscode_menu() -> Callable[[], HeadedMenu]: def init_service() -> None: - dispatch( + store.dispatch( RegisterSettingAppAction( menu_item=ActionItem(label='VSCode', icon='󰨞', action=generate_vscode_menu), category=SettingsCategory.REMOTE, diff --git a/ubo_app/services/080-docker/image_.py b/ubo_app/services/080-docker/image_.py index b9f01180..46e852fb 100644 --- a/ubo_app/services/080-docker/image_.py +++ b/ubo_app/services/080-docker/image_.py @@ -20,7 +20,7 @@ from ubo_app.constants import DOCKER_CREDENTIALS_TEMPLATE from ubo_app.logging import logger -from ubo_app.store.main import autorun, dispatch, subscribe_event, view +from ubo_app.store.main import store from ubo_app.store.services.docker import ( DockerImageSetDockerIdAction, DockerImageSetStatusAction, @@ -62,7 +62,7 @@ def update_container(image_id: str, container: Container) -> None: 'Container running image found', extra={'image': image_id, 'path': IMAGES[image_id].path}, ) - dispatch( + store.dispatch( DockerImageSetStatusAction( image=image_id, status=ImageStatus.RUNNING, @@ -82,7 +82,7 @@ def update_container(image_id: str, container: Container) -> None: "Container for the image found, but it's not running", extra={'image': image_id, 'path': IMAGES[image_id].path}, ) - dispatch( + store.dispatch( DockerImageSetStatusAction( image=image_id, status=ImageStatus.CREATED, @@ -97,35 +97,35 @@ def _monitor_events(image_id: str, get_docker_id: Callable[[], str]) -> None: # decode=True, filters={'type': ['image', 'container']}, ) - subscribe_event(FinishEvent, events.close) + store.subscribe_event(FinishEvent, events.close) for event in events: logger.verbose('Docker image event', extra={'event': event}) if event['Type'] == 'image': if event['status'] == 'pull' and event['id'] == path: try: image = docker_client.images.get(path) - dispatch( + store.dispatch( DockerImageSetStatusAction( image=image_id, status=ImageStatus.AVAILABLE, ), ) if isinstance(image, Image) and image.id: - dispatch( + store.dispatch( DockerImageSetDockerIdAction( image=image_id, docker_id=image.id, ), ) except docker.errors.DockerException: - dispatch( + store.dispatch( DockerImageSetStatusAction( image=image_id, status=ImageStatus.NOT_AVAILABLE, ), ) elif event['status'] == 'delete' and event['id'] == get_docker_id(): - dispatch( + store.dispatch( DockerImageSetStatusAction( image=image_id, status=ImageStatus.NOT_AVAILABLE, @@ -137,14 +137,14 @@ def _monitor_events(image_id: str, get_docker_id: Callable[[], str]) -> None: # if container: update_container(image_id, container) elif event['status'] == 'die' and event['from'] == path: - dispatch( + store.dispatch( DockerImageSetStatusAction( image=image_id, status=ImageStatus.CREATED, ), ) elif event['status'] == 'destroy' and event['from'] == path: - dispatch( + store.dispatch( DockerImageSetStatusAction( image=image_id, status=ImageStatus.AVAILABLE, @@ -165,7 +165,7 @@ def act() -> None: raise docker.errors.ImageNotFound(path) # noqa: TRY301 if image.id: - dispatch( + store.dispatch( DockerImageSetDockerIdAction( image=image_id, docker_id=image.id, @@ -182,7 +182,7 @@ def act() -> None: 'Container running image not found', extra={'image': image_id, 'path': path}, ) - dispatch( + store.dispatch( DockerImageSetStatusAction( image=image_id, status=ImageStatus.AVAILABLE, @@ -193,7 +193,7 @@ def act() -> None: 'Image not found', extra={'image': image_id, 'path': path}, ) - dispatch( + store.dispatch( DockerImageSetStatusAction( image=image_id, status=ImageStatus.NOT_AVAILABLE, @@ -204,7 +204,7 @@ def act() -> None: 'Image error', extra={'image': image_id, 'path': path}, ) - dispatch( + store.dispatch( DockerImageSetStatusAction( image=image_id, status=ImageStatus.ERROR, @@ -213,7 +213,7 @@ def act() -> None: finally: docker_client.close() - @autorun(lambda state: getattr(state.docker, image_id).docker_id) + @store.autorun(lambda state: getattr(state.docker, image_id).docker_id) def get_docker_id(docker_id: str) -> str: return docker_id @@ -222,11 +222,11 @@ def get_docker_id(docker_id: str) -> str: to_thread(act) -@autorun(lambda state: state.docker.service.usernames) +@store.autorun(lambda state: state.docker.service.usernames) def _reactive_fetch_image(usernames: dict[str, str]) -> Callable[[ImageState], None]: def fetch_image(image: ImageState) -> None: def act() -> None: - dispatch( + store.dispatch( DockerImageSetStatusAction( image=image.id, status=ImageStatus.FETCHING, @@ -251,7 +251,7 @@ def act() -> None: 'Image error', extra={'image': IMAGES[image.id].path}, ) - dispatch( + store.dispatch( DockerImageSetStatusAction( image=image.id, status=ImageStatus.ERROR, @@ -312,7 +312,7 @@ async def _process_environment_variables(image_id: str) -> dict[str, str]: return result -@autorun(lambda state: state.docker) +@store.autorun(lambda state: state.docker) def _run_container_generator(docker_state: DockerState) -> Callable[[ImageState], None]: def run_container(image: ImageState) -> None: async def act() -> None: @@ -325,7 +325,7 @@ async def act() -> None: hosts = {} for key, value in IMAGES[image.id].hosts.items(): if not hasattr(docker_state, value): - dispatch( + store.dispatch( NotificationsAddAction( notification=Notification( title='Dependency error', @@ -336,7 +336,7 @@ async def act() -> None: ) return if not getattr(docker_state, value).container_ip: - dispatch( + store.dispatch( NotificationsAddAction( notification=Notification( title='Dependency error', @@ -412,7 +412,7 @@ def go_up(self: DockerQRCodePage) -> None: self.ids.slider.animated_value = len(self.ips) - 1 - self.index -@view(lambda state: state.ip.interfaces) +@store.view(lambda state: state.ip.interfaces) def image_menu( interfaces: Sequence[IpNetworkInterface], image: ImageState, @@ -519,7 +519,9 @@ def action() -> PageWidget: def image_menu_generator(image_id: str) -> Callable[[], Callable[[], HeadedMenu]]: """Get the menu items for the Docker service.""" - _image_menu = autorun(lambda state: getattr(state.docker, image_id))(image_menu) + _image_menu = store.autorun(lambda state: getattr(state.docker, image_id))( + image_menu, + ) def open_image_menu() -> Callable[[], HeadedMenu]: check_container(image_id) diff --git a/ubo_app/services/080-docker/setup.py b/ubo_app/services/080-docker/setup.py index 9c0a2a09..e81a7ea2 100644 --- a/ubo_app/services/080-docker/setup.py +++ b/ubo_app/services/080-docker/setup.py @@ -22,7 +22,7 @@ RegisterSettingAppAction, SettingsCategory, ) -from ubo_app.store.main import autorun, dispatch +from ubo_app.store.main import store from ubo_app.store.services.docker import ( DockerRemoveUsernameAction, DockerSetStatusAction, @@ -54,14 +54,14 @@ def install_docker() -> None: """Install Docker.""" async def act() -> None: - dispatch(DockerSetStatusAction(status=DockerStatus.INSTALLING)) + store.dispatch(DockerSetStatusAction(status=DockerStatus.INSTALLING)) result = await send_command( 'docker', 'install', has_output=True, ) if result != 'installed': - dispatch( + store.dispatch( NotificationsAddAction( notification=Notification( title='Docker', @@ -83,7 +83,7 @@ def run_docker() -> None: async def act() -> None: await send_command('docker', 'start') - dispatch(DockerSetStatusAction(status=DockerStatus.UNKNOWN)) + store.dispatch(DockerSetStatusAction(status=DockerStatus.UNKNOWN)) create_task(act()) @@ -93,7 +93,7 @@ def stop_docker() -> None: async def act() -> None: await send_command('docker', 'stop') - dispatch(DockerSetStatusAction(status=DockerStatus.UNKNOWN)) + store.dispatch(DockerSetStatusAction(status=DockerStatus.UNKNOWN)) create_task(act()) @@ -125,14 +125,14 @@ async def check_docker() -> None: docker_client.close() if is_running: - dispatch(DockerSetStatusAction(status=DockerStatus.RUNNING)) + store.dispatch(DockerSetStatusAction(status=DockerStatus.RUNNING)) elif is_installed: - dispatch(DockerSetStatusAction(status=DockerStatus.NOT_RUNNING)) + store.dispatch(DockerSetStatusAction(status=DockerStatus.NOT_RUNNING)) else: - dispatch(DockerSetStatusAction(status=DockerStatus.NOT_INSTALLED)) + store.dispatch(DockerSetStatusAction(status=DockerStatus.NOT_INSTALLED)) -@autorun(lambda state: state.docker.service.status) +@store.autorun(lambda state: state.docker.service.status) def setup_menu(status: DockerStatus) -> HeadedMenu: """Get the menu items for the Docker service.""" title = 'Setup Docker' @@ -203,7 +203,7 @@ def setup_menu_action() -> Callable[[], HeadedMenu]: return setup_menu -@autorun(lambda state: state.docker) +@store.autorun(lambda state: state.docker) def docker_menu_items(state: DockerState) -> list[Item]: """Get the menu items for the Docker service.""" create_task(check_docker()) @@ -298,7 +298,7 @@ async def act() -> None: key=DOCKER_CREDENTIALS_TEMPLATE.format(registry), value=password, ) - dispatch( + store.dispatch( DockerStoreUsernameAction(registry=registry, username=username), ) except asyncio.CancelledError: @@ -307,7 +307,7 @@ async def act() -> None: explanation = exception.explanation or ( exception.response.content.decode('utf8') if exception.response else '' ) - dispatch( + store.dispatch( NotificationsAddAction( notification=Notification( title='Docker Credentials Error', @@ -326,10 +326,10 @@ async def act() -> None: def clear_credentials(registry: str) -> None: """Clear an entry in docker credentials.""" secrets.clear_secret(DOCKER_CREDENTIALS_TEMPLATE.format(registry)) - dispatch(DockerRemoveUsernameAction(registry=registry)) + store.dispatch(DockerRemoveUsernameAction(registry=registry)) -@autorun(lambda state: state.docker.service.usernames) +@store.autorun(lambda state: state.docker.service.usernames) def settings_menu_items(usernames: dict[str, str]) -> Sequence[Item]: """Get the settings menu items for the Docker service.""" return [ @@ -371,8 +371,8 @@ def init_service() -> None: 'docker_usernames', lambda state: state.docker.service.usernames, ) - dispatch(RegisterRegularAppAction(menu_item=DOCKER_MAIN_MENU)) - dispatch( + store.dispatch(RegisterRegularAppAction(menu_item=DOCKER_MAIN_MENU)) + store.dispatch( RegisterSettingAppAction( category=SettingsCategory.APPS, menu_item=SubMenuItem( @@ -390,7 +390,7 @@ def init_service() -> None: create_task( monitor_unit( 'docker.socket', - lambda status: dispatch( + lambda status: store.dispatch( DockerSetStatusAction( status=DockerStatus.RUNNING if status in ('active', 'activating', 'reloading') diff --git a/ubo_app/setup.py b/ubo_app/setup.py index 6de57d2f..23d8c5fb 100644 --- a/ubo_app/setup.py +++ b/ubo_app/setup.py @@ -109,9 +109,9 @@ async def fake_monitor_unit(unit: str, callback: Callable[[str], None]) -> None: from kivy.clock import mainthread import ubo_app.display as _ # noqa: F401 - from ubo_app.store.main import subscribe_event + from ubo_app.store.main import store - subscribe_event(FinishEvent, mainthread(clear_signal_handlers)) + store.subscribe_event(FinishEvent, mainthread(clear_signal_handlers)) signal.signal(signal.SIGTERM, signal_handler) signal.signal(signal.SIGINT, signal_handler) @@ -137,9 +137,9 @@ def signal_handler(signum: int, _: object) -> None: if signum == signal.SIGINT: logger.info('Exiting gracefully, sending the signal again will force exit!') - from ubo_app.store.main import dispatch + from ubo_app.store.main import store - dispatch(FinishAction()) + store.dispatch(FinishAction()) elif signum == signal.SIGTERM: logger.info( 'Exiting forcefully, sending the signal again will not be caught!', diff --git a/ubo_app/side_effects.py b/ubo_app/side_effects.py index e6e30232..26d6dd64 100644 --- a/ubo_app/side_effects.py +++ b/ubo_app/side_effects.py @@ -11,13 +11,7 @@ from redux import FinishAction from ubo_app.store.core import PowerOffEvent, RebootEvent -from ubo_app.store.main import ( - ScreenshotEvent, - SnapshotEvent, - dispatch, - store, - subscribe_event, -) +from ubo_app.store.main import ScreenshotEvent, SnapshotEvent, store from ubo_app.store.services.audio import AudioPlayChimeAction from ubo_app.store.services.notifications import Chime from ubo_app.store.update_manager import ( @@ -35,7 +29,7 @@ def power_off() -> None: """Power off the device.""" - dispatch(AudioPlayChimeAction(name=Chime.FAILURE), FinishAction()) + store.dispatch(AudioPlayChimeAction(name=Chime.FAILURE), FinishAction()) if IS_RPI: def power_off_system(*_: list[object]) -> None: @@ -51,7 +45,7 @@ def power_off_system(*_: list[object]) -> None: def reboot() -> None: """Reboot the device.""" - dispatch(AudioPlayChimeAction(name=Chime.FAILURE), FinishAction()) + store.dispatch(AudioPlayChimeAction(name=Chime.FAILURE), FinishAction()) if IS_RPI: def reboot_system(*_: list[object]) -> None: @@ -102,11 +96,11 @@ def setup_side_effects() -> None: """Set up the application.""" initialize_board() - subscribe_event(PowerOffEvent, power_off) - subscribe_event(RebootEvent, reboot) - subscribe_event(UpdateManagerUpdateEvent, update) - subscribe_event(UpdateManagerCheckEvent, check_version) - subscribe_event(ScreenshotEvent, take_screenshot) - subscribe_event(SnapshotEvent, take_snapshot) + store.subscribe_event(PowerOffEvent, power_off) + store.subscribe_event(RebootEvent, reboot) + store.subscribe_event(UpdateManagerUpdateEvent, update) + store.subscribe_event(UpdateManagerCheckEvent, check_version) + store.subscribe_event(ScreenshotEvent, take_screenshot) + store.subscribe_event(SnapshotEvent, take_snapshot) - dispatch(UpdateManagerSetStatusAction(status=UpdateStatus.CHECKING)) + store.dispatch(UpdateManagerSetStatusAction(status=UpdateStatus.CHECKING)) diff --git a/ubo_app/store/core/_menus.py b/ubo_app/store/core/_menus.py index 7b3898cd..46cd9b18 100644 --- a/ubo_app/store/core/_menus.py +++ b/ubo_app/store/core/_menus.py @@ -19,7 +19,7 @@ RebootAction, SettingsCategory, ) -from ubo_app.store.main import autorun, dispatch +from ubo_app.store.main import store from ubo_app.store.services.notifications import Notification, NotificationsDisplayEvent from ubo_app.store.update_manager.utils import ( BASE_IMAGE, @@ -87,7 +87,7 @@ ) -@autorun( +@store.autorun( lambda state: state.notifications.unread_count, options=AutorunOptions(default_value='Notifications (not loaded)'), ) @@ -95,7 +95,7 @@ def notifications_title(unread_count: int) -> str: return f'Notifications ({unread_count})' -@autorun( +@store.autorun( lambda state: state.notifications.notifications, options=AutorunOptions(default_value=[]), ) @@ -109,7 +109,7 @@ def notifications_menu_items(notifications: Sequence[Notification]) -> list[Item color='black', background_color=notification.color, action=functools.partial( - dispatch, + store.dispatch, NotificationsDisplayEvent( notification=notification, index=index, @@ -124,7 +124,7 @@ def notifications_menu_items(notifications: Sequence[Notification]) -> list[Item ] -@autorun( +@store.autorun( lambda state: len(state.notifications.notifications), options=AutorunOptions(default_value='white'), ) @@ -162,12 +162,12 @@ def notifications_color(unread_count: int) -> str: items=[ ActionItem( label='Reboot', - action=lambda: dispatch(RebootAction()), + action=lambda: store.dispatch(RebootAction()), icon='󰜉', ), ActionItem( label='Power off', - action=lambda: dispatch(PowerOffAction()), + action=lambda: store.dispatch(PowerOffAction()), icon='󰐥', ), ], diff --git a/ubo_app/store/main.py b/ubo_app/store/main.py index 0cad87a9..828e2ad2 100644 --- a/ubo_app/store/main.py +++ b/ubo_app/store/main.py @@ -101,6 +101,7 @@ class SnapshotEvent(BaseEvent): ... ActionType = ( + # Core Actions CombineReducerAction | StatusIconsAction | UpdateManagerAction @@ -108,32 +109,34 @@ class SnapshotEvent(BaseEvent): ... | InitAction | FinishAction | StatusIconsAction - | KeypadAction - | MainAction - | LightDMAction - | SensorsAction - | SSHAction + # Services Actions | AudioAction | CameraAction - | WiFiAction + | DockerAction | IpAction + | KeypadAction + | LightDMAction | NotificationsAction - | DockerAction | RgbRingAction | RPiConnectAction + | SensorsAction + | SSHAction | VoiceAction | VSCodeAction + | WiFiAction ) EventType = ( + # Core Events MainEvent - | KeypadEvent + | ScreenshotEvent + # Services Events + | AudioEvent | CameraEvent - | WiFiEvent | IpEvent - | ScreenshotEvent - | SnapshotEvent + | KeypadEvent | NotificationsEvent - | AudioEvent + | SnapshotEvent + | WiFiEvent ) root_reducer, root_reducer_id = combine_reducers( @@ -261,7 +264,7 @@ def action_middleware(action: ActionType) -> ActionType: def event_middleware(event: EventType) -> EventType | None: - if is_finalizing.is_set(): + if _is_finalizing.is_set(): return None logger.debug( 'Event dispatched', @@ -283,22 +286,13 @@ def event_middleware(event: EventType) -> EventType | None: ), ) -autorun = store.autorun -dispatch = store.dispatch -subscribe = store.subscribe -subscribe_event = store.subscribe_event -view = store.view +_is_finalizing = Event() +store.subscribe_event(FinishEvent, lambda: _is_finalizing.set()) -is_finalizing = Event() - -subscribe_event(FinishEvent, lambda: is_finalizing.set()) - -dispatch(InitAction()) +store.dispatch(InitAction()) if DEBUG_MODE: - subscribe( + store.subscribe( lambda state: logger.verbose('State updated', extra={'state': state}), ) - -__all__ = ('autorun', 'dispatch', 'subscribe', 'subscribe_event') diff --git a/ubo_app/store/update_manager/utils.py b/ubo_app/store/update_manager/utils.py index c8f48ebf..6ed8e0cc 100644 --- a/ubo_app/store/update_manager/utils.py +++ b/ubo_app/store/update_manager/utils.py @@ -23,7 +23,7 @@ ) from ubo_app.logging import logger from ubo_app.store.core import RebootEvent -from ubo_app.store.main import autorun, dispatch +from ubo_app.store.main import store from ubo_app.store.services.notifications import ( Chime, Importance, @@ -84,7 +84,7 @@ async def check_version() -> None: latest_version = data['info']['version'] serial_number = read_serial_number() - dispatch( + store.dispatch( with_state=lambda state: UpdateManagerSetVersionsAction( flash_notification=state is None or state.main.path[:2] != ['main', 'about'], @@ -96,7 +96,7 @@ async def check_version() -> None: ) except Exception: logger.exception('Failed to check for updates') - dispatch(UpdateManagerSetStatusAction(status=UpdateStatus.FAILED_TO_CHECK)) + store.dispatch(UpdateManagerSetStatusAction(status=UpdateStatus.FAILED_TO_CHECK)) return @@ -113,7 +113,7 @@ async def update() -> None: ) async def download_files() -> None: - dispatch( + store.dispatch( NotificationsAddAction( notification=Notification( id=UPDATE_MANAGER_NOTIFICATION_ID, @@ -155,7 +155,7 @@ async def download_files() -> None: await process.wait() (UPDATE_ASSETS_PATH / 'install.sh').chmod(0o755) - dispatch( + store.dispatch( NotificationsAddAction( notification=Notification( id=UPDATE_MANAGER_NOTIFICATION_ID, @@ -186,7 +186,7 @@ async def download_files() -> None: ) if process.stdout is None: logger.info('Failed to update (pip has no stdout)') - dispatch( + store.dispatch( NotificationsAddAction( notification=Notification( title='Failed to update', @@ -207,7 +207,7 @@ async def download_files() -> None: break if line.startswith(('Collecting', 'Requirement already satisfied')): counter += 1 - dispatch( + store.dispatch( NotificationsAddAction( notification=Notification( id=UPDATE_MANAGER_NOTIFICATION_ID, @@ -232,7 +232,7 @@ async def download_files() -> None: # Update the packages count estimate for the next update Path(packages_count_path).write_text(str(counter), encoding='utf-8') - dispatch( + store.dispatch( NotificationsAddAction( notification=Notification( id=UPDATE_MANAGER_NOTIFICATION_ID, @@ -249,7 +249,7 @@ async def download_files() -> None: actions=[ NotificationActionItem( icon='󰜉', - action=lambda: dispatch(RebootEvent()), + action=lambda: store.dispatch(RebootEvent()), ), ], display_type=NotificationDisplayType.STICKY, @@ -267,7 +267,7 @@ async def download_files() -> None: await download_files() except Exception: logger.exception('Failed to update') - dispatch( + store.dispatch( NotificationsAddAction( notification=Notification( title='Failed to update', @@ -283,7 +283,7 @@ async def download_files() -> None: return -@autorun(lambda state: state.update_manager) +@store.autorun(lambda state: state.update_manager) def about_menu_items(state: UpdateManagerState) -> list[Item]: """Get the update menu items.""" if state.update_status is UpdateStatus.CHECKING: @@ -298,7 +298,7 @@ def about_menu_items(state: UpdateManagerState) -> list[Item]: return [ ActionItem( label='Failed to check for updates', - action=lambda: dispatch( + action=lambda: store.dispatch( UpdateManagerSetStatusAction(status=UpdateStatus.CHECKING), ), icon='󰜺', @@ -310,7 +310,7 @@ def about_menu_items(state: UpdateManagerState) -> list[Item]: ActionItem( label='Already up to date!', icon='󰄬', - action=lambda: dispatch( + action=lambda: store.dispatch( UpdateManagerSetStatusAction(status=UpdateStatus.CHECKING), ), background_color=SUCCESS_COLOR, @@ -321,7 +321,7 @@ def about_menu_items(state: UpdateManagerState) -> list[Item]: return [ ActionItem( label=f'Update to v{state.latest_version}', - action=lambda: dispatch( + action=lambda: store.dispatch( UpdateManagerSetStatusAction(status=UpdateStatus.UPDATING), ), icon='󰬬', @@ -344,7 +344,7 @@ class _UpdateManagerServiceState(TypedDict): progress: int -@autorun( +@store.autorun( lambda state: _UpdateManagerServiceState( is_running=state.update_manager.is_update_service_active, is_presented=any( @@ -356,7 +356,7 @@ class _UpdateManagerServiceState(TypedDict): ) def _(state: _UpdateManagerServiceState) -> None: if state['is_running']: - dispatch( + store.dispatch( NotificationsAddAction( notification=Notification( id=UPDATE_MANAGER_SECOND_PHASE_NOTIFICATION_ID, @@ -378,7 +378,7 @@ def _(state: _UpdateManagerServiceState) -> None: ), ) else: - dispatch( + store.dispatch( NotificationsClearByIdAction( id=UPDATE_MANAGER_SECOND_PHASE_NOTIFICATION_ID, ), diff --git a/ubo_app/utils/bus_provider.py b/ubo_app/utils/bus_provider.py index bc573f92..9d493930 100644 --- a/ubo_app/utils/bus_provider.py +++ b/ubo_app/utils/bus_provider.py @@ -9,7 +9,7 @@ from redux import FinishEvent from sdbus import SdBus, sd_bus_open_system, sd_bus_open_user, set_default_bus -from ubo_app.store.main import subscribe_event +from ubo_app.store.main import store if TYPE_CHECKING: from headless_kivy.config import Thread @@ -45,4 +45,4 @@ def get_user_bus() -> SdBus: return user_buses[thread] -subscribe_event(FinishEvent, clean_up, keep_ref=False) +store.subscribe_event(FinishEvent, clean_up, keep_ref=False) diff --git a/ubo_app/utils/persistent_store.py b/ubo_app/utils/persistent_store.py index a6d3b13b..5b9330ef 100644 --- a/ubo_app/utils/persistent_store.py +++ b/ubo_app/utils/persistent_store.py @@ -26,9 +26,9 @@ def register_persistent_store( selector: Callable[[RootState], T], ) -> None: """Register a part of the store to be persistent in the filesystem.""" - from ubo_app.store.main import autorun, store, subscribe_event + from ubo_app.store.main import store - @autorun(selector) + @store.autorun(selector) async def write(value: T) -> None: if value is None: return @@ -45,7 +45,7 @@ def unsubscribe() -> None: unsubscribe_event() write.unsubscribe() - unsubscribe_event = subscribe_event(FinishEvent, unsubscribe) + unsubscribe_event = store.subscribe_event(FinishEvent, unsubscribe) @overload diff --git a/ubo_app/utils/qrcode.py b/ubo_app/utils/qrcode.py index fe3fc0b7..27b69a4a 100644 --- a/ubo_app/utils/qrcode.py +++ b/ubo_app/utils/qrcode.py @@ -10,7 +10,7 @@ from typing_extensions import TypeVar -from ubo_app.store.main import dispatch, subscribe_event +from ubo_app.store.main import store from ubo_app.store.services.camera import ( CameraBarcodeEvent, CameraStartViewfinderAction, @@ -65,7 +65,7 @@ async def qrcode_input( if prompt: notification_future: Future[None] = loop.create_future() - dispatch( + store.dispatch( NotificationsAddAction( notification=Notification( id='qrcode', @@ -107,7 +107,7 @@ def handle_barcode_event(event: CameraBarcodeEvent) -> None: loop.call_soon_threadsafe(future.set_result, (event.code, event.group_dict)) kivy_color = get_color_from_hex('#21E693') - dispatch( + store.dispatch( RgbRingBlinkAction( color=( round(kivy_color[0] * 255), @@ -123,16 +123,16 @@ def handle_cancel(event: CameraStopViewfinderEvent) -> None: if event.id == prompt_id: loop.call_soon_threadsafe(future.cancel) - subscribe_event( + store.subscribe_event( CameraBarcodeEvent, handle_barcode_event, keep_ref=False, ) - subscribe_event( + store.subscribe_event( CameraStopViewfinderEvent, handle_cancel, ) - dispatch(CameraStartViewfinderAction(id=prompt_id, pattern=pattern)) + store.dispatch(CameraStartViewfinderAction(id=prompt_id, pattern=pattern)) result = await future