[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