super-tts-app/src/core/app/handlers/tasks.rs
Line | Count | Source |
1 | | // SPDX-License-Identifier: GPL-3.0-only |
2 | | |
3 | | //! Shared `Task` builders re-used across handlers, so the same daemon call + |
4 | | //! result-to-`Message` mapping isn't re-rolled at each call site. |
5 | | |
6 | | use crate::daemon::client::{ |
7 | | get_current_audio_theme, get_custom_models_dir, get_notification_method, |
8 | | get_update_check_enabled, get_update_status, get_volume, list_backends, list_stage_backends, |
9 | | ping_daemon, |
10 | | }; |
11 | | use crate::state::AudioTheme; |
12 | | use crate::ui::messages::{ |
13 | | BackendMessage, DaemonMessage, Message, ModelsPageMessage, NotificationMethodMessage, |
14 | | UpdateMessage, |
15 | | }; |
16 | | use cosmic::prelude::*; |
17 | | use log::warn; |
18 | | use super_tts_shared::models::protocol::SYNTHESIS_STAGE; |
19 | | |
20 | | /// Ping the daemon and map the result to `DaemonConnected` / `DaemonError`. |
21 | | /// Shared by the periodic keep-alive, the reconnect retries, and the startup |
22 | | /// ping. |
23 | 0 | pub(in crate::core::app) fn ping_task() -> Task<cosmic::Action<Message>> { |
24 | 0 | Task::perform(ping_daemon(), |result| { |
25 | 0 | cosmic::Action::App(Message::Daemon(match result { |
26 | 0 | Ok(_) => DaemonMessage::DaemonConnected, |
27 | 0 | Err(e) => DaemonMessage::DaemonError(e), |
28 | | })) |
29 | 0 | }) |
30 | 0 | } |
31 | | |
32 | | /// Reload both backend catalogs: the installed list and the annotated registry. |
33 | | /// |
34 | | /// They carry different halves of one picture — `/backends` names what is |
35 | | /// installed (and its version), `/registry/backends` annotates each entry with |
36 | | /// the `installed_version` it reads off disk per request — and every question a |
37 | | /// card asks needs both. Refreshed apart, a card can report a version from one |
38 | | /// and an update from the other and have them disagree. |
39 | | /// |
40 | | /// No index refresh: the annotation is computed from local state, so the cached |
41 | | /// index is enough and a network round-trip would only make this slower than |
42 | | /// the moment that needs it. |
43 | 0 | pub(in crate::core::app) fn reload_backend_catalogs() -> Task<cosmic::Action<Message>> { |
44 | 0 | Task::batch([reload_backends(), fetch_registry_catalog(false)]) |
45 | 0 | } |
46 | | |
47 | | /// Reload the installed-backend catalog and the subset of it that can fill the |
48 | | /// synthesis stage, mapping each to its own message. |
49 | | /// |
50 | | /// Both, because the two answer different questions and different surfaces ask |
51 | | /// them. The Library manages every installed backend; the Models page's "Load a |
52 | | /// backend" sheet may only offer the ones `POST /pipeline/{stage}` will accept, |
53 | | /// and a backend serving nothing this stage can run is refused there. Offering |
54 | | /// it anyway hands the user a pick that fails on click. They hold the same |
55 | | /// backends while synthesis is the only role — which is precisely why one list |
56 | | /// would quietly stand in for the other until a second stage existed. |
57 | 0 | pub(in crate::core::app) fn reload_backends() -> Task<cosmic::Action<Message>> { |
58 | 0 | Task::batch([ |
59 | 0 | Task::perform(list_backends(), |result| { |
60 | 0 | cosmic::Action::App(match result { |
61 | 0 | Ok(backends) => Message::Backend(BackendMessage::BackendsLoaded(backends)), |
62 | 0 | Err(e) => Message::Backend(BackendMessage::BackendsError(e.to_string())), |
63 | | }) |
64 | 0 | }), |
65 | 0 | Task::perform(list_stage_backends(SYNTHESIS_STAGE), |result| { |
66 | 0 | cosmic::Action::App(match result { |
67 | 0 | Ok(backends) => Message::Backend(BackendMessage::StageBackendsLoaded(backends)), |
68 | 0 | Err(e) => Message::Backend(BackendMessage::BackendsError(e.to_string())), |
69 | | }) |
70 | 0 | }), |
71 | | ]) |
72 | 0 | } |
73 | | |
74 | | /// Fetch the full annotated registry catalog (optionally refreshing the index |
75 | | /// first) and map the result to `RegistryListLoaded` / `RegistryListFailed`. |
76 | | /// The catalog always includes incompatible entries; filtering is client-side. |
77 | 0 | pub(in crate::core::app) fn fetch_registry_catalog(refresh: bool) -> Task<cosmic::Action<Message>> { |
78 | 0 | let filters = crate::daemon::registry::ListFilters { |
79 | 0 | include_incompatible: Some(true), |
80 | 0 | ..Default::default() |
81 | 0 | }; |
82 | 0 | Task::perform( |
83 | 0 | async move { |
84 | 0 | if refresh { |
85 | 0 | let _ = crate::daemon::registry::refresh().await; |
86 | 0 | } |
87 | 0 | crate::daemon::registry::list(&filters).await |
88 | 0 | }, |
89 | 0 | |r| { |
90 | 0 | cosmic::Action::App(Message::ModelsPage(match r { |
91 | 0 | Ok(resp) => ModelsPageMessage::RegistryListLoaded(resp), |
92 | 0 | Err(e) => ModelsPageMessage::RegistryListFailed(e.to_string()), |
93 | | })) |
94 | 0 | }, |
95 | | ) |
96 | 0 | } |
97 | | |
98 | | /// Reload models, device info, and per-setting state on reconnect. Each setting |
99 | | /// is fetched with its own dedicated GET call — no bulk `fetch_daemon_config`. |
100 | 0 | pub(in crate::core::app) fn build_load_settings_tasks() -> Task<cosmic::Action<Message>> { |
101 | 0 | Task::batch([ |
102 | 0 | Task::perform(get_current_audio_theme(), |result| match result { |
103 | 0 | Ok(theme) => cosmic::Action::App(Message::Daemon( |
104 | 0 | DaemonMessage::CurrentAudioThemeLoaded(theme), |
105 | 0 | )), |
106 | 0 | Err(e) => { |
107 | 0 | warn!("Failed to load audio theme: {e}"); |
108 | 0 | cosmic::Action::App(Message::Daemon(DaemonMessage::CurrentAudioThemeLoaded( |
109 | 0 | AudioTheme::default(), |
110 | 0 | ))) |
111 | | } |
112 | 0 | }), |
113 | 0 | Task::perform(get_volume(), |result| match result { |
114 | 0 | Ok(vol) => cosmic::Action::App(Message::Daemon(DaemonMessage::VolumeLoaded(vol))), |
115 | 0 | Err(e) => { |
116 | 0 | warn!("Failed to load volume: {e}"); |
117 | 0 | cosmic::Action::App(Message::Daemon(DaemonMessage::VolumeLoaded(100))) |
118 | | } |
119 | 0 | }), |
120 | 0 | Task::perform(get_custom_models_dir(), |result| match result { |
121 | 0 | Ok(dir) => { |
122 | 0 | cosmic::Action::App(Message::Daemon(DaemonMessage::CustomModelsDirLoaded(dir))) |
123 | | } |
124 | 0 | Err(e) => { |
125 | 0 | warn!("Failed to load custom models dir: {e}"); |
126 | 0 | cosmic::Action::App(Message::Daemon(DaemonMessage::CustomModelsDirLoaded(None))) |
127 | | } |
128 | 0 | }), |
129 | 0 | Task::perform(get_notification_method(), |result| { |
130 | | use super_tts_shared::models::notification_method::NotificationMethod; |
131 | 0 | match result { |
132 | 0 | Ok(method_str) => { |
133 | 0 | let method = method_str.parse::<NotificationMethod>().unwrap_or_default(); |
134 | 0 | cosmic::Action::App(Message::NotificationMethod( |
135 | 0 | NotificationMethodMessage::Loaded(method), |
136 | 0 | )) |
137 | | } |
138 | 0 | Err(e) => { |
139 | 0 | warn!("Failed to load notification method: {e}"); |
140 | 0 | cosmic::Action::App(Message::NotificationMethod( |
141 | 0 | NotificationMethodMessage::Loaded(NotificationMethod::default()), |
142 | 0 | )) |
143 | | } |
144 | | } |
145 | 0 | }), |
146 | 0 | refresh_update_status(), |
147 | 0 | load_update_check_enabled(), |
148 | | ]) |
149 | 0 | } |
150 | | |
151 | | /// Re-fetch the self-update status. Used by connection-time settings loads |
152 | | /// (`build_load_settings_tasks`), by `on_nav_select` when the Updates page is |
153 | | /// opened, and by the `SettingsChanged { setting }` SSE handler for |
154 | | /// `update_check_enabled`/`update_beta_optin` (the latter because |
155 | | /// `beta_optin_effective` rides this status, not a separate field), so its |
156 | | /// data isn't stale from whenever the app last connected or last re-fetched. |
157 | 0 | pub(in crate::core::app) fn refresh_update_status() -> Task<cosmic::Action<Message>> { |
158 | 0 | Task::perform(get_update_status(), |result| match result { |
159 | 0 | Ok(status) => cosmic::Action::App(Message::Update(UpdateMessage::StatusLoaded(status))), |
160 | 0 | Err(e) => cosmic::Action::App(Message::Update(UpdateMessage::StatusError(e.to_string()))), |
161 | 0 | }) |
162 | 0 | } |
163 | | |
164 | | /// Load the automatic-check-enabled setting, defaulting to `true` (its |
165 | | /// documented daemon-side default) on a fetch failure rather than leaving |
166 | | /// the Updates page's toggler in a misleading off state. Also re-used by the |
167 | | /// `SettingsChanged { setting: "update_check_enabled" }` SSE handler so a |
168 | | /// change from another client is reflected here too. |
169 | 0 | pub(in crate::core::app) fn load_update_check_enabled() -> Task<cosmic::Action<Message>> { |
170 | 0 | Task::perform(get_update_check_enabled(), |result| match result { |
171 | 0 | Ok(enabled) => { |
172 | 0 | cosmic::Action::App(Message::Update(UpdateMessage::AutoCheckLoaded(enabled))) |
173 | | } |
174 | 0 | Err(e) => { |
175 | 0 | warn!("Failed to load update-check-enabled setting: {e}"); |
176 | 0 | cosmic::Action::App(Message::Update(UpdateMessage::AutoCheckLoaded(true))) |
177 | | } |
178 | 0 | }) |
179 | 0 | } |