cli: Add pImpl to libdnf5::cli::progressbar::MultiProgressBar

This commit is contained in:
Marek Blaha 2025-07-10 13:03:53 +02:00
parent f30888b5b8
commit 8afd594761
2 changed files with 99 additions and 62 deletions

View File

@ -27,6 +27,8 @@ along with libdnf. If not, see <https://www.gnu.org/licenses/>.
#include "libdnf5-cli/defs.h"
#include "libdnf5/common/impl_ptr.hpp"
#include <iostream>
#include <memory>
#include <vector>
@ -42,21 +44,28 @@ public:
explicit MultiProgressBar();
~MultiProgressBar();
MultiProgressBar(const MultiProgressBar & src) = delete;
MultiProgressBar & operator=(const MultiProgressBar & src) = delete;
MultiProgressBar(MultiProgressBar && src) noexcept = delete;
MultiProgressBar & operator=(MultiProgressBar && src) noexcept = delete;
void add_bar(std::unique_ptr<ProgressBar> && bar);
// In interactive mode MultiProgressBar doesn't print a newline after unfinished progressbars.
// Finished progressbars always end with a newline.
void print() {
std::cerr << *this;
std::cerr << std::flush;
}
void print();
LIBDNF_CLI_API friend std::ostream & operator<<(std::ostream & stream, MultiProgressBar & mbar);
/// Returns the total bar.
DownloadProgressBar & get_total_bar() noexcept;
/// Sets the minimum number of registered progress bars to show the total bar.
void set_total_bar_visible_limit(std::size_t value) noexcept { total_bar_visible_limit = value; }
void set_total_bar_visible_limit(std::size_t value) noexcept;
/// Sets the visibility of number widget in the total bar.
void set_total_bar_number_widget_visible(bool value) noexcept { total.set_number_widget_visible(value); }
void set_total_bar_number_widget_visible(bool value) noexcept;
/// Allows to preset the value of the total number of progress bars.
/// If the value is lower than the current number of registered progress bars, it is automatically increased.
@ -67,14 +76,8 @@ public:
std::size_t get_total_num_of_bars() const noexcept;
private:
std::size_t total_bar_visible_limit{0};
std::vector<std::unique_ptr<ProgressBar>> bars_all;
std::vector<ProgressBar *> bars_todo;
std::vector<ProgressBar *> bars_done;
DownloadProgressBar total;
// Whether the last line was printed without a new line ending (such as an in progress bar)
bool line_printed{false};
std::size_t num_of_lines_to_clear{0};
class LIBDNF_CLI_LOCAL Impl;
ImplPtr<Impl> p_impl;
};

View File

@ -32,68 +32,100 @@ along with libdnf. If not, see <https://www.gnu.org/licenses/>.
namespace libdnf5::cli::progressbar {
class MultiProgressBar::Impl {
public:
Impl();
MultiProgressBar::MultiProgressBar() : total(0, _("Total")) {
std::size_t total_bar_visible_limit{0};
std::vector<std::unique_ptr<ProgressBar>> bars_all;
std::vector<ProgressBar *> bars_todo;
std::vector<ProgressBar *> bars_done;
DownloadProgressBar total;
// Whether the last line was printed without a new line ending (such as an in progress bar)
bool line_printed{false};
std::size_t num_of_lines_to_clear{0};
};
MultiProgressBar::Impl::Impl() : total(0, _("Total")) {
total.set_auto_finish(false);
total.start();
}
MultiProgressBar::MultiProgressBar() : p_impl(new Impl()) {
if (tty::is_interactive()) {
std::cerr << tty::cursor_hide;
}
}
MultiProgressBar::~MultiProgressBar() {
if (tty::is_interactive()) {
std::cerr << tty::cursor_show;
}
}
void MultiProgressBar::print() {
std::cerr << *this;
std::cerr << std::flush;
}
void MultiProgressBar::set_total_bar_visible_limit(std::size_t value) noexcept {
p_impl->total_bar_visible_limit = value;
}
DownloadProgressBar & MultiProgressBar::get_total_bar() noexcept {
return p_impl->total;
}
void MultiProgressBar::set_total_bar_number_widget_visible(bool value) noexcept {
p_impl->total.set_number_widget_visible(value);
}
void MultiProgressBar::add_bar(std::unique_ptr<ProgressBar> && bar) {
bars_todo.push_back(bar.get());
p_impl->bars_todo.push_back(bar.get());
// if the number is not set, automatically find and set the next available
if (bar->get_number() == 0) {
int number = 0;
for (auto i : bars_todo) {
for (auto i : p_impl->bars_todo) {
number = std::max(number, i->get_number());
}
bar->set_number(number + 1);
}
bars_all.push_back(std::move(bar));
p_impl->bars_all.push_back(std::move(bar));
// update total (in [num/total]) in total progress bar
auto registered_bars_count = static_cast<int32_t>(bars_all.size());
if (total.get_total() < registered_bars_count) {
total.set_total(registered_bars_count);
auto registered_bars_count = static_cast<int32_t>(p_impl->bars_all.size());
if (p_impl->total.get_total() < registered_bars_count) {
p_impl->total.set_total(registered_bars_count);
}
// update total (in [num/total]) in all bars to do
for (auto & i : bars_todo) {
i->set_total(total.get_total());
for (auto & i : p_impl->bars_todo) {
i->set_total(p_impl->total.get_total());
}
}
void MultiProgressBar::set_total_num_of_bars(std::size_t value) noexcept {
if (value < bars_all.size()) {
value = bars_all.size();
if (value < p_impl->bars_all.size()) {
value = p_impl->bars_all.size();
}
auto num_of_bars = static_cast<int>(value);
if (num_of_bars != total.get_total()) {
total.set_total(num_of_bars);
if (num_of_bars != p_impl->total.get_total()) {
p_impl->total.set_total(num_of_bars);
// update total (in [num/total]) in all bars to do
for (auto & i : bars_todo) {
i->set_total(total.get_total());
for (auto & i : p_impl->bars_todo) {
i->set_total(p_impl->total.get_total());
}
}
}
std::size_t MultiProgressBar::get_total_num_of_bars() const noexcept {
return static_cast<std::size_t>(total.get_total());
return static_cast<std::size_t>(p_impl->total.get_total());
}
@ -106,30 +138,30 @@ std::ostream & operator<<(std::ostream & stream, MultiProgressBar & mbar) {
text_buffer.str("");
text_buffer.clear();
std::size_t last_num_of_lines_to_clear = mbar.num_of_lines_to_clear;
std::size_t last_num_of_lines_to_clear = mbar.p_impl->num_of_lines_to_clear;
std::size_t num_of_lines_permanent = 0;
if (is_interactive && mbar.num_of_lines_to_clear > 0) {
if (mbar.num_of_lines_to_clear > 1) {
if (is_interactive && mbar.p_impl->num_of_lines_to_clear > 0) {
if (mbar.p_impl->num_of_lines_to_clear > 1) {
// Move the cursor up by the number of lines we want to write over
text_buffer << "\033[" << (mbar.num_of_lines_to_clear - 1) << "A";
text_buffer << "\033[" << (mbar.p_impl->num_of_lines_to_clear - 1) << "A";
}
text_buffer << "\r";
}
// Number of lines that need to be cleared, including the last line even if it is empty
mbar.num_of_lines_to_clear = 0;
mbar.line_printed = false;
mbar.p_impl->num_of_lines_to_clear = 0;
mbar.p_impl->line_printed = false;
// store numbers of bars in progress
std::vector<int32_t> numbers;
for (auto * bar : mbar.bars_todo) {
for (auto * bar : mbar.p_impl->bars_todo) {
numbers.insert(numbers.begin(), bar->get_number());
}
// print completed bars first and remove them from the list
for (std::size_t i = 0; i < mbar.bars_todo.size(); i++) {
auto * bar = mbar.bars_todo[i];
for (std::size_t i = 0; i < mbar.p_impl->bars_todo.size(); i++) {
auto * bar = mbar.p_impl->bars_todo[i];
if (!bar->is_finished()) {
continue;
}
@ -139,14 +171,14 @@ std::ostream & operator<<(std::ostream & stream, MultiProgressBar & mbar) {
text_buffer << std::endl;
num_of_lines_permanent++;
num_of_lines_permanent += bar->get_messages().size();
mbar.bars_done.push_back(bar);
mbar.p_impl->bars_done.push_back(bar);
// TODO(dmach): use iterator
mbar.bars_todo.erase(mbar.bars_todo.begin() + static_cast<int>(i));
mbar.p_impl->bars_todo.erase(mbar.p_impl->bars_todo.begin() + static_cast<int>(i));
i--;
}
// then print incomplete
for (auto & bar : mbar.bars_todo) {
for (auto & bar : mbar.p_impl->bars_todo) {
bar->set_number(numbers.back());
numbers.pop_back();
@ -160,13 +192,13 @@ std::ostream & operator<<(std::ostream & stream, MultiProgressBar & mbar) {
bar->update();
continue;
}
if (mbar.line_printed) {
if (mbar.p_impl->line_printed) {
text_buffer << std::endl;
}
text_buffer << *bar;
mbar.line_printed = true;
mbar.num_of_lines_to_clear++;
mbar.num_of_lines_to_clear += bar->get_messages().size();
mbar.p_impl->line_printed = true;
mbar.p_impl->num_of_lines_to_clear++;
mbar.p_impl->num_of_lines_to_clear += bar->get_messages().size();
}
// then print the "total" progress bar
@ -174,7 +206,7 @@ std::ostream & operator<<(std::ostream & stream, MultiProgressBar & mbar) {
int64_t ticks = 0;
int64_t total_ticks = 0;
for (auto & bar : mbar.bars_done) {
for (auto & bar : mbar.p_impl->bars_done) {
total_numbers = std::max(total_numbers, bar->get_total());
// completed bars can be unfinished
// add only processed ticks to both values
@ -182,15 +214,16 @@ std::ostream & operator<<(std::ostream & stream, MultiProgressBar & mbar) {
ticks += bar->get_ticks();
}
for (auto & bar : mbar.bars_todo) {
for (auto & bar : mbar.p_impl->bars_todo) {
total_numbers = std::max(total_numbers, bar->get_total());
total_ticks += bar->get_total_ticks();
ticks += bar->get_ticks();
}
if ((mbar.bars_all.size() >= mbar.total_bar_visible_limit) && (is_interactive || mbar.bars_todo.empty())) {
if (mbar.line_printed) {
if ((mbar.p_impl->bars_all.size() >= mbar.p_impl->total_bar_visible_limit) &&
(is_interactive || mbar.p_impl->bars_todo.empty())) {
if (mbar.p_impl->line_printed) {
text_buffer << std::endl;
}
// print divider
@ -199,23 +232,24 @@ std::ostream & operator<<(std::ostream & stream, MultiProgressBar & mbar) {
text_buffer << std::endl;
// print Total progress bar
mbar.total.set_number(static_cast<int>(mbar.bars_done.size()));
auto & mbar_total = mbar.get_total_bar();
mbar_total.set_number(static_cast<int>(mbar.p_impl->bars_done.size()));
mbar.total.set_total_ticks(total_ticks);
mbar.total.set_ticks(ticks);
mbar_total.set_total_ticks(total_ticks);
mbar_total.set_ticks(ticks);
if (mbar.bars_todo.empty()) {
if (mbar.p_impl->bars_todo.empty()) {
// all bars have finished, set the "Total" bar as finished too according to their states
mbar.total.set_state(ProgressBarState::SUCCESS);
mbar_total.set_state(ProgressBarState::SUCCESS);
}
text_buffer << mbar.total;
text_buffer << mbar_total;
// +1 for the divider line, +1 for the total bar line
mbar.num_of_lines_to_clear += 2;
if (mbar.total.is_finished()) {
mbar.p_impl->num_of_lines_to_clear += 2;
if (mbar_total.is_finished()) {
text_buffer << std::endl;
} else {
mbar.line_printed = true;
mbar.p_impl->line_printed = true;
}
}
@ -226,10 +260,10 @@ std::ostream & operator<<(std::ostream & stream, MultiProgressBar & mbar) {
// TODO(amatej): It would be sufficient to do this only once after all progressbars have
// finished but unfortunaly MultiProgressBar doesn't have a pImpl so we cannot
// store the highest line count it had. We could fix this when breaking ABI.
size_t all_written = num_of_lines_permanent + mbar.num_of_lines_to_clear;
size_t all_written = num_of_lines_permanent + mbar.p_impl->num_of_lines_to_clear;
if (last_num_of_lines_to_clear > all_written) {
auto delta = last_num_of_lines_to_clear - all_written;
if (!mbar.line_printed) {
if (!mbar.p_impl->line_printed) {
text_buffer << tty::clear_line;
}
for (std::size_t i = 0; i < delta; i++) {