Status system reworked
This commit is contained in:
1 parent
33567db5a3
commit
a29ba6e4d2
18 files changed
+315
-334
No files matched your search
@@ -75,8 +75,8 @@ goes back to `$VISUAL` / `$EDITOR` / nano / vim / vi. The choice is stored in
|
||||
`db backup` writes a copy, `db restore` loads a file. On `quit` you are asked
|
||||
to save if there are unsaved changes (`y` save, `n` discard, `c` cancel the
|
||||
exit); any other answer asks again, so a mistyped key never loses data.
|
||||
* The stored status is never shown directly: a `wait` task displays as
|
||||
`Wait HH:MM` until that time passes, then as `Limit reset`. Tasks are sorted
|
||||
* The stored status is not always what is shown: a `wait` task displays as
|
||||
`Wait HH:MM` until its account's wait ends, then as `Limit reset`. Tasks are sorted
|
||||
by computed status (Limit reset, Testing, Prompt, Wait, Uploading, Frozen,
|
||||
Done); Wait and Limit reset tasks then sort by time (earliest first: when
|
||||
the wait ends / ended), then account A-Z (unassigned last), then name. The panels are cleared and redrawn after changes.
|
||||
@@ -88,29 +88,22 @@ goes back to `$VISUAL` / `$EDITOR` / nano / vim / vi. The choice is stored in
|
||||
Done tasks (`-` if it has none). Rows are sorted most free first: Free, Taken, Busy;
|
||||
within Taken and Busy fewer tasks first, ties by name (see `levelOf` in `Display.cpp` and
|
||||
`StatusUtil::isBusy` / `isTaken` to change the rules).
|
||||
* Waits belong to the account: `acc wait <name> <time>` (or `tsk restat <task> wait
|
||||
<time>`, which sets its account's wait). Until the time the account's Prompt
|
||||
and Testing tasks show `Wait HH:MM`; after it they show `Limit reset`, until
|
||||
you run `acc unwait <name>`. Uploading, Frozen and Done tasks are not
|
||||
affected, and the tasks keep their own stored status underneath. A wait that
|
||||
no task shows (no tasks, or only Done / Frozen / Uploading ones) is listed in
|
||||
the account row as `(wait)` or `(limit reset)` and keeps the account Busy.
|
||||
Tasks without an account keep their own wait.
|
||||
* A task you set explicitly to `testing` or `prompt` (`tsk restat <task>
|
||||
testing`, `tsk add <task> <acc> testing`) shows that status, even while its
|
||||
account is waiting: that task ignores the wait, and the account's other tasks
|
||||
keep following it. `tsk restat <task> wait` (no time) makes it follow the
|
||||
wait again, and a new `acc wait` or `acc unwait` applies to every task again.
|
||||
Adding a task without a status leaves it following the account.
|
||||
* Auto unwait: once an account's limit has reset (its tasks show `Limit reset`),
|
||||
setting a task on it explicitly to `testing` or `prompt` clears that finished
|
||||
wait, because the account is in use again, and its tasks go back to their own
|
||||
status. A wait that is still running is never cleared automatically (use
|
||||
`acc unwait`).
|
||||
* A time is only optional while the account is already waiting: then
|
||||
`tsk restat <task> wait` (or `tsk add <task> <acc> wait`) just makes the task
|
||||
follow that wait. Otherwise (no account, or its wait is over) a time is
|
||||
required. Old databases with per-task waits are converted on load.
|
||||
* Waits: an account hits its limit until a time (`acc wait <name> <time>`). A task
|
||||
that is **set to `wait`** is waiting for that: `tsk restat <task> wait <time>`
|
||||
sets its account's wait to the time and marks the task Wait (several waiting
|
||||
tasks share the account's time), and `tsk restat <task> wait` with no time marks
|
||||
it Wait while the account is already waiting (otherwise a time is required).
|
||||
Tasks without an account keep their own wait time.
|
||||
* When the time passes the account stops waiting by itself, and every task that
|
||||
was Wait now shows `Limit reset`: the conversation can be continued. Those
|
||||
tasks stay Limit reset until you set them to something else. `acc unwait
|
||||
<name>` ends the wait early, with the same result.
|
||||
* Prompt and Testing tasks are not waiting: they show as they are, with
|
||||
`(account-waiting)` added while their account waits, e.g. `Testing
|
||||
(account-waiting)`. The account row shows when the wait ends in the RESET
|
||||
column, and stays Busy while it waits; if none of its tasks is set to Wait
|
||||
(e.g. you deleted them) its TASKS column lists `(wait)`.
|
||||
* Old databases with per-task waits are converted on load.
|
||||
* Deleting an account leaves its tasks unassigned (`-`).
|
||||
* Names are case-insensitive; the original spelling is kept.
|
||||
* Notes live in the database (SQLite TEXT, binary-safe, SQLite's default limit is ~1 GB). The task table shows `-` when a task has no notes, otherwise the number of lines (e.g. `3 lines`); open them with `tsk notes <name>`.
|
||||
@@ -129,13 +122,13 @@ against it behaves exactly like typing the commands. Build it with the project
|
||||
|
||||
lls_account_add(db, "Acrux");
|
||||
lls_task_add(db, "Breeze A", "Acrux", "testing");
|
||||
lls_account_set_wait(db, "Acrux", "4:30pm"); /* the account hit its limit */
|
||||
lls_task_add(db, "Breeze B", "Acrux", "wait 4:30pm"); /* Acrux hit its limit; Breeze B waits for it */
|
||||
|
||||
lls_task_list* tasks;
|
||||
lls_tasks(db, &tasks); /* display order */
|
||||
for (int i = 0; i < lls_task_list_count(tasks); ++i) {
|
||||
const lls_task_info* t = lls_task_list_get(tasks, i);
|
||||
printf("%s %s\n", t->name, t->status_label); /* "Breeze A Wait 16:30" */
|
||||
printf("%s %s\n", t->name, t->status_label); /* "Breeze A Testing (account-waiting)" ... */
|
||||
}
|
||||
lls_task_list_free(tasks);
|
||||
|
||||
@@ -148,9 +141,11 @@ against it behaves exactly like typing the commands. Build it with the project
|
||||
* After a change, `lls_last_messages(db)` holds what the prompt would have
|
||||
printed ("Account 'A' is back in use: ..."). Queries do not reset it.
|
||||
* Statuses are the text you would type: `"prompt"`, `"testing"`, `"wait 4:30pm"`,
|
||||
`"wait"` (only while the account is waiting)... Waits belong to the account;
|
||||
a task set to `"testing"` / `"prompt"` while it waits ignores the wait
|
||||
(`overrides_wait` is 1) and `"wait"` makes it follow the account again.
|
||||
`"wait"` (only while the account is waiting)... A task set to wait waits for
|
||||
its account; when the time passes the account stops waiting by itself and
|
||||
those tasks show Limit reset. Prompt / Testing tasks keep their status, with
|
||||
`(account-waiting)` in `status_label` (and `account_waiting` = 1) while the
|
||||
account waits.
|
||||
* `lls_tasks` / `lls_accounts` return lists in the order of the panels, with the
|
||||
computed status (`status`, `status_label`, `until`) and the stored one underneath
|
||||
(`stored_status`). Strings in a list are valid until `lls_task_list_free`.
|
||||
@@ -165,7 +160,7 @@ against it behaves exactly like typing the commands. Build it with the project
|
||||
with lls.Database("tasks.db") as db:
|
||||
db.add_account("Acrux")
|
||||
db.add_task("Breeze A", account="Acrux", status="testing")
|
||||
db.set_account_wait("Acrux", "4:30pm")
|
||||
db.add_task("Breeze B", account="Acrux", status="wait 4:30pm") # sets Acrux's wait
|
||||
for task in db.tasks():
|
||||
print(task.name, task.account, task.status_label, task.until)
|
||||
for account in db.accounts():
|
||||
|
||||
+5
-4
@@ -27,8 +27,9 @@ int main(int argc, char** argv) {
|
||||
lls_task_add(db, "Breeze B", "Acrux", NULL); /* NULL = prompt */
|
||||
lls_task_add(db, "Breeze C", "Beta", "done");
|
||||
|
||||
/* The wait belongs to the account: Acrux's Prompt / Testing tasks now show Wait. */
|
||||
if (!check(db, lls_account_set_wait(db, "Acrux", "11:59pm"), "set_wait")) {
|
||||
/* Acrux hit its limit: "Breeze B" waits for it (Breeze A stays Testing, with a note). */
|
||||
if (!check(db, lls_account_set_wait(db, "Acrux", "11:59pm"), "set_wait") ||
|
||||
!check(db, lls_task_set_status(db, "Breeze B", "wait"), "set_status")) {
|
||||
lls_close(db);
|
||||
return 1;
|
||||
}
|
||||
@@ -36,10 +37,10 @@ int main(int argc, char** argv) {
|
||||
|
||||
lls_task_list* tasks = NULL;
|
||||
if (check(db, lls_tasks(db, &tasks), "tasks")) {
|
||||
printf("%-10s %-8s %-12s %s\n", "TASK", "ACCOUNT", "STATUS", "NOTES");
|
||||
printf("%-10s %-8s %-26s %s\n", "TASK", "ACCOUNT", "STATUS", "NOTES");
|
||||
for (int i = 0; i < lls_task_list_count(tasks); ++i) {
|
||||
const lls_task_info* t = lls_task_list_get(tasks, i);
|
||||
printf("%-10s %-8s %-12s %d lines\n", t->name, t->account ? t->account : "-", t->status_label,
|
||||
printf("%-10s %-8s %-26s %d lines\n", t->name, t->account ? t->account : "-", t->status_label,
|
||||
t->notes_lines);
|
||||
}
|
||||
lls_task_list_free(tasks);
|
||||
|
||||
@@ -46,8 +46,6 @@ public:
|
||||
void setTaskAccount(const std::string& name, const std::optional<std::string>& account);
|
||||
void setTaskStatus(const std::string& name, const std::string& status);
|
||||
void setTaskNotes(const std::string& name, const std::string& notes);
|
||||
void setTaskWaitOverride(const std::string& name, bool value);
|
||||
void clearWaitOverrides(const std::string& account); // every task of the account follows its wait again
|
||||
|
||||
private:
|
||||
using Param = std::optional<std::string>;
|
||||
|
||||
@@ -8,7 +8,6 @@ struct TaskRow {
|
||||
std::optional<std::string> account;
|
||||
std::string status; // serialized, see StatusUtil::serialize
|
||||
std::string notes; // free text, any length (stored as SQLite TEXT)
|
||||
bool waitOverride = false; // set to Testing / Prompt while its account waits: ignores that wait
|
||||
};
|
||||
|
||||
struct AccountRow {
|
||||
|
||||
+9
-7
@@ -26,14 +26,16 @@ std::optional<Status> parse(const std::string& text, std::time_t now);
|
||||
std::string serialize(const Status& status);
|
||||
std::optional<Status> deserialize(const std::string& text);
|
||||
|
||||
// Prompt and Testing tasks follow their account's wait; Uploading, Frozen and
|
||||
// Done tasks do not.
|
||||
bool followsAccountWait(StatusKind kind);
|
||||
// Prompt and Testing show as they are, with "(account-waiting)" appended while
|
||||
// their account waits. Other statuses are shown without a note.
|
||||
bool showsAccountWaiting(StatusKind kind);
|
||||
|
||||
// The status to show for a task. If its account has a wait, the tasks that
|
||||
// follow it show Wait until then and Limit reset after; otherwise the stored
|
||||
// status is used (see effective).
|
||||
Status resolve(const Status& stored, const std::optional<std::time_t>& accountWait, std::time_t now);
|
||||
// The status to show for a task. A task set to Wait on an account waits for the
|
||||
// account: Wait until the account's wait ends, Limit reset after that (or if the
|
||||
// account is not waiting). `accountWait` is when the account's wait ends (ended).
|
||||
// Everything else shows its stored status (see effective).
|
||||
Status resolve(const Status& stored, bool hasAccount, const std::optional<std::time_t>& accountWait,
|
||||
std::time_t now);
|
||||
|
||||
// The status shown to the user: an expired wait becomes LimitReset.
|
||||
Status effective(const Status& status, std::time_t now);
|
||||
|
||||
+18
-10
@@ -22,10 +22,15 @@ private:
|
||||
ErrorCode code_;
|
||||
};
|
||||
|
||||
// All the rules for changing accounts and tasks (waits, auto-unwait, status
|
||||
// handling...). The prompt and the C API are both thin layers over this class.
|
||||
// Each change appends human-readable notes (what the prompt prints), which
|
||||
// takeMessages() hands over.
|
||||
// All the rules for changing accounts and tasks. The prompt and the C API are
|
||||
// both thin layers over this class. Each change appends human-readable notes
|
||||
// (what the prompt prints), which takeMessages() hands over.
|
||||
//
|
||||
// How waits work: an account hits its limit until a time ("acc wait"). A task set
|
||||
// to Wait is waiting for that: it shows Wait until then, and when the time passes
|
||||
// the account stops waiting by itself and the task shows Limit reset (the
|
||||
// conversation can be continued). Prompt / Testing tasks are not waiting: they
|
||||
// show as they are, with "(account-waiting)" appended while the account waits.
|
||||
class Tracker {
|
||||
public:
|
||||
explicit Tracker(Database& db);
|
||||
@@ -43,16 +48,19 @@ public:
|
||||
void deleteAccount(const std::string& name);
|
||||
void renameAccount(const std::string& name, const std::string& newName);
|
||||
|
||||
// The account hits its limit until `until` (or a clock time such as "16:30",
|
||||
// "4:30pm"; a time that has passed today means tomorrow).
|
||||
// The account hit its limit until `until` (or a clock time such as "16:30",
|
||||
// "4:30pm"; a time that has passed today means tomorrow). Tasks set to Wait
|
||||
// follow the new time.
|
||||
void setAccountWait(const std::string& name, std::time_t until);
|
||||
void setAccountWaitText(const std::string& name, const std::string& timeText);
|
||||
// Returns false if the account was not waiting.
|
||||
// Ends the wait now: its Wait tasks become Limit reset. Returns false if the
|
||||
// account was not waiting.
|
||||
bool clearAccountWait(const std::string& name);
|
||||
|
||||
// ---- Tasks --------------------------------------------------------------
|
||||
// `statusText` is what the user would type: "testing", "wait 4pm", "wait"...
|
||||
// nullopt = Prompt, without the auto-unwait that an explicit status triggers.
|
||||
// nullopt = Prompt. "wait <time>" sets the account's wait too; "wait" alone
|
||||
// joins the account's running wait (and is an error if it is not waiting).
|
||||
void addTask(const std::string& name, const std::optional<std::string>& account,
|
||||
const std::optional<std::string>& statusText);
|
||||
void deleteTask(const std::string& name);
|
||||
@@ -72,8 +80,8 @@ private:
|
||||
std::string existingAccount(const std::string& name) const;
|
||||
TaskRow existingTask(const std::string& name) const;
|
||||
std::optional<std::time_t> runningWait(const std::string& account) const;
|
||||
void taskInUse(const std::string& taskName, const std::string& accountName);
|
||||
void makeTaskFollowAccount(const TaskRow& task);
|
||||
void settle();
|
||||
int endAccountWait(const std::string& account, std::time_t endedAt);
|
||||
void note(const std::string& message);
|
||||
|
||||
Database& db_;
|
||||
|
||||
+13
-9
@@ -12,34 +12,38 @@
|
||||
// How taken an account is, judged by its tasks:
|
||||
// Free - only Done, Frozen or Uploading tasks, or no tasks
|
||||
// Taken - has a Prompt task (and nothing Busy)
|
||||
// Busy - has a Testing, Limit reset or Wait task, or a wait nothing else shows
|
||||
// Busy - has a Testing, Limit reset or Wait task, or is waiting itself
|
||||
enum class AccountLevel { Busy, Taken, Free };
|
||||
|
||||
std::string accountLevelName(AccountLevel level); // "busy", "taken", "free"
|
||||
|
||||
enum class WaitState { None, Waiting, LimitReset };
|
||||
// An account waits from the moment it hits its limit until the wait time; after
|
||||
// that it is not waiting any more (its Wait tasks are then Limit reset).
|
||||
enum class WaitState { None, Waiting };
|
||||
|
||||
// A task as the user sees it: the status is computed from the stored status,
|
||||
// the account's wait and the current time.
|
||||
struct TaskInfo {
|
||||
std::string name;
|
||||
std::optional<std::string> account;
|
||||
Status status; // as shown (Wait / Limit reset come from the account)
|
||||
Status status; // as shown (a Wait task becomes Limit reset when its account's wait ends)
|
||||
Status storedStatus; // what is saved underneath
|
||||
// What the panel shows: "Wait 16:30", "Limit reset", "Testing (account-waiting)"...
|
||||
std::string statusLabel;
|
||||
// Prompt / Testing while its account waits: shown as they are, with
|
||||
// "(account-waiting)" appended.
|
||||
bool accountWaiting = false;
|
||||
std::string notes;
|
||||
std::size_t noteLines = 0;
|
||||
// Set to Testing / Prompt while its account waits: it shows its own status
|
||||
// and ignores the wait, while the account's other tasks keep following it.
|
||||
bool overridesWait = false;
|
||||
};
|
||||
|
||||
struct AccountInfo {
|
||||
std::string name;
|
||||
AccountLevel level = AccountLevel::Free;
|
||||
WaitState waitState = WaitState::None;
|
||||
std::time_t waitUntil = 0; // when the wait ends (or ended); 0 if none
|
||||
// True when the account has a wait that none of its tasks shows (it has no
|
||||
// tasks, or only Done / Frozen / Uploading ones). It still makes the account Busy.
|
||||
std::time_t waitUntil = 0; // when the wait ends; 0 if it is not waiting
|
||||
// True when the account is waiting but none of its tasks is set to Wait
|
||||
// (e.g. it has no tasks). It still makes the account Busy.
|
||||
bool waitListed = false;
|
||||
std::vector<std::string> tasks; // tasks at the account's level (Free: its Uploading/Frozen/Done ones)
|
||||
};
|
||||
|
||||
+15
-16
@@ -14,13 +14,13 @@
|
||||
* - Statuses are the text you would type at the prompt: "prompt", "testing",
|
||||
* "uploading", "frozen", "done", "limit-reset", "wait 4:30pm", "wait 16:30"
|
||||
* or just "wait" (only while the task's account is already waiting).
|
||||
* - A wait belongs to the account: setting a task to "wait <time>" sets its
|
||||
* account's wait, and the account's Prompt / Testing tasks then show Wait
|
||||
* until that time and Limit reset after it. A task you explicitly set to
|
||||
* "testing" or "prompt" while the wait is running shows that status instead
|
||||
* (overrides_wait = 1) and the other tasks keep waiting; "wait" makes it follow
|
||||
* the account again. If the wait is already over (Limit reset), setting a task
|
||||
* to testing / prompt clears it: the account is in use again (see the README).
|
||||
* - Waits: an account hits its limit until a time. A task set to "wait" is waiting
|
||||
* for that: "wait <time>" sets the account's wait and marks the task Wait; "wait"
|
||||
* alone marks it Wait while the account is already waiting. When the time passes
|
||||
* the account stops waiting by itself and its Wait tasks show Limit reset (the
|
||||
* conversation can be continued). Prompt / Testing tasks are not waiting: they
|
||||
* keep their status, with "(account-waiting)" added to the label while the
|
||||
* account waits (account_waiting = 1).
|
||||
* - Times are Unix seconds (UTC); clock-time text like "4:30pm" is read in the
|
||||
* local time zone, and a time that has passed today means tomorrow.
|
||||
*
|
||||
@@ -75,8 +75,7 @@ extern "C" {
|
||||
|
||||
/* ---- Account wait states (lls_account_info.wait_state) -------------------- */
|
||||
#define LLS_WAIT_NONE 0
|
||||
#define LLS_WAIT_WAITING 1 /* wait_until is in the future */
|
||||
#define LLS_WAIT_LIMIT_RESET 2 /* wait_until has passed; stays until cleared */
|
||||
#define LLS_WAIT_WAITING 1 /* waiting until wait_until; after that it is NONE again (automatically) */
|
||||
|
||||
typedef struct lls_db lls_db;
|
||||
typedef struct lls_task_list lls_task_list;
|
||||
@@ -86,13 +85,13 @@ typedef struct lls_account_list lls_account_list;
|
||||
typedef struct lls_task_info {
|
||||
const char* name;
|
||||
const char* account; /* NULL when the task has no account */
|
||||
int status; /* LLS_STATUS_*, as shown (Wait / Limit reset come from the account) */
|
||||
const char* status_label; /* "Prompt", "Limit reset", "Wait 16:30", ... */
|
||||
int status; /* LLS_STATUS_*, as shown (Wait becomes Limit reset when the account's wait ends) */
|
||||
const char* status_label; /* "Prompt", "Limit reset", "Wait 16:30", "Testing (account-waiting)", ... */
|
||||
int stored_status; /* LLS_STATUS_*, what is saved underneath */
|
||||
int64_t until; /* for Wait / Limit reset: when the wait ends (ended); else 0 */
|
||||
const char* notes; /* the full text, "" if none (any length) */
|
||||
int notes_lines; /* number of lines in the notes */
|
||||
int overrides_wait; /* 1 if its account waits but this task ignores it (set to Testing / Prompt) */
|
||||
int account_waiting; /* 1 if it is Prompt / Testing while its account waits (the label says so) */
|
||||
} lls_task_info;
|
||||
|
||||
/* An account with its computed state. */
|
||||
@@ -101,8 +100,8 @@ typedef struct lls_account_info {
|
||||
int level; /* LLS_LEVEL_* */
|
||||
const char* level_label; /* "Busy", "Taken", "Free" */
|
||||
int wait_state; /* LLS_WAIT_* */
|
||||
int64_t wait_until; /* when the wait ends (ended); 0 if none */
|
||||
int wait_listed; /* 1 if the wait is not shown by any task (it still makes the account Busy) */
|
||||
int64_t wait_until; /* when the wait ends; 0 if it is not waiting */
|
||||
int wait_listed; /* 1 if it is waiting but no task is set to Wait (it is still Busy) */
|
||||
int task_count;
|
||||
const char* const* tasks; /* the tasks at the account's level (a Free account: its Uploading/Frozen/Done ones) */
|
||||
} lls_account_info;
|
||||
@@ -152,9 +151,9 @@ LLS_API int lls_account_add(lls_db* db, const char* name);
|
||||
LLS_API int lls_account_delete(lls_db* db, const char* name); /* its tasks become unassigned */
|
||||
LLS_API int lls_account_rename(lls_db* db, const char* name, const char* new_name);
|
||||
/* The account hit its limit until a clock time ("16:30", "4:30pm") / until a Unix time. */
|
||||
LLS_API int lls_account_set_wait(lls_db* db, const char* name, const char* time_text);
|
||||
LLS_API int lls_account_set_wait(lls_db* db, const char* name, const char* time_text); /* tasks set to Wait follow it */
|
||||
LLS_API int lls_account_set_wait_at(lls_db* db, const char* name, int64_t until);
|
||||
/* Clears its wait / limit reset. Sets *was_waiting (if not NULL) to 1 or 0. */
|
||||
/* Ends the wait now: its Wait tasks become Limit reset. Sets *was_waiting (if not NULL) to 1 or 0. */
|
||||
LLS_API int lls_account_clear_wait(lls_db* db, const char* name, int* was_waiting);
|
||||
|
||||
/* ---- Tasks ---------------------------------------------------------------- */
|
||||
|
||||
Binary file not shown.
+9
-9
@@ -54,7 +54,6 @@ class Level(IntEnum):
|
||||
class WaitState(IntEnum):
|
||||
NONE = 0
|
||||
WAITING = 1
|
||||
LIMIT_RESET = 2
|
||||
|
||||
|
||||
# ---- Errors ------------------------------------------------------------------------
|
||||
@@ -92,13 +91,13 @@ _ERRORS = {E_INVALID: InvalidArgument, E_NOT_FOUND: NotFound, E_EXISTS: AlreadyE
|
||||
class Task:
|
||||
name: str
|
||||
account: Optional[str]
|
||||
status: Status # as shown: Wait / Limit reset come from the account
|
||||
status_label: str # "Prompt", "Limit reset", "Wait 16:30", ...
|
||||
status: Status # as shown (Wait becomes Limit reset when the account's wait ends)
|
||||
status_label: str # "Prompt", "Limit reset", "Wait 16:30", "Testing (account-waiting)"...
|
||||
stored_status: Status # what is saved underneath
|
||||
until: Optional[datetime] # for Wait / Limit reset: when the wait ends (ended)
|
||||
notes: str
|
||||
notes_lines: int
|
||||
overrides_wait: bool # its account waits, but this task (set to Testing / Prompt) ignores it
|
||||
account_waiting: bool # Prompt / Testing while its account waits (the label says so)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -108,7 +107,7 @@ class Account:
|
||||
level_label: str # "Busy", "Taken", "Free"
|
||||
wait_state: WaitState
|
||||
wait_until: Optional[datetime]
|
||||
wait_listed: bool # the wait is not shown by any task (account is still Busy)
|
||||
wait_listed: bool # waiting, but no task is set to Wait (the account is still Busy)
|
||||
tasks: Tuple[str, ...] # tasks at the account's level
|
||||
|
||||
|
||||
@@ -124,7 +123,7 @@ class _TaskInfo(ctypes.Structure):
|
||||
("until", ctypes.c_int64),
|
||||
("notes", ctypes.c_char_p),
|
||||
("notes_lines", ctypes.c_int),
|
||||
("overrides_wait", ctypes.c_int),
|
||||
("account_waiting", ctypes.c_int),
|
||||
]
|
||||
|
||||
|
||||
@@ -348,14 +347,15 @@ class Database:
|
||||
|
||||
def set_account_wait(self, name: str, until: Union[str, datetime, int, float]) -> None:
|
||||
"""The account hit its limit until `until`: a clock time such as "16:30" / "4:30pm"
|
||||
(a time that has passed today means tomorrow), a datetime, or Unix seconds."""
|
||||
(a time that has passed today means tomorrow), a datetime, or Unix seconds. Tasks
|
||||
set to "wait" follow it; when the time passes they become Limit reset."""
|
||||
if isinstance(until, str):
|
||||
self._call("lls_account_set_wait", _enc(name), _enc(until))
|
||||
else:
|
||||
self._call("lls_account_set_wait_at", _enc(name), _seconds(until))
|
||||
|
||||
def clear_account_wait(self, name: str) -> bool:
|
||||
"""Clears its wait / limit reset. Returns False if it was not waiting."""
|
||||
"""Ends the wait now (its Wait tasks become Limit reset). False if it was not waiting."""
|
||||
was_waiting = ctypes.c_int(0)
|
||||
self._call("lls_account_clear_wait", _enc(name), ctypes.byref(was_waiting))
|
||||
return bool(was_waiting.value)
|
||||
@@ -402,7 +402,7 @@ class Database:
|
||||
until=_when(info.until),
|
||||
notes=_dec(info.notes),
|
||||
notes_lines=info.notes_lines,
|
||||
overrides_wait=bool(info.overrides_wait),
|
||||
account_waiting=bool(info.account_waiting),
|
||||
))
|
||||
return result
|
||||
finally:
|
||||
|
||||
+83
-125
@@ -115,13 +115,16 @@ class OrderingTests(Base):
|
||||
for account in ("A", "B", "C"):
|
||||
self.db.add_account(account)
|
||||
self.db.add_task("task-" + account, account)
|
||||
self.db.set_account_wait("A", "6pm")
|
||||
self.db.set_account_wait("B", "2pm")
|
||||
self.db.set_account_wait("C", "4pm")
|
||||
self.db.set_task_status("task-A", "wait 6pm")
|
||||
self.db.set_task_status("task-B", "wait 2pm")
|
||||
self.db.set_task_status("task-C", "wait 4pm")
|
||||
self.assertEqual([t.name for t in self.db.tasks()], ["task-B", "task-C", "task-A"])
|
||||
self.at(17) # B and C are over now: Limit reset first, ordered by when they ended
|
||||
self.assertEqual([t.status_label for t in self.db.tasks()], ["Limit reset", "Limit reset", "Wait 18:00"])
|
||||
self.assertEqual([t.name for t in self.db.tasks()], ["task-B", "task-C", "task-A"])
|
||||
self.db.add_account("Z") # any change saves what time did: same order afterwards
|
||||
self.assertEqual([t.name for t in self.db.tasks()], ["task-B", "task-C", "task-A"])
|
||||
self.assertEqual(self.db.task("task-B").stored_status, lls.Status.LIMIT_RESET)
|
||||
|
||||
def test_accounts_most_free_first(self):
|
||||
for account in ("Busy1", "Taken1", "Free1", "Free2", "Busy2"):
|
||||
@@ -150,40 +153,81 @@ class WaitTests(Base):
|
||||
self.db.add_task("a-done", "A", "done")
|
||||
self.db.add_task("b-prompt", "B")
|
||||
|
||||
def test_wait_belongs_to_the_account(self):
|
||||
self.db.set_account_wait("A", "4:30pm")
|
||||
self.assertEqual(self.statuses(), {"a-testing": "Wait 16:30", "a-prompt": "Wait 16:30",
|
||||
def test_only_tasks_set_to_wait_wait(self):
|
||||
self.db.set_task_status("a-prompt", "wait 4:30pm")
|
||||
self.assertEqual(self.statuses(), {"a-testing": "Testing (account-waiting)", "a-prompt": "Wait 16:30",
|
||||
"a-done": "Done", "b-prompt": "Prompt"})
|
||||
self.assertEqual(self.db.task("a-testing").stored_status, lls.Status.TESTING) # kept underneath
|
||||
self.assertEqual(self.db.task("a-testing").until, NOON.replace(hour=16, minute=30).astimezone())
|
||||
account = self.db.account("A")
|
||||
self.assertEqual((account.level, account.wait_state), (lls.Level.BUSY, lls.WaitState.WAITING))
|
||||
self.assertEqual(self.db.task("a-prompt").until, NOON.replace(hour=16, minute=30).astimezone())
|
||||
|
||||
self.at(17) # the wait is over -> Limit reset until cleared
|
||||
self.assertEqual(self.db.task("a-prompt").status, lls.Status.LIMIT_RESET)
|
||||
self.assertEqual(self.db.account("A").wait_state, lls.WaitState.LIMIT_RESET)
|
||||
def test_prompt_and_testing_show_as_they_are_with_a_note(self):
|
||||
self.db.set_account_wait("A", "6pm") # nothing is set to wait
|
||||
testing = self.db.task("a-testing")
|
||||
self.assertEqual((testing.status, testing.status_label, testing.account_waiting),
|
||||
(lls.Status.TESTING, "Testing (account-waiting)", True))
|
||||
prompt = self.db.task("a-prompt")
|
||||
self.assertEqual((prompt.status, prompt.status_label), (lls.Status.PROMPT, "Prompt (account-waiting)"))
|
||||
self.assertEqual(self.db.task("a-done").status_label, "Done") # no note on other statuses
|
||||
self.assertFalse(self.db.task("b-prompt").account_waiting) # another account
|
||||
account = self.db.account("A")
|
||||
self.assertEqual((account.level, account.wait_listed), (lls.Level.BUSY, True))
|
||||
|
||||
self.db.set_task_status("a-testing", "testing") # setting it again changes nothing
|
||||
self.assertEqual(self.db.task("a-testing").status_label, "Testing (account-waiting)")
|
||||
self.assertEqual(self.db.messages, ["Task 'a-testing' set to Testing."])
|
||||
|
||||
def test_the_account_stops_waiting_by_itself_and_waiting_tasks_become_limit_reset(self):
|
||||
self.db.add_task("w1", "A", "wait 4pm")
|
||||
self.db.add_task("w2", "A", "wait") # joins the running wait
|
||||
self.assertEqual(self.db.task("w2").status_label, "Wait 16:00")
|
||||
self.at(17) # the time has passed
|
||||
self.assertEqual(self.statuses()["w1"], "Limit reset")
|
||||
self.assertEqual(self.statuses()["w2"], "Limit reset")
|
||||
self.assertEqual(self.statuses()["a-testing"], "Testing") # no note any more
|
||||
self.assertEqual(self.statuses()["a-prompt"], "Prompt")
|
||||
account = self.db.account("A")
|
||||
self.assertEqual((account.wait_state, account.wait_until), (lls.WaitState.NONE, None))
|
||||
self.assertEqual(account.tasks[:2], ("w1", "w2")) # Limit reset keeps it Busy
|
||||
|
||||
self.db.add_account("Z") # a change saves what time did
|
||||
self.assertEqual(self.db.task("w1").stored_status, lls.Status.LIMIT_RESET)
|
||||
self.db.set_task_status("w1", "testing") # handled: leaves Limit reset
|
||||
self.assertEqual(self.db.task("w1").status_label, "Testing")
|
||||
self.assertEqual(self.db.task("w2").status_label, "Limit reset")
|
||||
|
||||
def test_a_new_wait_leaves_limit_reset_tasks_alone(self):
|
||||
self.db.add_task("w1", "A", "wait 1pm")
|
||||
self.at(14)
|
||||
self.db.add_task("w2", "A", "wait 8pm") # the account hit its limit again
|
||||
self.assertEqual(self.statuses()["w1"], "Limit reset")
|
||||
self.assertEqual(self.statuses()["w2"], "Wait 20:00")
|
||||
self.assertEqual(self.db.account("A").wait_state, lls.WaitState.WAITING)
|
||||
|
||||
def test_unwait_ends_the_wait_now(self):
|
||||
self.db.add_task("w1", "A", "wait 6pm")
|
||||
self.assertTrue(self.db.clear_account_wait("A"))
|
||||
self.assertFalse(self.db.clear_account_wait("A"))
|
||||
self.assertEqual(self.db.task("a-testing").status, lls.Status.TESTING)
|
||||
self.assertEqual(self.db.task("a-prompt").status, lls.Status.PROMPT)
|
||||
self.assertIn("1 task now Limit reset", self.db.messages[0])
|
||||
self.assertEqual(self.statuses()["w1"], "Limit reset")
|
||||
self.assertEqual(self.db.account("A").wait_state, lls.WaitState.NONE)
|
||||
self.assertEqual(self.db.task("a-testing").status_label, "Testing")
|
||||
self.assertFalse(self.db.clear_account_wait("A")) # it was not waiting any more
|
||||
|
||||
def test_changing_the_account_wait_moves_waiting_tasks(self):
|
||||
self.db.add_task("w1", "A", "wait 4pm")
|
||||
self.db.set_account_wait("A", "7pm")
|
||||
self.assertEqual(self.statuses()["w1"], "Wait 19:00")
|
||||
|
||||
def test_time_in_the_past_means_tomorrow(self):
|
||||
self.db.set_account_wait("A", "9am") # it is noon
|
||||
until = self.db.account("A").wait_until
|
||||
self.assertEqual((until.day, until.hour), (16, 9))
|
||||
self.assertEqual(self.db.account("A").wait_state, lls.WaitState.WAITING)
|
||||
|
||||
def test_set_wait_with_datetime_and_seconds(self):
|
||||
self.db.set_account_wait("A", NOON.replace(hour=15))
|
||||
self.assertEqual(self.db.task("a-prompt").status_label, "Wait 15:00")
|
||||
self.assertEqual(self.db.account("A").wait_until, NOON.replace(hour=15).astimezone())
|
||||
self.db.set_account_wait("A", int(NOON.replace(hour=20).timestamp()))
|
||||
self.assertEqual(self.db.task("a-prompt").status_label, "Wait 20:00")
|
||||
|
||||
def test_task_wait_sets_the_account_wait(self):
|
||||
self.db.set_task_status("b-prompt", "wait 3pm")
|
||||
self.assertEqual(self.db.account("B").wait_state, lls.WaitState.WAITING)
|
||||
self.assertEqual(self.db.task("b-prompt").status_label, "Wait 15:00")
|
||||
self.assertEqual(self.db.account("A").wait_until, NOON.replace(hour=20).astimezone())
|
||||
|
||||
def test_wait_without_time_only_while_account_is_waiting(self):
|
||||
with self.assertRaises(lls.InvalidArgument):
|
||||
@@ -196,120 +240,34 @@ class WaitTests(Base):
|
||||
self.db.set_account_wait("B", "4pm")
|
||||
self.db.add_task("follower", "B", "wait") # fine now
|
||||
self.assertEqual(self.db.task("follower").status_label, "Wait 16:00")
|
||||
self.db.set_task_status("b-prompt", "wait")
|
||||
self.assertEqual(self.db.task("b-prompt").status_label, "Wait 16:00")
|
||||
self.at(17) # over: a time is required again
|
||||
with self.assertRaises(lls.InvalidArgument):
|
||||
self.db.set_task_status("b-prompt", "wait")
|
||||
self.db.add_task("late", "B", "wait")
|
||||
|
||||
def test_explicit_testing_or_prompt_clears_a_finished_wait(self):
|
||||
self.db.set_account_wait("A", "1pm")
|
||||
self.at(14)
|
||||
self.assertEqual(self.db.task("a-prompt").status, lls.Status.LIMIT_RESET)
|
||||
self.db.add_task("implicit", "A") # no status given: nothing cleared
|
||||
self.assertEqual(self.db.account("A").wait_state, lls.WaitState.LIMIT_RESET)
|
||||
self.db.set_task_status("a-testing", "uploading") # not an "in use" status
|
||||
self.assertEqual(self.db.account("A").wait_state, lls.WaitState.LIMIT_RESET)
|
||||
self.db.add_task("resumed", "A", "testing") # explicit: the account is in use again
|
||||
self.assertTrue(any("back in use" in m for m in self.db.messages))
|
||||
self.assertEqual(self.db.account("A").wait_state, lls.WaitState.NONE) # (queries keep the messages)
|
||||
self.assertEqual(self.db.task("resumed").status, lls.Status.TESTING)
|
||||
self.assertEqual(self.db.task("a-prompt").status, lls.Status.PROMPT)
|
||||
self.assertTrue(any("back in use" in m for m in self.db.messages))
|
||||
def test_a_task_without_an_account_keeps_its_own_wait(self):
|
||||
self.db.add_task("loose", None, "wait 4pm")
|
||||
self.assertEqual(self.db.task("loose").status_label, "Wait 16:00")
|
||||
self.at(17)
|
||||
self.assertEqual(self.db.task("loose").status_label, "Limit reset")
|
||||
|
||||
def test_explicit_testing_or_prompt_shows_that_status_while_waiting(self):
|
||||
self.db.set_account_wait("A", "6pm")
|
||||
self.db.set_task_status("a-testing", "testing") # explicit
|
||||
task = self.db.task("a-testing")
|
||||
self.assertEqual((task.status, task.status_label, task.overrides_wait), (lls.Status.TESTING, "Testing", True))
|
||||
self.assertTrue(any("ignores it" in m for m in self.db.messages))
|
||||
|
||||
self.db.set_task_status("a-prompt", "prompt")
|
||||
self.assertEqual(self.db.task("a-prompt").status_label, "Prompt")
|
||||
self.db.add_task("n1", "A", "testing")
|
||||
self.assertEqual(self.db.task("n1").status, lls.Status.TESTING)
|
||||
self.db.add_task("n2", "A") # no status given: follows the wait
|
||||
self.assertEqual(self.db.task("n2").status, lls.Status.WAIT)
|
||||
self.assertFalse(self.db.task("n2").overrides_wait)
|
||||
|
||||
# the account itself is still waiting, nothing was cleared
|
||||
self.assertEqual(self.db.account("A").wait_state, lls.WaitState.WAITING)
|
||||
|
||||
def test_other_tasks_keep_waiting_and_wait_makes_a_task_follow_again(self):
|
||||
self.db.set_account_wait("A", "6pm")
|
||||
self.db.add_task("a-other", "A")
|
||||
self.db.set_task_status("a-testing", "testing")
|
||||
self.assertEqual(self.db.task("a-other").status, lls.Status.WAIT)
|
||||
self.assertEqual(self.db.task("a-prompt").status, lls.Status.WAIT)
|
||||
|
||||
self.db.set_task_status("a-testing", "wait") # no time: follow the running wait again
|
||||
task = self.db.task("a-testing")
|
||||
self.assertEqual((task.status, task.overrides_wait), (lls.Status.WAIT, False))
|
||||
self.assertIn("now follows account 'A'", self.db.messages[0])
|
||||
|
||||
def test_a_new_wait_applies_to_every_task_again(self):
|
||||
self.db.set_account_wait("A", "6pm")
|
||||
self.db.set_task_status("a-testing", "testing")
|
||||
self.db.set_account_wait("A", "7pm") # the account hit its limit again
|
||||
task = self.db.task("a-testing")
|
||||
self.assertEqual((task.status_label, task.overrides_wait), ("Wait 19:00", False))
|
||||
|
||||
def test_unwait_clears_the_overrides_too(self):
|
||||
self.db.set_account_wait("A", "6pm")
|
||||
self.db.set_task_status("a-testing", "testing")
|
||||
self.assertTrue(self.db.clear_account_wait("A"))
|
||||
self.assertFalse(self.db.task("a-testing").overrides_wait)
|
||||
self.db.set_account_wait("A", "8pm")
|
||||
self.assertEqual(self.db.task("a-testing").status_label, "Wait 20:00")
|
||||
|
||||
def test_an_override_survives_the_wait_ending(self):
|
||||
self.db.set_account_wait("A", "1pm")
|
||||
self.at(12, 30)
|
||||
self.db.set_task_status("a-testing", "testing") # pinned while the wait runs
|
||||
self.at(14) # the wait is over
|
||||
self.assertEqual(self.db.task("a-testing").status, lls.Status.TESTING)
|
||||
self.assertEqual(self.db.task("a-prompt").status, lls.Status.LIMIT_RESET)
|
||||
self.assertEqual(self.db.account("A").wait_state, lls.WaitState.LIMIT_RESET)
|
||||
# resuming another task after the reset clears the finished wait for everyone
|
||||
self.db.set_task_status("a-prompt", "prompt")
|
||||
self.assertEqual(self.db.account("A").wait_state, lls.WaitState.NONE)
|
||||
self.assertEqual(self.db.task("a-testing").status, lls.Status.TESTING)
|
||||
self.assertEqual(self.db.task("a-prompt").status, lls.Status.PROMPT)
|
||||
|
||||
def test_the_override_ends_when_the_status_changes_or_the_task_moves(self):
|
||||
self.db.set_account_wait("A", "6pm")
|
||||
self.db.set_task_status("a-testing", "testing")
|
||||
self.db.set_task_status("a-testing", "uploading")
|
||||
self.assertFalse(self.db.task("a-testing").overrides_wait)
|
||||
self.db.set_task_status("a-testing", "testing")
|
||||
self.assertTrue(self.db.task("a-testing").overrides_wait)
|
||||
self.db.set_task_account("a-testing", "B") # B is not waiting
|
||||
task = self.db.task("a-testing")
|
||||
self.assertEqual((task.account, task.status, task.overrides_wait), ("B", lls.Status.TESTING, False))
|
||||
|
||||
def test_a_wait_no_task_shows_when_every_task_ignores_it(self):
|
||||
self.db.set_account_wait("A", "6pm")
|
||||
self.db.set_task_status("a-testing", "testing")
|
||||
self.db.set_task_status("a-prompt", "prompt") # a-done is Done
|
||||
def test_deleting_the_waiting_tasks_keeps_the_account_waiting(self):
|
||||
self.db.add_task("w1", "A", "wait 4pm")
|
||||
self.db.delete_task("w1")
|
||||
account = self.db.account("A")
|
||||
self.assertEqual((account.level, account.wait_state, account.wait_listed),
|
||||
(lls.Level.BUSY, lls.WaitState.WAITING, True))
|
||||
self.assertEqual(self.db.task("a-prompt").status_label, "Prompt (account-waiting)")
|
||||
|
||||
def test_overrides_are_saved(self):
|
||||
self.db.set_account_wait("A", "6pm")
|
||||
self.db.set_task_status("a-testing", "testing")
|
||||
self.db.save()
|
||||
with lls.Database(self.path) as other:
|
||||
other.set_clock(NOON)
|
||||
self.assertTrue(other.task("a-testing").overrides_wait)
|
||||
self.assertEqual(other.task("a-prompt").status, lls.Status.WAIT)
|
||||
|
||||
def test_account_wait_nothing_shows(self):
|
||||
def test_account_wait_with_no_waiting_tasks(self):
|
||||
self.db.add_account("Empty")
|
||||
self.db.set_account_wait("Empty", "5pm")
|
||||
account = self.db.account("Empty")
|
||||
self.assertEqual((account.level, account.wait_listed, account.tasks), (lls.Level.BUSY, True, ()))
|
||||
self.db.add_task("only-done", "Empty", "done")
|
||||
self.assertTrue(self.db.account("Empty").wait_listed)
|
||||
self.assertEqual(self.db.account("Empty").tasks, ())
|
||||
self.at(18) # over: nothing left to show
|
||||
account = self.db.account("Empty")
|
||||
self.assertEqual((account.level, account.wait_state), (lls.Level.FREE, lls.WaitState.NONE))
|
||||
|
||||
def test_messages(self):
|
||||
self.db.set_account_wait("A", "4:30pm")
|
||||
@@ -338,7 +296,7 @@ class NotesAndPersistenceTests(Base):
|
||||
self.db.add_account("A")
|
||||
self.db.add_task("t", "A", "testing")
|
||||
self.db.set_task_notes("t", "one\ntwo")
|
||||
self.db.set_account_wait("A", "11pm")
|
||||
self.db.set_task_status("t", "wait 11pm")
|
||||
self.assertTrue(self.db.dirty)
|
||||
self.db.save()
|
||||
self.assertFalse(self.db.dirty)
|
||||
|
||||
+11
-26
@@ -3,6 +3,7 @@
|
||||
#include <sqlite3.h>
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <ctime>
|
||||
#include <iomanip>
|
||||
#include <memory>
|
||||
@@ -20,8 +21,7 @@ const char* const kSchema =
|
||||
" id TEXT NOT NULL PRIMARY KEY COLLATE NOCASE,"
|
||||
" account TEXT COLLATE NOCASE REFERENCES Account(id) ON UPDATE CASCADE ON DELETE SET NULL,"
|
||||
" status TEXT NOT NULL DEFAULT 'prompt',"
|
||||
" notes TEXT NOT NULL DEFAULT '',"
|
||||
" wait_override INTEGER NOT NULL DEFAULT 0"
|
||||
" notes TEXT NOT NULL DEFAULT ''"
|
||||
");"
|
||||
"CREATE INDEX IF NOT EXISTS idx_task_account ON Task(account);";
|
||||
|
||||
@@ -109,23 +109,18 @@ void Database::createSchema() {
|
||||
exec("ALTER TABLE Account ADD COLUMN cooldown TEXT;");
|
||||
}
|
||||
|
||||
// Files from before a task could ignore its account's wait have no such column.
|
||||
bool hasOverride = false;
|
||||
for (const Row& row : query("PRAGMA table_info(Task)")) {
|
||||
hasOverride = hasOverride || (row.size() > 1 && row[1] && *row[1] == "wait_override");
|
||||
}
|
||||
if (!hasOverride) {
|
||||
exec("ALTER TABLE Task ADD COLUMN wait_override INTEGER NOT NULL DEFAULT 0;");
|
||||
}
|
||||
|
||||
// Waits used to be set on each task. They belong to the account now: move
|
||||
// them over (the latest one wins) and let those tasks follow the account.
|
||||
// Waits used to be set on each task. They belong to the account now: the
|
||||
// account takes the latest of its tasks' waits (they stay Wait and follow it).
|
||||
// Done once per file (PRAGMA user_version), not on every load.
|
||||
const std::vector<Row> version = query("PRAGMA user_version");
|
||||
if (version.empty() || !version[0][0] || std::atoi(version[0][0]->c_str()) < 2) {
|
||||
exec(
|
||||
"UPDATE Account SET cooldown = max(coalesce(cooldown, ''), ("
|
||||
" SELECT max(substr(Task.status, 6)) FROM Task"
|
||||
" WHERE Task.account = Account.id AND Task.status LIKE 'wait %'))"
|
||||
" WHERE EXISTS (SELECT 1 FROM Task WHERE Task.account = Account.id AND Task.status LIKE 'wait %');"
|
||||
"UPDATE Task SET status = 'prompt' WHERE account IS NOT NULL AND status LIKE 'wait %';");
|
||||
"PRAGMA user_version = 2;");
|
||||
}
|
||||
}
|
||||
|
||||
void Database::exec(const std::string& sql) {
|
||||
@@ -298,7 +293,7 @@ std::optional<std::string> Database::findTask(const std::string& name) const {
|
||||
}
|
||||
|
||||
std::optional<TaskRow> Database::getTask(const std::string& name) const {
|
||||
std::vector<Row> rows = query("SELECT id, account, status, notes, wait_override FROM Task WHERE id = ?1", {name});
|
||||
std::vector<Row> rows = query("SELECT id, account, status, notes FROM Task WHERE id = ?1", {name});
|
||||
if (rows.empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
@@ -307,19 +302,17 @@ std::optional<TaskRow> Database::getTask(const std::string& name) const {
|
||||
task.account = rows[0][1];
|
||||
task.status = *rows[0][2];
|
||||
task.notes = *rows[0][3];
|
||||
task.waitOverride = rows[0][4] && *rows[0][4] != "0";
|
||||
return task;
|
||||
}
|
||||
|
||||
std::vector<TaskRow> Database::listTasks() const {
|
||||
std::vector<TaskRow> tasks;
|
||||
for (const Row& row : query("SELECT id, account, status, notes, wait_override FROM Task ORDER BY id")) {
|
||||
for (const Row& row : query("SELECT id, account, status, notes FROM Task ORDER BY id")) {
|
||||
TaskRow task;
|
||||
task.id = *row[0];
|
||||
task.account = row[1];
|
||||
task.status = *row[2];
|
||||
task.notes = *row[3];
|
||||
task.waitOverride = row[4] && *row[4] != "0";
|
||||
tasks.push_back(task);
|
||||
}
|
||||
return tasks;
|
||||
@@ -346,14 +339,6 @@ void Database::setTaskStatus(const std::string& name, const std::string& status)
|
||||
run("UPDATE Task SET status = ?2 WHERE id = ?1", {name, status});
|
||||
}
|
||||
|
||||
void Database::setTaskWaitOverride(const std::string& name, bool value) {
|
||||
run("UPDATE Task SET wait_override = ?2 WHERE id = ?1", {name, std::string(value ? "1" : "0")});
|
||||
}
|
||||
|
||||
void Database::clearWaitOverrides(const std::string& account) {
|
||||
run("UPDATE Task SET wait_override = 0 WHERE account = ?1", {account});
|
||||
}
|
||||
|
||||
void Database::setTaskNotes(const std::string& name, const std::string& notes) {
|
||||
run("UPDATE Task SET notes = ?2 WHERE id = ?1", {name, notes});
|
||||
}
|
||||
+4
-4
@@ -37,15 +37,15 @@ std::string Display::renderTasks(std::time_t now) const {
|
||||
}
|
||||
table.addRow({task.name,
|
||||
task.account ? *task.account : "-",
|
||||
StatusUtil::label(task.status),
|
||||
task.statusLabel,
|
||||
notesSummary(task.noteLines)});
|
||||
}
|
||||
return table.render();
|
||||
}
|
||||
|
||||
std::string Display::renderAccounts(std::time_t now) const {
|
||||
// The TASKS column lists the tasks at the account's level. A wait that no
|
||||
// task is showing is listed as "(wait)" / "(limit reset)".
|
||||
// The TASKS column lists the tasks at the account's level. An account that is
|
||||
// waiting while none of its tasks is set to Wait is listed as "(wait)".
|
||||
TextTable table({"ACCOUNT", "STATUS", "RESET", "TASKS"});
|
||||
for (const AccountInfo& account : buildAccounts(db_, now)) {
|
||||
if (settings_.hiddenAccountLevels.count(account.level) > 0) {
|
||||
@@ -54,7 +54,7 @@ std::string Display::renderAccounts(std::time_t now) const {
|
||||
|
||||
std::vector<std::string> entries = account.tasks;
|
||||
if (account.waitListed) {
|
||||
entries.push_back(account.waitState == WaitState::Waiting ? "(wait)" : "(limit reset)");
|
||||
entries.push_back("(wait)");
|
||||
}
|
||||
|
||||
std::string status = accountLevelName(account.level);
|
||||
|
||||
+3
-3
@@ -59,9 +59,9 @@ void Repl::registerCommands() {
|
||||
add("acc rename", "acc rename <name> <new>", "rename an account", 2, 2,
|
||||
{ArgKind::Account, ArgKind::None}, &Repl::cmdAccRename);
|
||||
add("acc wait", "acc wait <name> <time>",
|
||||
"the account hit its limit until <time>: its Prompt/Testing tasks show Wait, then Limit reset",
|
||||
"the account hit its limit until <time>; tasks set to 'wait' wait for it, then show Limit reset",
|
||||
2, kUnlimited, {ArgKind::Account, ArgKind::None}, &Repl::cmdAccWait);
|
||||
add("acc unwait", "acc unwait <name>", "clear an account's wait / limit reset (its tasks show their own status again)",
|
||||
add("acc unwait", "acc unwait <name>", "end an account's wait now (its Wait tasks become Limit reset)",
|
||||
1, 1, {ArgKind::Account}, &Repl::cmdAccUnwait);
|
||||
add("acc ls", "acc ls", "show the account table", 0, 0, {}, &Repl::cmdAccLs);
|
||||
|
||||
@@ -73,7 +73,7 @@ void Repl::registerCommands() {
|
||||
{ArgKind::Task, ArgKind::None}, &Repl::cmdTskRename);
|
||||
add("tsk reacc", "tsk reacc <name> <new>", "move a task to another account ('-' = none)", 2, 2,
|
||||
{ArgKind::Task, ArgKind::AccountOrDash}, &Repl::cmdTskReacc);
|
||||
add("tsk restat", "tsk restat <name> <new>", "change a task's status (testing/prompt ignore the account's wait; plain 'wait' follows it again)", 2, kUnlimited,
|
||||
add("tsk restat", "tsk restat <name> <new>", "change a task's status ('wait <time>' sets its account's wait; plain 'wait' joins a running one)", 2, kUnlimited,
|
||||
{ArgKind::Task, ArgKind::Status}, &Repl::cmdTskRestat);
|
||||
add("tsk notes", "tsk notes <name>", "edit a task's notes in your editor (see 'editor')", 1, 1,
|
||||
{ArgKind::Task}, &Repl::cmdTskNotes);
|
||||
|
||||
+19
-8
@@ -212,8 +212,8 @@ std::optional<Status> parse(const std::string& text, std::time_t now) {
|
||||
}
|
||||
|
||||
std::string serialize(const Status& status) {
|
||||
if (status.kind == StatusKind::Wait) {
|
||||
return "wait " + formatTime(status.waitUntil, "%Y-%m-%dT%H:%M");
|
||||
if (status.kind == StatusKind::Wait || (status.kind == StatusKind::LimitReset && status.waitUntil != 0)) {
|
||||
return kindName(status.kind) + " " + formatTime(status.waitUntil, "%Y-%m-%dT%H:%M");
|
||||
}
|
||||
return kindName(status.kind);
|
||||
}
|
||||
@@ -228,9 +228,13 @@ std::optional<Status> deserialize(const std::string& text) {
|
||||
if (!kind) {
|
||||
return std::nullopt;
|
||||
}
|
||||
if (*kind != StatusKind::Wait) {
|
||||
const bool hasTime = (*kind == StatusKind::Wait || *kind == StatusKind::LimitReset);
|
||||
if (!hasTime) {
|
||||
return make(*kind);
|
||||
}
|
||||
if (argument.empty() && *kind == StatusKind::LimitReset) {
|
||||
return make(*kind); // a Limit reset without a time
|
||||
}
|
||||
|
||||
std::tm parts{};
|
||||
std::istringstream timeIn(argument);
|
||||
@@ -240,21 +244,28 @@ std::optional<Status> deserialize(const std::string& text) {
|
||||
}
|
||||
parts.tm_isdst = -1;
|
||||
|
||||
Status status = make(StatusKind::Wait);
|
||||
Status status = make(*kind);
|
||||
status.waitUntil = std::mktime(&parts);
|
||||
return status;
|
||||
}
|
||||
|
||||
bool followsAccountWait(StatusKind kind) {
|
||||
bool showsAccountWaiting(StatusKind kind) {
|
||||
return kind == StatusKind::Prompt || kind == StatusKind::Testing;
|
||||
}
|
||||
|
||||
Status resolve(const Status& stored, const std::optional<std::time_t>& accountWait, std::time_t now) {
|
||||
if (accountWait && followsAccountWait(stored.kind)) {
|
||||
Status shown = make(now < *accountWait ? StatusKind::Wait : StatusKind::LimitReset);
|
||||
Status resolve(const Status& stored, bool hasAccount, const std::optional<std::time_t>& accountWait,
|
||||
std::time_t now) {
|
||||
if (stored.kind == StatusKind::Wait && hasAccount) {
|
||||
if (accountWait && now < *accountWait) {
|
||||
Status shown = make(StatusKind::Wait);
|
||||
shown.waitUntil = *accountWait;
|
||||
return shown;
|
||||
}
|
||||
// The account is not waiting any more: the conversation can be continued.
|
||||
Status shown = make(StatusKind::LimitReset);
|
||||
shown.waitUntil = accountWait ? *accountWait : stored.waitUntil;
|
||||
return shown;
|
||||
}
|
||||
return effective(stored, now);
|
||||
}
|
||||
|
||||
|
||||
+87
-67
@@ -2,6 +2,22 @@
|
||||
|
||||
#include "StringUtil.h"
|
||||
|
||||
namespace {
|
||||
|
||||
Status waitStatus(std::time_t until) {
|
||||
Status status = StatusUtil::make(StatusKind::Wait);
|
||||
status.waitUntil = until;
|
||||
return status;
|
||||
}
|
||||
|
||||
Status limitResetStatus(std::time_t endedAt) {
|
||||
Status status = StatusUtil::make(StatusKind::LimitReset);
|
||||
status.waitUntil = endedAt;
|
||||
return status;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TrackerError::TrackerError(ErrorCode code, const std::string& message)
|
||||
: std::runtime_error(message), code_(code) {}
|
||||
|
||||
@@ -65,7 +81,7 @@ TaskRow Tracker::existingTask(const std::string& name) const {
|
||||
return *found;
|
||||
}
|
||||
|
||||
// The account's wait if it is still running (not yet a Limit reset).
|
||||
// The account's wait if it is still running.
|
||||
std::optional<std::time_t> Tracker::runningWait(const std::string& account) const {
|
||||
std::optional<AccountRow> row = db_.getAccount(account);
|
||||
if (!row || !row->cooldown) {
|
||||
@@ -78,44 +94,44 @@ std::optional<std::time_t> Tracker::runningWait(const std::string& account) cons
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// A task was explicitly set to Testing or Prompt. If its account's limit has reset
|
||||
// (a Limit reset), the account is in use again: the finished wait is cleared and
|
||||
// the account's tasks go back to their own status. If the wait is still running,
|
||||
// only this task ignores it (it shows the status you gave it); the account's other
|
||||
// tasks keep following the wait.
|
||||
void Tracker::taskInUse(const std::string& taskName, const std::string& accountName) {
|
||||
std::optional<AccountRow> account = db_.getAccount(accountName);
|
||||
if (!account || !account->cooldown) {
|
||||
return;
|
||||
// The account stops waiting: its Wait tasks become Limit reset (remembering when
|
||||
// the wait ended, which keeps them in order). Returns how many tasks changed.
|
||||
int Tracker::endAccountWait(const std::string& account, std::time_t endedAt) {
|
||||
int changed = 0;
|
||||
for (const TaskRow& task : db_.listTasks()) {
|
||||
if (!task.account || !StringUtil::equalsIgnoreCase(*task.account, account)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::optional<std::time_t> until = StatusUtil::timeFromText(*account->cooldown);
|
||||
if (until && *until > now()) {
|
||||
db_.setTaskWaitOverride(taskName, true);
|
||||
note("Account '" + account->id + "' is waiting until " + StatusUtil::clockLabel(*until) + "; '" + taskName +
|
||||
"' ignores it (tsk restat " + taskName + " wait to follow it again).");
|
||||
return;
|
||||
std::optional<Status> stored = StatusUtil::deserialize(task.status);
|
||||
if (stored && stored->kind == StatusKind::Wait) {
|
||||
db_.setTaskStatus(task.id, StatusUtil::serialize(limitResetStatus(endedAt)));
|
||||
++changed;
|
||||
}
|
||||
|
||||
db_.setAccountCooldown(account->id, std::nullopt);
|
||||
db_.clearWaitOverrides(account->id);
|
||||
note("Account '" + account->id + "' is back in use: its finished wait (Limit reset) was cleared.");
|
||||
}
|
||||
db_.setAccountCooldown(account, std::nullopt);
|
||||
return changed;
|
||||
}
|
||||
|
||||
// Prompt and Testing tasks follow their account's wait; anything else becomes Prompt.
|
||||
void Tracker::makeTaskFollowAccount(const TaskRow& task) {
|
||||
std::optional<Status> current = StatusUtil::deserialize(task.status);
|
||||
if (!current || !StatusUtil::followsAccountWait(current->kind)) {
|
||||
db_.setTaskStatus(task.id, StatusUtil::serialize(StatusUtil::make(StatusKind::Prompt)));
|
||||
// Time passing ends waits by itself. The panels already show the result (see
|
||||
// Views.cpp); this saves it, and runs before every change.
|
||||
void Tracker::settle() {
|
||||
const std::time_t current = now();
|
||||
for (const AccountRow& account : db_.listAccountRows()) {
|
||||
if (!account.cooldown) {
|
||||
continue;
|
||||
}
|
||||
if (task.waitOverride) {
|
||||
db_.setTaskWaitOverride(task.id, false);
|
||||
const std::optional<std::time_t> until = StatusUtil::timeFromText(*account.cooldown);
|
||||
if (until && *until > current) {
|
||||
continue; // still waiting
|
||||
}
|
||||
endAccountWait(account.id, until ? *until : current);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Accounts -----------------------------------------------------------------
|
||||
|
||||
void Tracker::addAccount(const std::string& name) {
|
||||
settle();
|
||||
requireName(name);
|
||||
if (db_.findAccount(name)) {
|
||||
throw TrackerError(ErrorCode::Exists, "Account '" + name + "' already exists.");
|
||||
@@ -125,12 +141,14 @@ void Tracker::addAccount(const std::string& name) {
|
||||
}
|
||||
|
||||
void Tracker::deleteAccount(const std::string& name) {
|
||||
settle();
|
||||
const std::string account = existingAccount(name);
|
||||
db_.removeAccount(account);
|
||||
note("Account '" + account + "' deleted.");
|
||||
}
|
||||
|
||||
void Tracker::renameAccount(const std::string& name, const std::string& newName) {
|
||||
settle();
|
||||
const std::string account = existingAccount(name);
|
||||
requireName(newName);
|
||||
if (!StringUtil::equalsIgnoreCase(account, newName) && db_.findAccount(newName)) {
|
||||
@@ -141,11 +159,22 @@ void Tracker::renameAccount(const std::string& name, const std::string& newName)
|
||||
}
|
||||
|
||||
void Tracker::setAccountWait(const std::string& name, std::time_t until) {
|
||||
settle();
|
||||
const std::string account = existingAccount(name);
|
||||
db_.setAccountCooldown(account, StatusUtil::timeToText(until));
|
||||
db_.clearWaitOverrides(account); // a new limit applies to every task of the account
|
||||
|
||||
// The tasks waiting on the account follow the new time.
|
||||
for (const TaskRow& task : db_.listTasks()) {
|
||||
if (!task.account || !StringUtil::equalsIgnoreCase(*task.account, account)) {
|
||||
continue;
|
||||
}
|
||||
std::optional<Status> stored = StatusUtil::deserialize(task.status);
|
||||
if (stored && stored->kind == StatusKind::Wait) {
|
||||
db_.setTaskStatus(task.id, StatusUtil::serialize(waitStatus(until)));
|
||||
}
|
||||
}
|
||||
note("Account '" + account + "' waits until " + StatusUtil::clockLabel(until) +
|
||||
": its Prompt and Testing tasks show Wait, then Limit reset (acc unwait " + account + " to clear).");
|
||||
": tasks set to wait show Wait, then Limit reset (acc unwait " + account + " to end it now).");
|
||||
}
|
||||
|
||||
void Tracker::setAccountWaitText(const std::string& name, const std::string& timeText) {
|
||||
@@ -158,6 +187,7 @@ void Tracker::setAccountWaitText(const std::string& name, const std::string& tim
|
||||
}
|
||||
|
||||
bool Tracker::clearAccountWait(const std::string& name) {
|
||||
settle();
|
||||
std::optional<AccountRow> account = db_.getAccount(name);
|
||||
if (!account) {
|
||||
throw TrackerError(ErrorCode::NotFound, "No account named '" + name + "'.");
|
||||
@@ -166,9 +196,11 @@ bool Tracker::clearAccountWait(const std::string& name) {
|
||||
note("Account '" + account->id + "' is not waiting.");
|
||||
return false;
|
||||
}
|
||||
db_.setAccountCooldown(account->id, std::nullopt);
|
||||
db_.clearWaitOverrides(account->id);
|
||||
note("Wait cleared on '" + account->id + "'.");
|
||||
const int changed = endAccountWait(account->id, now());
|
||||
note("Wait ended on '" + account->id + "'" +
|
||||
(changed > 0 ? " (" + std::to_string(changed) + (changed == 1 ? " task" : " tasks") + " now Limit reset)"
|
||||
: std::string()) +
|
||||
".");
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -176,6 +208,7 @@ bool Tracker::clearAccountWait(const std::string& name) {
|
||||
|
||||
void Tracker::addTask(const std::string& name, const std::optional<std::string>& accountName,
|
||||
const std::optional<std::string>& statusText) {
|
||||
settle();
|
||||
requireName(name);
|
||||
if (db_.findTask(name)) {
|
||||
throw TrackerError(ErrorCode::Exists, "Task '" + name + "' already exists.");
|
||||
@@ -187,40 +220,38 @@ void Tracker::addTask(const std::string& name, const std::optional<std::string>&
|
||||
}
|
||||
|
||||
Status status = StatusUtil::make(StatusKind::Prompt);
|
||||
const bool bareWait = statusText && StatusUtil::isBareWait(*statusText);
|
||||
bool setsAccountWait = false;
|
||||
if (statusText) {
|
||||
if (bareWait) {
|
||||
// "wait" without a time: only while the account is already waiting.
|
||||
if (!account || !runningWait(*account)) {
|
||||
if (StatusUtil::isBareWait(*statusText)) {
|
||||
// "wait" without a time: join the account's running wait.
|
||||
const std::optional<std::time_t> until = account ? runningWait(*account) : std::nullopt;
|
||||
if (!until) {
|
||||
throw TrackerError(ErrorCode::Invalid,
|
||||
"A time is required unless the task's account is already waiting: wait <time>");
|
||||
}
|
||||
status = waitStatus(*until);
|
||||
} else {
|
||||
status = parseStatus(*statusText);
|
||||
setsAccountWait = (status.kind == StatusKind::Wait && account);
|
||||
}
|
||||
}
|
||||
|
||||
// A wait on a task that has an account is the account's wait; the task itself
|
||||
// stays Prompt and follows it.
|
||||
const bool accountWait = (status.kind == StatusKind::Wait && account);
|
||||
const Status stored = accountWait ? StatusUtil::make(StatusKind::Prompt) : status;
|
||||
|
||||
db_.addTask(name, account, StatusUtil::serialize(stored));
|
||||
db_.addTask(name, account, StatusUtil::serialize(status));
|
||||
note("Task '" + name + "' added.");
|
||||
if (accountWait) {
|
||||
setAccountWait(*account, status.waitUntil);
|
||||
} else if (statusText && !bareWait && account && StatusUtil::followsAccountWait(status.kind)) {
|
||||
taskInUse(name, *account); // an explicit Testing / Prompt
|
||||
if (setsAccountWait) {
|
||||
setAccountWait(*account, status.waitUntil); // a wait on a task is its account's wait
|
||||
}
|
||||
}
|
||||
|
||||
void Tracker::deleteTask(const std::string& name) {
|
||||
settle();
|
||||
const TaskRow task = existingTask(name);
|
||||
db_.removeTask(task.id);
|
||||
note("Task '" + task.id + "' deleted.");
|
||||
}
|
||||
|
||||
void Tracker::renameTask(const std::string& name, const std::string& newName) {
|
||||
settle();
|
||||
const TaskRow task = existingTask(name);
|
||||
requireName(newName);
|
||||
if (!StringUtil::equalsIgnoreCase(task.id, newName) && db_.findTask(newName)) {
|
||||
@@ -231,23 +262,22 @@ void Tracker::renameTask(const std::string& name, const std::string& newName) {
|
||||
}
|
||||
|
||||
void Tracker::setTaskAccount(const std::string& name, const std::optional<std::string>& accountName) {
|
||||
settle();
|
||||
const TaskRow task = existingTask(name);
|
||||
std::optional<std::string> account;
|
||||
if (accountName) {
|
||||
account = existingAccount(*accountName);
|
||||
}
|
||||
db_.setTaskAccount(task.id, account);
|
||||
if (task.waitOverride) {
|
||||
db_.setTaskWaitOverride(task.id, false);
|
||||
}
|
||||
note("Task '" + task.id + "' now on account " + (account ? "'" + *account + "'" : std::string("(none)")) + ".");
|
||||
}
|
||||
|
||||
void Tracker::setTaskStatus(const std::string& name, const std::string& statusText) {
|
||||
settle();
|
||||
const TaskRow task = existingTask(name);
|
||||
|
||||
if (StatusUtil::isBareWait(statusText)) {
|
||||
// "wait" without a time: the task follows its account's running wait again.
|
||||
// "wait" without a time: the task waits on its account's running wait.
|
||||
const std::optional<std::time_t> until = task.account ? runningWait(*task.account) : std::nullopt;
|
||||
if (!until) {
|
||||
throw TrackerError(
|
||||
@@ -255,33 +285,23 @@ void Tracker::setTaskStatus(const std::string& name, const std::string& statusTe
|
||||
task.account ? "Account '" + *task.account + "' is not waiting, so a time is required: wait <time>"
|
||||
: "Task '" + task.id + "' has no account, so a time is required: wait <time>");
|
||||
}
|
||||
makeTaskFollowAccount(task);
|
||||
note("Task '" + task.id + "' now follows account '" + *task.account + "' (waiting until " +
|
||||
db_.setTaskStatus(task.id, StatusUtil::serialize(waitStatus(*until)));
|
||||
note("Task '" + task.id + "' now waits on account '" + *task.account + "' (until " +
|
||||
StatusUtil::clockLabel(*until) + ").");
|
||||
return;
|
||||
}
|
||||
|
||||
const Status status = parseStatus(statusText);
|
||||
db_.setTaskStatus(task.id, StatusUtil::serialize(status));
|
||||
note("Task '" + task.id + "' set to " + StatusUtil::label(status) + ".");
|
||||
|
||||
if (status.kind == StatusKind::Wait && task.account) {
|
||||
// The wait belongs to the account. Keep an active task as it is so it
|
||||
// follows the wait; any other status becomes Prompt.
|
||||
makeTaskFollowAccount(task);
|
||||
setAccountWait(*task.account, status.waitUntil);
|
||||
return;
|
||||
}
|
||||
|
||||
db_.setTaskStatus(task.id, StatusUtil::serialize(status));
|
||||
if (task.waitOverride) {
|
||||
db_.setTaskWaitOverride(task.id, false);
|
||||
}
|
||||
note("Task '" + task.id + "' set to " + StatusUtil::label(status) + ".");
|
||||
if (task.account && StatusUtil::followsAccountWait(status.kind)) {
|
||||
taskInUse(task.id, *task.account); // an explicit Testing / Prompt
|
||||
setAccountWait(*task.account, status.waitUntil); // a wait on a task is its account's wait
|
||||
}
|
||||
}
|
||||
|
||||
bool Tracker::setTaskNotes(const std::string& name, const std::string& notes) {
|
||||
settle();
|
||||
const TaskRow task = existingTask(name);
|
||||
if (task.notes == notes) {
|
||||
note("Notes unchanged.");
|
||||
|
||||
+11
-6
@@ -96,8 +96,9 @@ std::vector<TaskInfo> buildTasks(const Database& db, std::time_t now) {
|
||||
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);
|
||||
info.status = StatusUtil::resolve(info.storedStatus, row.account.has_value(), accountWait, now);
|
||||
info.accountWaiting = accountWait && *accountWait > now && StatusUtil::showsAccountWaiting(info.status.kind);
|
||||
info.statusLabel = StatusUtil::label(info.status) + (info.accountWaiting ? " (account-waiting)" : "");
|
||||
tasks.push_back(info);
|
||||
}
|
||||
|
||||
@@ -123,22 +124,26 @@ std::vector<AccountInfo> buildAccounts(const Database& db, std::time_t now) {
|
||||
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) {
|
||||
if (task.status.kind == StatusKind::Wait) {
|
||||
showsWait = true;
|
||||
}
|
||||
}
|
||||
|
||||
// The account waits until its wait time; after that it has stopped waiting
|
||||
// by itself (its Wait tasks show Limit reset), whether or not the old value
|
||||
// is still stored.
|
||||
AccountInfo info;
|
||||
info.name = row.id;
|
||||
if (row.cooldown) {
|
||||
if (std::optional<std::time_t> until = StatusUtil::timeFromText(*row.cooldown)) {
|
||||
std::optional<std::time_t> until = StatusUtil::timeFromText(*row.cooldown);
|
||||
if (until && *until > now) {
|
||||
info.waitState = WaitState::Waiting;
|
||||
info.waitUntil = *until;
|
||||
info.waitState = (*until > now) ? WaitState::Waiting : WaitState::LimitReset;
|
||||
info.waitListed = !showsWait;
|
||||
}
|
||||
}
|
||||
|
||||
if (!busy.empty() || info.waitListed) {
|
||||
if (!busy.empty() || info.waitState == WaitState::Waiting) {
|
||||
info.level = AccountLevel::Busy;
|
||||
info.tasks = busy;
|
||||
} else if (!taken.empty()) {
|
||||
|
||||
+2
-6
@@ -29,7 +29,6 @@ lls_db::lls_db() : database(), tracker(database) {}
|
||||
|
||||
struct lls_task_list {
|
||||
std::vector<TaskInfo> items;
|
||||
std::vector<std::string> labels;
|
||||
std::vector<lls_task_info> infos;
|
||||
};
|
||||
|
||||
@@ -52,7 +51,6 @@ static_assert(static_cast<int>(AccountLevel::Taken) == LLS_LEVEL_TAKEN, "level n
|
||||
static_assert(static_cast<int>(AccountLevel::Free) == LLS_LEVEL_FREE, "level numbering");
|
||||
static_assert(static_cast<int>(WaitState::None) == LLS_WAIT_NONE, "wait state numbering");
|
||||
static_assert(static_cast<int>(WaitState::Waiting) == LLS_WAIT_WAITING, "wait state numbering");
|
||||
static_assert(static_cast<int>(WaitState::LimitReset) == LLS_WAIT_LIMIT_RESET, "wait state numbering");
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -309,24 +307,22 @@ int lls_tasks(lls_db* db, lls_task_list** out_list) {
|
||||
std::unique_ptr<lls_task_list> list(new lls_task_list);
|
||||
list->items = db->tracker.tasks();
|
||||
const std::size_t count = list->items.size();
|
||||
list->labels.resize(count);
|
||||
list->infos.resize(count); // sized once: the pointers below stay valid
|
||||
|
||||
for (std::size_t i = 0; i < count; ++i) {
|
||||
const TaskInfo& task = list->items[i];
|
||||
list->labels[i] = StatusUtil::label(task.status);
|
||||
|
||||
lls_task_info& info = list->infos[i];
|
||||
info.name = task.name.c_str();
|
||||
info.account = task.account ? task.account->c_str() : nullptr;
|
||||
info.status = static_cast<int>(task.status.kind);
|
||||
info.status_label = list->labels[i].c_str();
|
||||
info.status_label = task.statusLabel.c_str();
|
||||
info.stored_status = static_cast<int>(task.storedStatus.kind);
|
||||
const bool timed = task.status.kind == StatusKind::Wait || task.status.kind == StatusKind::LimitReset;
|
||||
info.until = timed ? static_cast<int64_t>(task.status.waitUntil) : 0;
|
||||
info.notes = task.notes.c_str();
|
||||
info.notes_lines = static_cast<int>(task.noteLines);
|
||||
info.overrides_wait = task.overridesWait ? 1 : 0;
|
||||
info.account_waiting = task.accountWaiting ? 1 : 0;
|
||||
}
|
||||
*out_list = list.release();
|
||||
}, true);
|
||||
|
||||
Reference in new issue
Block a user