Coverage Report

Created: 2026-11-04 02:14

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
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
}