[pve-devel] [PATCH installer 3/6] tui, ui: switch over to JSON-based protocol

Christoph Heiss c.heiss at proxmox.com
Wed Dec 6 12:34:52 CET 2023


Signed-off-by: Christoph Heiss <c.heiss at proxmox.com>
---
 Proxmox/UI/StdIO.pm                           |  41 ++++--
 .../src/views/install_progress.rs             | 117 ++++++++----------
 2 files changed, 83 insertions(+), 75 deletions(-)

diff --git a/Proxmox/UI/StdIO.pm b/Proxmox/UI/StdIO.pm
index a97245c..25d1c82 100644
--- a/Proxmox/UI/StdIO.pm
+++ b/Proxmox/UI/StdIO.pm
@@ -3,10 +3,26 @@ package Proxmox::UI::StdIO;
 use strict;
 use warnings;
 
+use JSON qw(from_json to_json);
+
 use base qw(Proxmox::UI::Base);
 
 use Proxmox::Log;
 
+my sub send_msg : prototype($$) {
+    my ($type, %values) = @_;
+
+    my $json = to_json({ type => $type, %values }, { utf8 => 1, canonical => 1 });
+    print STDOUT "$json\n";
+}
+
+my sub recv_msg : prototype() {
+    my $response = <STDIN> // ''; # FIXME: error handling?
+    chomp($response);
+
+    return eval { from_json($response, { utf8 => 1 }) };
+}
+
 sub init {
     my ($self) = @_;
 
@@ -16,34 +32,33 @@ sub init {
 sub message {
     my ($self, $msg) = @_;
 
-    print STDOUT "message: $msg\n";
+    &send_msg('message', message => $msg);
 }
 
 sub error {
     my ($self, $msg) = @_;
-    log_err("error: $msg\n");
-    print STDOUT "error: $msg\n";
+
+    log_error("error: $msg");
+    &send_msg('error', message => $msg);
 }
 
 sub finished {
     my ($self, $success, $msg) = @_;
 
     my $state = $success ? 'ok' : 'err';
-    log_info("finished: $state, $msg\n");
-    print STDOUT "finished: $state, $msg\n";
+    log_info("finished: $state, $msg");
+    &send_msg('finished', state => $state, message => $msg);
 }
 
 sub prompt {
     my ($self, $query) = @_;
 
-    $query =~ s/\n/ /g; # FIXME: use a better serialisation (e.g., JSON)
-    print STDOUT "prompt: $query\n";
-
-    my $response = <STDIN> // ''; # FIXME: error handling?
-
-    chomp($response);
+    &send_msg('prompt', query => $query);
+    my $response = &recv_msg();
 
-    return lc($response) eq 'ok';
+    if (defined($response) && $response->{type} eq 'prompt-answer') {
+	return lc($response->{answer}) eq 'ok';
+    }
 }
 
 sub display_html {
@@ -57,7 +72,7 @@ sub progress {
 
     $text = '' if !defined($text);
 
-    print STDOUT "progress: $ratio $text\n";
+    &send_msg('progress', ratio => $ratio, text => $text);
 }
 
 sub process_events {
diff --git a/proxmox-tui-installer/src/views/install_progress.rs b/proxmox-tui-installer/src/views/install_progress.rs
index 01c9941..741529f 100644
--- a/proxmox-tui-installer/src/views/install_progress.rs
+++ b/proxmox-tui-installer/src/views/install_progress.rs
@@ -1,17 +1,16 @@
-use std::{
-    io::{BufRead, BufReader, Write},
-    str::FromStr,
-    sync::{Arc, Mutex},
-    thread,
-    time::Duration,
-};
-
 use cursive::{
     utils::Counter,
     view::{Nameable, Resizable, ViewWrapper},
     views::{Dialog, DummyView, LinearLayout, PaddedView, ProgressBar, TextContent, TextView},
     CbSink, Cursive,
 };
+use serde::Deserialize;
+use std::{
+    io::{BufRead, BufReader, Write},
+    sync::{Arc, Mutex},
+    thread,
+    time::Duration,
+};
 
 use crate::{abort_install_button, prompt_dialog, setup::InstallConfig, InstallerState};
 use proxmox_installer_common::setup::spawn_low_level_installer;
@@ -95,7 +94,7 @@ impl InstallProgressView {
                     Err(err) => return Err(format!("low-level installer exited early: {err}")),
                 };
 
-                let msg = match line.parse::<UiMessage>() {
+                let msg = match serde_json::from_str::<UiMessage>(&line) {
                     Ok(msg) => msg,
                     Err(stray) => {
                         // Not a fatal error, so don't abort the installation by returning
@@ -105,26 +104,26 @@ impl InstallProgressView {
                 };
 
                 let result = match msg.clone() {
-                    UiMessage::Info(s) => cb_sink.send(Box::new(|siv| {
-                        siv.add_layer(Dialog::info(s).title("Information"));
+                    UiMessage::Info { message } => cb_sink.send(Box::new(|siv| {
+                        siv.add_layer(Dialog::info(message).title("Information"));
                     })),
-                    UiMessage::Error(s) => cb_sink.send(Box::new(|siv| {
-                        siv.add_layer(Dialog::info(s).title("Error"));
+                    UiMessage::Error { message } => cb_sink.send(Box::new(|siv| {
+                        siv.add_layer(Dialog::info(message).title("Error"));
                     })),
-                    UiMessage::Prompt(s) => cb_sink.send({
+                    UiMessage::Prompt { query } => cb_sink.send({
                         let writer = writer.clone();
-                        Box::new(move |siv| Self::show_prompt(siv, &s, writer))
+                        Box::new(move |siv| Self::show_prompt(siv, &query, writer))
                     }),
-                    UiMessage::Progress(ratio, s) => {
-                        counter.set(ratio);
-                        progress_text.set_content(s);
+                    UiMessage::Progress { ratio, text } => {
+                        counter.set((ratio * 100.).floor() as usize);
+                        progress_text.set_content(text);
                         Ok(())
                     }
-                    UiMessage::Finished(success, msg) => {
+                    UiMessage::Finished { state, message } => {
                         counter.set(100);
-                        progress_text.set_content(msg.to_owned());
+                        progress_text.set_content(message.to_owned());
                         cb_sink.send(Box::new(move |siv| {
-                            Self::prepare_for_reboot(siv, success, &msg)
+                            Self::prepare_for_reboot(siv, state == "ok", &message);
                         }))
                     }
                 };
@@ -189,6 +188,19 @@ impl InstallProgressView {
     }
 
     fn show_prompt<W: Write + 'static>(siv: &mut Cursive, text: &str, writer: Arc<Mutex<W>>) {
+        let send_answer = |writer: Arc<Mutex<W>>, answer| {
+            if let Ok(mut writer) = writer.lock() {
+                let _ = writeln!(
+                    writer,
+                    "{}",
+                    serde_json::json!({
+                        "type" : "prompt-answer",
+                        "answer" : answer,
+                    })
+                );
+            }
+        };
+
         prompt_dialog(
             siv,
             "Prompt",
@@ -197,16 +209,12 @@ impl InstallProgressView {
             Box::new({
                 let writer = writer.clone();
                 move |_| {
-                    if let Ok(mut writer) = writer.lock() {
-                        let _ = writeln!(writer, "ok");
-                    }
+                    send_answer(writer.clone(), "ok");
                 }
             }),
             "Cancel",
             Box::new(move |_| {
-                if let Ok(mut writer) = writer.lock() {
-                    let _ = writeln!(writer);
-                }
+                send_answer(writer.clone(), "cancel");
             }),
         );
     }
@@ -216,40 +224,25 @@ impl ViewWrapper for InstallProgressView {
     cursive::wrap_impl!(self.view: PaddedView<LinearLayout>);
 }
 
-#[derive(Clone, Debug)]
+#[derive(Clone, Debug, Deserialize, PartialEq)]
+#[serde(tag = "type", rename_all = "lowercase")]
 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}")),
-        }
-    }
+    #[serde(rename = "message")]
+    Info {
+        message: String,
+    },
+    Error {
+        message: String,
+    },
+    Prompt {
+        query: String,
+    },
+    Finished {
+        state: String,
+        message: String,
+    },
+    Progress {
+        ratio: f32,
+        text: String,
+    },
 }
-- 
2.42.0





More information about the pve-devel mailing list