| // Copyright 2019 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/extensions/api/terminal/startup_status.h" |
| |
| #include <unistd.h> |
| |
| #include <algorithm> |
| #include <array> |
| #include <memory> |
| #include <vector> |
| |
| #include "base/functional/bind.h" |
| #include "base/location.h" |
| #include "base/strings/strcat.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/time/time.h" |
| #include "chrome/grit/generated_resources.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "ui/base/l10n/l10n_util.h" |
| |
| namespace extensions { |
| |
| namespace { |
| |
| const char kCursorHide[] = "\x1b[?25l"; |
| const char kCursorShow[] = "\x1b[?25h"; |
| const char kColor0Normal[] = "\x1b[0m"; // Default. |
| const char kColor1RedBright[] = "\x1b[1;31m"; |
| const char kColor2GreenBright[] = "\x1b[1;32m"; |
| const char kColor3Yellow[] = "\x1b[33m"; |
| const char kColor5Purple[] = "\x1b[35m"; |
| const char kEraseInLine[] = "\x1b[K"; |
| constexpr std::array kSpinnerCharacters = {'|', '/', '-', '\\'}; |
| |
| std::string MoveForward(int i) { |
| return base::StringPrintf("\x1b[%dC", i); |
| } |
| |
| } // namespace |
| |
| StartupStatusPrinter::StartupStatusPrinter( |
| base::RepeatingCallback<void(const std::string& output)> print, |
| bool verbose) |
| : print_(std::move(print)), verbose_(verbose) {} |
| |
| StartupStatusPrinter::~StartupStatusPrinter() = default; |
| |
| // Starts showing the progress indicator. |
| void StartupStatusPrinter::StartShowingSpinner() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| show_progress_timer_ = std::make_unique<base::RepeatingTimer>(); |
| show_progress_timer_->Start( |
| FROM_HERE, base::Milliseconds(300), |
| base::BindRepeating(&StartupStatusPrinter::PrintProgress, |
| // We own the timer, so this'll never get called after |
| // we're destroyed. |
| base::Unretained(this))); |
| } |
| |
| void StartupStatusPrinter::PrintStageWithColor(int stage_index, |
| const char* color, |
| const std::string& stage_name) { |
| DCHECK_GE(stage_index, 0); |
| DCHECK_LE(stage_index, max_stage_); |
| InitializeProgress(); |
| stage_index_ = stage_index; |
| auto output = verbose_ ? stage_name : ""; |
| std::string progress(stage_index_, '='); |
| std::string padding(max_stage_ - stage_index_, ' '); |
| Print(base::StringPrintf("\r%s[%s%s] %s%s%s ", kColor5Purple, |
| progress.c_str(), padding.c_str(), kEraseInLine, |
| color, output.c_str())); |
| end_of_line_index_ = 4 + max_stage_ + output.size(); |
| } |
| |
| void StartupStatusPrinter::PrintStage(int stage_index, |
| const std::string& stage_name) { |
| PrintStageWithColor(stage_index, kColor3Yellow, stage_name); |
| } |
| |
| void StartupStatusPrinter::PrintError(const std::string& output) { |
| InitializeProgress(); |
| Print(base::StringPrintf("\r%s%s%s", MoveForward(end_of_line_index_).c_str(), |
| kColor1RedBright, output.c_str())); |
| end_of_line_index_ += output.size(); |
| Print( |
| base::StringPrintf("\r%s%s%s", kEraseInLine, kColor0Normal, kCursorShow)); |
| } |
| |
| void StartupStatusPrinter::PrintSucceeded() { |
| InitializeProgress(); |
| if (verbose_) { |
| auto output = base::StrCat( |
| {l10n_util::GetStringUTF8(IDS_CROSTINI_TERMINAL_STATUS_READY), "\r\n"}); |
| PrintStageWithColor(max_stage_, kColor2GreenBright, output); |
| } |
| Print( |
| base::StringPrintf("\r%s%s%s", kEraseInLine, kColor0Normal, kCursorShow)); |
| } |
| |
| void StartupStatusPrinter::Print(const std::string& output) { |
| print_.Run(output); |
| } |
| |
| void StartupStatusPrinter::InitializeProgress() { |
| if (progress_initialized_) { |
| return; |
| } |
| progress_initialized_ = true; |
| Print(base::StringPrintf("%s%s[%s] ", kCursorHide, kColor5Purple, |
| std::string(max_stage_, ' ').c_str())); |
| } |
| |
| void StartupStatusPrinter::PrintProgress() { |
| InitializeProgress(); |
| spinner_index_++; |
| Print(base::StringPrintf("\r%s%s%c", MoveForward(stage_index_).c_str(), |
| kColor5Purple, |
| kSpinnerCharacters[spinner_index_ & 0x3])); |
| } |
| |
| StartupStatus::StartupStatus(std::unique_ptr<StartupStatusPrinter> printer, |
| int max_stage) |
| : printer_(std::move(printer)), max_stage_(max_stage) { |
| printer_->set_max_stage(max_stage); |
| } |
| |
| StartupStatus::~StartupStatus() = default; |
| |
| void StartupStatus::OnConnectingToVsh() { |
| const std::string& stage_string = |
| l10n_util::GetStringUTF8(IDS_CROSTINI_TERMINAL_STATUS_CONNECT_CONTAINER); |
| printer()->PrintStage(max_stage_, stage_string); |
| } |
| void StartupStatus::StartShowingSpinner() { |
| printer()->StartShowingSpinner(); |
| } |
| |
| void StartupStatus::OnFinished(bool success, |
| const std::string& failure_reason) { |
| if (success) { |
| printer()->PrintSucceeded(); |
| } else { |
| printer()->PrintError(failure_reason); |
| } |
| } |
| |
| } // namespace extensions |