C and Python API + Smarter account-based wait times
This commit is contained in:
1 parent
35c568333d
commit
10badb282c
18 files changed
+2204
-429
No files matched your search
+3
-10
@@ -6,14 +6,7 @@
|
||||
|
||||
#include "Database.h"
|
||||
#include "Status.h"
|
||||
|
||||
// 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
|
||||
enum class AccountLevel { Busy, Taken, Free };
|
||||
|
||||
std::string accountLevelName(AccountLevel level); // "busy", "taken", "free"
|
||||
#include "Views.h"
|
||||
|
||||
struct DisplaySettings {
|
||||
bool showTasks = true; // auto-print the task table after each change
|
||||
@@ -22,8 +15,8 @@ struct DisplaySettings {
|
||||
std::set<AccountLevel> hiddenAccountLevels;
|
||||
};
|
||||
|
||||
// Builds the computed views. The status shown is derived from the stored
|
||||
// status and the current time, never copied straight from the database.
|
||||
// Renders the task and account panels as text tables, from the computed views
|
||||
// (see Views.h), honouring the hidden levels in the settings.
|
||||
class Display {
|
||||
public:
|
||||
explicit Display(const Database& db);
|
||||
|
||||
+2
-9
@@ -12,6 +12,7 @@
|
||||
#include "Database.h"
|
||||
#include "Display.h"
|
||||
#include "LineEditor.h"
|
||||
#include "Tracker.h"
|
||||
#include "Status.h"
|
||||
|
||||
// Interactive prompt: commands are typed live, one per line.
|
||||
@@ -39,8 +40,6 @@ private:
|
||||
void execute(const std::string& line);
|
||||
void refresh() const;
|
||||
void say(const std::string& message);
|
||||
void setAccountWait(const std::string& account, std::time_t until);
|
||||
void noteAccountWait(const std::string& taskName);
|
||||
void flushMessages();
|
||||
bool endOfInput(); // true if the program should exit
|
||||
|
||||
@@ -48,13 +47,6 @@ private:
|
||||
// Asks until the reply is valid (y / n, and c when allowed). nullopt if input closed.
|
||||
std::optional<Answer> ask(const std::string& question, bool allowCancel) const;
|
||||
|
||||
// Helpers
|
||||
static void requireName(const std::string& name);
|
||||
static Status parseStatusFrom(const Args& args, std::size_t from);
|
||||
static bool isBareWait(const Args& args, std::size_t from);
|
||||
std::optional<std::time_t> runningWait(const std::string& account) const;
|
||||
void makeTaskFollowAccount(const TaskRow& task);
|
||||
|
||||
// Account commands
|
||||
bool cmdAccAdd(const Args& args);
|
||||
bool cmdAccDel(const Args& args);
|
||||
@@ -92,6 +84,7 @@ private:
|
||||
Database& db_;
|
||||
Display& display_;
|
||||
Config& config_;
|
||||
Tracker tracker_;
|
||||
std::vector<Command> commands_;
|
||||
std::vector<CommandSpec> specs_;
|
||||
Completer completer_;
|
||||
|
||||
@@ -45,6 +45,12 @@ std::string clockLabel(std::time_t value); // "16:30"
|
||||
// "16:30" for today, "tomorrow 06:40" for the next day, otherwise "2026-10-26 16:30".
|
||||
std::string resetLabel(std::time_t value, std::time_t now);
|
||||
|
||||
// True when the text is just "wait" (no time given).
|
||||
bool isBareWait(const std::string& text);
|
||||
|
||||
// One-line list of the accepted statuses, for error messages and help.
|
||||
std::string helpText();
|
||||
|
||||
std::string label(const Status& status); // "Prompt", "Limit reset", "Wait 16:30", ...
|
||||
std::string kindName(StatusKind kind); // lowercase token: "prompt", "limit-reset", ...
|
||||
std::vector<StatusKind> allKinds();
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
#pragma once
|
||||
|
||||
#include <ctime>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "Database.h"
|
||||
#include "Views.h"
|
||||
|
||||
enum class ErrorCode { Invalid, NotFound, Exists, Database };
|
||||
|
||||
// What went wrong, with a message that can be shown to the user as it is.
|
||||
class TrackerError : public std::runtime_error {
|
||||
public:
|
||||
TrackerError(ErrorCode code, const std::string& message);
|
||||
|
||||
ErrorCode code() const;
|
||||
|
||||
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.
|
||||
class Tracker {
|
||||
public:
|
||||
explicit Tracker(Database& db);
|
||||
|
||||
const Database& database() const;
|
||||
|
||||
// For tests and simulations: pretend the time is `fixedNow`. 0 = real clock.
|
||||
void setClock(std::time_t fixedNow);
|
||||
std::time_t now() const;
|
||||
|
||||
std::vector<std::string> takeMessages();
|
||||
|
||||
// ---- Accounts -----------------------------------------------------------
|
||||
void addAccount(const std::string& name);
|
||||
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).
|
||||
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.
|
||||
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.
|
||||
void addTask(const std::string& name, const std::optional<std::string>& account,
|
||||
const std::optional<std::string>& statusText);
|
||||
void deleteTask(const std::string& name);
|
||||
void renameTask(const std::string& name, const std::string& newName);
|
||||
void setTaskAccount(const std::string& name, const std::optional<std::string>& account);
|
||||
void setTaskStatus(const std::string& name, const std::string& statusText);
|
||||
// Returns false (and changes nothing) if the notes are the same already.
|
||||
bool setTaskNotes(const std::string& name, const std::string& notes);
|
||||
|
||||
// ---- Computed views -----------------------------------------------------
|
||||
std::vector<TaskInfo> tasks() const;
|
||||
std::vector<AccountInfo> accounts() const;
|
||||
|
||||
private:
|
||||
static void requireName(const std::string& name);
|
||||
Status parseStatus(const std::string& text) const;
|
||||
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 resumeAccount(const std::string& account);
|
||||
void makeTaskFollowAccount(const TaskRow& task);
|
||||
void noteAccountWait(const std::string& taskName);
|
||||
void note(const std::string& message);
|
||||
|
||||
Database& db_;
|
||||
std::time_t fixedNow_;
|
||||
std::vector<std::string> messages_;
|
||||
};
|
||||
@@ -0,0 +1,51 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <ctime>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "Database.h"
|
||||
#include "Status.h"
|
||||
|
||||
// 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
|
||||
enum class AccountLevel { Busy, Taken, Free };
|
||||
|
||||
std::string accountLevelName(AccountLevel level); // "busy", "taken", "free"
|
||||
|
||||
enum class WaitState { None, Waiting, LimitReset };
|
||||
|
||||
// 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 storedStatus; // what is saved underneath
|
||||
std::string notes;
|
||||
std::size_t noteLines = 0;
|
||||
};
|
||||
|
||||
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.
|
||||
bool waitListed = false;
|
||||
std::vector<std::string> tasks; // tasks at the account's level (Free: its Uploading/Frozen/Done ones)
|
||||
};
|
||||
|
||||
// Tasks in display order: status priority (Limit reset, Testing, Prompt, Wait,
|
||||
// Uploading, Frozen, Done); for Wait and Limit reset then time, earliest first;
|
||||
// then account A-Z (none last); then task name.
|
||||
std::vector<TaskInfo> buildTasks(const Database& db, std::time_t now);
|
||||
|
||||
// Accounts in display order, most free first: Free, Taken, Busy; within Taken
|
||||
// and Busy fewer entries first; ties by name.
|
||||
std::vector<AccountInfo> buildAccounts(const Database& db, std::time_t now);
|
||||
@@ -0,0 +1,185 @@
|
||||
/*
|
||||
* lls C API - query and change the task / account database from C, or from
|
||||
* Python through ctypes / cffi (see python/lls.py).
|
||||
*
|
||||
* Model
|
||||
* - lls_open() loads the database file into memory (or starts empty). Changes
|
||||
* stay in memory until lls_save(); lls_is_dirty() tells whether there is
|
||||
* anything to save. Nothing is saved automatically.
|
||||
* - Every function that can fail returns LLS_OK (0) or an error code, and the
|
||||
* message is available from lls_last_error(). Changes also leave human
|
||||
* readable notes in lls_last_messages() (what the interactive prompt prints,
|
||||
* e.g. "Account 'A' is back in use: its finished wait was cleared.").
|
||||
* - Names are case-insensitive but keep their original spelling.
|
||||
* - 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 (see the README).
|
||||
* - 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.
|
||||
*
|
||||
* Threads: a lls_db must not be used from several threads at once.
|
||||
* Strings: every const char* returned by the library is owned by it. Those in a
|
||||
* list stay valid until the list is freed; lls_last_error / lls_last_messages /
|
||||
* lls_path stay valid until the next call on the same lls_db.
|
||||
*/
|
||||
#ifndef LLS_API_H
|
||||
#define LLS_API_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#if defined(_WIN32)
|
||||
# ifdef LLS_BUILDING_API
|
||||
# define LLS_API __declspec(dllexport)
|
||||
# else
|
||||
# define LLS_API __declspec(dllimport)
|
||||
# endif
|
||||
#else
|
||||
# define LLS_API __attribute__((visibility("default")))
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define LLS_API_VERSION 1
|
||||
|
||||
/* ---- Result codes --------------------------------------------------------- */
|
||||
#define LLS_OK 0
|
||||
#define LLS_E_INVALID 1 /* bad argument: NULL, invalid name, unknown status or time */
|
||||
#define LLS_E_NOT_FOUND 2 /* no such task or account */
|
||||
#define LLS_E_EXISTS 3 /* a task or account with that name already exists */
|
||||
#define LLS_E_DATABASE 4 /* SQLite or file problem */
|
||||
#define LLS_E_INTERNAL 5
|
||||
|
||||
/* ---- Status kinds (lls_task_info.status, .stored_status) ------------------ */
|
||||
#define LLS_STATUS_PROMPT 0 /* free to accept prompts */
|
||||
#define LLS_STATUS_LIMIT_RESET 1 /* the account's wait is over */
|
||||
#define LLS_STATUS_TESTING 2
|
||||
#define LLS_STATUS_WAIT 3 /* the account's wait is running */
|
||||
#define LLS_STATUS_UPLOADING 4
|
||||
#define LLS_STATUS_FROZEN 5
|
||||
#define LLS_STATUS_DONE 6
|
||||
|
||||
/* ---- Account levels (lls_account_info.level), most taken first ------------ */
|
||||
#define LLS_LEVEL_BUSY 0 /* Testing, Limit reset or Wait task, or a wait nothing else shows */
|
||||
#define LLS_LEVEL_TAKEN 1 /* has a Prompt task */
|
||||
#define LLS_LEVEL_FREE 2 /* only Uploading / Frozen / Done tasks, or none */
|
||||
|
||||
/* ---- 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 */
|
||||
|
||||
typedef struct lls_db lls_db;
|
||||
typedef struct lls_task_list lls_task_list;
|
||||
typedef struct lls_account_list lls_account_list;
|
||||
|
||||
/* A task as the user sees it. */
|
||||
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 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 */
|
||||
} lls_task_info;
|
||||
|
||||
/* An account with its computed state. */
|
||||
typedef struct lls_account_info {
|
||||
const char* name;
|
||||
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) */
|
||||
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;
|
||||
|
||||
/* ---- Library -------------------------------------------------------------- */
|
||||
|
||||
LLS_API int lls_api_version(void); /* compare with LLS_API_VERSION */
|
||||
|
||||
/* Name of a status kind: "prompt", "limit-reset", "testing", "wait", ... (NULL if unknown). */
|
||||
LLS_API const char* lls_status_name(int status);
|
||||
|
||||
/* Parses a clock time as `acc wait` does ("16:30", "4:30pm", "4 pm"); now = 0 means the
|
||||
* current time. Returns LLS_OK or LLS_E_INVALID. */
|
||||
LLS_API int lls_parse_time(const char* text, int64_t now, int64_t* out_until);
|
||||
|
||||
/* ---- Database ------------------------------------------------------------- */
|
||||
|
||||
/* Opens `path` (loaded into memory; created by the first lls_save if it does not exist).
|
||||
* On failure *out_db is NULL; the message is in lls_last_error(NULL). */
|
||||
LLS_API int lls_open(const char* path, lls_db** out_db);
|
||||
LLS_API void lls_close(lls_db* db); /* does not save */
|
||||
|
||||
/* Message of the last failed call on `db`; with db == NULL, of the last failed lls_open
|
||||
* on this thread. Empty string if the last call succeeded. */
|
||||
LLS_API const char* lls_last_error(const lls_db* db);
|
||||
/* Notes from the last call that changed something, one per line, no trailing newline.
|
||||
* Queries (lls_tasks / lls_accounts) do not reset them; a failed change leaves none. */
|
||||
LLS_API const char* lls_last_messages(const lls_db* db);
|
||||
|
||||
LLS_API const char* lls_path(const lls_db* db);
|
||||
LLS_API int lls_is_dirty(const lls_db* db); /* 1 if there are unsaved changes */
|
||||
LLS_API int lls_save(lls_db* db);
|
||||
/* Writes a copy. file == NULL: "<path>.<timestamp>.bak". The path used is copied to
|
||||
* used_path (if not NULL), truncated to used_path_size. */
|
||||
LLS_API int lls_backup(lls_db* db, const char* file, char* used_path, size_t used_path_size);
|
||||
/* Replaces the in-memory data with the contents of `file` (not saved until lls_save). */
|
||||
LLS_API int lls_restore(lls_db* db, const char* file);
|
||||
|
||||
/* Pretends the time is `now` (Unix seconds) for every later call: useful for tests and
|
||||
* simulations. 0 goes back to the real clock. */
|
||||
LLS_API void lls_set_clock(lls_db* db, int64_t now);
|
||||
LLS_API int64_t lls_now(const lls_db* db);
|
||||
|
||||
/* ---- Accounts ------------------------------------------------------------- */
|
||||
|
||||
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_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. */
|
||||
LLS_API int lls_account_clear_wait(lls_db* db, const char* name, int* was_waiting);
|
||||
|
||||
/* ---- Tasks ---------------------------------------------------------------- */
|
||||
|
||||
/* account == NULL: no account. status == NULL: prompt (see the notes at the top). */
|
||||
LLS_API int lls_task_add(lls_db* db, const char* name, const char* account, const char* status);
|
||||
LLS_API int lls_task_delete(lls_db* db, const char* name);
|
||||
LLS_API int lls_task_rename(lls_db* db, const char* name, const char* new_name);
|
||||
LLS_API int lls_task_set_account(lls_db* db, const char* name, const char* account); /* NULL = none */
|
||||
LLS_API int lls_task_set_status(lls_db* db, const char* name, const char* status);
|
||||
/* Stores the text as it is (any length, any number of lines; "" clears). */
|
||||
LLS_API int lls_task_set_notes(lls_db* db, const char* name, const char* notes);
|
||||
|
||||
/* ---- Queries -------------------------------------------------------------- */
|
||||
|
||||
/* Tasks in display order: status priority (Limit reset, Testing, Prompt, Wait, Uploading,
|
||||
* Frozen, Done); Wait / Limit reset by time; then account A-Z (none last); then name. */
|
||||
LLS_API int lls_tasks(lls_db* db, lls_task_list** out_list);
|
||||
LLS_API int lls_task_list_count(const lls_task_list* list);
|
||||
LLS_API const lls_task_info* lls_task_list_get(const lls_task_list* list, int index); /* NULL if out of range */
|
||||
LLS_API void lls_task_list_free(lls_task_list* list);
|
||||
|
||||
/* Accounts in display order, most free first: Free, Taken, Busy; ties by name. */
|
||||
LLS_API int lls_accounts(lls_db* db, lls_account_list** out_list);
|
||||
LLS_API int lls_account_list_count(const lls_account_list* list);
|
||||
LLS_API const lls_account_info* lls_account_list_get(const lls_account_list* list, int index);
|
||||
LLS_API void lls_account_list_free(lls_account_list* list);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* LLS_API_H */
|
||||
Reference in new issue
Block a user