#include "Views.h" #include #include #include "StringUtil.h" namespace { // Each account's wait (when its limit ends), keyed by lowercase name. std::map loadAccountWaits(const Database& db) { std::map waits; for (const AccountRow& account : db.listAccountRows()) { if (account.cooldown) { if (std::optional 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 buildTasks(const Database& db, std::time_t now) { const std::map waits = loadAccountWaits(db); std::vector 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 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 buildAccounts(const Database& db, std::time_t now) { const std::vector tasks = buildTasks(db, now); // already in display order std::vector accounts; for (const AccountRow& row : db.listAccountRows()) { std::vector busy; std::vector taken; std::vector 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 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; }