kopia lustrzana https://gitlab.com/tomaszg/geostat
				
				
				
			
		
			
				
	
	
		
			362 wiersze
		
	
	
		
			12 KiB
		
	
	
	
		
			C++
		
	
	
			
		
		
	
	
			362 wiersze
		
	
	
		
			12 KiB
		
	
	
	
		
			C++
		
	
	
| #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(std::string db_file) {
 | |
| 	int res;
 | |
| 
 | |
| 	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);") ||
 | |
| 		!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";
 | |
| 		throw 1;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| OCdb::~OCdb() {
 | |
| 	sqlite3_close(db);
 | |
| }
 | |
| 
 | |
| bool OCdb::request(std::string req) {
 | |
| 	int res;
 | |
| 
 | |
| 	res = sqlite3_prepare_v2(db, req.c_str(), req.length() + 1, &stmt, NULL);
 | |
| 	if (res != SQLITE_OK) {
 | |
| 		Debug(1) << "Request \"" << req << "\" failed:\n" << sqlite3_errmsg(db);
 | |
| 		return 0;
 | |
| 	}
 | |
| 	res = sqlite3_step(stmt);
 | |
| 	if (res != SQLITE_DONE) {
 | |
| 		Debug(1) << "Request \"" << req << "\" failed:\n" << sqlite3_errmsg(db);
 | |
| 		return 0;
 | |
| 	}
 | |
| 	sqlite3_finalize(stmt);
 | |
| 	return 1;
 | |
| }
 | |
| 
 | |
| bool OCdb::init(std::string dump_path) {
 | |
| 	request("DELETE FROM caches;");
 | |
| 	request("DELETE FROM logs;");
 | |
| 	request("DELETE FROM revision;");
 | |
| 	
 | |
| 	std::ifstream file(dump_path + "index.json");
 | |
| 	json j;
 | |
| 	file >> j;
 | |
| 	revision = j["revision"];
 | |
| 	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>());
 | |
| 	}
 | |
| 	request("COMMIT;");
 | |
| 	return 1;
 | |
| }
 | |
| 
 | |
| bool OCdb::init_part(std::string json_file) {
 | |
| 	Debug(2) << "Processing file: " << json_file << '\n';
 | |
| 	
 | |
| 	std::ifstream file(json_file);
 | |
| 	json j;
 | |
| 	file >> j;
 | |
| 
 | |
| 	Debug(5) << "File: " << json_file << " read.\n";
 | |
| 	for (auto& el : j.items()) {
 | |
| 		parse_item(el.value());
 | |
| 	}
 | |
| 	return 1;
 | |
| }
 | |
| 
 | |
| bool OCdb::update(Okapi& oc) {
 | |
| 	Debug(2) << "Fetching changelog since revision " << revision << ".\n";
 | |
| 	std::string output = oc.get_changelog_json(revision);
 | |
| 	json j = json::parse(output);
 | |
| 
 | |
| 	request("BEGIN TRANSACTION;");
 | |
| 	for (auto& el : j["changelog"].items()) {
 | |
| 		parse_item(el.value());
 | |
| 	}
 | |
| 	set_revision(j["revision"]);
 | |
| 	request("COMMIT;");
 | |
| 	if (j["more"])
 | |
| 		update(oc);
 | |
| 	return 1;
 | |
| }
 | |
| 
 | |
| bool OCdb::parse_item(json j) {
 | |
| 	if (j.count("object_type") > 0 && j["object_type"] == "geocache") {
 | |
| 		if (j.count("change_type") > 0 && j["change_type"] == "replace") {
 | |
| 			Debug(5) << "Inserting/updating cache " << j["object_key"]["code"].get<std::string>() << ".\n";
 | |
| 	// 				if (j["object_key"]["code"] != j["data"]["code"]) {
 | |
| 	// 					Debug(1) << "Code change " << j["object_key"]["code"] << " -> " << j["data"]["code"] <<'\n';
 | |
| 	// 				}
 | |
| 			update_cache(j);
 | |
| 		}
 | |
| 		else if (j.count("change_type") > 0 && j["change_type"] == "delete") {
 | |
| 			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);
 | |
| 		}
 | |
| 		else {
 | |
| 			Debug(1) << "Incorrect change type: " << j["change_type"] << ".\n";
 | |
| 		}
 | |
| 	}
 | |
| 	else if (j.count("object_type") > 0 && j["object_type"] == "log") {
 | |
| 		if (j["change_type"] == "replace") {
 | |
| 			Debug(3) << "Updating log " << j["object_key"]["uuid"] << ".\n";
 | |
| 			update_log(j);
 | |
| 		}
 | |
| 		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);
 | |
| 		}
 | |
| 		else {
 | |
| 			Debug(1) << "Incorrect change type: " << j["change_type"] << ".\n";
 | |
| 		}
 | |
| 	}
 | |
| 	else {
 | |
| 		Debug(1) << "Incorrect object type: " << j["object_type"] << ".\n";
 | |
| 	}
 | |
| 	return 1;
 | |
| }
 | |
| 
 | |
| bool OCdb::update_cache(json j) {
 | |
| // (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())
 | |
| 		return 2;
 | |
| 
 | |
| 	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) {
 | |
| 		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) {
 | |
| 		Debug(1) << "Request \"" << sql << "\" failed:\n" << sqlite3_errmsg(db);
 | |
| 		return 0;
 | |
| 	}
 | |
| 	sqlite3_finalize(stmt);
 | |
| 	return 1;
 | |
| }
 | |
| 
 | |
| bool OCdb::update_log(json j) {
 | |
| // 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())
 | |
| 		return 2;
 | |
| 
 | |
| 	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) {
 | |
| 		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) {
 | |
| 		Debug(1) << "Request \"" << sql << "\" failed:\n" << sqlite3_errmsg(db);
 | |
| 		return 0;
 | |
| 	}
 | |
| 	sqlite3_finalize(stmt);
 | |
| 	return 1;
 | |
| }
 | |
| 
 | |
| 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) {
 | |
| 		Debug(1) << "Request \"" << sql << "\" failed:\n" << sqlite3_errmsg(db);
 | |
| 		return 0;
 | |
| 	}
 | |
| 	res = sqlite3_step(stmt);
 | |
| 	if (res != SQLITE_ROW) {
 | |
| 		Debug(1) << "Request \"" << sql << "\" failed:\n" << sqlite3_errmsg(db);
 | |
| 		return 0;
 | |
| 	}
 | |
| 	revision = sqlite3_column_int(stmt, 0);
 | |
| 	sqlite3_finalize(stmt);
 | |
| 	Debug(2) << "Revision: " << revision << '\n';
 | |
| 
 | |
| 	return 1;
 | |
| }
 | |
| 
 | |
| Caches OCdb::get_user_caches_not_found(std::string uuid) {
 | |
| 	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) {
 | |
| 		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.insert(c);
 | |
| 		res = sqlite3_step(stmt);
 | |
| 	}
 | |
| 
 | |
| 	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(std::string uuid, __attribute__((unused)) int count) {
 | |
| 	int res;
 | |
| 	Caches cc;
 | |
| 	Cache c;
 | |
| //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::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) {
 | |
| 		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)));
 | |
|         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.insert(c);
 | |
| 		res = sqlite3_step(stmt);
 | |
| 	}
 | |
| 
 | |
| 	if (res != SQLITE_DONE)	{
 | |
| 		Debug(1) << "Request \"" << sql << "\" failed:\n" << sqlite3_errmsg(db);
 | |
| 		throw 0;
 | |
| 	}
 | |
| // 	sqlite3_finalize(stmt);
 | |
| 	return cc;
 | |
| }
 | |
| 
 | |
| int OCdb::get_revision() {
 | |
| 	return revision;
 | |
| }
 | |
| 
 | |
| void OCdb::set_revision(int rev) {
 | |
| 	revision = rev;
 | |
| 	request("UPDATE revision SET revision = " + std::to_string(revision) + ';');
 | |
| }
 |