// SPDX-License-Identifier: EUPL-1.2+ // SPDX-FileCopyrightText: 2025 Johannes Süllner use super::{ BUTTON_TEXT_SIZE, Event, InstallResult, MARGIN_VERTICAL, Page, TITLE_TEXT_SIZE, UpdateResult, }; use crate::center_horizontal_in_container; use iced::{ Length, Subscription, Task, widget::{Space, column, container, row, text}, }; use iced_aw::Spinner; use iced_term::Terminal; use lsblk::BlockDevice; use std::fs; const RESULT_CODE_FILE: &str = "/tmp/spectrum-installer_result-code"; const LOG_FILE: &str = "/tmp/spectrum-installer_log"; pub struct PageInstallation { terminal: Option, installation_completed: bool, installation_result: Option, } impl PageInstallation { pub fn new(device: BlockDevice) -> Self { let term = Terminal::new( 0, iced_term::settings::Settings { backend: iced_term::settings::BackendSettings { program: String::from("/usr/bin/sh"), args: Vec::from([ String::from("-c"), // Install enforcing a new partition table. // HACK: Logging to /tmp as iced_term does not offer any other way of obtaining the commands result. String::from( [ "( /usr/bin/systemd-repart \ --definitions=/usr/share/spectrum-installer/repart.d \ --empty=force \ --dry-run=no ", &device.fullname.display().to_string(), "; echo $? > ", RESULT_CODE_FILE, ") 2>&1 | tee ", LOG_FILE, ] .concat(), ), ]), }, ..Default::default() }, ); match term { Ok(term) => Self { terminal: Some(term), installation_completed: false, installation_result: None, }, Err(e) => Self { terminal: None, installation_completed: true, installation_result: Some(Err((0, e.to_string()))), }, } } } #[derive(Clone, Debug)] pub enum PageInstallationEvent { Terminal(iced_term::Event), Finish, } impl Page for PageInstallation { fn update(&mut self, event: Event) -> UpdateResult { if let Event::PageInstallation(page_event) = event { match page_event { PageInstallationEvent::Finish => { if let Some(installation_result) = self.installation_result.clone() { return ( Some(Box::new(super::completion::PageCompletion::new( installation_result, ))), Task::none(), ); } } PageInstallationEvent::Terminal(iced_term::Event::BackendCall(_, cmd)) => { if let Some(term) = &mut self.terminal { match term.handle(iced_term::Command::ProxyToBackend(cmd)) { iced_term::actions::Action::Shutdown => { let code = fs::read_to_string(RESULT_CODE_FILE); let log = fs::read_to_string(LOG_FILE); self.installation_result = if let (Ok(code_as_str), Ok(log)) = (code, log) { if let Ok(code) = code_as_str.trim().parse::() { self.installation_completed = true; if code == 0 { Some(Ok(log)) } else { Some(Err((code, log))) } } else { None } } else { None }; } _ => {} } } } } } (None, Task::none()) } fn subscription(&self) -> Subscription { if let Some(term) = &self.terminal { return Subscription::run_with_id(term.id, term.subscription()) .map::<_, _>(|t| Event::PageInstallation(PageInstallationEvent::Terminal(t))); } Subscription::none() } fn view(&self) -> iced::Element<'_, Event> { let title_row: iced::Element = if !self.installation_completed { row![ Spinner::new().height(25), Space::with_width(MARGIN_VERTICAL), text("Installing ...").size(TITLE_TEXT_SIZE) ] .into() } else { text( if self.installation_result.as_ref().is_some_and(|r| r.is_ok()) { "Installation completed." } else { "Installation failed." }, ) .size(TITLE_TEXT_SIZE) .into() }; let installation_status: iced::Element = if let Some(term) = &self.terminal { iced_term::TerminalView::show(term) .map::<_>(|t| Event::PageInstallation(PageInstallationEvent::Terminal(t))) .into() } else { text("Could not start installation.").into() }; let main_content = column![ Space::with_height(MARGIN_VERTICAL), center_horizontal_in_container!(title_row), Space::with_height(MARGIN_VERTICAL), container(installation_status) .width(Length::Fill) .center_x(Length::Fill) .height(Length::Fill), ]; super::layout::bottom_buttons_layout( [[( text("Finish").size(BUTTON_TEXT_SIZE).into(), if self.installation_completed { Some(Event::PageInstallation(PageInstallationEvent::Finish)) } else { None }, )]], main_content.into(), ) } }