/* stask - simple task manager * See LICENSE file for copyright and license details. */ #include #include #include #include #include #include "stask.h" static void now_str(char *buf, int len) { time_t t; struct tm *tm; t = time(NULL); tm = localtime(&t); strftime(buf, len, "%Y-%m-%d", tm); } static int ensure_dir(const char *path) { char tmp[1024]; char *p; snprintf(tmp, sizeof(tmp), "%s", path); for (p = tmp + 1; *p; p++) { if (*p == '/') { *p = '\0'; mkdir(tmp, 0755); *p = '/'; } } return mkdir(tmp, 0755); } int db_open(TaskDB *tdb) { const char *home; char dir[1024]; int rc; const char *sql; home = getenv("HOME"); if (!home) home = "/tmp"; snprintf(dir, sizeof(dir), "%s/.local/share/stask", home); ensure_dir(dir); snprintf(tdb->dbpath, sizeof(tdb->dbpath), "%s/stask.db", dir); snprintf(tdb->docdir, sizeof(tdb->docdir), "%s", dir); rc = sqlite3_open(tdb->dbpath, &tdb->db); if (rc != SQLITE_OK) { fprintf(stderr, "stask: cannot open db: %s\n", sqlite3_errmsg(tdb->db)); return -1; } sql = "CREATE TABLE IF NOT EXISTS lists (" " id INTEGER PRIMARY KEY AUTOINCREMENT," " name TEXT UNIQUE NOT NULL," " created TEXT NOT NULL" ");" "CREATE TABLE IF NOT EXISTS tasks (" " id INTEGER PRIMARY KEY AUTOINCREMENT," " list_id INTEGER NOT NULL," " text TEXT NOT NULL," " done INTEGER NOT NULL DEFAULT 0," " added TEXT NOT NULL," " FOREIGN KEY(list_id) REFERENCES lists(id)" " ON DELETE CASCADE" ");"; rc = sqlite3_exec(tdb->db, sql, NULL, NULL, NULL); if (rc != SQLITE_OK) { fprintf(stderr, "stask: schema error: %s\n", sqlite3_errmsg(tdb->db)); return -1; } /* enable foreign keys */ sqlite3_exec(tdb->db, "PRAGMA foreign_keys = ON;", NULL, NULL, NULL); return 0; } void db_close(TaskDB *tdb) { if (tdb->db) sqlite3_close(tdb->db); tdb->db = NULL; } int list_create(TaskDB *tdb, const char *name) { sqlite3_stmt *stmt; char date[32]; int rc; now_str(date, sizeof(date)); rc = sqlite3_prepare_v2(tdb->db, "INSERT INTO lists (name, created) VALUES (?, ?);", -1, &stmt, NULL); if (rc != SQLITE_OK) return -1; sqlite3_bind_text(stmt, 1, name, -1, SQLITE_STATIC); sqlite3_bind_text(stmt, 2, date, -1, SQLITE_STATIC); rc = sqlite3_step(stmt); sqlite3_finalize(stmt); return rc == SQLITE_DONE ? 0 : -1; } int list_delete(TaskDB *tdb, const char *name) { sqlite3_stmt *stmt; int rc; /* delete tasks first (in case FK not enforced) */ rc = sqlite3_prepare_v2(tdb->db, "DELETE FROM tasks WHERE list_id = " "(SELECT id FROM lists WHERE name = ?);", -1, &stmt, NULL); if (rc == SQLITE_OK) { sqlite3_bind_text(stmt, 1, name, -1, SQLITE_STATIC); sqlite3_step(stmt); sqlite3_finalize(stmt); } rc = sqlite3_prepare_v2(tdb->db, "DELETE FROM lists WHERE name = ?;", -1, &stmt, NULL); if (rc != SQLITE_OK) return -1; sqlite3_bind_text(stmt, 1, name, -1, SQLITE_STATIC); rc = sqlite3_step(stmt); sqlite3_finalize(stmt); return rc == SQLITE_DONE ? 0 : -1; } int list_all(TaskDB *tdb, List **out, int *count) { sqlite3_stmt *stmt; int rc, n, cap; List *arr; *out = NULL; *count = 0; rc = sqlite3_prepare_v2(tdb->db, "SELECT id, name, created FROM lists " "ORDER BY name;", -1, &stmt, NULL); if (rc != SQLITE_OK) return -1; n = 0; cap = 16; arr = malloc(cap * sizeof(List)); if (!arr) { sqlite3_finalize(stmt); return -1; } while (sqlite3_step(stmt) == SQLITE_ROW) { if (n >= cap) { cap *= 2; arr = realloc(arr, cap * sizeof(List)); if (!arr) { sqlite3_finalize(stmt); return -1; } } arr[n].id = sqlite3_column_int(stmt, 0); snprintf(arr[n].name, sizeof(arr[n].name), "%s", sqlite3_column_text(stmt, 1)); snprintf(arr[n].created, sizeof(arr[n].created), "%s", sqlite3_column_text(stmt, 2)); n++; } sqlite3_finalize(stmt); *out = arr; *count = n; return 0; } int list_by_name(TaskDB *tdb, const char *name, List *out) { sqlite3_stmt *stmt; int rc; rc = sqlite3_prepare_v2(tdb->db, "SELECT id, name, created FROM lists " "WHERE name = ?;", -1, &stmt, NULL); if (rc != SQLITE_OK) return -1; sqlite3_bind_text(stmt, 1, name, -1, SQLITE_STATIC); rc = sqlite3_step(stmt); if (rc != SQLITE_ROW) { sqlite3_finalize(stmt); return -1; } out->id = sqlite3_column_int(stmt, 0); snprintf(out->name, sizeof(out->name), "%s", sqlite3_column_text(stmt, 1)); snprintf(out->created, sizeof(out->created), "%s", sqlite3_column_text(stmt, 2)); sqlite3_finalize(stmt); return 0; } int task_add(TaskDB *tdb, int list_id, const char *text) { sqlite3_stmt *stmt; char date[32]; int rc; now_str(date, sizeof(date)); rc = sqlite3_prepare_v2(tdb->db, "INSERT INTO tasks (list_id, text, done, added)" " VALUES (?, ?, 0, ?);", -1, &stmt, NULL); if (rc != SQLITE_OK) return -1; sqlite3_bind_int(stmt, 1, list_id); sqlite3_bind_text(stmt, 2, text, -1, SQLITE_STATIC); sqlite3_bind_text(stmt, 3, date, -1, SQLITE_STATIC); rc = sqlite3_step(stmt); sqlite3_finalize(stmt); return rc == SQLITE_DONE ? 0 : -1; } int task_del(TaskDB *tdb, int task_id) { sqlite3_stmt *stmt; int rc; rc = sqlite3_prepare_v2(tdb->db, "DELETE FROM tasks WHERE id = ?;", -1, &stmt, NULL); if (rc != SQLITE_OK) return -1; sqlite3_bind_int(stmt, 1, task_id); rc = sqlite3_step(stmt); sqlite3_finalize(stmt); return rc == SQLITE_DONE ? 0 : -1; } int task_done(TaskDB *tdb, int task_id) { sqlite3_stmt *stmt; int rc; rc = sqlite3_prepare_v2(tdb->db, "UPDATE tasks SET done = 1 WHERE id = ?;", -1, &stmt, NULL); if (rc != SQLITE_OK) return -1; sqlite3_bind_int(stmt, 1, task_id); rc = sqlite3_step(stmt); sqlite3_finalize(stmt); return rc == SQLITE_DONE ? 0 : -1; } int task_undo(TaskDB *tdb, int task_id) { sqlite3_stmt *stmt; int rc; rc = sqlite3_prepare_v2(tdb->db, "UPDATE tasks SET done = 0 WHERE id = ?;", -1, &stmt, NULL); if (rc != SQLITE_OK) return -1; sqlite3_bind_int(stmt, 1, task_id); rc = sqlite3_step(stmt); sqlite3_finalize(stmt); return rc == SQLITE_DONE ? 0 : -1; } int task_all(TaskDB *tdb, int list_id, Task **out, int *count) { sqlite3_stmt *stmt; int rc, n, cap; Task *arr; *out = NULL; *count = 0; rc = sqlite3_prepare_v2(tdb->db, "SELECT id, list_id, text, done, added " "FROM tasks WHERE list_id = ? " "ORDER BY done ASC, id ASC;", -1, &stmt, NULL); if (rc != SQLITE_OK) return -1; sqlite3_bind_int(stmt, 1, list_id); n = 0; cap = 32; arr = malloc(cap * sizeof(Task)); if (!arr) { sqlite3_finalize(stmt); return -1; } while (sqlite3_step(stmt) == SQLITE_ROW) { if (n >= cap) { cap *= 2; arr = realloc(arr, cap * sizeof(Task)); if (!arr) { sqlite3_finalize(stmt); return -1; } } arr[n].id = sqlite3_column_int(stmt, 0); arr[n].list_id = sqlite3_column_int(stmt, 1); snprintf(arr[n].text, sizeof(arr[n].text), "%s", sqlite3_column_text(stmt, 2)); arr[n].done = sqlite3_column_int(stmt, 3); snprintf(arr[n].added, sizeof(arr[n].added), "%s", sqlite3_column_text(stmt, 4)); n++; } sqlite3_finalize(stmt); *out = arr; *count = n; return 0; } int task_count(TaskDB *tdb, int list_id, int *total, int *done) { sqlite3_stmt *stmt; int rc; *total = 0; *done = 0; rc = sqlite3_prepare_v2(tdb->db, "SELECT COUNT(*), SUM(done) FROM tasks " "WHERE list_id = ?;", -1, &stmt, NULL); if (rc != SQLITE_OK) return -1; sqlite3_bind_int(stmt, 1, list_id); if (sqlite3_step(stmt) == SQLITE_ROW) { *total = sqlite3_column_int(stmt, 0); *done = sqlite3_column_int(stmt, 1); } sqlite3_finalize(stmt); return 0; } int task_get(TaskDB *tdb, int task_id, Task *out) { sqlite3_stmt *stmt; int rc; rc = sqlite3_prepare_v2(tdb->db, "SELECT id, list_id, text, done, added " "FROM tasks WHERE id = ?;", -1, &stmt, NULL); if (rc != SQLITE_OK) return -1; sqlite3_bind_int(stmt, 1, task_id); rc = sqlite3_step(stmt); if (rc != SQLITE_ROW) { sqlite3_finalize(stmt); return -1; } out->id = sqlite3_column_int(stmt, 0); out->list_id = sqlite3_column_int(stmt, 1); snprintf(out->text, sizeof(out->text), "%s", sqlite3_column_text(stmt, 2)); out->done = sqlite3_column_int(stmt, 3); snprintf(out->added, sizeof(out->added), "%s", sqlite3_column_text(stmt, 4)); sqlite3_finalize(stmt); return 0; } int task_update_text(TaskDB *tdb, int task_id, const char *text) { sqlite3_stmt *stmt; int rc; rc = sqlite3_prepare_v2(tdb->db, "UPDATE tasks SET text = ? WHERE id = ?;", -1, &stmt, NULL); if (rc != SQLITE_OK) return -1; sqlite3_bind_text(stmt, 1, text, -1, SQLITE_STATIC); sqlite3_bind_int(stmt, 2, task_id); rc = sqlite3_step(stmt); sqlite3_finalize(stmt); return rc == SQLITE_DONE ? 0 : -1; } char * task_list_name(TaskDB *tdb, int list_id) { sqlite3_stmt *stmt; int rc; static char name[128]; rc = sqlite3_prepare_v2(tdb->db, "SELECT name FROM lists WHERE id = ?;", -1, &stmt, NULL); if (rc != SQLITE_OK) return NULL; sqlite3_bind_int(stmt, 1, list_id); rc = sqlite3_step(stmt); if (rc != SQLITE_ROW) { sqlite3_finalize(stmt); return NULL; } snprintf(name, sizeof(name), "%s", sqlite3_column_text(stmt, 0)); sqlite3_finalize(stmt); return name; }