91 lines
3.7 KiB
C++
91 lines
3.7 KiB
C++
#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. 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);
|
|
|
|
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 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);
|
|
// 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. "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);
|
|
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 settle();
|
|
int endAccountWait(const std::string& account, std::time_t endedAt);
|
|
void note(const std::string& message);
|
|
|
|
Database& db_;
|
|
std::time_t fixedNow_;
|
|
std::vector<std::string> messages_;
|
|
};
|