1use crate::state::{AppState, PackageItem, modal::CascadeMode};
4
5fn maybe_show_long_run_auth_preflight(app: &mut AppState) {
7 if app.long_run_auth_preflight_warned {
8 return;
9 }
10 let settings = crate::theme::settings();
11 let readiness = crate::logic::long_run_auth::evaluate_long_run_auth_readiness(&settings);
12 if readiness.should_warn {
13 app.long_run_auth_preflight_warned = true;
14 app.toast_message = Some(crate::logic::long_run_auth::build_long_run_warning_message(
15 app,
16 ));
17 app.toast_expires_at = Some(std::time::Instant::now() + std::time::Duration::from_secs(8));
18 }
19}
20
21pub fn start_integrated_install(app: &mut AppState, item: &PackageItem, dry_run: bool) {
38 use crate::events::start_execution;
39 use crate::state::modal::PreflightHeaderChips;
40
41 app.dry_run = dry_run;
42 maybe_show_long_run_auth_preflight(app);
43 let items = vec![item.clone()];
44 let header_chips = PreflightHeaderChips::default();
45
46 let username = std::env::var("USER").unwrap_or_else(|_| "user".to_string());
48 if let Some(lockout_msg) = crate::logic::faillock::get_lockout_message_if_locked(&username, app)
49 {
50 app.modal = crate::state::Modal::Alert {
52 message: lockout_msg,
53 };
54 return;
55 }
56
57 let settings = crate::theme::settings();
58 if crate::logic::password::should_use_interactive_auth_handoff(&settings) {
59 match crate::events::try_interactive_auth_handoff() {
60 Ok(true) => start_execution(
61 app,
62 &items,
63 crate::state::PreflightAction::Install,
64 header_chips,
65 None,
66 ),
67 Ok(false) => {
68 app.modal = crate::state::Modal::Alert {
69 message: crate::i18n::t(app, "app.errors.authentication_failed"),
70 };
71 }
72 Err(e) => {
73 app.modal = crate::state::Modal::Alert { message: e };
74 }
75 }
76 } else if crate::logic::password::resolve_auth_mode(&settings)
77 == crate::logic::privilege::AuthMode::PasswordlessOnly
78 && crate::logic::password::should_use_passwordless_sudo(&settings)
79 {
80 start_execution(
81 app,
82 &items,
83 crate::state::PreflightAction::Install,
84 header_chips,
85 None,
86 );
87 } else {
88 app.modal = crate::state::Modal::PasswordPrompt {
89 purpose: crate::state::modal::PasswordPurpose::Install,
90 items,
91 input: crate::state::SecureString::default(),
92 cursor: 0,
93 error: None,
94 };
95 app.pending_exec_header_chips = Some(header_chips);
96 }
97}
98
99pub fn start_integrated_install_all(app: &mut AppState, items: &[PackageItem], dry_run: bool) {
116 use crate::events::start_execution;
117 use crate::state::modal::PreflightHeaderChips;
118
119 app.dry_run = dry_run;
120 maybe_show_long_run_auth_preflight(app);
121 let items_vec = items.to_vec();
122 let header_chips = PreflightHeaderChips::default();
123
124 let username = std::env::var("USER").unwrap_or_else(|_| "user".to_string());
126 if let Some(lockout_msg) = crate::logic::faillock::get_lockout_message_if_locked(&username, app)
127 {
128 app.modal = crate::state::Modal::Alert {
130 message: lockout_msg,
131 };
132 return;
133 }
134
135 let settings = crate::theme::settings();
136 if crate::logic::password::should_use_interactive_auth_handoff(&settings) {
137 match crate::events::try_interactive_auth_handoff() {
138 Ok(true) => start_execution(
139 app,
140 &items_vec,
141 crate::state::PreflightAction::Install,
142 header_chips,
143 None,
144 ),
145 Ok(false) => {
146 app.modal = crate::state::Modal::Alert {
147 message: crate::i18n::t(app, "app.errors.authentication_failed"),
148 };
149 }
150 Err(e) => {
151 app.modal = crate::state::Modal::Alert { message: e };
152 }
153 }
154 } else if crate::logic::password::resolve_auth_mode(&settings)
155 == crate::logic::privilege::AuthMode::PasswordlessOnly
156 && crate::logic::password::should_use_passwordless_sudo(&settings)
157 {
158 start_execution(
159 app,
160 &items_vec,
161 crate::state::PreflightAction::Install,
162 header_chips,
163 None,
164 );
165 } else {
166 app.modal = crate::state::Modal::PasswordPrompt {
167 purpose: crate::state::modal::PasswordPurpose::Install,
168 items: items_vec,
169 input: crate::state::SecureString::default(),
170 cursor: 0,
171 error: None,
172 };
173 app.pending_exec_header_chips = Some(header_chips);
174 }
175}
176
177pub fn start_integrated_remove_all(
196 app: &mut AppState,
197 names: &[String],
198 dry_run: bool,
199 cascade_mode: CascadeMode,
200) {
201 use crate::events::start_execution;
202 use crate::state::modal::PreflightHeaderChips;
203
204 app.dry_run = dry_run;
205 maybe_show_long_run_auth_preflight(app);
206 app.remove_cascade_mode = cascade_mode;
207
208 let items: Vec<PackageItem> = names
210 .iter()
211 .map(|name| PackageItem {
212 name: name.clone(),
213 version: String::new(),
214 description: String::new(),
215 source: crate::state::Source::Official {
216 repo: String::new(),
217 arch: String::new(),
218 },
219 popularity: None,
220 out_of_date: None,
221 orphaned: false,
222 })
223 .collect();
224
225 let username = std::env::var("USER").unwrap_or_else(|_| "user".to_string());
229 if let Some(lockout_msg) = crate::logic::faillock::get_lockout_message_if_locked(&username, app)
230 {
231 app.modal = crate::state::Modal::Alert {
233 message: lockout_msg,
234 };
235 return;
236 }
237 let header_chips = PreflightHeaderChips::default();
238 let settings = crate::theme::settings();
239 if crate::logic::password::should_use_interactive_auth_handoff(&settings) {
240 match crate::events::try_interactive_auth_handoff() {
241 Ok(true) => {
242 start_execution(
243 app,
244 &items,
245 crate::state::PreflightAction::Remove,
246 header_chips,
247 None,
248 );
249 }
250 Ok(false) => {
251 app.modal = crate::state::Modal::Alert {
252 message: crate::i18n::t(app, "app.errors.authentication_failed"),
253 };
254 }
255 Err(e) => {
256 app.modal = crate::state::Modal::Alert { message: e };
257 }
258 }
259 return;
260 }
261
262 app.modal = crate::state::Modal::PasswordPrompt {
263 purpose: crate::state::modal::PasswordPurpose::Remove,
264 items,
265 input: crate::state::SecureString::default(),
266 cursor: 0,
267 error: None,
268 };
269 app.pending_exec_header_chips = Some(header_chips);
270}
271
272#[cfg(test)]
273mod tests {
274 use super::maybe_show_long_run_auth_preflight;
275
276 #[test]
277 fn preflight_warning_is_latched_once_per_session() {
278 let _guard = crate::global_test_mutex_lock();
279 unsafe {
280 std::env::set_var("PACSEA_INTEGRATION_TEST", "1");
281 std::env::set_var("PACSEA_TEST_PRIVILEGE_AVAILABLE", "none");
282 }
283 let mut app = crate::state::AppState::default();
284 assert!(!app.long_run_auth_preflight_warned);
285
286 maybe_show_long_run_auth_preflight(&mut app);
287 let first_toast = app.toast_message.clone();
288 assert!(app.long_run_auth_preflight_warned);
289 assert!(first_toast.is_some());
290
291 maybe_show_long_run_auth_preflight(&mut app);
292 assert_eq!(app.toast_message, first_toast);
293
294 unsafe {
295 std::env::remove_var("PACSEA_TEST_PRIVILEGE_AVAILABLE");
296 std::env::remove_var("PACSEA_INTEGRATION_TEST");
297 }
298 }
299}