#include "progress-bar.hh"
#include "util.hh"
#include "sync.hh"
#include "store-api.hh"
#include <map>
#include <atomic>
#include <sys/ioctl.h>
#include <iostream>
namespace nix {
class ProgressBar : public Logger
{
private:
struct ActInfo
{
std::string s, s2;
ActivityType type = actUnknown;
uint64_t done = 0;
uint64_t expected = 0;
uint64_t running = 0;
uint64_t failed = 0;
std::map<ActivityType, uint64_t> expectedByType;
};
struct ActivitiesByType
{
std::map<Activity::t, std::list<ActInfo>::iterator> its;
uint64_t done = 0;
uint64_t expected = 0;
uint64_t failed = 0;
};
struct State
{
std::list<ActInfo> activities;
std::map<Activity::t, std::list<ActInfo>::iterator> its;
std::map<ActivityType, ActivitiesByType> activitiesByType;
};
Sync<State> state_;
int width = 0;
public:
ProgressBar()
{
struct winsize ws;
if (ioctl(1, TIOCGWINSZ, &ws) == 0)
width = ws.ws_col;
}
~ProgressBar()
{
auto state(state_.lock());
std::string status = getStatus(*state);
writeToStderr("\r\e[K");
if (status != "")
writeToStderr("[" + status + "]\n");
}
void log(Verbosity lvl, const FormatOrString & fs) override
{
auto state(state_.lock());
log(*state, lvl, fs.s);
}
void log(State & state, Verbosity lvl, const std::string & s)
{
writeToStderr("\r\e[K" + s + "\n");
update(state);
}
void createActivity(State & state, Activity::t activity, const std::string & s, ActivityType type = actUnknown)
{
state.activities.emplace_back(ActInfo{s, "", type});
auto i = std::prev(state.activities.end());
state.its.emplace(activity, i);
state.activitiesByType[type].its.emplace(activity, i);
}
void deleteActivity(State & state, Activity::t activity)
{
auto i = state.its.find(activity);
if (i != state.its.end()) {
auto & act = state.activitiesByType[i->second->type];
act.done += i->second->done;
act.failed += i->second->failed;
for (auto & j : i->second->expectedByType)
state.activitiesByType[j.first].expected -= j.second;
act.its.erase(activity);
state.activities.erase(i->second);
state.its.erase(i);
}
}
void updateActivity(State & state, Activity::t activity, const std::string & s2)
{
auto i = state.its.find(activity);
assert(i != state.its.end());
ActInfo info = *i->second;
state.activities.erase(i->second);
info.s2 = s2;
state.activities.emplace_back(info);
i->second = std::prev(state.activities.end());
}
void update()
{
auto state(state_.lock());
update(*state);
}
void update(State & state)
{
std::string line = "\r";
std::string status = getStatus(state);
if (!status.empty()) {
line += '[';
line += status;
line += "]";
}
if (!state.activities.empty()) {
if (!status.empty()) line += " ";
auto i = state.activities.rbegin();
while (i != state.activities.rend() && i->s.empty() && i->s2.empty())
++i;
if (i != state.activities.rend()) {
line += i->s;
if (!i->s2.empty()) {
if (!i->s.empty()) line += ": ";
line += i->s2;
}
}
}
line += "\e[K";
writeToStderr(std::string(line, 0, width - 1));
}
std::string getStatus(State & state)
{
auto MiB = 1024.0 * 1024.0;
std::string res;
auto renderActivity = [&](ActivityType type, const std::string & itemFmt, const std::string & numberFmt, double unit) {
auto & act = state.activitiesByType[type];
uint64_t done = act.done, expected = act.done, running = 0, failed = act.failed;
for (auto & j : act.its) {
done += j.second->done;
expected += j.second->expected;
running += j.second->running;
failed += j.second->failed;
}
expected = std::max(expected, act.expected);
std::string s;
if (running || done || expected || failed) {
if (running)
s = fmt(ANSI_BLUE + numberFmt + ANSI_NORMAL "/" ANSI_GREEN + numberFmt + ANSI_NORMAL "/" + numberFmt,
running / unit, done / unit, expected / unit);
else if (expected != done)
s = fmt(ANSI_GREEN + numberFmt + ANSI_NORMAL "/" + numberFmt,
done / unit, expected / unit);
else
s = fmt(done ? ANSI_GREEN + numberFmt + ANSI_NORMAL : numberFmt, done / unit);
s = fmt(itemFmt, s);
if (failed)
s += fmt(" (" ANSI_RED "%d failed" ANSI_NORMAL ")", failed / unit);
}
return s;
};
auto showActivity = [&](ActivityType type, const std::string & itemFmt, const std::string & numberFmt, double unit) {
auto s = renderActivity(type, itemFmt, numberFmt, unit);
if (s.empty()) return;
if (!res.empty()) res += ", ";
res += s;
};
showActivity(actBuilds, "%s built", "%d", 1);
auto s1 = renderActivity(actCopyPaths, "%s copied", "%d", 1);
auto s2 = renderActivity(actCopyPath, "%s MiB", "%.1f", MiB);
if (!s1.empty() || !s2.empty()) {
if (!res.empty()) res += ", ";
if (s1.empty()) res += "0 copied"; else res += s1;
if (!s2.empty()) { res += " ("; res += s2; res += ')'; }
}
showActivity(actDownload, "%s MiB DL", "%.1f", MiB);
return res;
}
void event(const Event & ev) override
{
auto state(state_.lock());
if (ev.type == evStartActivity) {
Activity::t act = ev.getI(0);
createActivity(*state, act, ev.getS(2), (ActivityType) ev.getI(1));
}
if (ev.type == evStopActivity) {
Activity::t act = ev.getI(0);
deleteActivity(*state, act);
}
if (ev.type == evProgress) {
auto i = state->its.find(ev.getI(0));
assert(i != state->its.end());
ActInfo & actInfo = *i->second;
actInfo.done = ev.getI(1);
actInfo.expected = ev.getI(2);
actInfo.running = ev.getI(3);
actInfo.failed = ev.getI(4);
}
if (ev.type == evSetExpected) {
auto i = state->its.find(ev.getI(0));
assert(i != state->its.end());
ActInfo & actInfo = *i->second;
auto type = (ActivityType) ev.getI(1);
auto & j = actInfo.expectedByType[type];
state->activitiesByType[type].expected -= j;
j = ev.getI(2);
state->activitiesByType[type].expected += j;
}
if (ev.type == evBuildOutput) {
Activity::t act = ev.getI(0);
auto s = trim(ev.getS(1));
if (!s.empty())
updateActivity(*state, act, s);
}
update(*state);
}
};
StartProgressBar::StartProgressBar()
{
if (isatty(STDERR_FILENO)) {
prev = logger;
logger = new ProgressBar();
}
}
StartProgressBar::~StartProgressBar()
{
if (prev) {
auto bar = logger;
logger = prev;
delete bar;
}
}
}