cli: Add pImpl to libdnf5::cli::progressbar::ProgressBar

This commit is contained in:
Marek Blaha 2025-07-10 14:29:51 +02:00
parent 8afd594761
commit dc068cf54c
2 changed files with 224 additions and 121 deletions

View File

@ -23,6 +23,8 @@ along with libdnf. If not, see <https://www.gnu.org/licenses/>.
#include "libdnf5-cli/defs.h"
#include "libdnf5/common/impl_ptr.hpp"
#include <chrono>
#include <string>
#include <vector>
@ -54,64 +56,61 @@ public:
explicit ProgressBar(int64_t total_ticks);
explicit ProgressBar(int64_t total_ticks, const std::string & description);
virtual ~ProgressBar() = default;
virtual ~ProgressBar();
ProgressBar(const ProgressBar & src);
ProgressBar & operator=(const ProgressBar & src);
ProgressBar(ProgressBar && src) noexcept;
ProgressBar & operator=(ProgressBar && src) noexcept;
void reset();
// ticks
int64_t get_ticks() const noexcept { return ticks; }
int64_t get_ticks() const noexcept;
void set_ticks(int64_t value);
void add_ticks(int64_t value);
// total ticks
int64_t get_total_ticks() const noexcept { return total_ticks; }
int64_t get_total_ticks() const noexcept;
void set_total_ticks(int64_t value);
// number
int32_t get_number() const noexcept { return number; }
void set_number(int32_t value) { number = value; }
int32_t get_number() const noexcept;
void set_number(int32_t value);
// total
int32_t get_total() const noexcept { return total; }
void set_total(int32_t value) { total = value; }
int32_t get_total() const noexcept;
void set_total(int32_t value);
// workflow
void start();
ProgressBarState get_state() const noexcept { return state; }
void set_state(ProgressBarState value) {
update();
state = value;
}
bool is_finished() const noexcept {
return get_state() != ProgressBarState::READY && get_state() != ProgressBarState::STARTED;
}
bool is_failed() const noexcept {
return get_state() == ProgressBarState::ERROR || get_state() == ProgressBarState::WARNING;
}
ProgressBarState get_state() const noexcept;
void set_state(ProgressBarState value);
bool is_finished() const noexcept;
bool is_failed() const noexcept;
// description
std::string get_description() const noexcept { return description; }
void set_description(const std::string & value) { description = value; }
std::string get_description() const noexcept;
void set_description(const std::string & value);
// messages
void add_message(MessageType type, const std::string & message) { messages.emplace_back(type, message); }
void add_message(MessageType type, const std::string & message);
/// remove the last message
void pop_message();
const std::vector<Message> & get_messages() const noexcept { return messages; }
const std::vector<Message> & get_messages() const noexcept;
// auto-finish feature; turn off if you want to handle state manually
bool get_auto_finish() const noexcept { return auto_finish; }
void set_auto_finish(bool value) { auto_finish = value; }
bool get_auto_finish() const noexcept;
void set_auto_finish(bool value);
// stats
void update();
int32_t get_percent_done() const noexcept { return percent_done; }
int64_t get_current_speed() const noexcept { return current_speed; }
int64_t get_average_speed() const noexcept { return average_speed; }
int64_t get_elapsed_seconds() const noexcept { return elapsed_seconds; }
int64_t get_remaining_seconds() const noexcept { return remaining_seconds; }
int32_t get_percent_done() const noexcept;
int64_t get_current_speed() const noexcept;
int64_t get_average_speed() const noexcept;
int64_t get_elapsed_seconds() const noexcept;
int64_t get_remaining_seconds() const noexcept;
std::chrono::time_point<std::chrono::system_clock> get_begin() { return begin; }
std::chrono::time_point<std::chrono::system_clock> get_begin();
LIBDNF_CLI_API friend std::ostream & operator<<(std::ostream & os, ProgressBar & bar);
@ -119,36 +118,8 @@ protected:
virtual void to_stream(std::ostream & stream) = 0;
private:
// ticks
int64_t ticks = -1;
int64_t total_ticks = -1;
// numbers
int32_t number = 0;
int32_t total = 0;
// description
std::string description;
// messages
std::vector<Message> messages;
ProgressBarState state = ProgressBarState::READY;
int32_t percent_done = -1;
int64_t elapsed_seconds = 0;
int64_t remaining_seconds = 0;
int64_t average_speed = 0;
bool auto_finish = true;
std::chrono::time_point<std::chrono::system_clock> begin = std::chrono::system_clock::from_time_t(0);
std::chrono::time_point<std::chrono::system_clock> end = std::chrono::system_clock::from_time_t(0);
// current (average) speed over the last second
std::chrono::time_point<std::chrono::system_clock> current_speed_window_start = std::chrono::system_clock::now();
int64_t current_speed = 0;
int64_t current_speed_window_ticks = 0;
class LIBDNF_CLI_LOCAL Impl;
ImplPtr<Impl> p_impl;
};

View File

@ -23,33 +23,164 @@ along with libdnf. If not, see <https://www.gnu.org/licenses/>.
namespace libdnf5::cli::progressbar {
class ProgressBar::Impl {
public:
explicit Impl(int64_t total_ticks) : total_ticks{total_ticks} {}
Impl(int64_t total_ticks, const std::string & description) : total_ticks{total_ticks}, description{description} {}
ProgressBar::ProgressBar(int64_t total_ticks) : total_ticks{total_ticks} {}
// ticks
int64_t ticks = -1;
int64_t total_ticks = -1;
// numbers
int32_t number = 0;
int32_t total = 0;
// description
std::string description;
// messages
std::vector<ProgressBar::Message> messages;
ProgressBarState state = ProgressBarState::READY;
int32_t percent_done = -1;
int64_t elapsed_seconds = 0;
int64_t remaining_seconds = 0;
int64_t average_speed = 0;
bool auto_finish = true;
std::chrono::time_point<std::chrono::system_clock> begin = std::chrono::system_clock::from_time_t(0);
std::chrono::time_point<std::chrono::system_clock> end = std::chrono::system_clock::from_time_t(0);
// current (average) speed over the last second
std::chrono::time_point<std::chrono::system_clock> current_speed_window_start = std::chrono::system_clock::now();
int64_t current_speed = 0;
int64_t current_speed_window_ticks = 0;
};
ProgressBar::~ProgressBar() = default;
ProgressBar::ProgressBar(int64_t total_ticks) : p_impl(new Impl(total_ticks)) {}
ProgressBar::ProgressBar(int64_t total_ticks, const std::string & description)
: total_ticks{total_ticks},
description{description} {}
: p_impl(new Impl(total_ticks, description)) {}
ProgressBar::ProgressBar(const ProgressBar & src) = default;
ProgressBar & ProgressBar::operator=(const ProgressBar & src) = default;
ProgressBar::ProgressBar(ProgressBar && src) noexcept = default;
ProgressBar & ProgressBar::operator=(ProgressBar && src) noexcept = default;
int64_t ProgressBar::get_ticks() const noexcept {
return p_impl->ticks;
}
int64_t ProgressBar::get_total_ticks() const noexcept {
return p_impl->total_ticks;
}
int32_t ProgressBar::get_number() const noexcept {
return p_impl->number;
}
void ProgressBar::set_number(int32_t value) {
p_impl->number = value;
}
int32_t ProgressBar::get_total() const noexcept {
return p_impl->total;
}
void ProgressBar::set_total(int32_t value) {
p_impl->total = value;
}
ProgressBarState ProgressBar::get_state() const noexcept {
return p_impl->state;
}
void ProgressBar::set_state(ProgressBarState value) {
update();
p_impl->state = value;
}
bool ProgressBar::is_finished() const noexcept {
return get_state() != ProgressBarState::READY && get_state() != ProgressBarState::STARTED;
}
bool ProgressBar::is_failed() const noexcept {
return get_state() == ProgressBarState::ERROR || get_state() == ProgressBarState::WARNING;
}
std::string ProgressBar::get_description() const noexcept {
return p_impl->description;
}
void ProgressBar::set_description(const std::string & value) {
p_impl->description = value;
}
void ProgressBar::add_message(MessageType type, const std::string & message) {
p_impl->messages.emplace_back(type, message);
}
const std::vector<ProgressBar::Message> & ProgressBar::get_messages() const noexcept {
return p_impl->messages;
}
bool ProgressBar::get_auto_finish() const noexcept {
return p_impl->auto_finish;
}
void ProgressBar::set_auto_finish(bool value) {
p_impl->auto_finish = value;
}
int32_t ProgressBar::get_percent_done() const noexcept {
return p_impl->percent_done;
}
int64_t ProgressBar::get_current_speed() const noexcept {
return p_impl->current_speed;
}
int64_t ProgressBar::get_average_speed() const noexcept {
return p_impl->average_speed;
}
int64_t ProgressBar::get_elapsed_seconds() const noexcept {
return p_impl->elapsed_seconds;
}
int64_t ProgressBar::get_remaining_seconds() const noexcept {
return p_impl->remaining_seconds;
}
std::chrono::time_point<std::chrono::system_clock> ProgressBar::get_begin() {
return p_impl->begin;
}
void ProgressBar::reset() {
ticks = -1;
total_ticks = -1;
number = 0;
total = 0;
description = "";
messages.clear();
state = ProgressBarState::READY;
percent_done = -1;
elapsed_seconds = 0;
remaining_seconds = 0;
average_speed = 0;
auto_finish = true;
begin = std::chrono::system_clock::from_time_t(0);
end = std::chrono::system_clock::from_time_t(0);
current_speed_window_start = std::chrono::system_clock::now();
current_speed = 0;
current_speed_window_ticks = 0;
p_impl->ticks = -1;
p_impl->total_ticks = -1;
p_impl->number = 0;
p_impl->total = 0;
p_impl->description = "";
p_impl->messages.clear();
p_impl->state = ProgressBarState::READY;
p_impl->percent_done = -1;
p_impl->elapsed_seconds = 0;
p_impl->remaining_seconds = 0;
p_impl->average_speed = 0;
p_impl->auto_finish = true;
p_impl->begin = std::chrono::system_clock::from_time_t(0);
p_impl->end = std::chrono::system_clock::from_time_t(0);
p_impl->current_speed_window_start = std::chrono::system_clock::now();
p_impl->current_speed = 0;
p_impl->current_speed_window_ticks = 0;
}
@ -58,27 +189,27 @@ void ProgressBar::set_ticks(int64_t value) {
return;
}
auto new_ticks = value;
if (total_ticks >= 0) {
new_ticks = std::min(new_ticks, total_ticks);
if (p_impl->total_ticks >= 0) {
new_ticks = std::min(new_ticks, p_impl->total_ticks);
}
if (new_ticks >= ticks) {
current_speed_window_ticks += new_ticks - ticks;
if (new_ticks >= p_impl->ticks) {
p_impl->current_speed_window_ticks += new_ticks - p_impl->ticks;
} else {
current_speed_window_ticks = 0;
p_impl->current_speed_window_ticks = 0;
}
ticks = new_ticks;
p_impl->ticks = new_ticks;
}
void ProgressBar::add_ticks(int64_t value) {
set_ticks(ticks + value);
set_ticks(p_impl->ticks + value);
}
void ProgressBar::start() {
if (begin == std::chrono::system_clock::from_time_t(0)) {
begin = std::chrono::system_clock::now();
state = ProgressBarState::STARTED;
if (p_impl->begin == std::chrono::system_clock::from_time_t(0)) {
p_impl->begin = std::chrono::system_clock::now();
p_impl->state = ProgressBarState::STARTED;
}
}
@ -88,65 +219,66 @@ void ProgressBar::update() {
return;
}
if (total_ticks < 0) {
if (p_impl->total_ticks < 0) {
// unknown total ticks
percent_done = -1;
} else if (total_ticks == 0) {
p_impl->percent_done = -1;
} else if (p_impl->total_ticks == 0) {
// can't divide by zero, consider progressbar 100% complete
percent_done = 100;
} else if (ticks >= total_ticks) {
percent_done = 100;
p_impl->percent_done = 100;
} else if (p_impl->ticks >= p_impl->total_ticks) {
p_impl->percent_done = 100;
} else {
percent_done = static_cast<int32_t>(static_cast<float>(ticks) / static_cast<float>(total_ticks) * 100);
p_impl->percent_done =
static_cast<int32_t>(static_cast<float>(p_impl->ticks) / static_cast<float>(p_impl->total_ticks) * 100);
}
auto now = std::chrono::high_resolution_clock::now();
// compute the current speed (ticks per second)
auto delta = now - current_speed_window_start;
auto delta = now - p_impl->current_speed_window_start;
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(delta).count();
if (ms > 950) {
current_speed = current_speed_window_ticks * 1000 / ms;
p_impl->current_speed = p_impl->current_speed_window_ticks * 1000 / ms;
// reset the window
current_speed_window_ticks = 0;
current_speed_window_start = now;
} else if (current_speed == 0 && ms != 0) {
current_speed = current_speed_window_ticks * 1000 / ms;
p_impl->current_speed_window_ticks = 0;
p_impl->current_speed_window_start = now;
} else if (p_impl->current_speed == 0 && ms != 0) {
p_impl->current_speed = p_impl->current_speed_window_ticks * 1000 / ms;
}
// compute average speed
delta = now - begin;
delta = now - p_impl->begin;
ms = std::chrono::duration_cast<std::chrono::milliseconds>(delta).count();
if (ms == 0) {
average_speed = 0;
p_impl->average_speed = 0;
} else {
average_speed = ticks * 1000 / ms;
p_impl->average_speed = p_impl->ticks * 1000 / ms;
}
// compute elapsed seconds
// round the result to display 00m00s less frequently
elapsed_seconds = std::chrono::round<std::chrono::seconds>(delta).count();
p_impl->elapsed_seconds = std::chrono::round<std::chrono::seconds>(delta).count();
// compute remaining time
if (total_ticks >= 0) {
int64_t remaining_ticks = total_ticks - ticks;
if (current_speed != 0) {
remaining_seconds = remaining_ticks / current_speed;
if (p_impl->total_ticks >= 0) {
int64_t remaining_ticks = p_impl->total_ticks - p_impl->ticks;
if (p_impl->current_speed != 0) {
p_impl->remaining_seconds = remaining_ticks / p_impl->current_speed;
}
} else {
// unknown total ticks
remaining_seconds = -1;
p_impl->remaining_seconds = -1;
}
if (total_ticks >= 0 && ticks >= total_ticks) {
if (auto_finish && state == ProgressBarState::STARTED) {
if (p_impl->total_ticks >= 0 && p_impl->ticks >= p_impl->total_ticks) {
if (p_impl->auto_finish && p_impl->state == ProgressBarState::STARTED) {
// avoid calling set_state() because it triggers update() and ends up in an endless recursion
state = ProgressBarState::SUCCESS;
p_impl->state = ProgressBarState::SUCCESS;
}
end = now;
percent_done = 100;
remaining_seconds = 0;
p_impl->end = now;
p_impl->percent_done = 100;
p_impl->remaining_seconds = 0;
}
}
@ -155,13 +287,13 @@ void ProgressBar::set_total_ticks(int64_t value) {
if (is_finished()) {
return;
}
total_ticks = value;
p_impl->total_ticks = value;
}
void ProgressBar::pop_message() {
if (!messages.empty()) {
messages.pop_back();
if (!p_impl->messages.empty()) {
p_impl->messages.pop_back();
}
}