Files
LLS/src/Views.cpp
T

164 lines
5.7 KiB
C++

#include "Views.h"
#include <algorithm>
#include <map>
#include "StringUtil.h"
namespace {
// Each account's wait (when its limit ends), keyed by lowercase name.
std::map<std::string, std::time_t> loadAccountWaits(const Database& db) {
std::map<std::string, std::time_t> waits;
for (const AccountRow& account : db.listAccountRows()) {
if (account.cooldown) {
if (std::optional<std::time_t> until = StatusUtil::timeFromText(*account.cooldown)) {
waits[StringUtil::lower(account.id)] = *until;
}
}
}
return waits;
}
bool taskLess(const TaskInfo& a, const TaskInfo& b) {
const int rankA = StatusUtil::rank(a.status.kind);
const int rankB = StatusUtil::rank(b.status.kind);
if (rankA != rankB) {
return rankA < rankB;
}
const StatusKind kind = a.status.kind; // same kind as b: ranks are unique per kind
if ((kind == StatusKind::Wait || kind == StatusKind::LimitReset) && a.status.waitUntil != b.status.waitUntil) {
return a.status.waitUntil < b.status.waitUntil;
}
if (a.account.has_value() != b.account.has_value()) {
return a.account.has_value();
}
const std::string accountA = StringUtil::lower(a.account.value_or(""));
const std::string accountB = StringUtil::lower(b.account.value_or(""));
if (accountA != accountB) {
return accountA < accountB;
}
return StringUtil::lower(a.name) < StringUtil::lower(b.name);
}
AccountLevel levelOf(StatusKind kind) {
if (StatusUtil::isBusy(kind)) {
return AccountLevel::Busy;
}
if (StatusUtil::isTaken(kind)) {
return AccountLevel::Taken;
}
return AccountLevel::Free;
}
// Free first, then Taken, then Busy.
int levelRank(AccountLevel level) {
switch (level) {
case AccountLevel::Free: return 0;
case AccountLevel::Taken: return 1;
case AccountLevel::Busy: return 2;
}
return 2;
}
// Entries the account lists: its tasks, plus the wait if it is listed on its own.
std::size_t entryCount(const AccountInfo& account) {
return account.tasks.size() + (account.waitListed ? 1 : 0);
}
} // namespace
std::string accountLevelName(AccountLevel level) {
switch (level) {
case AccountLevel::Busy: return "busy";
case AccountLevel::Taken: return "taken";
case AccountLevel::Free: return "free";
}
return "free";
}
std::vector<TaskInfo> buildTasks(const Database& db, std::time_t now) {
const std::map<std::string, std::time_t> waits = loadAccountWaits(db);
std::vector<TaskInfo> tasks;
for (const TaskRow& row : db.listTasks()) {
TaskInfo info;
info.name = row.id;
info.account = row.account;
info.notes = row.notes;
info.noteLines = StringUtil::lineCount(row.notes);
info.storedStatus = StatusUtil::deserialize(row.status).value_or(StatusUtil::make(StatusKind::Prompt));
std::optional<std::time_t> accountWait;
if (row.account) {
auto found = waits.find(StringUtil::lower(*row.account));
if (found != waits.end()) {
accountWait = found->second;
}
}
info.overridesWait = row.waitOverride && accountWait && StatusUtil::followsAccountWait(info.storedStatus.kind);
info.status = StatusUtil::resolve(info.storedStatus, info.overridesWait ? std::nullopt : accountWait, now);
tasks.push_back(info);
}
std::sort(tasks.begin(), tasks.end(), taskLess);
return tasks;
}
std::vector<AccountInfo> buildAccounts(const Database& db, std::time_t now) {
const std::vector<TaskInfo> tasks = buildTasks(db, now); // already in display order
std::vector<AccountInfo> accounts;
for (const AccountRow& row : db.listAccountRows()) {
std::vector<std::string> busy;
std::vector<std::string> taken;
std::vector<std::string> free;
bool showsWait = false;
for (const TaskInfo& task : tasks) {
if (!task.account || !StringUtil::equalsIgnoreCase(*task.account, row.id)) {
continue;
}
switch (levelOf(task.status.kind)) {
case AccountLevel::Busy: busy.push_back(task.name); break;
case AccountLevel::Taken: taken.push_back(task.name); break;
case AccountLevel::Free: free.push_back(task.name); break;
}
if (task.status.kind == StatusKind::Wait || task.status.kind == StatusKind::LimitReset) {
showsWait = true;
}
}
AccountInfo info;
info.name = row.id;
if (row.cooldown) {
if (std::optional<std::time_t> until = StatusUtil::timeFromText(*row.cooldown)) {
info.waitUntil = *until;
info.waitState = (*until > now) ? WaitState::Waiting : WaitState::LimitReset;
info.waitListed = !showsWait;
}
}
if (!busy.empty() || info.waitListed) {
info.level = AccountLevel::Busy;
info.tasks = busy;
} else if (!taken.empty()) {
info.level = AccountLevel::Taken;
info.tasks = taken;
} else {
info.level = AccountLevel::Free;
info.tasks = free;
}
accounts.push_back(info);
}
// Most free first. db.listAccountRows is alphabetical and stable_sort keeps
// that order for ties; Free accounts are all equally free.
std::stable_sort(accounts.begin(), accounts.end(), [](const AccountInfo& a, const AccountInfo& b) {
if (a.level != b.level) {
return levelRank(a.level) < levelRank(b.level);
}
return a.level != AccountLevel::Free && entryCount(a) < entryCount(b);
});
return accounts;
}