C and Python API + Smarter account-based wait times
This commit is contained in:
1 parent
35c568333d
commit
10badb282c
18 files changed
+2204
-429
No files matched your search
+162
@@ -0,0 +1,162 @@
|
||||
#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.status = StatusUtil::resolve(info.storedStatus, 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;
|
||||
}
|
||||
Reference in new issue
Block a user