164 lines
5.7 KiB
C++
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;
|
|
}
|