3 view
::{Nameable, Resizable, ViewWrapper}
,
4 views
::{Dialog, DummyView, LinearLayout, PaddedView, ProgressBar, TextContent, TextView}
,
7 use serde
::Deserialize
;
9 io
::{BufRead, BufReader, Write}
,
15 use crate::{abort_install_button, prompt_dialog, setup::InstallConfig, InstallerState}
;
16 use proxmox_installer_common
::setup
::spawn_low_level_installer
;
18 pub struct InstallProgressView
{
19 view
: PaddedView
<LinearLayout
>,
22 impl InstallProgressView
{
23 pub fn new(siv
: &mut Cursive
) -> Self {
24 let cb_sink
= siv
.cb_sink().clone();
25 let state
= siv
.user_data
::<InstallerState
>().unwrap();
26 let progress_text
= TextContent
::new("starting the installation ..");
29 let progress_text
= progress_text
.clone();
30 let state
= state
.clone();
31 move |counter
: Counter
| Self::progress_task(counter
, cb_sink
, state
, progress_text
)
34 let progress_bar
= ProgressBar
::new().with_task(progress_task
).full_width();
35 let view
= PaddedView
::lrtb(
40 LinearLayout
::vertical()
41 .child(PaddedView
::lrtb(1, 1, 0, 0, progress_bar
))
43 .child(TextView
::new_with_content(progress_text
).center())
44 .child(PaddedView
::lrtb(
49 LinearLayout
::horizontal().child(abort_install_button()),
59 state
: InstallerState
,
60 progress_text
: TextContent
,
62 let mut child
= match spawn_low_level_installer(state
.in_test_mode
) {
65 let _
= cb_sink
.send(Box
::new(move |siv
| {
67 Dialog
::text(err
.to_string())
69 .button("Ok", Cursive
::quit
),
81 .ok_or("failed to get stdin reader")?
;
83 let mut writer
= child
.stdin
.take().ok_or("failed to get stdin writer")?
;
85 serde_json
::to_writer(&mut writer
, &InstallConfig
::from(state
.options
))
86 .map_err(|err
| format
!("failed to serialize install config: {err}"))?
;
87 writeln
!(writer
).map_err(|err
| format
!("failed to write install config: {err}"))?
;
89 let writer
= Arc
::new(Mutex
::new(writer
));
91 for line
in reader
.lines() {
92 let line
= match line
{
94 Err(err
) => return Err(format
!("low-level installer exited early: {err}")),
97 let msg
= match serde_json
::from_str
::<UiMessage
>(&line
) {
100 // Not a fatal error, so don't abort the installation by returning
101 eprintln
!("low-level installer: {stray}");
106 let result
= match msg
.clone() {
107 UiMessage
::Info { message }
=> cb_sink
.send(Box
::new(|siv
| {
108 siv
.add_layer(Dialog
::info(message
).title("Information"));
110 UiMessage
::Error { message }
=> cb_sink
.send(Box
::new(|siv
| {
111 siv
.add_layer(Dialog
::info(message
).title("Error"));
113 UiMessage
::Prompt { query }
=> cb_sink
.send({
114 let writer
= writer
.clone();
115 Box
::new(move |siv
| Self::show_prompt(siv
, &query
, writer
))
117 UiMessage
::Progress { ratio, text }
=> {
118 counter
.set((ratio
* 100.).floor() as usize);
119 progress_text
.set_content(text
);
122 UiMessage
::Finished { state, message }
=> {
124 progress_text
.set_content(message
.to_owned());
125 cb_sink
.send(Box
::new(move |siv
| {
126 Self::prepare_for_reboot(siv
, state
== "ok", &message
);
131 if let Err(err
) = result
{
132 eprintln
!("error during message handling: {err}");
133 eprintln
!(" message was: '{msg:?}");
140 if let Err(err
) = inner() {
141 let message
= format
!("installation failed: {err}");
143 .send(Box
::new(|siv
| {
145 Dialog
::text(message
)
147 .button("Exit", Cursive
::quit
),
154 fn prepare_for_reboot(siv
: &mut Cursive
, success
: bool
, msg
: &str) {
155 const DIALOG_ID
: &str = "autoreboot-dialog";
156 let title
= if success { "Success" }
else { "Failure" }
;
158 // If the dialog was previously created, just update its content and we're done.
159 if let Some(mut dialog
) = siv
.find_name
::<Dialog
>(DIALOG_ID
) {
160 dialog
.set_content(TextView
::new(msg
));
164 // For rebooting, we just need to quit the installer,
165 // our caller does the actual reboot.
169 .button("Reboot now", Cursive
::quit
)
170 .with_name(DIALOG_ID
),
174 .user_data
::<InstallerState
>()
175 .map(|state
| state
.options
.autoreboot
)
176 .unwrap_or_default();
178 if autoreboot
&& success
{
179 let cb_sink
= siv
.cb_sink();
181 let cb_sink
= cb_sink
.clone();
183 thread
::sleep(Duration
::from_secs(5));
184 let _
= cb_sink
.send(Box
::new(Cursive
::quit
));
190 fn show_prompt
<W
: Write
+ '
static>(siv
: &mut Cursive
, text
: &str, writer
: Arc
<Mutex
<W
>>) {
191 let send_answer
= |writer
: Arc
<Mutex
<W
>>, answer
| {
192 if let Ok(mut writer
) = writer
.lock() {
197 "type" : "prompt-answer",
210 let writer
= writer
.clone();
212 send_answer(writer
.clone(), "ok");
217 send_answer(writer
.clone(), "cancel");
223 impl ViewWrapper
for InstallProgressView
{
224 cursive
::wrap_impl
!(self.view
: PaddedView
<LinearLayout
>);
227 #[derive(Clone, Debug, Deserialize, PartialEq)]
228 #[serde(tag = "type", rename_all = "lowercase")]
230 #[serde(rename = "message")]
256 fn run_low_level_installer_test_session() {
257 env
::set_current_dir("..").expect("failed to change working directory");
258 let mut child
= spawn_low_level_installer(true)
259 .expect("failed to run low-level installer test session");
261 let mut reader
= child
265 .expect("failed to get stdin reader");
267 let mut writer
= child
.stdin
.take().expect("failed to get stdin writer");
269 serde_json
::to_writer(&mut writer
, &serde_json
::json
!({ "autoreboot": false }
))
270 .expect("failed to serialize install config");
272 writeln
!(writer
).expect("failed to write install config: {err}");
274 let mut next_msg
= || {
275 let mut line
= String
::new();
276 reader
.read_line(&mut line
).expect("a line");
278 match serde_json
::from_str
::<UiMessage
>(&line
) {
279 Ok(msg
) => Some(msg
),
280 Err(err
) => panic
!("unexpected error: '{err}'"),
286 Some(UiMessage
::Prompt
{
287 query
: "Reply anything?".to_owned()
291 serde_json
::to_writer(
293 &serde_json
::json
!({"type": "prompt-answer", "answer": "ok"}
),
295 .expect("failed to write prompt answer");
296 writeln
!(writer
).expect("failed to write prompt answer");
300 Some(UiMessage
::Info
{
301 message
: "Test Message - got ok".to_owned()
305 for i
in (1..=1000).step_by(3) {
308 Some(UiMessage
::Progress
{
309 ratio
: (i
as f32) / 1000.,
310 text
: format
!("foo {i}"),
317 Some(UiMessage
::Finished
{
318 state
: "ok".to_owned(),
319 message
: "Installation finished - reboot now?".to_owned(),
323 // Should be nothing left to read now
324 let mut line
= String
::new();
325 assert_eq
!(reader
.read_line(&mut line
).expect("success"), 0);
327 // Give the low-level installer some time to exit properly
328 std
::thread
::sleep(Duration
::new(1, 0));
330 match child
.try_wait() {
331 Ok(Some(status
)) => assert
!(
333 "low-level installer did not exit successfully"
336 child
.kill().expect("could not kill low-level installer");
337 panic
!("low-level install was not successful");
339 Err(err
) => panic
!("failed to wait for low-level installer: {err}"),