#pragma once #include #include #include #include #include #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 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& account, const std::optional& 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& 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 tasks() const; std::vector 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 runningWait(const std::string& account) const; void taskInUse(const std::string& taskName, const std::string& accountName); void makeTaskFollowAccount(const TaskRow& task); void note(const std::string& message); Database& db_; std::time_t fixedNow_; std::vector messages_; };