/* vim:set ts=2 sw=2 sts=2 et: */
/**
* \author Marcus Holland-Moritz (github@mhxnet.de)
* \copyright Copyright (c) Marcus Holland-Moritz
*
* This file is part of dwarfs.
*
* dwarfs is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* dwarfs is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with dwarfs. If not, see .
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
namespace dwarfs::writer {
namespace {
constexpr std::array asc_bar{
{"=", "=", "=", "=", "=", "=", "=", "="}};
constexpr std::array uni_bar{
{"▏", "▎", "▍", "▌", "▋", "▊", "▉", "█"}};
constexpr std::array asc_spinner_def{
{"-", "\\", "|", "/"}};
constexpr std::array uni_spinner_def{
{"🌑", "🌒", "🌓", "🌔", "🌕", "🌖", "🌗", "🌘"}};
constexpr std::span asc_spinner{asc_spinner_def};
constexpr std::span uni_spinner{uni_spinner_def};
std::string progress_bar(size_t width, double frac, bool unicode) {
size_t barlen = 8 * width * frac;
size_t w = barlen / 8;
size_t c = barlen % 8;
auto bar = unicode ? uni_bar.data() : asc_bar.data();
std::string rv;
for (size_t i = 0; i < width; ++i) {
if (i == (width - 1)) {
rv.append(bar[0]);
} else if (i == w) {
rv.append(bar[c]);
} else {
rv.append(i < w ? bar[7] : " ");
}
}
return rv;
}
void output_context_line(terminal const& term, std::ostream& os,
internal::progress::context& ctx, size_t width,
bool unicode_bar, bool colored) {
auto st = ctx.get_status();
size_t progress_w = 0;
size_t speed_w = 0;
// Progress bar width and speed width are both fixed
if (st.bytes_processed) {
speed_w = 12;
if (st.bytes_total) {
progress_w = width / 4;
}
}
assert(width >= progress_w + speed_w + 1);
std::string path;
if (st.path) {
path = *st.path;
utf8_sanitize(path);
}
size_t status_w = width - (progress_w + speed_w + 1);
auto path_len = !path.empty() ? utf8_display_width(path) : 0;
size_t extra_len = !path.empty() && !st.status_string.empty() ? 2 : 0;
if (status_w <
st.context.size() + st.status_string.size() + path_len + extra_len) {
// need to shorten things
if (path_len > 0) {
auto max_path_len =
status_w -
std::min(status_w,
st.context.size() + st.status_string.size() + extra_len);
if (max_path_len > 0) {
shorten_path_string(
path, static_cast(std::filesystem::path::preferred_separator),
max_path_len);
path_len = utf8_display_width(path);
}
}
if (path_len == 0 &&
status_w < st.context.size() + st.status_string.size()) {
if (status_w < st.context.size()) {
st.context.clear();
}
st.status_string.resize(status_w - st.context.size());
}
}
if (path_len > 0) {
if (!st.status_string.empty()) {
st.status_string += ": ";
}
st.status_string += path;
}
std::string progress;
std::string speed;
if (st.bytes_processed) {
ctx.speed.put(*st.bytes_processed);
speed = fmt::format("{}/s", size_with_unit(ctx.speed.num_per_second()));
if (st.bytes_total) {
double frac = static_cast(*st.bytes_processed) / *st.bytes_total;
progress = progress_bar(progress_w, frac, unicode_bar);
}
}
os << term.colored(st.context, st.color, colored, termstyle::BOLD);
os << term.colored(fmt::format("{:<{}} {}{}", st.status_string,
status_w - st.context.size(), progress, speed),
st.color, colored);
}
} // namespace
console_writer::console_writer(std::shared_ptr term,
std::ostream& os, progress_mode pg_mode,
display_mode mode, logger_options const& options)
: stream_logger(term, os, options)
, frac_(0.0)
, pg_mode_(pg_mode)
, mode_(mode) {}
void console_writer::rewind(std::ostream& os, int next_rewind_lines) {
if (!statebuf_.empty()) {
auto& term = this->term();
auto clear_line = term.clear_line();
auto rewind_line = term.rewind_line();
os << term.carriage_return();
int num_erase = rewind_lines_ - next_rewind_lines;
for (int i = 0; i < rewind_lines_; ++i) {
os << rewind_line;
if (num_erase > 0) {
os << clear_line;
--num_erase;
}
}
}
rewind_lines_ = next_rewind_lines;
}
void console_writer::preamble(std::ostream& os) { rewind(os, rewind_lines_); }
void console_writer::postamble(std::ostream& os) {
if (pg_mode_ == UNICODE || pg_mode_ == ASCII) {
os << statebuf_;
}
}
std::string_view console_writer::get_newline() const {
return pg_mode_ != NONE ? "\x1b[K\n" : "\n";
}
void console_writer::update(writer_progress& prog, bool last) {
if (pg_mode_ == NONE && !last) {
return;
}
auto newline = get_newline();
std::ostringstream oss;
lazy_value width([this] { return term().width(); });
bool fancy = pg_mode_ == ASCII || pg_mode_ == UNICODE;
auto update_chunk_size = [](internal::progress::scan_progress& sp) {
if (auto usec = sp.usec.load(); usec > 10'000) {
auto bytes = sp.bytes.load();
auto bytes_per_second = (bytes << 20) / usec;
sp.chunk_size.store(std::min(
UINT64_C(1) << 25,
std::max(UINT64_C(1) << 15, std::bit_ceil(bytes_per_second / 32))));
sp.bytes_per_sec.store(bytes_per_second);
}
};
auto& p = prog.get_internal();
update_chunk_size(p.hash);
update_chunk_size(p.similarity);
update_chunk_size(p.categorize);
if (last || fancy) {
if (fancy) {
for (size_t i = 0; i < width.get(); ++i) {
oss << (pg_mode_ == UNICODE ? "⎯" : "-");
}
oss << "\n";
}
switch (mode_) {
case NORMAL:
if (fancy) {
oss << term().colored(p.status(width.get()), termcolor::BOLD_CYAN,
log_is_colored())
<< newline;
}
oss << p.dirs_scanned << " dirs, " << p.symlinks_scanned << "/"
<< p.hardlinks << " soft/hard links, " << p.files_scanned << "/"
<< p.files_found << " files, " << p.specials_found << " other"
<< newline
<< "original size: " << size_with_unit(p.original_size)
<< ", hashed: " << size_with_unit(p.hash.bytes) << " ("
<< p.hash.scans << " files, " << size_with_unit(p.hash.bytes_per_sec)
<< "/s)" << newline
<< "scanned: " << size_with_unit(p.similarity.bytes) << " ("
<< p.similarity.scans << " files, "
<< size_with_unit(p.similarity.bytes_per_sec) << "/s)"
<< ", categorizing: " << size_with_unit(p.categorize.bytes_per_sec)
<< "/s" << newline
<< "saved by deduplication: "
<< size_with_unit(p.saved_by_deduplication) << " ("
<< p.duplicate_files << " files), saved by segmenting: "
<< size_with_unit(p.saved_by_segmentation) << newline
<< "filesystem: " << size_with_unit(p.filesystem_size) << " in "
<< p.block_count << " blocks (" << p.chunk_count << " chunks, ";
if (p.fragments_written > 0) {
oss << p.fragments_written << "/" << p.fragments_found
<< " fragments, ";
} else {
oss << p.fragments_found << " fragments, " << p.inodes_scanned << "/";
}
oss << p.files_found - p.duplicate_files - p.hardlinks << " inodes)"
<< newline
<< "compressed filesystem: " << p.blocks_written << " blocks/"
<< size_with_unit(p.compressed_size) << " written" << newline;
break;
case REWRITE:
oss << "filesystem: " << size_with_unit(p.filesystem_size) << " in "
<< p.block_count << " blocks (" << p.chunk_count << " chunks, "
<< p.inodes_written << " inodes)" << newline
<< "compressed filesystem: " << p.blocks_written << "/"
<< p.block_count << " blocks/" << size_with_unit(p.compressed_size)
<< " written" << newline;
break;
}
}
if (pg_mode_ == NONE) {
if (INFO <= log_threshold()) {
std::lock_guard lock(log_mutex());
write_nolock(oss.str());
}
return;
}
size_t orig = p.original_size - (p.saved_by_deduplication + p.symlink_size);
double frac_fs =
orig > 0 ? double(p.filesystem_size + p.saved_by_segmentation) / orig
: 0.0;
double frac_comp =
p.block_count > 0 ? double(p.blocks_written) / p.block_count : 0.0;
double frac = mode_ == NORMAL ? (frac_fs + frac_comp) / 2.0 : frac_comp;
if (last) {
frac = 1.0;
}
if (frac > frac_) {
frac_ = frac;
}
if (pg_mode_ == SIMPLE) {
std::string tmp =
fmt::format(" ==> {0:.0f}% done, {1} blocks/{2} written", 100 * frac_,
p.blocks_written.load(), size_with_unit(p.compressed_size));
std::lock_guard lock(log_mutex());
if (tmp != statebuf_) {
auto t = get_current_time_string();
statebuf_ = tmp;
write_nolock(fmt::format("- {}{}\n", t, statebuf_));
}
if (last) {
write_nolock(oss.str());
}
} else {
auto w = width.get();
auto spinner{pg_mode_ == UNICODE ? uni_spinner : asc_spinner};
oss << progress_bar(w - 8, frac_, pg_mode_ == UNICODE)
<< fmt::format("{:3.0f}% ", 100 * frac_)
<< spinner[counter_ % spinner.size()] << '\n';
++counter_;
std::vector> ctxs;
if (w >= 60) {
ctxs = p.get_active_contexts();
}
for (auto const& c : ctxs) {
output_context_line(term(), oss, *c, w, pg_mode_ == UNICODE,
log_is_colored());
oss << newline;
}
std::lock_guard lock(log_mutex());
statebuf_ = oss.str();
oss.clear();
oss.seekp(0);
rewind(oss, (mode_ == NORMAL ? 9 : 4) + ctxs.size());
oss << statebuf_;
write_nolock(oss.str());
}
}
} // namespace dwarfs::writer