[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