diff --git a/src/client/cli/cmd/animated_spinner.cpp b/src/client/cli/cmd/animated_spinner.cpp index 2373ecd45ef..e09ba9d8719 100644 --- a/src/client/cli/cmd/animated_spinner.cpp +++ b/src/client/cli/cmd/animated_spinner.cpp @@ -29,8 +29,8 @@ void clear_line(std::ostream& out) } } // namespace -mp::AnimatedSpinner::AnimatedSpinner(std::ostream& cout) - : spinner{'|', '/', '-', '\\'}, cout{cout}, running{false} +mp::AnimatedSpinner::AnimatedSpinner(std::ostream& cout, bool is_live) + : spinner{'|', '/', '-', '\\'}, cout{cout}, is_live{is_live}, running{false} { } @@ -46,9 +46,18 @@ void mp::AnimatedSpinner::start(const std::string& start_message) { current_message = start_message; running = true; - clear_line(cout); - cout << start_message << " " << std::flush; - t = std::thread(&AnimatedSpinner::draw, this); + + if (is_live) + { + clear_line(cout); + cout << start_message << " " << std::flush; + t = std::thread(&AnimatedSpinner::draw, this); + } + else + { + cout << start_message << "\n" << std::flush; + t = std::thread(&AnimatedSpinner::draw_plain, this); + } } } @@ -69,16 +78,23 @@ void mp::AnimatedSpinner::stop() if (t.joinable()) t.join(); } - clear_line(cout); + + if (is_live) + clear_line(cout); } void mp::AnimatedSpinner::print(std::ostream& stream, const std::string& message) { - stop(); - - stream << message; - - start(); + if (is_live) + { + stop(); + stream << message; + start(); + } + else + { + stream << message << std::flush; + } } void mp::AnimatedSpinner::draw() @@ -95,3 +111,14 @@ void mp::AnimatedSpinner::draw() cout << "\b" << " " << std::flush; } + +void mp::AnimatedSpinner::draw_plain() +{ + std::unique_lock lock{mutex}; + while (running) + { + cv.wait_for(lock, std::chrono::seconds(5)); + if (running) + cout << "." << std::flush; + } +} diff --git a/src/client/cli/cmd/animated_spinner.h b/src/client/cli/cmd/animated_spinner.h index 58c18eba9ff..226158e40a0 100644 --- a/src/client/cli/cmd/animated_spinner.h +++ b/src/client/cli/cmd/animated_spinner.h @@ -27,7 +27,7 @@ namespace multipass class AnimatedSpinner { public: - explicit AnimatedSpinner(std::ostream& cout); + AnimatedSpinner(std::ostream& cout, bool is_live); ~AnimatedSpinner(); void start(const std::string& message); @@ -37,8 +37,10 @@ class AnimatedSpinner private: void draw(); + void draw_plain(); const std::vector spinner; std::ostream& cout; + const bool is_live; bool running; std::string current_message; std::mutex mutex; diff --git a/src/client/cli/cmd/clone.cpp b/src/client/cli/cmd/clone.cpp index 8bde7000d09..4fde19a3e06 100644 --- a/src/client/cli/cmd/clone.cpp +++ b/src/client/cli/cmd/clone.cpp @@ -33,7 +33,7 @@ mp::ReturnCodeVariant cmd::Clone::run(ArgParser* parser) return parser->returnCodeFrom(parscode); } - AnimatedSpinner spinner{cout}; + AnimatedSpinner spinner{cout, term->cout_is_live()}; auto action_on_success = [this, &spinner](CloneReply& reply) -> ReturnCodeVariant { spinner.stop(); cout << reply.reply_message(); diff --git a/src/client/cli/cmd/launch.cpp b/src/client/cli/cmd/launch.cpp index 94f486c760c..65048ae59f8 100644 --- a/src/client/cli/cmd/launch.cpp +++ b/src/client/cli/cmd/launch.cpp @@ -468,7 +468,7 @@ mp::ReturnCodeVariant cmd::Launch::request_launch(const ArgParser* parser) { if (!spinner) spinner = std::make_unique( - cout); // Creating just in time to work around canonical/multipass#2075 + cout, term->cout_is_live()); // Creating just in time to work around canonical/multipass#2075 if (timer) timer->resume(); diff --git a/src/client/cli/cmd/mount.cpp b/src/client/cli/cmd/mount.cpp index 2030ddb990b..7638b507f27 100644 --- a/src/client/cli/cmd/mount.cpp +++ b/src/client/cli/cmd/mount.cpp @@ -78,7 +78,7 @@ mp::ReturnCodeVariant cmd::Mount::run(mp::ArgParser* parser) return parser->returnCodeFrom(ret); } - mp::AnimatedSpinner spinner{cout}; + mp::AnimatedSpinner spinner{cout, term->cout_is_live()}; auto on_success = [&spinner](mp::MountReply& reply) -> ReturnCodeVariant { spinner.stop(); diff --git a/src/client/cli/cmd/restart.cpp b/src/client/cli/cmd/restart.cpp index 6a97424ea2d..e296fce3587 100644 --- a/src/client/cli/cmd/restart.cpp +++ b/src/client/cli/cmd/restart.cpp @@ -38,7 +38,7 @@ mp::ReturnCodeVariant cmd::Restart::run(mp::ArgParser* parser) if (ret != ParseCode::Ok) return parser->returnCodeFrom(ret); - AnimatedSpinner spinner{cout}; + AnimatedSpinner spinner{cout, term->cout_is_live()}; auto on_success = [this, &spinner](mp::RestartReply& reply) -> ReturnCodeVariant { spinner.stop(); if (term->is_live() && update_available(reply.update_info())) diff --git a/src/client/cli/cmd/restore.cpp b/src/client/cli/cmd/restore.cpp index fbb4feba93e..6b9fecb6b0e 100644 --- a/src/client/cli/cmd/restore.cpp +++ b/src/client/cli/cmd/restore.cpp @@ -31,7 +31,7 @@ mp::ReturnCodeVariant cmd::Restore::run(mp::ArgParser* parser) if (auto ret = parse_args(parser); ret != ParseCode::Ok) return parser->returnCodeFrom(ret); - AnimatedSpinner spinner{cout}; + AnimatedSpinner spinner{cout, term->cout_is_live()}; auto on_success = [this, &spinner](mp::RestoreReply& reply) -> ReturnCodeVariant { spinner.stop(); diff --git a/src/client/cli/cmd/snapshot.cpp b/src/client/cli/cmd/snapshot.cpp index 7b2ba492dc3..a3965c47a90 100644 --- a/src/client/cli/cmd/snapshot.cpp +++ b/src/client/cli/cmd/snapshot.cpp @@ -31,7 +31,7 @@ mp::ReturnCodeVariant cmd::Snapshot::run(mp::ArgParser* parser) if (auto ret = parse_args(parser); ret != ParseCode::Ok) return parser->returnCodeFrom(ret); - AnimatedSpinner spinner{cout}; + AnimatedSpinner spinner{cout, term->cout_is_live()}; auto on_success = [this, &spinner](mp::SnapshotReply& reply) -> ReturnCodeVariant { spinner.stop(); diff --git a/src/client/cli/cmd/start.cpp b/src/client/cli/cmd/start.cpp index e1bd276e32f..7c8b931b3a5 100644 --- a/src/client/cli/cmd/start.cpp +++ b/src/client/cli/cmd/start.cpp @@ -55,7 +55,7 @@ mp::ReturnCodeVariant cmd::Start::run(mp::ArgParser* parser) return parser->returnCodeFrom(ret); } - AnimatedSpinner spinner{cout}; + AnimatedSpinner spinner{cout, term->cout_is_live()}; auto on_success = [&spinner, this](mp::StartReply& reply) -> ReturnCodeVariant { spinner.stop(); diff --git a/src/client/cli/cmd/stop.cpp b/src/client/cli/cmd/stop.cpp index a94101a2042..5eb4e42fdd3 100644 --- a/src/client/cli/cmd/stop.cpp +++ b/src/client/cli/cmd/stop.cpp @@ -39,7 +39,7 @@ mp::ReturnCodeVariant cmd::Stop::run(mp::ArgParser* parser) auto on_success = [](mp::StopReply& reply) -> ReturnCodeVariant { return ReturnCode::Ok; }; - AnimatedSpinner spinner{cout}; + AnimatedSpinner spinner{cout, term->cout_is_live()}; auto on_failure = [this, &spinner](grpc::Status& status) -> ReturnCodeVariant { spinner.stop(); diff --git a/src/client/cli/cmd/suspend.cpp b/src/client/cli/cmd/suspend.cpp index 67413acd620..24d3f41ed03 100644 --- a/src/client/cli/cmd/suspend.cpp +++ b/src/client/cli/cmd/suspend.cpp @@ -38,7 +38,7 @@ mp::ReturnCodeVariant cmd::Suspend::run(mp::ArgParser* parser) auto on_success = [](mp::SuspendReply& reply) -> ReturnCodeVariant { return ReturnCode::Ok; }; - AnimatedSpinner spinner{cout}; + AnimatedSpinner spinner{cout, term->cout_is_live()}; auto on_failure = [this, &spinner](grpc::Status& status) -> ReturnCodeVariant { spinner.stop(); return standard_failure_handler_for(name(), cerr, status); diff --git a/src/client/cli/cmd/wait_ready.cpp b/src/client/cli/cmd/wait_ready.cpp index db7c8bcef65..5b695cd1ee6 100644 --- a/src/client/cli/cmd/wait_ready.cpp +++ b/src/client/cli/cmd/wait_ready.cpp @@ -36,7 +36,7 @@ mp::ReturnCodeVariant cmd::WaitReady::run(mp::ArgParser* parser) return parser->returnCodeFrom(ret); } - mp::AnimatedSpinner spinner{cout}; + mp::AnimatedSpinner spinner{cout, term->cout_is_live()}; spinner.start("Waiting for the Multipass daemon to be ready"); std::unique_ptr timer; diff --git a/tests/unit/test_common_callbacks.cpp b/tests/unit/test_common_callbacks.cpp index 53421d3b298..954853f1ccf 100644 --- a/tests/unit/test_common_callbacks.cpp +++ b/tests/unit/test_common_callbacks.cpp @@ -44,7 +44,7 @@ struct TestSpinnerCallbacks : public Test std::ostringstream out, err; std::istringstream in; mpt::StubTerminal term{out, err, in}; - mp::AnimatedSpinner spinner{out}; + mp::AnimatedSpinner spinner{out, true}; }; enum class CommonCallbackType