321 lines
12 KiB
C++
321 lines
12 KiB
C++
#include "Database.h"
|
||
#include "BoostLog.h"
|
||
#include <sqlite3.h>
|
||
#include <sstream>
|
||
|
||
bool Database::open(const std::string &path) {
|
||
bool ret = true;
|
||
int result = sqlite3_open(path.c_str(), &m_sqlite3);
|
||
if (result != SQLITE_OK) {
|
||
ret = false;
|
||
LOG(error) << "open database failed.";
|
||
}
|
||
initialize();
|
||
return ret;
|
||
}
|
||
|
||
static int selectTaskCallback(void *data, int argc, char **argv, char **columnName) {
|
||
auto tasks = reinterpret_cast<Tasks *>(data);
|
||
Task task;
|
||
for (int i = 0; i < argc; i++) {
|
||
if (argv[i] == nullptr) continue;
|
||
if (strcmp(columnName[i], "id") == 0) {
|
||
task.id = std::atol(argv[i]);
|
||
} else if (strcmp(columnName[i], "create_time") == 0) {
|
||
task.createTime = std::atol(argv[i]);
|
||
} else if (strcmp(columnName[i], "content") == 0) {
|
||
task.content = argv[i];
|
||
} else if (strcmp(columnName[i], "comment") == 0) {
|
||
task.comment = argv[i];
|
||
} else if (strcmp(columnName[i], "finished") == 0) {
|
||
task.finished = std::atol(argv[i]);
|
||
} else if (strcmp(columnName[i], "parent_id") == 0) {
|
||
task.parentId = std::atol(argv[i]);
|
||
}
|
||
}
|
||
tasks->push_back(task);
|
||
return 0;
|
||
}
|
||
|
||
Tasks Database::tasks() {
|
||
Tasks ret;
|
||
char *error = nullptr;
|
||
if (sqlite3_exec(m_sqlite3, "select * from tasks", selectTaskCallback, &ret, &error) != SQLITE_OK) {
|
||
LOG(error) << "sqlite3_exec() failed: " << error << std::endl;
|
||
sqlite3_free(error);
|
||
}
|
||
std::unordered_map<int, Task *> tasks;
|
||
for (auto iterator = ret.begin(); iterator != ret.end();) {
|
||
if (iterator->parentId >= 0) {
|
||
if (tasks.count(iterator->parentId) > 0) {
|
||
auto parentTask = tasks.at(iterator->parentId);
|
||
parentTask->children.push_back(*iterator);
|
||
tasks.insert({iterator->id, &parentTask->children.back()});
|
||
} else {
|
||
LOG(warning) << "task`s parent id " << iterator->parentId << " not existed.";
|
||
}
|
||
iterator = ret.erase(iterator);
|
||
} else {
|
||
tasks.insert({iterator->id, &(*iterator)});
|
||
++iterator;
|
||
}
|
||
}
|
||
return ret;
|
||
}
|
||
|
||
bool Database::addTask(uint64_t createTime, const std::string &content, const std::string &comment, int parentId,
|
||
bool finished) {
|
||
bool ret = true;
|
||
std::ostringstream oss;
|
||
oss << "INSERT INTO tasks (create_time,content,comment,parent_id,finished) VALUES (" << createTime << ",\""
|
||
<< content << "\",\"" << comment << "\"," << parentId << "," << finished << ");";
|
||
auto sql = oss.str();
|
||
char *error = nullptr;
|
||
int result = sqlite3_exec(m_sqlite3, sql.c_str(), NULL, NULL, &error);
|
||
if (result != SQLITE_OK) {
|
||
LOG(error) << "add task failed: " << error << ", sql: " << sql;
|
||
sqlite3_free(error);
|
||
ret = false;
|
||
}
|
||
return ret;
|
||
}
|
||
|
||
bool Database::removeTask(int id) {
|
||
bool ret = true;
|
||
std::ostringstream oss;
|
||
oss << "DELETE FROM tasks WHERE id = " << id << ";";
|
||
auto sql = oss.str();
|
||
char *error = nullptr;
|
||
int result = sqlite3_exec(m_sqlite3, sql.c_str(), NULL, NULL, &error);
|
||
if (result != SQLITE_OK) {
|
||
LOG(error) << "add task failed: " << error << ", sql: " << sql;
|
||
sqlite3_free(error);
|
||
ret = false;
|
||
}
|
||
return ret;
|
||
}
|
||
|
||
void Database::setTaskFinished(int id, bool finished, uint64_t finishedTime) {
|
||
std::ostringstream oss;
|
||
oss << "UPDATE tasks SET finished = " << finished << ", finished_time = " << finishedTime << " WHERE id = " << id;
|
||
auto sql = oss.str();
|
||
int result = sqlite3_exec(m_sqlite3, sql.c_str(), NULL, NULL, NULL);
|
||
if (result != SQLITE_OK) {
|
||
LOG(error) << "add task failed: " << sqlite3_errmsg(m_sqlite3) << ", sql: " << sql;
|
||
return;
|
||
}
|
||
}
|
||
|
||
void Database::updateTodayVisitCount(const std::string &url, const std::string &visitorUuid) {
|
||
sqlite3_stmt *stmt = nullptr;
|
||
const char *sql_select = "SELECT page_view_count FROM today_visit_count WHERE url = ? AND visitor_uuid = ?";
|
||
if (sqlite3_prepare_v2(m_sqlite3, sql_select, -1, &stmt, 0) != SQLITE_OK) {
|
||
LOG(error) << "Failed to prepare statement: " << sqlite3_errmsg(m_sqlite3);
|
||
return;
|
||
}
|
||
sqlite3_bind_text(stmt, 1, url.c_str(), -1, SQLITE_STATIC);
|
||
sqlite3_bind_text(stmt, 2, visitorUuid.c_str(), -1, SQLITE_STATIC);
|
||
int rc = sqlite3_step(stmt);
|
||
if (rc == SQLITE_ROW) { // 记录存在,执行UPDATE语句
|
||
sqlite3_finalize(stmt); // 释放SELECT语句的资源
|
||
const char *sql_update =
|
||
"UPDATE today_visit_count SET page_view_count = page_view_count + 1 WHERE url = ? AND visitor_uuid = ?";
|
||
if (sqlite3_prepare_v2(m_sqlite3, sql_update, -1, &stmt, 0) != SQLITE_OK) {
|
||
LOG(error) << "Failed to prepare statement: " << sqlite3_errmsg(m_sqlite3);
|
||
return;
|
||
}
|
||
sqlite3_bind_text(stmt, 1, url.c_str(), -1, SQLITE_STATIC);
|
||
sqlite3_bind_text(stmt, 2, visitorUuid.c_str(), -1, SQLITE_STATIC);
|
||
if (sqlite3_step(stmt) != SQLITE_DONE) {
|
||
LOG(error) << "Failed to execute statement: " << sqlite3_errmsg(m_sqlite3);
|
||
sqlite3_finalize(stmt);
|
||
return;
|
||
}
|
||
} else { // 记录不存在,执行INSERT语句
|
||
sqlite3_finalize(stmt); // 释放SELECT语句的资源
|
||
const char *sql_insert = "INSERT INTO today_visit_count (url, visitor_uuid, page_view_count) VALUES (?, ?, 1)";
|
||
if (sqlite3_prepare_v2(m_sqlite3, sql_insert, -1, &stmt, 0) != SQLITE_OK) {
|
||
LOG(error) << "Failed to prepare statement: " << sqlite3_errmsg(m_sqlite3);
|
||
return;
|
||
}
|
||
sqlite3_bind_text(stmt, 1, url.c_str(), -1, SQLITE_STATIC);
|
||
sqlite3_bind_text(stmt, 2, visitorUuid.c_str(), -1, SQLITE_STATIC);
|
||
if (sqlite3_step(stmt) != SQLITE_DONE) { // 执行INSERT语句
|
||
LOG(error) << "Failed to execute statement: " << sqlite3_errmsg(m_sqlite3);
|
||
sqlite3_finalize(stmt);
|
||
return;
|
||
}
|
||
}
|
||
// 释放语句资源
|
||
sqlite3_finalize(stmt);
|
||
}
|
||
|
||
void Database::clearTodayVisitRecord() {
|
||
char *message = nullptr;
|
||
constexpr auto sql = "DELETE FROM today_visit_count";
|
||
int rc = sqlite3_exec(m_sqlite3, sql, nullptr, nullptr, &message);
|
||
if (rc != SQLITE_OK) {
|
||
LOG(error) << "SQL error: " << message;
|
||
sqlite3_free(message);
|
||
}
|
||
}
|
||
|
||
static bool urlExists(sqlite3 *db, const std::string &url) {
|
||
std::string sql = "SELECT 1 FROM visit_count WHERE url = '" + url + "'";
|
||
sqlite3_stmt *stmt;
|
||
int rc = sqlite3_prepare_v2(db, sql.c_str(), -1, &stmt, nullptr);
|
||
if (rc != SQLITE_OK) {
|
||
LOG(error) << "Failed to prepare statement: " << sqlite3_errmsg(db);
|
||
return false;
|
||
}
|
||
rc = sqlite3_step(stmt);
|
||
bool exists = (rc == SQLITE_ROW);
|
||
sqlite3_finalize(stmt);
|
||
return exists;
|
||
}
|
||
|
||
void Database::updateVisitCount() {
|
||
constexpr auto sql = "SELECT url, COUNT(DISTINCT visitor_uuid) AS unique_visitors, SUM(page_view_count) AS "
|
||
"total_page_views FROM today_visit_count GROUP BY url";
|
||
sqlite3_stmt *stmt;
|
||
|
||
int rc = sqlite3_prepare_v2(m_sqlite3, sql, -1, &stmt, nullptr);
|
||
if (rc != SQLITE_OK) {
|
||
LOG(error) << "Failed to prepare statement: " << sqlite3_errmsg(m_sqlite3);
|
||
return;
|
||
}
|
||
|
||
std::unordered_map<std::string, std::pair<int, int>> urlData;
|
||
while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) {
|
||
std::string url = reinterpret_cast<const char *>(sqlite3_column_text(stmt, 0));
|
||
int uniqueVisitors = sqlite3_column_int(stmt, 1);
|
||
int totalPageViews = sqlite3_column_int(stmt, 2);
|
||
urlData[url] = {uniqueVisitors, totalPageViews};
|
||
}
|
||
|
||
sqlite3_finalize(stmt);
|
||
for (const auto &[url, data] : urlData) {
|
||
int uniqueVisitors = data.first;
|
||
int totalPageViews = data.second;
|
||
std::string updateSql;
|
||
if (urlExists(m_sqlite3, url)) {
|
||
updateSql = "UPDATE visit_count SET unique_visitor_count = unique_visitor_count + " +
|
||
std::to_string(uniqueVisitors) + ", page_view_count = page_view_count + " +
|
||
std::to_string(totalPageViews) + " WHERE url = '" + url + "'";
|
||
} else {
|
||
updateSql = "INSERT INTO visit_count (url, unique_visitor_count, page_view_count) VALUES ('" + url + "', " +
|
||
std::to_string(uniqueVisitors) + ", " + std::to_string(totalPageViews) + ")";
|
||
}
|
||
char *message = nullptr;
|
||
LOG(info) << updateSql;
|
||
rc = sqlite3_exec(m_sqlite3, updateSql.c_str(), nullptr, nullptr, &message);
|
||
if (rc != SQLITE_OK) {
|
||
LOG(error) << "SQL error: " << message;
|
||
sqlite3_free(message);
|
||
}
|
||
}
|
||
}
|
||
|
||
bool Database::addHomeBoxItem(const std::string &name, const std::string &location, int cost) {
|
||
bool ret = true;
|
||
std::ostringstream oss;
|
||
oss << "INSERT INTO homebox (name,location,cost) VALUES (\"" << name << "\",\"" << location << "\"," << cost
|
||
<< ");";
|
||
auto sql = oss.str();
|
||
char *error = nullptr;
|
||
int result = sqlite3_exec(m_sqlite3, sql.c_str(), NULL, NULL, &error);
|
||
if (result != SQLITE_OK) {
|
||
LOG(error) << "add task failed: " << error << ", sql: " << sql;
|
||
sqlite3_free(error);
|
||
ret = false;
|
||
}
|
||
return ret;
|
||
}
|
||
|
||
static int selectHomeBoxItemCallback(void *data, int argc, char **argv, char **columnName) {
|
||
auto items = reinterpret_cast<HomeBox::Items *>(data);
|
||
HomeBox::Item item;
|
||
for (int i = 0; i < argc; i++) {
|
||
if (argv[i] == nullptr) continue;
|
||
if (strcmp(columnName[i], "id") == 0) {
|
||
item.id = std::atol(argv[i]);
|
||
} else if (strcmp(columnName[i], "name") == 0) {
|
||
item.name = argv[i];
|
||
} else if (strcmp(columnName[i], "location") == 0) {
|
||
item.location = argv[i];
|
||
} else if (strcmp(columnName[i], "cost") == 0) {
|
||
item.cost = std::atol(argv[i]);
|
||
}
|
||
}
|
||
items->push_back(item);
|
||
return 0;
|
||
}
|
||
|
||
HomeBox::Items Database::homeBoxItems() {
|
||
HomeBox::Items ret;
|
||
char *error = nullptr;
|
||
if (sqlite3_exec(m_sqlite3, "select * from homebox", selectHomeBoxItemCallback, &ret, &error) != SQLITE_OK) {
|
||
LOG(error) << "sqlite3_exec() failed: " << error << std::endl;
|
||
sqlite3_free(error);
|
||
}
|
||
return ret;
|
||
}
|
||
|
||
void Database::initialize() {
|
||
const char *sql =
|
||
"CREATE TABLE IF NOT EXISTS tasks (id INTEGER PRIMARY KEY AUTOINCREMENT, create_time INTEGER NOT NULL, "
|
||
"parent_id INTEGER, content VARCHAR(512) NOT NULL, comment VARCHAR(512) NOT NULL, finished BOLL, finished_time "
|
||
"INTEGER);";
|
||
int result = sqlite3_exec(m_sqlite3, sql, NULL, NULL, NULL);
|
||
if (result != SQLITE_OK) {
|
||
LOG(error) << "Failed to create table: " << sqlite3_errmsg(m_sqlite3);
|
||
return;
|
||
}
|
||
|
||
const char *homeBoxSql = "CREATE TABLE IF NOT EXISTS homebox (id INTEGER PRIMARY KEY AUTOINCREMENT, "
|
||
"name VARCHAR(512) NOT NULL, location VARCHAR(512) NOT NULL, cost INTEGER);";
|
||
result = sqlite3_exec(m_sqlite3, homeBoxSql, NULL, NULL, NULL);
|
||
if (result != SQLITE_OK) {
|
||
LOG(error) << "Failed to create table: " << sqlite3_errmsg(m_sqlite3);
|
||
return;
|
||
}
|
||
|
||
const char *sql_create_visit_count = R"(
|
||
CREATE TABLE IF NOT EXISTS visit_count (
|
||
id INTEGER NOT NULL PRIMARY KEY,
|
||
url TEXT NOT NULL,
|
||
page_view_count INTEGER NOT NULL,
|
||
unique_visitor_count INTEGER NOT NULL
|
||
);
|
||
)";
|
||
|
||
char *message = nullptr;
|
||
result = sqlite3_exec(m_sqlite3, sql_create_visit_count, 0, 0, &message);
|
||
if (result != SQLITE_OK) {
|
||
LOG(error) << "Failed to create table: " << message << std::endl;
|
||
sqlite3_free(message);
|
||
}
|
||
|
||
const char *sql_create_today_visit_count = R"(
|
||
CREATE TABLE IF NOT EXISTS today_visit_count (
|
||
id INTEGER NOT NULL,
|
||
url TEXT NOT NULL,
|
||
visitor_uuid TEXT NOT NULL,
|
||
page_view_count INTEGER NOT NULL,
|
||
PRIMARY KEY (id)
|
||
);
|
||
)";
|
||
result = sqlite3_exec(m_sqlite3, sql_create_today_visit_count, 0, 0, &message);
|
||
if (result != SQLITE_OK) {
|
||
LOG(error) << "Failed to create table: " << message << std::endl;
|
||
sqlite3_free(message);
|
||
}
|
||
}
|
||
|
||
Database::~Database() {
|
||
if (m_sqlite3 != nullptr) {
|
||
sqlite3_close(m_sqlite3);
|
||
m_sqlite3 = nullptr;
|
||
}
|
||
}
|