[pve-devel] [PATCH installer v2 1/8] tui: move install progress dialog into own view module

Christoph Heiss c.heiss at proxmox.com
Fri Nov 10 15:17:19 CET 2023


While at it, convert it to a proper `View`-impl, instead of a functional
component.

No functional changes.

Signed-off-by: Christoph Heiss <c.heiss at proxmox.com>
---
Changes v1 -> v2:
  * moved separation of progress task function to separate patch

 proxmox-tui-installer/src/main.rs             | 233 +----------------
 .../src/views/install_progress.rs             | 241 ++++++++++++++++++
 proxmox-tui-installer/src/views/mod.rs        |   3 +
 3 files changed, 250 insertions(+), 227 deletions(-)
 create mode 100644 proxmox-tui-installer/src/views/install_progress.rs

diff --git a/proxmox-tui-installer/src/main.rs b/proxmox-tui-installer/src/main.rs
index a7b466f..e1411c6 100644
--- a/proxmox-tui-installer/src/main.rs
+++ b/proxmox-tui-installer/src/main.rs
@@ -1,25 +1,14 @@
 #![forbid(unsafe_code)]

-use std::{
-    collections::HashMap,
-    env,
-    io::{BufRead, BufReader, Write},
-    net::IpAddr,
-    str::FromStr,
-    sync::{Arc, Mutex},
-    thread,
-    time::Duration,
-};
+use std::{collections::HashMap, env, net::IpAddr};

 use cursive::{
     event::Event,
     theme::{ColorStyle, Effect, PaletteColor, Style},
-    utils::Counter,
     view::{Nameable, Offset, Resizable, ViewWrapper},
     views::{
         Button, Checkbox, Dialog, DummyView, EditView, Layer, LinearLayout, PaddedView, Panel,
-        ProgressBar, ResizedView, ScrollView, SelectView, StackView, TextContent, TextView,
-        ViewRef,
+        ResizedView, ScrollView, SelectView, StackView, TextView, ViewRef,
     },
     Cursive, CursiveRunnable, ScreenId, View, XY,
 };
@@ -36,14 +25,13 @@ use proxmox_installer_common::{
 };

 mod setup;
-use setup::InstallConfig;

 mod system;

 mod views;
 use views::{
-    BootdiskOptionsView, CidrAddressEditView, FormView, TableView, TableViewItem,
-    TimezoneOptionsView,
+    BootdiskOptionsView, CidrAddressEditView, FormView, InstallProgressView, TableView,
+    TableViewItem, TimezoneOptionsView,
 };

 // TextView::center() seems to garble the first two lines, so fix it manually here.
@@ -673,215 +661,6 @@ fn install_progress_dialog(siv: &mut Cursive) -> InstallerView {
     // Ensure the screen is updated independently of keyboard events and such
     siv.set_autorefresh(true);

-    let cb_sink = siv.cb_sink().clone();
-    let state = siv.user_data::<InstallerState>().unwrap();
-    let in_test_mode = state.in_test_mode;
-    let progress_text = TextContent::new("starting the installation ..");
-
-    let progress_task = {
-        let progress_text = progress_text.clone();
-        let options = state.options.clone();
-        move |counter: Counter| {
-            let child = {
-                use std::process::{Command, Stdio};
-
-                let (path, args, envs): (&str, &[&str], Vec<(&str, &str)>) = if in_test_mode {
-                    (
-                        "./proxmox-low-level-installer",
-                        &["-t", "start-session-test"],
-                        vec![("PERL5LIB", ".")],
-                    )
-                } else {
-                    ("proxmox-low-level-installer", &["start-session"], vec![])
-                };
-
-                Command::new(path)
-                    .args(args)
-                    .envs(envs)
-                    .stdin(Stdio::piped())
-                    .stdout(Stdio::piped())
-                    .spawn()
-            };
-
-            let mut child = match child {
-                Ok(child) => child,
-                Err(err) => {
-                    let _ = cb_sink.send(Box::new(move |siv| {
-                        siv.add_layer(
-                            Dialog::text(err.to_string())
-                                .title("Error")
-                                .button("Ok", Cursive::quit),
-                        );
-                    }));
-                    return;
-                }
-            };
-
-            let inner = || {
-                let reader = child.stdout.take().map(BufReader::new)?;
-                let mut writer = child.stdin.take()?;
-
-                serde_json::to_writer(&mut writer, &InstallConfig::from(options)).unwrap();
-                writeln!(writer).unwrap();
-
-                let writer = Arc::new(Mutex::new(writer));
-
-                for line in reader.lines() {
-                    let line = match line {
-                        Ok(line) => line,
-                        Err(_) => break,
-                    };
-
-                    let msg = match line.parse::<UiMessage>() {
-                        Ok(msg) => msg,
-                        Err(stray) => {
-                            eprintln!("low-level installer: {stray}");
-                            continue;
-                        }
-                    };
-
-                    match msg {
-                        UiMessage::Info(s) => cb_sink.send(Box::new(|siv| {
-                            siv.add_layer(Dialog::info(s).title("Information"));
-                        })),
-                        UiMessage::Error(s) => cb_sink.send(Box::new(|siv| {
-                            siv.add_layer(Dialog::info(s).title("Error"));
-                        })),
-                        UiMessage::Prompt(s) => cb_sink.send({
-                            let writer = writer.clone();
-                            Box::new(move |siv| {
-                                yes_no_dialog(
-                                    siv,
-                                    "Prompt",
-                                    &s,
-                                    Box::new({
-                                        let writer = writer.clone();
-                                        move |_| {
-                                            if let Ok(mut writer) = writer.lock() {
-                                                let _ = writeln!(writer, "ok");
-                                            }
-                                        }
-                                    }),
-                                    Box::new(move |_| {
-                                        if let Ok(mut writer) = writer.lock() {
-                                            let _ = writeln!(writer);
-                                        }
-                                    }),
-                                );
-                            })
-                        }),
-                        UiMessage::Progress(ratio, s) => {
-                            counter.set(ratio);
-                            progress_text.set_content(s);
-                            Ok(())
-                        }
-                        UiMessage::Finished(success, msg) => {
-                            counter.set(100);
-                            progress_text.set_content(msg.to_owned());
-                            cb_sink.send(Box::new(move |siv| {
-                                let title = if success { "Success" } else { "Failure" };
-
-                                // For rebooting, we just need to quit the installer,
-                                // our caller does the actual reboot.
-                                siv.add_layer(
-                                    Dialog::text(msg)
-                                        .title(title)
-                                        .button("Reboot now", Cursive::quit),
-                                );
-
-                                let autoreboot = siv
-                                    .user_data::<InstallerState>()
-                                    .map(|state| state.options.autoreboot)
-                                    .unwrap_or_default();
-
-                                if autoreboot && success {
-                                    let cb_sink = siv.cb_sink();
-                                    thread::spawn({
-                                        let cb_sink = cb_sink.clone();
-                                        move || {
-                                            thread::sleep(Duration::from_secs(5));
-                                            let _ = cb_sink.send(Box::new(Cursive::quit));
-                                        }
-                                    });
-                                }
-                            }))
-                        }
-                    }
-                    .unwrap();
-                }
-
-                Some(())
-            };
-
-            if inner().is_none() {
-                cb_sink
-                    .send(Box::new(|siv| {
-                        siv.add_layer(
-                            Dialog::text("low-level installer exited early")
-                                .title("Error")
-                                .button("Exit", Cursive::quit),
-                        );
-                    }))
-                    .unwrap();
-            }
-        }
-    };
-
-    let progress_bar = ProgressBar::new().with_task(progress_task).full_width();
-    let inner = PaddedView::lrtb(
-        1,
-        1,
-        1,
-        1,
-        LinearLayout::vertical()
-            .child(PaddedView::lrtb(1, 1, 0, 0, progress_bar))
-            .child(DummyView)
-            .child(TextView::new_with_content(progress_text).center())
-            .child(PaddedView::lrtb(
-                1,
-                1,
-                1,
-                0,
-                LinearLayout::horizontal().child(abort_install_button()),
-            )),
-    );
-
-    InstallerView::with_raw(state, inner)
-}
-
-enum UiMessage {
-    Info(String),
-    Error(String),
-    Prompt(String),
-    Finished(bool, String),
-    Progress(usize, String),
-}
-
-impl FromStr for UiMessage {
-    type Err = String;
-
-    fn from_str(s: &str) -> Result<Self, Self::Err> {
-        let (ty, rest) = s.split_once(": ").ok_or("invalid message: no type")?;
-
-        match ty {
-            "message" => Ok(UiMessage::Info(rest.to_owned())),
-            "error" => Ok(UiMessage::Error(rest.to_owned())),
-            "prompt" => Ok(UiMessage::Prompt(rest.to_owned())),
-            "finished" => {
-                let (state, rest) = rest.split_once(", ").ok_or("invalid message: no state")?;
-                Ok(UiMessage::Finished(state == "ok", rest.to_owned()))
-            }
-            "progress" => {
-                let (percent, rest) = rest.split_once(' ').ok_or("invalid progress message")?;
-                Ok(UiMessage::Progress(
-                    percent
-                        .parse::<f64>()
-                        .map(|v| (v * 100.).floor() as usize)
-                        .map_err(|err| err.to_string())?,
-                    rest.to_owned(),
-                ))
-            }
-            unknown => Err(format!("invalid message type {unknown}, rest: {rest}")),
-        }
-    }
+    let state = siv.user_data::<InstallerState>().cloned().unwrap();
+    InstallerView::with_raw(&state, InstallProgressView::new(siv))
 }
diff --git a/proxmox-tui-installer/src/views/install_progress.rs b/proxmox-tui-installer/src/views/install_progress.rs
new file mode 100644
index 0000000..4dca81b
--- /dev/null
+++ b/proxmox-tui-installer/src/views/install_progress.rs
@@ -0,0 +1,241 @@
+use std::{
+    io::{BufRead, BufReader, Write},
+    str::FromStr,
+    sync::{Arc, Mutex},
+    thread,
+    time::Duration,
+};
+
+use cursive::{
+    utils::Counter,
+    view::{Resizable, ViewWrapper},
+    views::{Dialog, DummyView, LinearLayout, PaddedView, ProgressBar, TextContent, TextView},
+    Cursive,
+};
+
+use crate::{abort_install_button, setup::InstallConfig, yes_no_dialog, InstallerState};
+
+pub struct InstallProgressView {
+    view: PaddedView<LinearLayout>,
+}
+
+impl InstallProgressView {
+    pub fn new(siv: &mut Cursive) -> Self {
+        let cb_sink = siv.cb_sink().clone();
+        let state = siv.user_data::<InstallerState>().unwrap();
+        let progress_text = TextContent::new("starting the installation ..");
+
+        let progress_task = {
+            let progress_text = progress_text.clone();
+            let state = state.clone();
+            move |counter: Counter| {
+                let child = {
+                    use std::process::{Command, Stdio};
+
+                    let (path, args, envs): (&str, &[&str], Vec<(&str, &str)>) =
+                        if state.in_test_mode {
+                            (
+                                "./proxmox-low-level-installer",
+                                &["-t", "start-session-test"],
+                                vec![("PERL5LIB", ".")],
+                            )
+                        } else {
+                            ("proxmox-low-level-installer", &["start-session"], vec![])
+                        };
+
+                    Command::new(path)
+                        .args(args)
+                        .envs(envs)
+                        .stdin(Stdio::piped())
+                        .stdout(Stdio::piped())
+                        .spawn()
+                };
+
+                let mut child = match child {
+                    Ok(child) => child,
+                    Err(err) => {
+                        let _ = cb_sink.send(Box::new(move |siv| {
+                            siv.add_layer(
+                                Dialog::text(err.to_string())
+                                    .title("Error")
+                                    .button("Ok", Cursive::quit),
+                            );
+                        }));
+                        return;
+                    }
+                };
+
+                let inner = || {
+                    let reader = child.stdout.take().map(BufReader::new)?;
+                    let mut writer = child.stdin.take()?;
+
+                    serde_json::to_writer(&mut writer, &InstallConfig::from(state.options))
+                        .unwrap();
+                    writeln!(writer).unwrap();
+
+                    let writer = Arc::new(Mutex::new(writer));
+
+                    for line in reader.lines() {
+                        let line = match line {
+                            Ok(line) => line,
+                            Err(_) => break,
+                        };
+
+                        let msg = match line.parse::<UiMessage>() {
+                            Ok(msg) => msg,
+                            Err(stray) => {
+                                eprintln!("low-level installer: {stray}");
+                                continue;
+                            }
+                        };
+
+                        match msg {
+                            UiMessage::Info(s) => cb_sink.send(Box::new(|siv| {
+                                siv.add_layer(Dialog::info(s).title("Information"));
+                            })),
+                            UiMessage::Error(s) => cb_sink.send(Box::new(|siv| {
+                                siv.add_layer(Dialog::info(s).title("Error"));
+                            })),
+                            UiMessage::Prompt(s) => cb_sink.send({
+                                let writer = writer.clone();
+                                Box::new(move |siv| {
+                                    yes_no_dialog(
+                                        siv,
+                                        "Prompt",
+                                        &s,
+                                        Box::new({
+                                            let writer = writer.clone();
+                                            move |_| {
+                                                if let Ok(mut writer) = writer.lock() {
+                                                    let _ = writeln!(writer, "ok");
+                                                }
+                                            }
+                                        }),
+                                        Box::new(move |_| {
+                                            if let Ok(mut writer) = writer.lock() {
+                                                let _ = writeln!(writer);
+                                            }
+                                        }),
+                                    );
+                                })
+                            }),
+                            UiMessage::Progress(ratio, s) => {
+                                counter.set(ratio);
+                                progress_text.set_content(s);
+                                Ok(())
+                            }
+                            UiMessage::Finished(success, msg) => {
+                                counter.set(100);
+                                progress_text.set_content(msg.to_owned());
+                                cb_sink.send(Box::new(move |siv| {
+                                    let title = if success { "Success" } else { "Failure" };
+
+                                    // For rebooting, we just need to quit the installer,
+                                    // our caller does the actual reboot.
+                                    siv.add_layer(
+                                        Dialog::text(msg)
+                                            .title(title)
+                                            .button("Reboot now", Cursive::quit),
+                                    );
+
+                                    let autoreboot = siv
+                                        .user_data::<InstallerState>()
+                                        .map(|state| state.options.autoreboot)
+                                        .unwrap_or_default();
+
+                                    if autoreboot && success {
+                                        let cb_sink = siv.cb_sink();
+                                        thread::spawn({
+                                            let cb_sink = cb_sink.clone();
+                                            move || {
+                                                thread::sleep(Duration::from_secs(5));
+                                                let _ = cb_sink.send(Box::new(Cursive::quit));
+                                            }
+                                        });
+                                    }
+                                }))
+                            }
+                        }
+                        .unwrap();
+                    }
+
+                    Some(())
+                };
+
+                if inner().is_none() {
+                    cb_sink
+                        .send(Box::new(|siv| {
+                            siv.add_layer(
+                                Dialog::text("low-level installer exited early")
+                                    .title("Error")
+                                    .button("Exit", Cursive::quit),
+                            );
+                        }))
+                        .unwrap();
+                }
+            }
+        };
+
+        let progress_bar = ProgressBar::new().with_task(progress_task).full_width();
+        let view = PaddedView::lrtb(
+            1,
+            1,
+            1,
+            1,
+            LinearLayout::vertical()
+                .child(PaddedView::lrtb(1, 1, 0, 0, progress_bar))
+                .child(DummyView)
+                .child(TextView::new_with_content(progress_text).center())
+                .child(PaddedView::lrtb(
+                    1,
+                    1,
+                    1,
+                    0,
+                    LinearLayout::horizontal().child(abort_install_button()),
+                )),
+        );
+
+        Self { view }
+    }
+}
+
+impl ViewWrapper for InstallProgressView {
+    cursive::wrap_impl!(self.view: PaddedView<LinearLayout>);
+}
+
+enum UiMessage {
+    Info(String),
+    Error(String),
+    Prompt(String),
+    Finished(bool, String),
+    Progress(usize, String),
+}
+
+impl FromStr for UiMessage {
+    type Err = String;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        let (ty, rest) = s.split_once(": ").ok_or("invalid message: no type")?;
+
+        match ty {
+            "message" => Ok(UiMessage::Info(rest.to_owned())),
+            "error" => Ok(UiMessage::Error(rest.to_owned())),
+            "prompt" => Ok(UiMessage::Prompt(rest.to_owned())),
+            "finished" => {
+                let (state, rest) = rest.split_once(", ").ok_or("invalid message: no state")?;
+                Ok(UiMessage::Finished(state == "ok", rest.to_owned()))
+            }
+            "progress" => {
+                let (percent, rest) = rest.split_once(' ').ok_or("invalid progress message")?;
+                Ok(UiMessage::Progress(
+                    percent
+                        .parse::<f64>()
+                        .map(|v| (v * 100.).floor() as usize)
+                        .map_err(|err| err.to_string())?,
+                    rest.to_owned(),
+                ))
+            }
+            unknown => Err(format!("invalid message type {unknown}, rest: {rest}")),
+        }
+    }
+}
diff --git a/proxmox-tui-installer/src/views/mod.rs b/proxmox-tui-installer/src/views/mod.rs
index 4d27532..3244e76 100644
--- a/proxmox-tui-installer/src/views/mod.rs
+++ b/proxmox-tui-installer/src/views/mod.rs
@@ -12,6 +12,9 @@ use proxmox_installer_common::utils::CidrAddress;
 mod bootdisk;
 pub use bootdisk::*;

+mod install_progress;
+pub use install_progress::*;
+
 mod table_view;
 pub use table_view::*;

--
2.42.0






More information about the pve-devel mailing list