107 lines
3.3 KiB
C
107 lines
3.3 KiB
C
|
|
#include "db.h"
|
||
|
|
#include <stdio.h>
|
||
|
|
#include <stdlib.h>
|
||
|
|
#include <string.h>
|
||
|
|
|
||
|
|
static sqlite3 *db = NULL;
|
||
|
|
|
||
|
|
bool db_init(const char *db_path) {
|
||
|
|
if (sqlite3_open_v2(db_path, &db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL) != SQLITE_OK) {
|
||
|
|
fprintf(stderr, "Error opening database: %s\n", sqlite3_errmsg(db));
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Enable WAL mode for better concurrency
|
||
|
|
char *err_msg = NULL;
|
||
|
|
if (sqlite3_exec(db, "PRAGMA journal_mode=WAL;", NULL, NULL, &err_msg) != SQLITE_OK) {
|
||
|
|
fprintf(stderr, "Failed to set WAL mode: %s\n", err_msg);
|
||
|
|
sqlite3_free(err_msg);
|
||
|
|
// Not fatal
|
||
|
|
}
|
||
|
|
|
||
|
|
const char *schema_sql =
|
||
|
|
"CREATE TABLE IF NOT EXISTS history ("
|
||
|
|
" id INTEGER PRIMARY KEY AUTOINCREMENT,"
|
||
|
|
" command TEXT NOT NULL,"
|
||
|
|
" cwd TEXT NOT NULL,"
|
||
|
|
" exit_code INTEGER NOT NULL,"
|
||
|
|
" duration INTEGER NOT NULL,"
|
||
|
|
" hostname TEXT NOT NULL,"
|
||
|
|
" username TEXT NOT NULL,"
|
||
|
|
" shell TEXT NOT NULL,"
|
||
|
|
" session TEXT NOT NULL,"
|
||
|
|
" timestamp INTEGER NOT NULL,"
|
||
|
|
" created_at DATETIME DEFAULT CURRENT_TIMESTAMP,"
|
||
|
|
" updated_at DATETIME DEFAULT CURRENT_TIMESTAMP"
|
||
|
|
");"
|
||
|
|
"CREATE INDEX IF NOT EXISTS idx_timestamp ON history(timestamp DESC);"
|
||
|
|
"CREATE INDEX IF NOT EXISTS idx_exit_code ON history(exit_code);"
|
||
|
|
"CREATE INDEX IF NOT EXISTS idx_hostname ON history(hostname);"
|
||
|
|
"CREATE INDEX IF NOT EXISTS idx_cwd ON history(cwd);";
|
||
|
|
|
||
|
|
if (sqlite3_exec(db, schema_sql, NULL, NULL, &err_msg) != SQLITE_OK) {
|
||
|
|
fprintf(stderr, "Schema creation failed: %s\n", err_msg);
|
||
|
|
sqlite3_free(err_msg);
|
||
|
|
sqlite3_close(db);
|
||
|
|
db = NULL;
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
void db_close(void) {
|
||
|
|
if (db) {
|
||
|
|
sqlite3_close(db);
|
||
|
|
db = NULL;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
sqlite3 *db_get_conn(void) {
|
||
|
|
return db;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool db_add_history(const HistoryEntry *entry) {
|
||
|
|
if (!db) return false;
|
||
|
|
|
||
|
|
const char *sql = "INSERT INTO history "
|
||
|
|
"(command, cwd, exit_code, duration, hostname, username, shell, session, timestamp) "
|
||
|
|
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?);";
|
||
|
|
|
||
|
|
sqlite3_stmt *stmt;
|
||
|
|
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) {
|
||
|
|
fprintf(stderr, "Prepare failed: %s\n", sqlite3_errmsg(db));
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
sqlite3_bind_text(stmt, 1, entry->command, -1, SQLITE_STATIC);
|
||
|
|
sqlite3_bind_text(stmt, 2, entry->cwd, -1, SQLITE_STATIC);
|
||
|
|
sqlite3_bind_int(stmt, 3, entry->exit_code);
|
||
|
|
sqlite3_bind_int(stmt, 4, entry->duration);
|
||
|
|
sqlite3_bind_text(stmt, 5, entry->hostname, -1, SQLITE_STATIC);
|
||
|
|
sqlite3_bind_text(stmt, 6, entry->username, -1, SQLITE_STATIC);
|
||
|
|
sqlite3_bind_text(stmt, 7, entry->shell, -1, SQLITE_STATIC);
|
||
|
|
sqlite3_bind_text(stmt, 8, entry->session, -1, SQLITE_STATIC);
|
||
|
|
sqlite3_bind_int64(stmt, 9, entry->timestamp);
|
||
|
|
|
||
|
|
int rc = sqlite3_step(stmt);
|
||
|
|
sqlite3_finalize(stmt);
|
||
|
|
|
||
|
|
if (rc != SQLITE_DONE) {
|
||
|
|
fprintf(stderr, "Insert failed: %s\n", sqlite3_errmsg(db));
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
void db_free_entry(HistoryEntry *entry) {
|
||
|
|
if (!entry) return;
|
||
|
|
free(entry->command);
|
||
|
|
free(entry->cwd);
|
||
|
|
free(entry->hostname);
|
||
|
|
free(entry->username);
|
||
|
|
free(entry->shell);
|
||
|
|
free(entry->session);
|
||
|
|
free(entry);
|
||
|
|
}
|