chromium/chrome/browser/extensions/api/terminal/startup_status.cc

// 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.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include "chrome/browser/extensions/api/terminal/startup_status.h"
#include <unistd.h>

#include <algorithm>
#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";
const char 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