2019-09-30 00:34:56 +00:00
# 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 ) {
2020-01-25 15:33:57 +00:00
int res = 0 ;
2019-09-30 00:34:56 +00:00
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); " ) | |
2019-09-30 00:34:56 +00:00
! request ( " CREATE TABLE IF NOT EXISTS logs (uuid TEXT PRIMARY KEY, cache_code TEXT, date TEXT, user TEXT, type TEXT); " ) )
throw 1 ;
2019-11-04 20:40:24 +00:00
if ( ! read_revision ( ) ) {
Debug ( 1 ) < < " Error reading database revision, database may be corrupt or empty. \n " ;
}
2019-09-30 00:34:56 +00:00
}
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 ) {
2019-11-04 20:44:08 +00:00
Debug ( 1 ) < < " Request \" " < < req < < " \" failed: \n "
< < sqlite3_errmsg ( db ) ;
2019-09-30 00:34:56 +00:00
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 ) ;
2019-09-30 00:34:56 +00:00
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; " ) ;
2019-11-04 20:44:08 +00:00
2019-09-30 00:34:56 +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; " ) ;
2019-09-30 00:34:56 +00:00
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; " ) ;
2019-09-30 00:34:56 +00:00
return 1 ;
}
bool OCdb : : init_part ( 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
2019-09-30 00:34:56 +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 " ;
2019-09-30 00:34:56 +00:00
for ( auto & el : j . items ( ) ) {
parse_item ( el . value ( ) ) ;
}
return 1 ;
}
bool OCdb : : update ( Okapi & oc ) {
2020-01-25 15:33:57 +00:00
if ( revision = = 0 ) {
Debug ( 1 ) < < " Cannot update database, you need to init it first with a full dump. \n " ;
throw 1 ;
}
2019-09-30 23:49:25 +00:00
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; " ) ;
2019-09-30 23:49:25 +00:00
for ( auto & el : j [ " changelog " ] . items ( ) ) {
parse_item ( el . value ( ) ) ;
}
2019-10-06 00:46:07 +00:00
set_revision ( j [ " revision " ] ) ;
request ( " COMMIT; " ) ;
2019-09-30 23:49:25 +00:00
if ( j [ " more " ] )
update ( oc ) ;
2019-09-30 00:34:56 +00:00
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 " ) {
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';
// }
2019-09-30 00:34:56 +00:00
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 " ;
2019-09-30 00:34:56 +00:00
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 {
2019-09-30 00:34:56 +00:00
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 " ) {
2019-09-30 00:34:56 +00:00
if ( j [ " change_type " ] = = " replace " ) {
2019-10-01 15:11:38 +00:00
Debug ( 3 ) < < " Updating log " < < j [ " object_key " ] [ " uuid " ] < < " . \n " ;
2019-09-30 00:34:56 +00:00
update_log ( j ) ;
2019-11-04 20:44:08 +00:00
} else if ( j . count ( " change_type " ) > 0 & & j [ " change_type " ] = = " delete " ) {
2019-09-30 00:34:56 +00:00
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 {
2019-09-30 00:34:56 +00:00
Debug ( 1 ) < < " Incorrect change type: " < < j [ " change_type " ] < < " . \n " ;
}
2019-11-04 20:44:08 +00:00
} else {
2019-09-30 00:34:56 +00:00
Debug ( 1 ) < < " Incorrect object type: " < < j [ " object_type " ] < < " . \n " ;
}
return 1 ;
}
bool OCdb : : update_cache ( 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)
2019-09-30 00:34:56 +00:00
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 > ( ) ;
2019-10-01 15:29:17 +00:00
if ( fields . empty ( ) & & fields2 . empty ( ) )
return 2 ;
2019-09-30 00:34:56 +00:00
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 + " ', " ;
2019-10-06 00:58:24 +00:00
for ( __attribute__ ( ( unused ) ) auto & i : fields )
2019-09-30 00:34:56 +00:00
sql + = " ?, " ;
2019-10-06 00:58:24 +00:00
for ( __attribute__ ( ( unused ) ) auto & i : fields2 )
2019-09-30 00:34:56 +00:00
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 ) ;
2019-09-30 00:34:56 +00:00
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 ) ;
2019-09-30 00:34:56 +00:00
return 0 ;
}
sqlite3_finalize ( stmt ) ;
return 1 ;
}
bool OCdb : : update_log ( json j ) {
2019-11-04 20:44:08 +00:00
// logs (uuid TEXT PRIMARY KEY, cache_code TEXT, date TEXT, user TEXT, type TEXT);"))
2019-09-30 00:34:56 +00:00
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 > ( ) ;
2019-10-01 21:57:04 +00:00
if ( j [ " data " ] . count ( " type " ) > 0 & & ! j [ " data " ] [ " type " ] . is_null ( ) )
fields [ " type " ] = j [ " data " ] [ " type " ] . get < std : : string > ( ) ;
2019-09-30 00:34:56 +00:00
2019-10-01 15:29:17 +00:00
if ( fields . empty ( ) )
return 2 ;
2019-09-30 00:34:56 +00:00
std : : string sql = " INSERT INTO logs (uuid, " ;
for ( auto & i : fields )
sql + = i . first + ' , ' ;
sql . pop_back ( ) ;
sql + = " ) VALUES (' " + uuid + " ', " ;
2019-10-06 00:58:24 +00:00
for ( __attribute__ ( ( unused ) ) auto & i : fields )
2019-09-30 00:34:56 +00:00
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 ) ;
2019-09-30 00:34:56 +00:00
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 ) ;
2019-09-30 00:34:56 +00:00
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
2019-10-04 00:30:17 +00:00
Caches OCdb : : get_user_caches_not_found ( std : : string uuid ) {
int res ;
Caches cc ;
Cache c ;
2019-10-06 11:08:37 +00:00
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 = ?); " ;
2019-10-04 00:30:17 +00:00
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-10-04 00:30:17 +00:00
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 ) ;
}
2019-11-04 20:44:08 +00:00
if ( res ! = SQLITE_DONE ) {
Debug ( 1 ) < < " Request \" " < < sql < < " \" failed: \n "
< < sqlite3_errmsg ( db ) ;
2019-10-04 00:30:17 +00:00
throw 0 ;
}
sqlite3_finalize ( stmt ) ;
return cc ;
}
2019-10-06 00:58:24 +00:00
Caches OCdb : : get_user_caches ( std : : string uuid , __attribute__ ( ( unused ) ) int count ) {
2019-10-04 00:30:17 +00:00
int res ;
2019-10-03 22:43:39 +00:00
Caches cc ;
2019-10-04 00:30:17 +00:00
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 = ?); " ;
2019-10-04 00:30:17 +00:00
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-10-04 00:30:17 +00:00
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
2019-10-04 00:30:17 +00:00
cc . insert ( 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 ) ;
2019-10-04 00:30:17 +00:00
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 ( ) {
return revision ;
}
void OCdb : : set_revision ( int rev ) {
revision = rev ;
request ( " UPDATE revision SET revision = " + std : : to_string ( revision ) + ' ; ' ) ;
}