Better account-based wait status
This commit is contained in:
1 parent
10badb282c
commit
33567db5a3
14 files changed
+183
-57
No files matched your search
@@ -91,17 +91,22 @@ goes back to `$VISUAL` / `$EDITOR` / nano / vim / vi. The choice is stored in
|
|||||||
* Waits belong to the account: `acc wait <name> <time>` (or `tsk restat <task> wait
|
* 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
|
<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
|
and Testing tasks show `Wait HH:MM`; after it they show `Limit reset`, until
|
||||||
you run `acc unwait <name>`, which also clears a stale one. Uploading, Frozen
|
you run `acc unwait <name>`. Uploading, Frozen and Done tasks are not
|
||||||
and Done tasks are not affected, and the tasks keep their own stored status
|
affected, and the tasks keep their own stored status underneath. A wait that
|
||||||
underneath. A wait that no task shows (no tasks, or only Done / Frozen /
|
no task shows (no tasks, or only Done / Frozen / Uploading ones) is listed in
|
||||||
Uploading ones) is listed in the account row as `(wait HH:MM)` or
|
the account row as `(wait)` or `(limit reset)` and keeps the account Busy.
|
||||||
`(limit reset)` and keeps the account Busy. Tasks without an account keep
|
Tasks without an account keep their own wait.
|
||||||
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`),
|
* Auto unwait: once an account's limit has reset (its tasks show `Limit reset`),
|
||||||
explicitly setting a task on it to `testing` or `prompt` (`tsk add <task> <acc>
|
setting a task on it explicitly to `testing` or `prompt` clears that finished
|
||||||
testing`, `tsk restat <task> testing`) clears that finished wait, because the
|
wait, because the account is in use again, and its tasks go back to their own
|
||||||
account is in use again. Adding a task without a status does not, and a wait
|
status. A wait that is still running is never cleared automatically (use
|
||||||
that is still running is never cleared automatically (use `acc unwait`).
|
`acc unwait`).
|
||||||
* A time is only optional while the account is already waiting: then
|
* 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
|
`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
|
follow that wait. Otherwise (no account, or its wait is over) a time is
|
||||||
@@ -143,7 +148,9 @@ 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
|
* 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.
|
printed ("Account 'A' is back in use: ..."). Queries do not reset it.
|
||||||
* Statuses are the text you would type: `"prompt"`, `"testing"`, `"wait 4:30pm"`,
|
* Statuses are the text you would type: `"prompt"`, `"testing"`, `"wait 4:30pm"`,
|
||||||
`"wait"` (only while the account is waiting)... Waits belong to the account.
|
`"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.
|
||||||
* `lls_tasks` / `lls_accounts` return lists in the order of the panels, with the
|
* `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
|
computed status (`status`, `status_label`, `until`) and the stored one underneath
|
||||||
(`stored_status`). Strings in a list are valid until `lls_task_list_free`.
|
(`stored_status`). Strings in a list are valid until `lls_task_list_free`.
|
||||||
|
|||||||
@@ -46,6 +46,8 @@ public:
|
|||||||
void setTaskAccount(const std::string& name, const std::optional<std::string>& account);
|
void setTaskAccount(const std::string& name, const std::optional<std::string>& account);
|
||||||
void setTaskStatus(const std::string& name, const std::string& status);
|
void setTaskStatus(const std::string& name, const std::string& status);
|
||||||
void setTaskNotes(const std::string& name, const std::string& notes);
|
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:
|
private:
|
||||||
using Param = std::optional<std::string>;
|
using Param = std::optional<std::string>;
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ struct TaskRow {
|
|||||||
std::optional<std::string> account;
|
std::optional<std::string> account;
|
||||||
std::string status; // serialized, see StatusUtil::serialize
|
std::string status; // serialized, see StatusUtil::serialize
|
||||||
std::string notes; // free text, any length (stored as SQLite TEXT)
|
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 {
|
struct AccountRow {
|
||||||
|
|||||||
+1
-2
@@ -72,9 +72,8 @@ private:
|
|||||||
std::string existingAccount(const std::string& name) const;
|
std::string existingAccount(const std::string& name) const;
|
||||||
TaskRow existingTask(const std::string& name) const;
|
TaskRow existingTask(const std::string& name) const;
|
||||||
std::optional<std::time_t> runningWait(const std::string& account) const;
|
std::optional<std::time_t> runningWait(const std::string& account) const;
|
||||||
void resumeAccount(const std::string& account);
|
void taskInUse(const std::string& taskName, const std::string& accountName);
|
||||||
void makeTaskFollowAccount(const TaskRow& task);
|
void makeTaskFollowAccount(const TaskRow& task);
|
||||||
void noteAccountWait(const std::string& taskName);
|
|
||||||
void note(const std::string& message);
|
void note(const std::string& message);
|
||||||
|
|
||||||
Database& db_;
|
Database& db_;
|
||||||
|
|||||||
@@ -28,6 +28,9 @@ struct TaskInfo {
|
|||||||
Status storedStatus; // what is saved underneath
|
Status storedStatus; // what is saved underneath
|
||||||
std::string notes;
|
std::string notes;
|
||||||
std::size_t noteLines = 0;
|
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 {
|
struct AccountInfo {
|
||||||
|
|||||||
+6
-1
@@ -16,7 +16,11 @@
|
|||||||
* or just "wait" (only while the task's account is already waiting).
|
* 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
|
* - 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
|
* account's wait, and the account's Prompt / Testing tasks then show Wait
|
||||||
* until that time and Limit reset after it (see the README).
|
* 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).
|
||||||
* - Times are Unix seconds (UTC); clock-time text like "4:30pm" is read in the
|
* - 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.
|
* local time zone, and a time that has passed today means tomorrow.
|
||||||
*
|
*
|
||||||
@@ -88,6 +92,7 @@ typedef struct lls_task_info {
|
|||||||
int64_t until; /* for Wait / Limit reset: when the wait ends (ended); else 0 */
|
int64_t until; /* for Wait / Limit reset: when the wait ends (ended); else 0 */
|
||||||
const char* notes; /* the full text, "" if none (any length) */
|
const char* notes; /* the full text, "" if none (any length) */
|
||||||
int notes_lines; /* number of lines in the notes */
|
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) */
|
||||||
} lls_task_info;
|
} lls_task_info;
|
||||||
|
|
||||||
/* An account with its computed state. */
|
/* An account with its computed state. */
|
||||||
|
|||||||
Binary file not shown.
@@ -98,6 +98,7 @@ class Task:
|
|||||||
until: Optional[datetime] # for Wait / Limit reset: when the wait ends (ended)
|
until: Optional[datetime] # for Wait / Limit reset: when the wait ends (ended)
|
||||||
notes: str
|
notes: str
|
||||||
notes_lines: int
|
notes_lines: int
|
||||||
|
overrides_wait: bool # its account waits, but this task (set to Testing / Prompt) ignores it
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
@@ -123,6 +124,7 @@ class _TaskInfo(ctypes.Structure):
|
|||||||
("until", ctypes.c_int64),
|
("until", ctypes.c_int64),
|
||||||
("notes", ctypes.c_char_p),
|
("notes", ctypes.c_char_p),
|
||||||
("notes_lines", ctypes.c_int),
|
("notes_lines", ctypes.c_int),
|
||||||
|
("overrides_wait", ctypes.c_int),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@@ -400,6 +402,7 @@ class Database:
|
|||||||
until=_when(info.until),
|
until=_when(info.until),
|
||||||
notes=_dec(info.notes),
|
notes=_dec(info.notes),
|
||||||
notes_lines=info.notes_lines,
|
notes_lines=info.notes_lines,
|
||||||
|
overrides_wait=bool(info.overrides_wait),
|
||||||
))
|
))
|
||||||
return result
|
return result
|
||||||
finally:
|
finally:
|
||||||
|
|||||||
+84
-3
@@ -215,11 +215,92 @@ class WaitTests(Base):
|
|||||||
self.assertEqual(self.db.task("a-prompt").status, lls.Status.PROMPT)
|
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))
|
self.assertTrue(any("back in use" in m for m in self.db.messages))
|
||||||
|
|
||||||
def test_a_running_wait_is_never_cleared_automatically(self):
|
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_account_wait("A", "6pm")
|
||||||
self.db.set_task_status("a-testing", "testing")
|
self.db.set_task_status("a-testing", "testing")
|
||||||
self.assertEqual(self.db.account("A").wait_state, lls.WaitState.WAITING)
|
self.db.set_account_wait("A", "7pm") # the account hit its limit again
|
||||||
self.assertEqual(self.db.task("a-testing").status, lls.Status.WAIT)
|
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
|
||||||
|
account = self.db.account("A")
|
||||||
|
self.assertEqual((account.level, account.wait_state, account.wait_listed),
|
||||||
|
(lls.Level.BUSY, lls.WaitState.WAITING, True))
|
||||||
|
|
||||||
|
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_nothing_shows(self):
|
||||||
self.db.add_account("Empty")
|
self.db.add_account("Empty")
|
||||||
|
|||||||
+23
-3
@@ -20,7 +20,8 @@ const char* const kSchema =
|
|||||||
" id TEXT NOT NULL PRIMARY KEY COLLATE NOCASE,"
|
" id TEXT NOT NULL PRIMARY KEY COLLATE NOCASE,"
|
||||||
" account TEXT COLLATE NOCASE REFERENCES Account(id) ON UPDATE CASCADE ON DELETE SET NULL,"
|
" account TEXT COLLATE NOCASE REFERENCES Account(id) ON UPDATE CASCADE ON DELETE SET NULL,"
|
||||||
" status TEXT NOT NULL DEFAULT 'prompt',"
|
" status TEXT NOT NULL DEFAULT 'prompt',"
|
||||||
" notes TEXT NOT NULL DEFAULT ''"
|
" notes TEXT NOT NULL DEFAULT '',"
|
||||||
|
" wait_override INTEGER NOT NULL DEFAULT 0"
|
||||||
");"
|
");"
|
||||||
"CREATE INDEX IF NOT EXISTS idx_task_account ON Task(account);";
|
"CREATE INDEX IF NOT EXISTS idx_task_account ON Task(account);";
|
||||||
|
|
||||||
@@ -108,6 +109,15 @@ void Database::createSchema() {
|
|||||||
exec("ALTER TABLE Account ADD COLUMN cooldown TEXT;");
|
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
|
// 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.
|
// them over (the latest one wins) and let those tasks follow the account.
|
||||||
exec(
|
exec(
|
||||||
@@ -288,7 +298,7 @@ std::optional<std::string> Database::findTask(const std::string& name) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::optional<TaskRow> Database::getTask(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 FROM Task WHERE id = ?1", {name});
|
std::vector<Row> rows = query("SELECT id, account, status, notes, wait_override FROM Task WHERE id = ?1", {name});
|
||||||
if (rows.empty()) {
|
if (rows.empty()) {
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
@@ -297,17 +307,19 @@ std::optional<TaskRow> Database::getTask(const std::string& name) const {
|
|||||||
task.account = rows[0][1];
|
task.account = rows[0][1];
|
||||||
task.status = *rows[0][2];
|
task.status = *rows[0][2];
|
||||||
task.notes = *rows[0][3];
|
task.notes = *rows[0][3];
|
||||||
|
task.waitOverride = rows[0][4] && *rows[0][4] != "0";
|
||||||
return task;
|
return task;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<TaskRow> Database::listTasks() const {
|
std::vector<TaskRow> Database::listTasks() const {
|
||||||
std::vector<TaskRow> tasks;
|
std::vector<TaskRow> tasks;
|
||||||
for (const Row& row : query("SELECT id, account, status, notes FROM Task ORDER BY id")) {
|
for (const Row& row : query("SELECT id, account, status, notes, wait_override FROM Task ORDER BY id")) {
|
||||||
TaskRow task;
|
TaskRow task;
|
||||||
task.id = *row[0];
|
task.id = *row[0];
|
||||||
task.account = row[1];
|
task.account = row[1];
|
||||||
task.status = *row[2];
|
task.status = *row[2];
|
||||||
task.notes = *row[3];
|
task.notes = *row[3];
|
||||||
|
task.waitOverride = row[4] && *row[4] != "0";
|
||||||
tasks.push_back(task);
|
tasks.push_back(task);
|
||||||
}
|
}
|
||||||
return tasks;
|
return tasks;
|
||||||
@@ -334,6 +346,14 @@ void Database::setTaskStatus(const std::string& name, const std::string& status)
|
|||||||
run("UPDATE Task SET status = ?2 WHERE id = ?1", {name, 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) {
|
void Database::setTaskNotes(const std::string& name, const std::string& notes) {
|
||||||
run("UPDATE Task SET notes = ?2 WHERE id = ?1", {name, notes});
|
run("UPDATE Task SET notes = ?2 WHERE id = ?1", {name, notes});
|
||||||
}
|
}
|
||||||
+1
-1
@@ -73,7 +73,7 @@ void Repl::registerCommands() {
|
|||||||
{ArgKind::Task, ArgKind::None}, &Repl::cmdTskRename);
|
{ArgKind::Task, ArgKind::None}, &Repl::cmdTskRename);
|
||||||
add("tsk reacc", "tsk reacc <name> <new>", "move a task to another account ('-' = none)", 2, 2,
|
add("tsk reacc", "tsk reacc <name> <new>", "move a task to another account ('-' = none)", 2, 2,
|
||||||
{ArgKind::Task, ArgKind::AccountOrDash}, &Repl::cmdTskReacc);
|
{ArgKind::Task, ArgKind::AccountOrDash}, &Repl::cmdTskReacc);
|
||||||
add("tsk restat", "tsk restat <name> <new>", "change a task's status (plain 'wait' works while its account is waiting)", 2, kUnlimited,
|
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,
|
||||||
{ArgKind::Task, ArgKind::Status}, &Repl::cmdTskRestat);
|
{ArgKind::Task, ArgKind::Status}, &Repl::cmdTskRestat);
|
||||||
add("tsk notes", "tsk notes <name>", "edit a task's notes in your editor (see 'editor')", 1, 1,
|
add("tsk notes", "tsk notes <name>", "edit a task's notes in your editor (see 'editor')", 1, 1,
|
||||||
{ArgKind::Task}, &Repl::cmdTskNotes);
|
{ArgKind::Task}, &Repl::cmdTskNotes);
|
||||||
|
|||||||
+38
-35
@@ -78,20 +78,28 @@ std::optional<std::time_t> Tracker::runningWait(const std::string& account) cons
|
|||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setting a task to Testing or Prompt after its account's limit has reset means the
|
// A task was explicitly set to Testing or Prompt. If its account's limit has reset
|
||||||
// account is in use again, so the finished wait is cleared (its Limit reset tasks
|
// (a Limit reset), the account is in use again: the finished wait is cleared and
|
||||||
// go back to their own status). A wait that is still running is left alone.
|
// the account's tasks go back to their own status. If the wait is still running,
|
||||||
void Tracker::resumeAccount(const std::string& account) {
|
// only this task ignores it (it shows the status you gave it); the account's other
|
||||||
std::optional<AccountRow> row = db_.getAccount(account);
|
// tasks keep following the wait.
|
||||||
if (!row || !row->cooldown) {
|
void Tracker::taskInUse(const std::string& taskName, const std::string& accountName) {
|
||||||
|
std::optional<AccountRow> account = db_.getAccount(accountName);
|
||||||
|
if (!account || !account->cooldown) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const std::optional<std::time_t> until = StatusUtil::timeFromText(*row->cooldown);
|
|
||||||
|
const std::optional<std::time_t> until = StatusUtil::timeFromText(*account->cooldown);
|
||||||
if (until && *until > now()) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
db_.setAccountCooldown(row->id, std::nullopt);
|
|
||||||
note("Account '" + row->id + "' is back in use: its finished wait (Limit reset) was cleared.");
|
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.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prompt and Testing tasks follow their account's wait; anything else becomes Prompt.
|
// Prompt and Testing tasks follow their account's wait; anything else becomes Prompt.
|
||||||
@@ -100,19 +108,8 @@ void Tracker::makeTaskFollowAccount(const TaskRow& task) {
|
|||||||
if (!current || !StatusUtil::followsAccountWait(current->kind)) {
|
if (!current || !StatusUtil::followsAccountWait(current->kind)) {
|
||||||
db_.setTaskStatus(task.id, StatusUtil::serialize(StatusUtil::make(StatusKind::Prompt)));
|
db_.setTaskStatus(task.id, StatusUtil::serialize(StatusUtil::make(StatusKind::Prompt)));
|
||||||
}
|
}
|
||||||
}
|
if (task.waitOverride) {
|
||||||
|
db_.setTaskWaitOverride(task.id, false);
|
||||||
// Tells the user when a task's own status is hidden by its account's wait.
|
|
||||||
void Tracker::noteAccountWait(const std::string& taskName) {
|
|
||||||
std::optional<TaskRow> task = db_.getTask(taskName);
|
|
||||||
std::optional<Status> stored = task ? StatusUtil::deserialize(task->status) : std::nullopt;
|
|
||||||
if (!task || !task->account || !stored || !StatusUtil::followsAccountWait(stored->kind)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
std::optional<AccountRow> account = db_.getAccount(*task->account);
|
|
||||||
if (account && account->cooldown) {
|
|
||||||
note("It shows as Wait / Limit reset because account '" + account->id + "' is waiting (acc unwait " +
|
|
||||||
account->id + " to clear).");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,6 +143,7 @@ void Tracker::renameAccount(const std::string& name, const std::string& newName)
|
|||||||
void Tracker::setAccountWait(const std::string& name, std::time_t until) {
|
void Tracker::setAccountWait(const std::string& name, std::time_t until) {
|
||||||
const std::string account = existingAccount(name);
|
const std::string account = existingAccount(name);
|
||||||
db_.setAccountCooldown(account, StatusUtil::timeToText(until));
|
db_.setAccountCooldown(account, StatusUtil::timeToText(until));
|
||||||
|
db_.clearWaitOverrides(account); // a new limit applies to every task of the account
|
||||||
note("Account '" + account + "' waits until " + StatusUtil::clockLabel(until) +
|
note("Account '" + account + "' waits until " + StatusUtil::clockLabel(until) +
|
||||||
": its Prompt and Testing tasks show Wait, then Limit reset (acc unwait " + account + " to clear).");
|
": its Prompt and Testing tasks show Wait, then Limit reset (acc unwait " + account + " to clear).");
|
||||||
}
|
}
|
||||||
@@ -169,6 +167,7 @@ bool Tracker::clearAccountWait(const std::string& name) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
db_.setAccountCooldown(account->id, std::nullopt);
|
db_.setAccountCooldown(account->id, std::nullopt);
|
||||||
|
db_.clearWaitOverrides(account->id);
|
||||||
note("Wait cleared on '" + account->id + "'.");
|
note("Wait cleared on '" + account->id + "'.");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -188,8 +187,9 @@ void Tracker::addTask(const std::string& name, const std::optional<std::string>&
|
|||||||
}
|
}
|
||||||
|
|
||||||
Status status = StatusUtil::make(StatusKind::Prompt);
|
Status status = StatusUtil::make(StatusKind::Prompt);
|
||||||
|
const bool bareWait = statusText && StatusUtil::isBareWait(*statusText);
|
||||||
if (statusText) {
|
if (statusText) {
|
||||||
if (StatusUtil::isBareWait(*statusText)) {
|
if (bareWait) {
|
||||||
// "wait" without a time: only while the account is already waiting.
|
// "wait" without a time: only while the account is already waiting.
|
||||||
if (!account || !runningWait(*account)) {
|
if (!account || !runningWait(*account)) {
|
||||||
throw TrackerError(ErrorCode::Invalid,
|
throw TrackerError(ErrorCode::Invalid,
|
||||||
@@ -209,11 +209,8 @@ void Tracker::addTask(const std::string& name, const std::optional<std::string>&
|
|||||||
note("Task '" + name + "' added.");
|
note("Task '" + name + "' added.");
|
||||||
if (accountWait) {
|
if (accountWait) {
|
||||||
setAccountWait(*account, status.waitUntil);
|
setAccountWait(*account, status.waitUntil);
|
||||||
} else {
|
} else if (statusText && !bareWait && account && StatusUtil::followsAccountWait(status.kind)) {
|
||||||
if (statusText && account && (status.kind == StatusKind::Testing || status.kind == StatusKind::Prompt)) {
|
taskInUse(name, *account); // an explicit Testing / Prompt
|
||||||
resumeAccount(*account);
|
|
||||||
}
|
|
||||||
noteAccountWait(name);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,6 +237,9 @@ void Tracker::setTaskAccount(const std::string& name, const std::optional<std::s
|
|||||||
account = existingAccount(*accountName);
|
account = existingAccount(*accountName);
|
||||||
}
|
}
|
||||||
db_.setTaskAccount(task.id, account);
|
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)")) + ".");
|
note("Task '" + task.id + "' now on account " + (account ? "'" + *account + "'" : std::string("(none)")) + ".");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -247,16 +247,17 @@ void Tracker::setTaskStatus(const std::string& name, const std::string& statusTe
|
|||||||
const TaskRow task = existingTask(name);
|
const TaskRow task = existingTask(name);
|
||||||
|
|
||||||
if (StatusUtil::isBareWait(statusText)) {
|
if (StatusUtil::isBareWait(statusText)) {
|
||||||
// "wait" without a time: the task follows its account's running wait.
|
// "wait" without a time: the task follows its account's running wait again.
|
||||||
if (!task.account || !runningWait(*task.account)) {
|
const std::optional<std::time_t> until = task.account ? runningWait(*task.account) : std::nullopt;
|
||||||
|
if (!until) {
|
||||||
throw TrackerError(
|
throw TrackerError(
|
||||||
ErrorCode::Invalid,
|
ErrorCode::Invalid,
|
||||||
task.account ? "Account '" + *task.account + "' is not waiting, so a time is required: wait <time>"
|
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>");
|
: "Task '" + task.id + "' has no account, so a time is required: wait <time>");
|
||||||
}
|
}
|
||||||
makeTaskFollowAccount(task);
|
makeTaskFollowAccount(task);
|
||||||
note("Task '" + task.id + "' now follows account '" + *task.account + "'.");
|
note("Task '" + task.id + "' now follows account '" + *task.account + "' (waiting until " +
|
||||||
noteAccountWait(task.id);
|
StatusUtil::clockLabel(*until) + ").");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -271,11 +272,13 @@ void Tracker::setTaskStatus(const std::string& name, const std::string& statusTe
|
|||||||
}
|
}
|
||||||
|
|
||||||
db_.setTaskStatus(task.id, StatusUtil::serialize(status));
|
db_.setTaskStatus(task.id, StatusUtil::serialize(status));
|
||||||
|
if (task.waitOverride) {
|
||||||
|
db_.setTaskWaitOverride(task.id, false);
|
||||||
|
}
|
||||||
note("Task '" + task.id + "' set to " + StatusUtil::label(status) + ".");
|
note("Task '" + task.id + "' set to " + StatusUtil::label(status) + ".");
|
||||||
if (task.account && (status.kind == StatusKind::Testing || status.kind == StatusKind::Prompt)) {
|
if (task.account && StatusUtil::followsAccountWait(status.kind)) {
|
||||||
resumeAccount(*task.account);
|
taskInUse(task.id, *task.account); // an explicit Testing / Prompt
|
||||||
}
|
}
|
||||||
noteAccountWait(task.id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Tracker::setTaskNotes(const std::string& name, const std::string& notes) {
|
bool Tracker::setTaskNotes(const std::string& name, const std::string& notes) {
|
||||||
|
|||||||
+2
-1
@@ -96,7 +96,8 @@ std::vector<TaskInfo> buildTasks(const Database& db, std::time_t now) {
|
|||||||
accountWait = found->second;
|
accountWait = found->second;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
info.status = StatusUtil::resolve(info.storedStatus, accountWait, now);
|
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);
|
tasks.push_back(info);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -326,6 +326,7 @@ int lls_tasks(lls_db* db, lls_task_list** out_list) {
|
|||||||
info.until = timed ? static_cast<int64_t>(task.status.waitUntil) : 0;
|
info.until = timed ? static_cast<int64_t>(task.status.waitUntil) : 0;
|
||||||
info.notes = task.notes.c_str();
|
info.notes = task.notes.c_str();
|
||||||
info.notes_lines = static_cast<int>(task.noteLines);
|
info.notes_lines = static_cast<int>(task.noteLines);
|
||||||
|
info.overrides_wait = task.overridesWait ? 1 : 0;
|
||||||
}
|
}
|
||||||
*out_list = list.release();
|
*out_list = list.release();
|
||||||
}, true);
|
}, true);
|
||||||
|
|||||||
Reference in new issue
Block a user