Coverage Report

Created: 2026-10-23 22:23

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
super-stt-app/src/core/app/handlers/models_page/install.rs
Line
Count
Source
1
// SPDX-License-Identifier: GPL-3.0-only
2
3
use crate::core::app::AppModel;
4
use crate::ui::messages::{BackendMessage, Message, ModelsPageMessage};
5
use cosmic::prelude::*;
6
7
impl AppModel {
8
0
    pub(in crate::core::app) fn handle_models_install_lifecycle(
9
0
        &mut self,
10
0
        message: ModelsPageMessage,
11
0
    ) -> Task<cosmic::Action<Message>> {
12
0
        match message {
13
            // Registry / Download-tab messages
14
0
            ModelsPageMessage::InstallBackend(source) => {
15
                // Clear a prior "failed to start" so the card reads "Installing…".
16
0
                self.registry.install_errors.remove(&source);
17
0
                let s = source.clone();
18
0
                Task::perform(
19
0
                    async move { crate::daemon::registry::install_by_source(&s).await },
20
0
                    move |res| {
21
0
                        cosmic::Action::App(Message::ModelsPage(match res {
22
0
                            Ok(a) => ModelsPageMessage::InstallAccepted {
23
0
                                source: source.clone(),
24
0
                                install_id: a.install_id,
25
0
                            },
26
0
                            Err(e) => ModelsPageMessage::InstallFailedToStart {
27
0
                                source: source.clone(),
28
0
                                error: e.to_string(),
29
0
                            },
30
                        }))
31
0
                    },
32
                )
33
            }
34
35
0
            ModelsPageMessage::InstallAccepted { source, install_id } => {
36
                use crate::state::registry::InstallStatus;
37
                use super_stt_shared::registry::events::InstallPhase;
38
                // The request was accepted — retire any prior start error.
39
0
                self.registry.install_errors.remove(&source);
40
0
                self.registry.installs.insert(
41
0
                    source,
42
0
                    InstallStatus {
43
0
                        install_id,
44
0
                        phase: InstallPhase::Downloading,
45
0
                        bytes_done: 0,
46
0
                        bytes_total: None,
47
0
                        error: None,
48
0
                    },
49
                );
50
0
                Task::none()
51
            }
52
53
0
            ModelsPageMessage::InstallFailedToStart { source, error } => {
54
0
                log::error!("install({source}) failed to start: {error}");
55
0
                let error = super_stt_shared::daemon::http_client::HttpError::Other(error)
56
0
                    .user_message(&super_stt_shared::SUPER_STT);
57
                // Drop the pending marker (there is no background install) and
58
                // record the reason so the Browse card shows "Failed" + a note
59
                // instead of silently snapping back to the Install button.
60
0
                self.registry.installs.remove(&source);
61
0
                self.registry.install_errors.insert(source, error);
62
0
                Task::none()
63
            }
64
65
0
            ModelsPageMessage::UpdateBackend(source) => {
66
0
                self.registry.install_errors.remove(&source);
67
0
                let s = source.clone();
68
0
                Task::perform(
69
0
                    async move { crate::daemon::registry::update(&s).await },
70
0
                    move |res| {
71
0
                        cosmic::Action::App(match res {
72
0
                            Ok(r) if r.noop => {
73
0
                                log::info!("update({source}) noop — already at {}", r.to_version);
74
                                // Nothing to install, but something to correct:
75
                                // the only way to reach this is an Update entry
76
                                // drawn from a stale `installed_version`. Reload
77
                                // the catalog that decides it, or the entry
78
                                // stays and every click no-ops again.
79
0
                                Message::ModelsPage(ModelsPageMessage::InstallCompleted { source })
80
                            }
81
0
                            Ok(r) => Message::ModelsPage(ModelsPageMessage::InstallAccepted {
82
0
                                source: source.clone(),
83
0
                                install_id: r.install_id.unwrap_or_default(),
84
0
                            }),
85
0
                            Err(e) => {
86
0
                                Message::ModelsPage(ModelsPageMessage::InstallFailedToStart {
87
0
                                    source: source.clone(),
88
0
                                    error: e.to_string(),
89
0
                                })
90
                            }
91
                        })
92
0
                    },
93
                )
94
            }
95
96
0
            _ => Task::none(),
97
        }
98
0
    }
99
100
    /// Uninstall lifecycle: kick off the request and surface its failure.
101
0
    pub(in crate::core::app) fn handle_models_uninstall(
102
0
        &mut self,
103
0
        message: ModelsPageMessage,
104
0
    ) -> Task<cosmic::Action<Message>> {
105
0
        match message {
106
0
            ModelsPageMessage::UninstallBackend(source) => {
107
                // Clear any stale failure so the row reads "in progress" on retry.
108
0
                self.registry.uninstall_errors.remove(&source);
109
0
                let s = source.clone();
110
0
                Task::perform(
111
0
                    async move { crate::daemon::registry::uninstall(&s).await },
112
0
                    move |res| {
113
0
                        cosmic::Action::App(match res {
114
0
                            Ok(_) => Message::Backend(BackendMessage::BackendsReload),
115
0
                            Err(error) => Message::ModelsPage(ModelsPageMessage::UninstallFailed {
116
0
                                source: source.clone(),
117
0
                                error: error.to_string(),
118
0
                            }),
119
                        })
120
0
                    },
121
                )
122
            }
123
124
0
            ModelsPageMessage::UninstallFailed { source, error } => {
125
0
                log::error!("uninstall({source}) failed: {error}");
126
0
                self.registry.uninstall_errors.insert(source, error);
127
0
                Task::none()
128
            }
129
130
0
            _ => Task::none(),
131
        }
132
0
    }
133
134
0
    pub(in crate::core::app) fn handle_models_install_progress(
135
0
        &mut self,
136
0
        message: ModelsPageMessage,
137
0
    ) -> Task<cosmic::Action<Message>> {
138
0
        match message {
139
            ModelsPageMessage::InstallProgress {
140
0
                install_id,
141
0
                source,
142
0
                phase,
143
0
                bytes_done,
144
0
                bytes_total,
145
            } => {
146
0
                if let Some(s) = self.registry.installs.get_mut(&source)
147
0
                    && s.install_id == install_id
148
                {
149
0
                    s.phase = phase;
150
0
                    if let Some(d) = bytes_done {
151
0
                        s.bytes_done = d;
152
0
                    }
153
0
                    if let Some(t) = bytes_total {
154
0
                        s.bytes_total = Some(t);
155
0
                    }
156
0
                }
157
0
                Task::none()
158
            }
159
160
0
            ModelsPageMessage::InstallCompleted { source } => {
161
                // The drawer's whole job is done the moment the thing it
162
                // previewed is installed: the preview now describes a backend
163
                // that is already there, and the Install button under it would
164
                // offer to fetch it again. Close it and empty it.
165
                //
166
                // Only for the install this drawer started — a Browse card's
167
                // install reports on the same channel, and it has its own card
168
                // to report on.
169
0
                if self.core.window.show_context
170
0
                    && self.context_page == crate::state::models::ContextPage::AddBackend
171
0
                    && self.registry.add_key() == source
172
0
                {
173
0
                    self.core.window.show_context = false;
174
0
                    self.registry.clear_add_sheet();
175
0
                }
176
0
                self.registry.installs.remove(&source);
177
0
                self.registry.install_errors.remove(&source);
178
                // Both catalogs, because they carry different halves of what
179
                // just changed. The backends list is what the Installed tab
180
                // draws; the registry catalog carries `installed_version`, and
181
                // that is what decides whether an Update entry is offered. Left
182
                // stale, the Update the user just ran stays on the menu and
183
                // does nothing when clicked. No index refresh: the annotation
184
                // is computed from local install state, so the cached index is
185
                // enough and a network round-trip would only slow this down.
186
0
                crate::core::app::handlers::tasks::reload_backend_catalogs()
187
            }
188
189
            ModelsPageMessage::InstallFailed {
190
0
                install_id,
191
0
                source,
192
0
                phase,
193
0
                error,
194
            } => {
195
0
                if let Some(s) = self.registry.installs.get_mut(&source)
196
0
                    && s.install_id == install_id
197
0
                {
198
0
                    s.error = Some(error);
199
0
                    s.phase = phase;
200
0
                }
201
0
                Task::none()
202
            }
203
204
0
            _ => Task::none(),
205
        }
206
0
    }
207
}