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

Christoph Heiss c.heiss at proxmox.com
Wed Jul 26 16:03:55 CEST 2023


While doing so, also pull the main progress thread task into a separate
function, as well as the spawning of the low-level installer.

No functional changes.

Signed-off-by: Christoph Heiss <c.heiss at proxmox.com>
---
 proxmox-tui-installer/src/main.rs             | 235 +---------------
 .../src/views/install_progress.rs             | 251 ++++++++++++++++++
 proxmox-tui-installer/src/views/mod.rs        |   3 +
 3 files changed, 261 insertions(+), 228 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 7bfaf9b..a356f6d 100644
--- a/proxmox-tui-installer/src/main.rs
+++ b/proxmox-tui-installer/src/main.rs
@@ -1,24 +1,12 @@
-use std::{
-    collections::HashMap,
-    env,
-    io::{BufRead, BufReader, Write},
-    net::IpAddr,
-    path::PathBuf,
-    str::FromStr,
-    sync::{Arc, Mutex},
-    thread,
-    time::Duration,
-};
+use std::{collections::HashMap, env, net::IpAddr, path::PathBuf};
 
 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,
 };
@@ -29,7 +17,7 @@ mod options;
 use options::*;
 
 mod setup;
-use setup::{InstallConfig, LocaleInfo, ProxmoxProduct, RuntimeInfo, SetupInfo};
+use setup::{LocaleInfo, ProxmoxProduct, RuntimeInfo, SetupInfo};
 
 mod system;
 
@@ -38,8 +26,8 @@ use utils::Fqdn;
 
 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.
@@ -720,215 +708,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 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};
-
-                #[cfg(not(debug_assertions))]
-                let (path, args, envs): (&str, [&str; 1], [(&str, &str); 0]) =
-                    ("proxmox-low-level-installer", ["start-session"], []);
-
-                #[cfg(debug_assertions)]
-                let (path, args, envs) = (
-                    PathBuf::from("./proxmox-low-level-installer"),
-                    ["-t", "start-session-test"],
-                    [("PERL5LIB", ".")],
-                );
-
-                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..e9d764a
--- /dev/null
+++ b/proxmox-tui-installer/src/views/install_progress.rs
@@ -0,0 +1,251 @@
+use std::{
+    io::{self, BufRead, BufReader, Write},
+    process,
+    str::FromStr,
+    sync::{Arc, Mutex},
+    thread,
+    time::Duration,
+};
+
+use cursive::{
+    utils::Counter,
+    view::{Resizable, ViewWrapper},
+    views::{Dialog, DummyView, LinearLayout, PaddedView, ProgressBar, TextContent, TextView},
+    CbSink, Cursive,
+};
+
+use crate::{
+    abort_install_button, options::InstallerOptions, 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 options = state.options.clone();
+            move |counter: Counter| progress_task(counter, cb_sink, options, progress_text)
+        };
+
+        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 }
+    }
+}
+
+fn progress_task(
+    counter: Counter,
+    cb_sink: CbSink,
+    options: InstallerOptions,
+    progress_text: TextContent,
+) {
+    let child = {
+        use std::process::{Command, Stdio};
+
+        #[cfg(not(debug_assertions))]
+        let (path, args, envs): (&str, [&str; 1], [(&str, &str); 0]) =
+            ("proxmox-low-level-installer", ["start-session"], []);
+
+        #[cfg(debug_assertions)]
+        let (path, args, envs) = (
+            std::path::PathBuf::from("./proxmox-low-level-installer"),
+            ["-t", "start-session-test"],
+            [("PERL5LIB", ".")],
+        );
+
+        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();
+    }
+}
+
+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 aa24fa4..18dbdcf 100644
--- a/proxmox-tui-installer/src/views/mod.rs
+++ b/proxmox-tui-installer/src/views/mod.rs
@@ -12,6 +12,9 @@ use crate::utils::CidrAddress;
 mod bootdisk;
 pub use bootdisk::*;
 
+mod install_progress;
+pub use install_progress::*;
+
 mod table_view;
 pub use table_view::*;
 
-- 
2.41.0






More information about the pve-devel mailing list