geostat/ocdb.cpp

371 wiersze
12 KiB
C++
Czysty Zwykły widok Historia

#include "ocdb.h"
#include "debug.h"
#include "okapi.h"
#include <string>
#include <fstream>
#include <map>
#include <sqlite3.h>
#include <nlohmann/json.hpp>
using json = nlohmann::json;
// using namespace std::literals::string_literals;
OCdb::OCdb(const std::string& db_file) {
int res = 0;
res = sqlite3_open(db_file.c_str(), &db);
if (res != SQLITE_OK) {
Debug(1) << sqlite3_errmsg(db);
throw 1;
}
if (!request("CREATE TABLE IF NOT EXISTS revision (revision INTEGER PRIMARY KEY);") ||
2019-11-04 20:44:08 +00:00
!request("CREATE TABLE IF NOT EXISTS caches (code TEXT PRIMARY KEY, name TEXT, location TEXT, type TEXT, status TEXT, size TEXT, difficulty REAL, terrain REAL, country TEXT, region TEXT, owner TEXT);") ||
!request("CREATE TABLE IF NOT EXISTS logs (uuid TEXT PRIMARY KEY, cache_code TEXT, date TEXT, user TEXT, type TEXT);"))
throw 1;
if (!read_revision()) {
Debug(1) << "Error reading database revision, database may be corrupt or empty.\n";
}
}
OCdb::~OCdb() {
sqlite3_close(db);
}
bool OCdb::request(const std::string& req) const {
int res;
res = sqlite3_prepare_v2(db, req.c_str(), req.length() + 1, &stmt, NULL);
if (res != SQLITE_OK) {
2019-11-04 20:44:08 +00:00
Debug(1) << "Request \"" << req << "\" failed:\n"
<< sqlite3_errmsg(db);
return 0;
}
res = sqlite3_step(stmt);
if (res != SQLITE_DONE) {
2019-11-04 20:44:08 +00:00
Debug(1) << "Request \"" << req << "\" failed:\n"
<< sqlite3_errmsg(db);
return 0;
}
sqlite3_finalize(stmt);
return 1;
}
bool OCdb::init(const std::string& dump_path) {
request("DELETE FROM caches;");
request("DELETE FROM logs;");
request("DELETE FROM revision;");
2019-11-04 20:44:08 +00:00
std::ifstream file(dump_path + "index.json");
json j;
file >> j;
revision = j["revision"];
2019-10-06 00:44:58 +00:00
request("BEGIN TRANSACTION;");
request("INSERT INTO revision VALUES (" + std::to_string(revision) + ");");
for (auto& el : j["data_files"].items()) {
init_part(dump_path + el.value().get<std::string>());
}
2019-10-06 00:46:07 +00:00
request("COMMIT;");
return 1;
}
bool OCdb::init_part(const std::string& json_file) {
2019-10-01 15:11:38 +00:00
Debug(2) << "Processing file: " << json_file << '\n';
2019-11-04 20:44:08 +00:00
std::ifstream file(json_file);
json j;
file >> j;
2019-10-01 15:11:38 +00:00
Debug(5) << "File: " << json_file << " read.\n";
for (auto& el : j.items()) {
parse_item(el.value());
}
return 1;
}
bool OCdb::update(const Okapi& oc) {
if (revision == 0) {
Debug(1) << "Cannot update database, you need to init it first with a full dump.\n";
throw 1;
}
Debug(2) << "Fetching changelog since revision " << revision << ".\n";
std::string output = oc.get_changelog_json(revision);
json j = json::parse(output);
2019-10-06 00:44:58 +00:00
request("BEGIN TRANSACTION;");
for (auto& el : j["changelog"].items()) {
parse_item(el.value());
}
2019-10-06 00:46:07 +00:00
set_revision(j["revision"]);
request("COMMIT;");
if (j["more"])
update(oc);
return 1;
}
bool OCdb::parse_item(const json& j) {
if (j.count("object_type") > 0 && j["object_type"] == "geocache") {
if (j.count("change_type") > 0 && j["change_type"] == "replace") {
2019-10-01 15:11:38 +00:00
Debug(5) << "Inserting/updating cache " << j["object_key"]["code"].get<std::string>() << ".\n";
2019-11-04 20:44:08 +00:00
// if (j["object_key"]["code"] != j["data"]["code"]) {
// Debug(1) << "Code change " << j["object_key"]["code"] << " -> " << j["data"]["code"] <<'\n';
// }
update_cache(j);
2019-11-04 20:44:08 +00:00
} else if (j.count("change_type") > 0 && j["change_type"] == "delete") {
2019-10-01 15:11:38 +00:00
Debug(2) << "Deleting cache " << j["object_key"]["code"].get<std::string>() << ".\n";
std::string sql = "DELETE FROM caches WHERE code='" + j["object_key"]["code"].get<std::string>() + "';";
request(sql);
2019-11-04 20:44:08 +00:00
} else {
Debug(1) << "Incorrect change type: " << j["change_type"] << ".\n";
}
2019-11-04 20:44:08 +00:00
} else if (j.count("object_type") > 0 && j["object_type"] == "log") {
if (j["change_type"] == "replace") {
2019-10-01 15:11:38 +00:00
Debug(3) << "Updating log " << j["object_key"]["uuid"] << ".\n";
update_log(j);
2019-11-04 20:44:08 +00:00
} else if (j.count("change_type") > 0 && j["change_type"] == "delete") {
Debug(2) << "Deleting log " << j["object_key"]["uuid"] << ".\n";
std::string sql = "DELETE FROM logs WHERE uuid='" + j["object_key"]["uuid"].get<std::string>() + "';";
request(sql);
2019-11-04 20:44:08 +00:00
} else {
Debug(1) << "Incorrect change type: " << j["change_type"] << ".\n";
}
2019-11-04 20:44:08 +00:00
} else {
Debug(1) << "Incorrect object type: " << j["object_type"] << ".\n";
}
return 1;
}
bool OCdb::update_cache(const json& j) {
2019-11-04 20:44:08 +00:00
// (code TEXT PRIMARY KEY, name TEXT, location TEXT, type TEXT, status TEXT, size TEXT, difficulty REAL, terrain REAL, country TEXT, region TEXT, owner TEXT)
std::map<std::string, std::string> fields;
std::map<std::string, float> fields2;
int res;
std::string code = j["object_key"]["code"].get<std::string>();
if (j["data"]["names"].count("pl") > 0 && !j["data"]["names"]["pl"].is_null())
fields["name"] = j["data"]["names"]["pl"].get<std::string>();
else if (j["data"]["names"].count("en") > 0 && !j["data"]["names"]["en"].is_null())
fields["name"] = j["data"]["names"]["en"].get<std::string>();
if (j["data"].count("location") > 0 && !j["data"]["location"].is_null())
fields["location"] = j["data"]["location"].get<std::string>();
if (j["data"].count("type") > 0 && !j["data"]["type"].is_null())
fields["type"] = j["data"]["type"].get<std::string>();
if (j["data"].count("status") > 0 && !j["data"]["status"].is_null())
fields["status"] = j["data"]["status"].get<std::string>();
if (j["data"].count("size2") > 0 && !j["data"]["size2"].is_null())
fields["size"] = j["data"]["size2"].get<std::string>();
if (j["data"].count("difficulty") > 0 && !j["data"]["difficulty"].is_null())
fields2["difficulty"] = j["data"]["difficulty"].get<float>();
if (j["data"].count("terrain") > 0 && !j["data"]["terrain"].is_null())
fields2["terrain"] = j["data"]["terrain"].get<float>();
if (j["data"].count("country") > 0 && !j["data"]["country"].is_null())
fields["country"] = j["data"]["country"].get<std::string>();
if (j["data"].count("region") > 0 && !j["data"]["region"].is_null())
fields["region"] = j["data"]["region"].get<std::string>();
else if (j["data"].count("state") > 0 && !j["data"]["state"].is_null())
fields["region"] = j["data"]["state"].get<std::string>();
if (j["data"]["owner"].count("uuid") > 0 && !j["data"]["owner"]["uuid"].is_null())
fields["owner"] = j["data"]["owner"]["uuid"].get<std::string>();
if (fields.empty() && fields2.empty())
2020-02-11 16:34:52 +00:00
return 1;
std::string sql = "INSERT INTO caches (code,";
for (auto& i : fields)
sql += i.first + ',';
for (auto& i : fields2)
sql += i.first + ',';
sql.pop_back();
sql += ") VALUES ('" + code + "',";
for (__attribute__((unused)) auto& i : fields)
sql += "?,";
for (__attribute__((unused)) auto& i : fields2)
sql += "?,";
sql.pop_back();
sql += ") ON CONFLICT(code) DO UPDATE SET ";
for (auto& i : fields)
sql += i.first + "=excluded." + i.first + ',';
for (auto& i : fields2)
sql += i.first + "=excluded." + i.first + ',';
sql.pop_back();
sql += ';';
res = sqlite3_prepare_v2(db, sql.c_str(), sql.length() + 1, &stmt, NULL);
if (res != SQLITE_OK) {
2019-11-04 20:44:08 +00:00
Debug(1) << "Request \"" << sql << "\" failed:\n"
<< sqlite3_errmsg(db);
return 0;
}
int n = 1;
for (auto& i : fields) {
sqlite3_bind_text(stmt, n++, i.second.c_str(), -1, nullptr);
}
for (auto& i : fields2) {
sqlite3_bind_double(stmt, n++, i.second);
}
res = sqlite3_step(stmt);
if (res != SQLITE_DONE) {
2019-11-04 20:44:08 +00:00
Debug(1) << "Request \"" << sql << "\" failed:\n"
<< sqlite3_errmsg(db);
return 0;
}
sqlite3_finalize(stmt);
return 1;
}
bool OCdb::update_log(const json& j) {
2019-11-04 20:44:08 +00:00
// logs (uuid TEXT PRIMARY KEY, cache_code TEXT, date TEXT, user TEXT, type TEXT);"))
std::map<std::string, std::string> fields;
int res;
std::string uuid = j["object_key"]["uuid"].get<std::string>();
if (j["data"].count("cache_code") > 0 && !j["data"]["cache_code"].is_null())
fields["cache_code"] = j["data"]["cache_code"].get<std::string>();
if (j["data"].count("date") > 0 && !j["data"]["date"].is_null())
fields["date"] = j["data"]["date"].get<std::string>();
if (j["data"]["user"].count("uuid") > 0 && !j["data"]["user"]["uuid"].is_null())
fields["user"] = j["data"]["user"]["uuid"].get<std::string>();
if (j["data"].count("type") > 0 && !j["data"]["type"].is_null())
fields["type"] = j["data"]["type"].get<std::string>();
if (fields.empty())
2020-02-11 16:34:52 +00:00
return 1;
std::string sql = "INSERT INTO logs (uuid,";
for (auto& i : fields)
sql += i.first + ',';
sql.pop_back();
sql += ") VALUES ('" + uuid + "',";
for (__attribute__((unused)) auto& i : fields)
sql += "?,";
sql.pop_back();
sql += ") ON CONFLICT(uuid) DO UPDATE SET ";
for (auto& i : fields)
sql += i.first + "=excluded." + i.first + ',';
sql.pop_back();
sql += ';';
res = sqlite3_prepare_v2(db, sql.c_str(), sql.length() + 1, &stmt, NULL);
if (res != SQLITE_OK) {
2019-11-04 20:44:08 +00:00
Debug(1) << "Request \"" << sql << "\" failed:\n"
<< sqlite3_errmsg(db);
return 0;
}
int n = 1;
for (auto& i : fields) {
sqlite3_bind_text(stmt, n++, i.second.c_str(), -1, nullptr);
}
res = sqlite3_step(stmt);
if (res != SQLITE_DONE) {
2019-11-04 20:44:08 +00:00
Debug(1) << "Request \"" << sql << "\" failed:\n"
<< sqlite3_errmsg(db);
return 0;
}
sqlite3_finalize(stmt);
return 1;
}
2019-09-30 23:48:49 +00:00
bool OCdb::read_revision() {
int res;
std::string sql = "SELECT revision FROM revision;";
res = sqlite3_prepare_v2(db, sql.c_str(), sql.length() + 1, &stmt, NULL);
if (res != SQLITE_OK) {
2019-11-04 20:44:08 +00:00
Debug(1) << "Request \"" << sql << "\" failed:\n"
<< sqlite3_errmsg(db);
2019-09-30 23:48:49 +00:00
return 0;
}
res = sqlite3_step(stmt);
if (res != SQLITE_ROW) {
2019-11-04 20:44:08 +00:00
Debug(1) << "Request \"" << sql << "\" failed:\n"
<< sqlite3_errmsg(db);
2019-09-30 23:48:49 +00:00
return 0;
}
revision = sqlite3_column_int(stmt, 0);
sqlite3_finalize(stmt);
Debug(2) << "Revision: " << revision << '\n';
return 1;
}
2019-10-03 22:43:39 +00:00
Caches OCdb::get_user_caches_not_found(const std::string& uuid) const {
int res;
Caches cc;
Cache c;
std::string sql = "SELECT code, location FROM caches WHERE status = 'Available' AND NOT EXISTS (SELECT cache_code FROM logs WHERE cache_code = code AND type = 'Found it' and user = ?);";
res = sqlite3_prepare_v2(db, sql.c_str(), sql.length() + 1, &stmt, NULL);
if (res != SQLITE_OK) {
2019-11-04 20:44:08 +00:00
Debug(1) << "Request \"" << sql << "\" failed:\n"
<< sqlite3_errmsg(db);
throw 0;
}
sqlite3_bind_text(stmt, 1, uuid.c_str(), -1, nullptr);
res = sqlite3_step(stmt);
while (res == SQLITE_ROW) {
c.code = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 0));
c.pos = get_lat_lon(reinterpret_cast<const char*>(sqlite3_column_text(stmt, 1)));
cc.push_back(c);
res = sqlite3_step(stmt);
}
2019-11-04 20:44:08 +00:00
if (res != SQLITE_DONE) {
Debug(1) << "Request \"" << sql << "\" failed:\n"
<< sqlite3_errmsg(db);
throw 0;
}
sqlite3_finalize(stmt);
return cc;
}
Caches OCdb::get_user_caches(const std::string& uuid, __attribute__((unused)) int count) const {
int res;
2019-10-03 22:43:39 +00:00
Caches cc;
Cache c;
2019-11-04 20:44:08 +00:00
//code TEXT PRIMARY KEY, name TEXT, location TEXT, type TEXT, status TEXT, size TEXT, difficulty REAL, terrain REAL, country TEXT, region TEXT, owner TEXT)
2019-11-04 10:09:47 +00:00
std::string sql = "SELECT code, location, type, size, difficulty, terrain, country, region, owner FROM caches WHERE EXISTS (SELECT cache_code FROM logs WHERE cache_code = code AND type = 'Found it' and user = ?);";
res = sqlite3_prepare_v2(db, sql.c_str(), sql.length() + 1, &stmt, NULL);
if (res != SQLITE_OK) {
2019-11-04 20:44:08 +00:00
Debug(1) << "Request \"" << sql << "\" failed:\n"
<< sqlite3_errmsg(db);
throw 0;
}
sqlite3_bind_text(stmt, 1, uuid.c_str(), -1, nullptr);
res = sqlite3_step(stmt);
while (res == SQLITE_ROW) {
c.code = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 0));
c.pos = get_lat_lon(reinterpret_cast<const char*>(sqlite3_column_text(stmt, 1)));
2019-11-04 20:44:08 +00:00
c.type = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 2));
c.size = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 3));
c.diff = sqlite3_column_double(stmt, 4);
c.terr = sqlite3_column_double(stmt, 5);
//c.country = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 6));
c.region = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 7));
c.owner_uuid = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 8)); // TODO: we don't know owner's nick
cc.push_back(c);
res = sqlite3_step(stmt);
}
2019-11-04 20:44:08 +00:00
if (res != SQLITE_DONE) {
Debug(1) << "Request \"" << sql << "\" failed:\n"
<< sqlite3_errmsg(db);
throw 0;
}
2019-11-04 20:44:08 +00:00
//sqlite3_finalize(stmt);
2019-10-03 22:43:39 +00:00
return cc;
}
2019-10-06 00:46:07 +00:00
int OCdb::get_revision() const {
2019-10-06 00:46:07 +00:00
return revision;
}
void OCdb::set_revision(int rev) {
revision = rev;
request("UPDATE revision SET revision = " + std::to_string(revision) + ';');
}