diff --git a/db.cpp b/db.cpp new file mode 100644 index 0000000..d1ce70c --- /dev/null +++ b/db.cpp @@ -0,0 +1,101 @@ +#include "db.h" + +sqlite3 *db = nullptr; + +db_transaction::db_transaction() +{ + sqlite3_exec(db, "BEGIN TRANSACTION;", nullptr, nullptr, nullptr); +} + +db_transaction::~db_transaction() +{ + sqlite3_exec(db, "COMMIT TRANSACTION;", nullptr, nullptr, nullptr); +} + +std::string get_string(sqlite3_stmt *row, int col) +{ + return std::string((char*)sqlite3_column_text(row, col)); +} + +void db_check_schema() { + bool settings_table_found = false; + + sqlite3_stmt *query; + SC(sqlite3_prepare_v2(db, "SELECT name FROM sqlite_master WHERE type='table';", -1, &query, nullptr)); + while(sqlite3_step(query) == SQLITE_ROW) { + if(get_string(query, 0) == "settings") + settings_table_found = true; + } + SC(sqlite3_finalize(query)); + + int schema_version = 0; + if(settings_table_found) { + const std::string schema_version_str = db_get_setting("schema_version"); + if(!schema_version_str.empty()) { + schema_version = std::stoi(schema_version_str); + } + } + if(schema_version < 1) { + const std::string db_init_sql = R"( +PRAGMA foreign_keys; +PRAGMA synchronous = NORMAL; +CREATE TABLE channels ( + channelId TEXT PRIMARY KEY, + name TEXT +); +CREATE TABLE "settings" ( + key TEXT NOT NULL PRIMARY KEY, + value TEXT NOT NULL +); +CREATE TABLE videos ( + videoId TEXT PRIMARY KEY, + channelId TEXT REFERENCES channels(channelId) ON DELETE CASCADE ON UPDATE CASCADE, + title TEXT, + description TEXT, + flags INTEGER DEFAULT 0 NOT NULL, + published TEXT +); +INSERT INTO settings(key, value) VALUES("schema_version", "1"); )"; + SC(sqlite3_exec(db, db_init_sql.c_str(), nullptr, nullptr, nullptr)); + } +} + +std::string db_get_setting(const std::string &key) +{ + sqlite3_stmt *query; + SC(sqlite3_prepare_v2(db, "SELECT value FROM settings WHERE key = ?1;", -1, &query, nullptr)); + SC(sqlite3_bind_text(query, 1, key.c_str(), -1, SQLITE_TRANSIENT)); + SC(sqlite3_step(query)); + const std::string value = get_string(query, 0); + SC(sqlite3_finalize(query)); + return value; +} + +void db_set_setting(const std::string &key, const std::string &value) +{ + sqlite3_stmt *query; + SC(sqlite3_prepare_v2(db, "INSERT INTO settings(key, value) values(?1, ?2) ON CONFLICT(key) DO UPDATE SET value=excluded.value;", -1, &query, nullptr)); + SC(sqlite3_bind_text(query, 1, key.c_str(), -1, SQLITE_TRANSIENT)); + SC(sqlite3_bind_text(query, 2, value.c_str(), -1, SQLITE_TRANSIENT)); + SC(sqlite3_step(query)); + SC(sqlite3_finalize(query)); +} + +std::map db_get_settings(const std::string &prefix) +{ + std::map result; + + sqlite3_stmt *query; + SC(sqlite3_prepare_v2(db, "SELECT key, value FROM settings WHERE key LIKE ?1;", -1, &query, nullptr)); + SC(sqlite3_bind_text(query, 1, (prefix + ":").c_str(), -1, SQLITE_TRANSIENT)); + while(sqlite3_step(query) == SQLITE_ROW) { + const std::string key = get_string(query, 0); + const std::string value = get_string(query, 1); + size_t i = key.find(':'); + if(i != std::string::npos) { + result.emplace(key.substr(i), value); + } + } + SC(sqlite3_finalize(query)); + return result; +} diff --git a/db.h b/db.h new file mode 100644 index 0000000..6232547 --- /dev/null +++ b/db.h @@ -0,0 +1,24 @@ +#pragma once + +#include +#include +#include + +extern sqlite3 *db; + +extern void tui_abort(const char *fmt, ...); +#define SC(x) { const int res = (x); if(res != SQLITE_OK && res != SQLITE_ROW && res != SQLITE_DONE) { tui_abort("Database error:\n%s failed: (%d) %s", #x, res, sqlite3_errstr(res)); }} + +class db_transaction { +public: + db_transaction(); + ~db_transaction(); +}; +std::string get_string(sqlite3_stmt *row, int col); + +void db_check_schema(); + +std::string db_get_setting(const std::string &key); +void db_set_setting(const std::string &key, const std::string &value); +std::map db_get_settings(const std::string &prefix); + diff --git a/main.cpp b/main.cpp index f28ecce..f6f4510 100644 --- a/main.cpp +++ b/main.cpp @@ -2,6 +2,7 @@ #include "tui.h" #include "yt.h" +#include "db.h" #include #include @@ -15,7 +16,6 @@ #include #include -#include static std::string user_home; @@ -30,8 +30,6 @@ size_t current_page_count = 0; size_t title_offset = 0; bool any_title_in_next_half = false; -#define SC(x) { const int res = (x); if(res != SQLITE_OK && res != SQLITE_ROW && res != SQLITE_DONE) { tui_abort("Database error:\n%s failed: (%d) %s", #x, res, sqlite3_errstr(res)); }} - static termpaint_attr* get_attr(const AttributeSetType type, const bool highlight=false) { return highlight ? attributes[type].highlight : attributes[type].normal; @@ -132,8 +130,6 @@ void draw_no_channels_msg() } } -sqlite3 *db; - void load_videos_for_channel(const std::string &channelId, bool force=false) { if(videos.find(channelId) != videos.end() && !force) @@ -415,6 +411,8 @@ bool save_json(const std::string &filename, const json &data) { } } +std::string database_filename = "yttool.sqlite"; + int main() { user_home = std::string(std::getenv("HOME")); @@ -448,9 +446,13 @@ int main() } } } + if(config.contains("database") && config["database"].is_string()) { + database_filename = config["database"]; + } } - sqlite3_open("yttool.sqlite", &db); + SC(sqlite3_open(database_filename.c_str(), &db)); + db_check_schema(); sqlite3_stmt *query; sqlite3_prepare_v2(db, "SELECT * FROM channels;", -1, &query, nullptr); diff --git a/yt-tool-tui.pro b/yt-tool-tui.pro index 804cc14..b426141 100644 --- a/yt-tool-tui.pro +++ b/yt-tool-tui.pro @@ -11,11 +11,13 @@ DEPENDPATH += $$PWD/termpaint unix:!macx: PRE_TARGETDEPS += $$PWD/termpaint/libtermpaint.a SOURCES += \ + db.cpp \ main.cpp \ tui.cpp \ yt.cpp HEADERS += \ + db.h \ tui.h \ yt.h diff --git a/yt.cpp b/yt.cpp index 67f2078..e695dcd 100644 --- a/yt.cpp +++ b/yt.cpp @@ -5,17 +5,11 @@ #include #include "tui.h" - -#define SC(x) { const int res = (x); if(res != SQLITE_OK && res != SQLITE_ROW && res != SQLITE_DONE) { tui_abort("Database error:\n%s failed: (%d) %s", #x, res, sqlite3_errstr(res)); }} +#include "db.h" using json = nlohmann::json; struct yt_config yt_config; -static std::string get_string(sqlite3_stmt *row, int col) -{ - return std::string((char*)sqlite3_column_text(row, col)); -} - static size_t curl_writecallback(void *data, size_t size, size_t nmemb, void *userp) { size_t to_add = size * nmemb;