mirror of
https://github.com/deltachat/deltachat-core.git
synced 2025-10-05 10:39:27 +02:00
788 lines
28 KiB
C
788 lines
28 KiB
C
/*******************************************************************************
|
|
*
|
|
* Delta Chat Core
|
|
* Copyright (C) 2017 Björn Petersen
|
|
* Contact: r10s@b44t.com, http://b44t.com
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify it under
|
|
* the terms of the GNU General Public License as published by the Free Software
|
|
* Foundation, either version 3 of the License, or (at your option) any later
|
|
* version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
* details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along with
|
|
* this program. If not, see http://www.gnu.org/licenses/ .
|
|
*
|
|
******************************************************************************/
|
|
|
|
|
|
#include "dc_context.h"
|
|
#include "dc_apeerstate.h"
|
|
|
|
|
|
/* This class wraps around SQLite. Some hints to the underlying database:
|
|
|
|
- `PRAGMA cache_size` and `PRAGMA page_size`: As we save BLOBs in external
|
|
files, caching is not that important; we rely on the system defaults here
|
|
(normally 2 MB cache, 1 KB page size on sqlite < 3.12.0, 4 KB for newer
|
|
versions)
|
|
|
|
- We use `sqlite3_last_insert_rowid()` to find out created records - for this
|
|
purpose, the primary ID has to be marked using `INTEGER PRIMARY KEY`, see
|
|
https://www.sqlite.org/c3ref/last_insert_rowid.html
|
|
|
|
- Some words to the "param" fields: These fields contains a string with
|
|
additonal, named parameters which must not be accessed by a search and/or
|
|
are very seldomly used. Moreover, this allows smart minor database updates. */
|
|
|
|
|
|
/*******************************************************************************
|
|
* Tools
|
|
******************************************************************************/
|
|
|
|
|
|
void mrsqlite3_log_error(mrsqlite3_t* ths, const char* msg_format, ...)
|
|
{
|
|
char* msg;
|
|
const char* notSetUp = "SQLite object not set up.";
|
|
va_list va;
|
|
|
|
va_start(va, msg_format);
|
|
msg = sqlite3_vmprintf(msg_format, va); if( msg == NULL ) { dc_log_error(ths->m_mailbox, 0, "Bad log format string \"%s\".", msg_format); }
|
|
dc_log_error(ths->m_mailbox, 0, "%s SQLite says: %s", msg, ths->m_cobj? sqlite3_errmsg(ths->m_cobj) : notSetUp);
|
|
sqlite3_free(msg);
|
|
va_end(va);
|
|
}
|
|
|
|
|
|
sqlite3_stmt* mrsqlite3_prepare_v2_(mrsqlite3_t* ths, const char* querystr)
|
|
{
|
|
sqlite3_stmt* retStmt = NULL;
|
|
|
|
if( ths == NULL || querystr == NULL || ths->m_cobj == NULL ) {
|
|
return NULL;
|
|
}
|
|
|
|
if( sqlite3_prepare_v2(ths->m_cobj,
|
|
querystr, -1 /*read `sql` up to the first null-byte*/,
|
|
&retStmt,
|
|
NULL /*tail not interesting, we use only single statements*/) != SQLITE_OK )
|
|
{
|
|
mrsqlite3_log_error(ths, "Query failed: %s", querystr);
|
|
return NULL;
|
|
}
|
|
|
|
/* success - the result must be freed using sqlite3_finalize() */
|
|
return retStmt;
|
|
}
|
|
|
|
|
|
int mrsqlite3_execute__(mrsqlite3_t* ths, const char* querystr)
|
|
{
|
|
int success = 0;
|
|
sqlite3_stmt* stmt = NULL;
|
|
int sqlState;
|
|
|
|
stmt = mrsqlite3_prepare_v2_(ths, querystr);
|
|
if( stmt == NULL ) {
|
|
goto cleanup;
|
|
}
|
|
|
|
sqlState = sqlite3_step(stmt);
|
|
if( sqlState != SQLITE_DONE && sqlState != SQLITE_ROW ) {
|
|
mrsqlite3_log_error(ths, "Cannot excecute \"%s\".", querystr);
|
|
goto cleanup;
|
|
}
|
|
|
|
success = 1;
|
|
|
|
cleanup:
|
|
if( stmt ) {
|
|
sqlite3_finalize(stmt);
|
|
}
|
|
return success;
|
|
}
|
|
|
|
|
|
/*******************************************************************************
|
|
* Main interface
|
|
******************************************************************************/
|
|
|
|
|
|
mrsqlite3_t* mrsqlite3_new(mrmailbox_t* mailbox)
|
|
{
|
|
mrsqlite3_t* ths = NULL;
|
|
int i;
|
|
|
|
if( (ths=calloc(1, sizeof(mrsqlite3_t)))==NULL ) {
|
|
exit(24); /* cannot allocate little memory, unrecoverable error */
|
|
}
|
|
|
|
ths->m_mailbox = mailbox;
|
|
|
|
for( i = 0; i < PREDEFINED_CNT; i++ ) {
|
|
ths->m_pd[i] = NULL;
|
|
}
|
|
|
|
pthread_mutex_init(&ths->m_critical_, NULL);
|
|
|
|
return ths;
|
|
}
|
|
|
|
|
|
void mrsqlite3_unref(mrsqlite3_t* ths)
|
|
{
|
|
if( ths == NULL ) {
|
|
return;
|
|
}
|
|
|
|
if( ths->m_cobj ) {
|
|
pthread_mutex_lock(&ths->m_critical_); /* as a very exeception, we do the locking inside the mrsqlite3-class - normally, this should be done by the caller! */
|
|
mrsqlite3_close__(ths);
|
|
pthread_mutex_unlock(&ths->m_critical_);
|
|
}
|
|
|
|
pthread_mutex_destroy(&ths->m_critical_);
|
|
free(ths);
|
|
}
|
|
|
|
|
|
int mrsqlite3_open__(mrsqlite3_t* ths, const char* dbfile, int flags)
|
|
{
|
|
if( ths == NULL || dbfile == NULL ) {
|
|
goto cleanup;
|
|
}
|
|
|
|
if( sqlite3_threadsafe() == 0 ) {
|
|
dc_log_error(ths->m_mailbox, 0, "Sqlite3 compiled thread-unsafe; this is not supported.");
|
|
goto cleanup;
|
|
}
|
|
|
|
if( ths->m_cobj ) {
|
|
dc_log_error(ths->m_mailbox, 0, "Cannot open, database \"%s\" already opened.", dbfile);
|
|
goto cleanup;
|
|
}
|
|
|
|
// Force serialized mode (SQLITE_OPEN_FULLMUTEX) explicitly.
|
|
// So, most of the explicit lock/unlocks on mrsqlite3_t object are no longer needed.
|
|
// However, locking is _also_ used for mrmailbox_t which _is_ still needed, so, we
|
|
// should remove locks only if we're really sure.
|
|
if( sqlite3_open_v2(dbfile, &ths->m_cobj,
|
|
SQLITE_OPEN_FULLMUTEX | ((flags&MR_OPEN_READONLY)? SQLITE_OPEN_READONLY : (SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE)),
|
|
NULL) != SQLITE_OK ) {
|
|
mrsqlite3_log_error(ths, "Cannot open database \"%s\".", dbfile); /* ususally, even for errors, the pointer is set up (if not, this is also checked by mrsqlite3_log_error()) */
|
|
goto cleanup;
|
|
}
|
|
|
|
// Only one process can make changes to the database at one time.
|
|
// busy_timeout defines, that if a seconds process wants write access, this second process will wait some milliseconds
|
|
// and try over until it gets write access or the given timeout is elapsed.
|
|
// If the second process does not get write access within the given timeout, sqlite3_step() will return the error SQLITE_BUSY.
|
|
// (without a busy_timeout, sqlite3_step() would return SQLITE_BUSY at once)
|
|
sqlite3_busy_timeout(ths->m_cobj, 10*1000);
|
|
|
|
if( !(flags&MR_OPEN_READONLY) )
|
|
{
|
|
int dbversion_before_update = 0;
|
|
|
|
/* Init tables to dbversion=0 */
|
|
if( !mrsqlite3_table_exists__(ths, "config") )
|
|
{
|
|
dc_log_info(ths->m_mailbox, 0, "First time init: creating tables in \"%s\".", dbfile);
|
|
|
|
mrsqlite3_execute__(ths, "CREATE TABLE config (id INTEGER PRIMARY KEY, keyname TEXT, value TEXT);");
|
|
mrsqlite3_execute__(ths, "CREATE INDEX config_index1 ON config (keyname);");
|
|
|
|
mrsqlite3_execute__(ths, "CREATE TABLE contacts (id INTEGER PRIMARY KEY,"
|
|
" name TEXT DEFAULT '',"
|
|
" addr TEXT DEFAULT '' COLLATE NOCASE,"
|
|
" origin INTEGER DEFAULT 0,"
|
|
" blocked INTEGER DEFAULT 0,"
|
|
" last_seen INTEGER DEFAULT 0," /* last_seen is for future use */
|
|
" param TEXT DEFAULT '');"); /* param is for future use, eg. for the status */
|
|
mrsqlite3_execute__(ths, "CREATE INDEX contacts_index1 ON contacts (name COLLATE NOCASE);"); /* needed for query contacts */
|
|
mrsqlite3_execute__(ths, "CREATE INDEX contacts_index2 ON contacts (addr COLLATE NOCASE);"); /* needed for query and on receiving mails */
|
|
mrsqlite3_execute__(ths, "INSERT INTO contacts (id,name,origin) VALUES (1,'self',262144), (2,'device',262144), (3,'rsvd',262144), (4,'rsvd',262144), (5,'rsvd',262144), (6,'rsvd',262144), (7,'rsvd',262144), (8,'rsvd',262144), (9,'rsvd',262144);");
|
|
#if !defined(MR_ORIGIN_INTERNAL) || MR_ORIGIN_INTERNAL!=262144
|
|
#error
|
|
#endif
|
|
|
|
mrsqlite3_execute__(ths, "CREATE TABLE chats (id INTEGER PRIMARY KEY, "
|
|
" type INTEGER DEFAULT 0,"
|
|
" name TEXT DEFAULT '',"
|
|
" draft_timestamp INTEGER DEFAULT 0,"
|
|
" draft_txt TEXT DEFAULT '',"
|
|
" blocked INTEGER DEFAULT 0,"
|
|
" grpid TEXT DEFAULT ''," /* contacts-global unique group-ID, see mrchat.c for details */
|
|
" param TEXT DEFAULT '');");
|
|
mrsqlite3_execute__(ths, "CREATE INDEX chats_index1 ON chats (grpid);");
|
|
mrsqlite3_execute__(ths, "CREATE TABLE chats_contacts (chat_id INTEGER, contact_id INTEGER);");
|
|
mrsqlite3_execute__(ths, "CREATE INDEX chats_contacts_index1 ON chats_contacts (chat_id);");
|
|
mrsqlite3_execute__(ths, "INSERT INTO chats (id,type,name) VALUES (1,120,'deaddrop'), (2,120,'rsvd'), (3,120,'trash'), (4,120,'msgs_in_creation'), (5,120,'starred'), (6,120,'archivedlink'), (7,100,'rsvd'), (8,100,'rsvd'), (9,100,'rsvd');");
|
|
#if !defined(MR_CHAT_TYPE_SINGLE) || MR_CHAT_TYPE_SINGLE!=100 || MR_CHAT_TYPE_GROUP!=120 || \
|
|
MR_CHAT_ID_DEADDROP!=1 || MR_CHAT_ID_TRASH!=3 || \
|
|
MR_CHAT_ID_MSGS_IN_CREATION!=4 || MR_CHAT_ID_STARRED!=5 || MR_CHAT_ID_ARCHIVED_LINK!=6 || \
|
|
MR_CHAT_NOT_BLOCKED!=0 || MR_CHAT_MANUALLY_BLOCKED!=1 || MR_CHAT_DEADDROP_BLOCKED!=2
|
|
#error
|
|
#endif
|
|
|
|
mrsqlite3_execute__(ths, "CREATE TABLE msgs (id INTEGER PRIMARY KEY,"
|
|
" rfc724_mid TEXT DEFAULT ''," /* forever-global-unique Message-ID-string, unfortunately, this cannot be easily used to communicate via IMAP */
|
|
" server_folder TEXT DEFAULT ''," /* folder as used on the server, the folder will change when messages are moved around. */
|
|
" server_uid INTEGER DEFAULT 0," /* UID as used on the server, the UID will change when messages are moved around, unique together with validity, see RFC 3501; the validity may differ from folder to folder. We use the server_uid for "markseen" and to delete messages as we check against the message-id, we ignore the validity for these commands. */
|
|
" chat_id INTEGER DEFAULT 0,"
|
|
" from_id INTEGER DEFAULT 0,"
|
|
" to_id INTEGER DEFAULT 0," /* to_id is needed to allow moving messages eg. from "deaddrop" to a normal chat, may be unset */
|
|
" timestamp INTEGER DEFAULT 0,"
|
|
" type INTEGER DEFAULT 0,"
|
|
" state INTEGER DEFAULT 0,"
|
|
" msgrmsg INTEGER DEFAULT 1," /* does the message come from a messenger? (0=no, 1=yes, 2=no, but the message is a reply to a messenger message) */
|
|
" bytes INTEGER DEFAULT 0," /* not used, added in ~ v0.1.12 */
|
|
" txt TEXT DEFAULT ''," /* as this is also used for (fulltext) searching, nothing but normal, plain text should go here */
|
|
" txt_raw TEXT DEFAULT '',"
|
|
" param TEXT DEFAULT '');");
|
|
mrsqlite3_execute__(ths, "CREATE INDEX msgs_index1 ON msgs (rfc724_mid);"); /* in our database, one email may be split up to several messages (eg. one per image), so the email-Message-ID may be used for several records; id is always unique */
|
|
mrsqlite3_execute__(ths, "CREATE INDEX msgs_index2 ON msgs (chat_id);");
|
|
mrsqlite3_execute__(ths, "CREATE INDEX msgs_index3 ON msgs (timestamp);"); /* for sorting */
|
|
mrsqlite3_execute__(ths, "CREATE INDEX msgs_index4 ON msgs (state);"); /* for selecting the count of fresh messages (as there are normally only few unread messages, an index over the chat_id is not required for _this_ purpose */
|
|
mrsqlite3_execute__(ths, "INSERT INTO msgs (id,msgrmsg,txt) VALUES (1,0,'marker1'), (2,0,'rsvd'), (3,0,'rsvd'), (4,0,'rsvd'), (5,0,'rsvd'), (6,0,'rsvd'), (7,0,'rsvd'), (8,0,'rsvd'), (9,0,'daymarker');"); /* make sure, the reserved IDs are not used */
|
|
|
|
mrsqlite3_execute__(ths, "CREATE TABLE jobs (id INTEGER PRIMARY KEY,"
|
|
" added_timestamp INTEGER,"
|
|
" desired_timestamp INTEGER DEFAULT 0,"
|
|
" action INTEGER,"
|
|
" foreign_id INTEGER,"
|
|
" param TEXT DEFAULT '');");
|
|
mrsqlite3_execute__(ths, "CREATE INDEX jobs_index1 ON jobs (desired_timestamp);");
|
|
|
|
if( !mrsqlite3_table_exists__(ths, "config") || !mrsqlite3_table_exists__(ths, "contacts")
|
|
|| !mrsqlite3_table_exists__(ths, "chats") || !mrsqlite3_table_exists__(ths, "chats_contacts")
|
|
|| !mrsqlite3_table_exists__(ths, "msgs") || !mrsqlite3_table_exists__(ths, "jobs") )
|
|
{
|
|
mrsqlite3_log_error(ths, "Cannot create tables in new database \"%s\".", dbfile);
|
|
goto cleanup; /* cannot create the tables - maybe we cannot write? */
|
|
}
|
|
|
|
mrsqlite3_set_config_int__(ths, "dbversion", 0);
|
|
}
|
|
else
|
|
{
|
|
dbversion_before_update = mrsqlite3_get_config_int__(ths, "dbversion", 0);
|
|
}
|
|
|
|
// (1) update low-level database structure.
|
|
// this should be done before updates that use high-level objects that rely themselves on the low-level structure.
|
|
int dbversion = dbversion_before_update;
|
|
int recalc_fingerprints = 0;
|
|
|
|
#define NEW_DB_VERSION 1
|
|
if( dbversion < NEW_DB_VERSION )
|
|
{
|
|
mrsqlite3_execute__(ths, "CREATE TABLE leftgrps ("
|
|
" id INTEGER PRIMARY KEY,"
|
|
" grpid TEXT DEFAULT '');");
|
|
mrsqlite3_execute__(ths, "CREATE INDEX leftgrps_index1 ON leftgrps (grpid);");
|
|
|
|
dbversion = NEW_DB_VERSION;
|
|
mrsqlite3_set_config_int__(ths, "dbversion", NEW_DB_VERSION);
|
|
}
|
|
#undef NEW_DB_VERSION
|
|
|
|
#define NEW_DB_VERSION 2
|
|
if( dbversion < NEW_DB_VERSION )
|
|
{
|
|
mrsqlite3_execute__(ths, "ALTER TABLE contacts ADD COLUMN authname TEXT DEFAULT '';");
|
|
|
|
dbversion = NEW_DB_VERSION;
|
|
mrsqlite3_set_config_int__(ths, "dbversion", NEW_DB_VERSION);
|
|
}
|
|
#undef NEW_DB_VERSION
|
|
|
|
#define NEW_DB_VERSION 7
|
|
if( dbversion < NEW_DB_VERSION )
|
|
{
|
|
mrsqlite3_execute__(ths, "CREATE TABLE keypairs ("
|
|
" id INTEGER PRIMARY KEY,"
|
|
" addr TEXT DEFAULT '' COLLATE NOCASE,"
|
|
" is_default INTEGER DEFAULT 0,"
|
|
" private_key,"
|
|
" public_key,"
|
|
" created INTEGER DEFAULT 0);");
|
|
|
|
dbversion = NEW_DB_VERSION;
|
|
mrsqlite3_set_config_int__(ths, "dbversion", NEW_DB_VERSION);
|
|
}
|
|
#undef NEW_DB_VERSION
|
|
|
|
#define NEW_DB_VERSION 10
|
|
if( dbversion < NEW_DB_VERSION )
|
|
{
|
|
mrsqlite3_execute__(ths, "CREATE TABLE acpeerstates ("
|
|
" id INTEGER PRIMARY KEY,"
|
|
" addr TEXT DEFAULT '' COLLATE NOCASE," /* no UNIQUE here, Autocrypt: requires the index above mail+type (type however, is not used at the moment, but to be future-proof, we do not use an index. instead we just check ourself if there is a record or not)*/
|
|
" last_seen INTEGER DEFAULT 0,"
|
|
" last_seen_autocrypt INTEGER DEFAULT 0,"
|
|
" public_key,"
|
|
" prefer_encrypted INTEGER DEFAULT 0);");
|
|
mrsqlite3_execute__(ths, "CREATE INDEX acpeerstates_index1 ON acpeerstates (addr);");
|
|
|
|
dbversion = NEW_DB_VERSION;
|
|
mrsqlite3_set_config_int__(ths, "dbversion", NEW_DB_VERSION);
|
|
}
|
|
#undef NEW_DB_VERSION
|
|
|
|
#define NEW_DB_VERSION 12
|
|
if( dbversion < NEW_DB_VERSION )
|
|
{
|
|
mrsqlite3_execute__(ths, "CREATE TABLE msgs_mdns ("
|
|
" msg_id INTEGER, "
|
|
" contact_id INTEGER);");
|
|
mrsqlite3_execute__(ths, "CREATE INDEX msgs_mdns_index1 ON msgs_mdns (msg_id);");
|
|
|
|
dbversion = NEW_DB_VERSION;
|
|
mrsqlite3_set_config_int__(ths, "dbversion", NEW_DB_VERSION);
|
|
}
|
|
#undef NEW_DB_VERSION
|
|
|
|
#define NEW_DB_VERSION 17
|
|
if( dbversion < NEW_DB_VERSION )
|
|
{
|
|
mrsqlite3_execute__(ths, "ALTER TABLE chats ADD COLUMN archived INTEGER DEFAULT 0;");
|
|
mrsqlite3_execute__(ths, "CREATE INDEX chats_index2 ON chats (archived);");
|
|
mrsqlite3_execute__(ths, "ALTER TABLE msgs ADD COLUMN starred INTEGER DEFAULT 0;");
|
|
mrsqlite3_execute__(ths, "CREATE INDEX msgs_index5 ON msgs (starred);");
|
|
|
|
dbversion = NEW_DB_VERSION;
|
|
mrsqlite3_set_config_int__(ths, "dbversion", NEW_DB_VERSION);
|
|
}
|
|
#undef NEW_DB_VERSION
|
|
|
|
#define NEW_DB_VERSION 18
|
|
if( dbversion < NEW_DB_VERSION )
|
|
{
|
|
mrsqlite3_execute__(ths, "ALTER TABLE acpeerstates ADD COLUMN gossip_timestamp INTEGER DEFAULT 0;");
|
|
mrsqlite3_execute__(ths, "ALTER TABLE acpeerstates ADD COLUMN gossip_key;");
|
|
|
|
dbversion = NEW_DB_VERSION;
|
|
mrsqlite3_set_config_int__(ths, "dbversion", NEW_DB_VERSION);
|
|
}
|
|
#undef NEW_DB_VERSION
|
|
|
|
#define NEW_DB_VERSION 27
|
|
if( dbversion < NEW_DB_VERSION )
|
|
{
|
|
mrsqlite3_execute__(ths, "DELETE FROM msgs WHERE chat_id=1 OR chat_id=2;"); /* chat.id=1 and chat.id=2 are the old deaddrops, the current ones are defined by chats.blocked=2 */
|
|
mrsqlite3_execute__(ths, "CREATE INDEX chats_contacts_index2 ON chats_contacts (contact_id);"); /* needed to find chat by contact list */
|
|
mrsqlite3_execute__(ths, "ALTER TABLE msgs ADD COLUMN timestamp_sent INTEGER DEFAULT 0;");
|
|
mrsqlite3_execute__(ths, "ALTER TABLE msgs ADD COLUMN timestamp_rcvd INTEGER DEFAULT 0;");
|
|
|
|
dbversion = NEW_DB_VERSION;
|
|
mrsqlite3_set_config_int__(ths, "dbversion", NEW_DB_VERSION);
|
|
}
|
|
#undef NEW_DB_VERSION
|
|
|
|
#define NEW_DB_VERSION 34
|
|
if( dbversion < NEW_DB_VERSION )
|
|
{
|
|
mrsqlite3_execute__(ths, "ALTER TABLE msgs ADD COLUMN hidden INTEGER DEFAULT 0;");
|
|
mrsqlite3_execute__(ths, "ALTER TABLE msgs_mdns ADD COLUMN timestamp_sent INTEGER DEFAULT 0;");
|
|
mrsqlite3_execute__(ths, "ALTER TABLE acpeerstates ADD COLUMN public_key_fingerprint TEXT DEFAULT '';"); /* do not add `COLLATE NOCASE` case-insensivity is not needed as we force uppercase on store - otoh case-sensivity may be neeed for other/upcoming fingerprint formats */
|
|
mrsqlite3_execute__(ths, "ALTER TABLE acpeerstates ADD COLUMN gossip_key_fingerprint TEXT DEFAULT '';"); /* do not add `COLLATE NOCASE` case-insensivity is not needed as we force uppercase on store - otoh case-sensivity may be neeed for other/upcoming fingerprint formats */
|
|
mrsqlite3_execute__(ths, "CREATE INDEX acpeerstates_index3 ON acpeerstates (public_key_fingerprint);");
|
|
mrsqlite3_execute__(ths, "CREATE INDEX acpeerstates_index4 ON acpeerstates (gossip_key_fingerprint);");
|
|
recalc_fingerprints = 1;
|
|
|
|
dbversion = NEW_DB_VERSION;
|
|
mrsqlite3_set_config_int__(ths, "dbversion", NEW_DB_VERSION);
|
|
}
|
|
#undef NEW_DB_VERSION
|
|
|
|
#define NEW_DB_VERSION 39
|
|
if( dbversion < NEW_DB_VERSION )
|
|
{
|
|
mrsqlite3_execute__(ths, "CREATE TABLE tokens ("
|
|
" id INTEGER PRIMARY KEY,"
|
|
" namespc INTEGER DEFAULT 0,"
|
|
" foreign_id INTEGER DEFAULT 0,"
|
|
" token TEXT DEFAULT '',"
|
|
" timestamp INTEGER DEFAULT 0);");
|
|
mrsqlite3_execute__(ths, "ALTER TABLE acpeerstates ADD COLUMN verified_key;");
|
|
mrsqlite3_execute__(ths, "ALTER TABLE acpeerstates ADD COLUMN verified_key_fingerprint TEXT DEFAULT '';"); /* do not add `COLLATE NOCASE` case-insensivity is not needed as we force uppercase on store - otoh case-sensivity may be neeed for other/upcoming fingerprint formats */
|
|
mrsqlite3_execute__(ths, "CREATE INDEX acpeerstates_index5 ON acpeerstates (verified_key_fingerprint);");
|
|
|
|
if( dbversion_before_update == 34 )
|
|
{
|
|
// migrate database from the use of verified-flags to verified_key,
|
|
// _only_ version 34 (0.17.0) has the fields public_key_verified and gossip_key_verified
|
|
// this block can be deleted in half a year or so (created 5/2018)
|
|
mrsqlite3_execute__(ths, "UPDATE acpeerstates SET verified_key=gossip_key, verified_key_fingerprint=gossip_key_fingerprint WHERE gossip_key_verified=2;");
|
|
mrsqlite3_execute__(ths, "UPDATE acpeerstates SET verified_key=public_key, verified_key_fingerprint=public_key_fingerprint WHERE public_key_verified=2;");
|
|
}
|
|
|
|
dbversion = NEW_DB_VERSION;
|
|
mrsqlite3_set_config_int__(ths, "dbversion", NEW_DB_VERSION);
|
|
}
|
|
#undef NEW_DB_VERSION
|
|
|
|
#define NEW_DB_VERSION 40
|
|
if( dbversion < NEW_DB_VERSION )
|
|
{
|
|
mrsqlite3_execute__(ths, "ALTER TABLE jobs ADD COLUMN thread INTEGER DEFAULT 0;");
|
|
|
|
dbversion = NEW_DB_VERSION;
|
|
mrsqlite3_set_config_int__(ths, "dbversion", NEW_DB_VERSION);
|
|
}
|
|
#undef NEW_DB_VERSION
|
|
|
|
// (2) updates that require high-level objects (the structure is complete now and all objects are usable)
|
|
if( recalc_fingerprints )
|
|
{
|
|
sqlite3_stmt* stmt = mrsqlite3_prepare_v2_(ths, "SELECT addr FROM acpeerstates;");
|
|
while( sqlite3_step(stmt) == SQLITE_ROW ) {
|
|
mrapeerstate_t* peerstate = mrapeerstate_new(ths->m_mailbox);
|
|
if( mrapeerstate_load_by_addr__(peerstate, ths, (const char*)sqlite3_column_text(stmt, 0))
|
|
&& mrapeerstate_recalc_fingerprint(peerstate) ) {
|
|
mrapeerstate_save_to_db__(peerstate, ths, 0/*don't create*/);
|
|
}
|
|
mrapeerstate_unref(peerstate);
|
|
}
|
|
sqlite3_finalize(stmt);
|
|
}
|
|
}
|
|
|
|
dc_log_info(ths->m_mailbox, 0, "Opened \"%s\" successfully.", dbfile);
|
|
return 1;
|
|
|
|
cleanup:
|
|
mrsqlite3_close__(ths);
|
|
return 0;
|
|
}
|
|
|
|
|
|
void mrsqlite3_close__(mrsqlite3_t* ths)
|
|
{
|
|
int i;
|
|
|
|
if( ths == NULL ) {
|
|
return;
|
|
}
|
|
|
|
if( ths->m_cobj )
|
|
{
|
|
for( i = 0; i < PREDEFINED_CNT; i++ ) {
|
|
if( ths->m_pd[i] ) {
|
|
sqlite3_finalize(ths->m_pd[i]);
|
|
ths->m_pd[i] = NULL;
|
|
}
|
|
}
|
|
|
|
sqlite3_close(ths->m_cobj);
|
|
ths->m_cobj = NULL;
|
|
}
|
|
|
|
dc_log_info(ths->m_mailbox, 0, "Database closed."); /* We log the information even if not real closing took place; this is to detect logic errors. */
|
|
}
|
|
|
|
|
|
int mrsqlite3_is_open(const mrsqlite3_t* ths)
|
|
{
|
|
if( ths == NULL || ths->m_cobj == NULL ) {
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
|
|
sqlite3_stmt* mrsqlite3_predefine__(mrsqlite3_t* ths, size_t idx, const char* querystr)
|
|
{
|
|
/* Predefines a statement or resets and reuses a statement.
|
|
|
|
The same idx MUST NOT be used at the same time from different threads and
|
|
you MUST NOT call this function with different strings for the same index. */
|
|
|
|
if( ths == NULL || ths->m_cobj == NULL || idx >= PREDEFINED_CNT ) {
|
|
return NULL;
|
|
}
|
|
|
|
if( ths->m_pd[idx] ) {
|
|
sqlite3_reset(ths->m_pd[idx]);
|
|
return ths->m_pd[idx]; /* fine, already prepared before */
|
|
}
|
|
|
|
/*prepare for the first time - this requires the querystring*/
|
|
if( querystr == NULL ) {
|
|
return NULL;
|
|
}
|
|
|
|
if( sqlite3_prepare_v2(ths->m_cobj,
|
|
querystr, -1 /*read `sql` up to the first null-byte*/,
|
|
&ths->m_pd[idx],
|
|
NULL /*tail not interesing, we use only single statements*/) != SQLITE_OK )
|
|
{
|
|
mrsqlite3_log_error(ths, "Preparing statement \"%s\" failed.", querystr);
|
|
return NULL;
|
|
}
|
|
|
|
return ths->m_pd[idx];
|
|
}
|
|
|
|
|
|
void mrsqlite3_reset_all_predefinitions(mrsqlite3_t* ths)
|
|
{
|
|
int i;
|
|
for( i = 0; i < PREDEFINED_CNT; i++ ) {
|
|
if( ths->m_pd[i] ) {
|
|
sqlite3_reset(ths->m_pd[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
int mrsqlite3_table_exists__(mrsqlite3_t* ths, const char* name)
|
|
{
|
|
int ret = 0;
|
|
char* querystr = NULL;
|
|
sqlite3_stmt* stmt = NULL;
|
|
int sqlState;
|
|
|
|
if( (querystr=sqlite3_mprintf("PRAGMA table_info(%s)", name)) == NULL ) { /* this statement cannot be used with binded variables */
|
|
dc_log_error(ths->m_mailbox, 0, "mrsqlite3_table_exists_(): Out of memory.");
|
|
goto cleanup;
|
|
}
|
|
|
|
if( (stmt=mrsqlite3_prepare_v2_(ths, querystr)) == NULL ) {
|
|
goto cleanup;
|
|
}
|
|
|
|
sqlState = sqlite3_step(stmt);
|
|
if( sqlState == SQLITE_ROW ) {
|
|
ret = 1; /* the table exists. Other states are SQLITE_DONE or SQLITE_ERROR in both cases we return 0. */
|
|
}
|
|
|
|
/* success - fall through to free allocated objects */
|
|
;
|
|
|
|
/* error/cleanup */
|
|
cleanup:
|
|
if( stmt ) {
|
|
sqlite3_finalize(stmt);
|
|
}
|
|
|
|
if( querystr ) {
|
|
sqlite3_free(querystr);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/*******************************************************************************
|
|
* Handle configuration
|
|
******************************************************************************/
|
|
|
|
|
|
int mrsqlite3_set_config__(mrsqlite3_t* ths, const char* key, const char* value)
|
|
{
|
|
int state;
|
|
sqlite3_stmt* stmt;
|
|
|
|
if( key == NULL ) {
|
|
dc_log_error(ths->m_mailbox, 0, "mrsqlite3_set_config(): Bad parameter.");
|
|
return 0;
|
|
}
|
|
|
|
if( !mrsqlite3_is_open(ths) ) {
|
|
dc_log_error(ths->m_mailbox, 0, "mrsqlite3_set_config(): Database not ready.");
|
|
return 0;
|
|
}
|
|
|
|
if( value )
|
|
{
|
|
/* insert/update key=value */
|
|
#define SELECT_v_FROM_config_k_STATEMENT "SELECT value FROM config WHERE keyname=?;"
|
|
stmt = mrsqlite3_predefine__(ths, SELECT_v_FROM_config_k, SELECT_v_FROM_config_k_STATEMENT);
|
|
sqlite3_bind_text (stmt, 1, key, -1, SQLITE_STATIC);
|
|
state=sqlite3_step(stmt);
|
|
if( state == SQLITE_DONE ) {
|
|
stmt = mrsqlite3_predefine__(ths, INSERT_INTO_config_kv, "INSERT INTO config (keyname, value) VALUES (?, ?);");
|
|
sqlite3_bind_text (stmt, 1, key, -1, SQLITE_STATIC);
|
|
sqlite3_bind_text (stmt, 2, value, -1, SQLITE_STATIC);
|
|
state=sqlite3_step(stmt);
|
|
|
|
}
|
|
else if( state == SQLITE_ROW ) {
|
|
stmt = mrsqlite3_predefine__(ths, UPDATE_config_vk, "UPDATE config SET value=? WHERE keyname=?;");
|
|
sqlite3_bind_text (stmt, 1, value, -1, SQLITE_STATIC);
|
|
sqlite3_bind_text (stmt, 2, key, -1, SQLITE_STATIC);
|
|
state=sqlite3_step(stmt);
|
|
}
|
|
else {
|
|
dc_log_error(ths->m_mailbox, 0, "mrsqlite3_set_config(): Cannot read value.");
|
|
return 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* delete key */
|
|
stmt = mrsqlite3_predefine__(ths, DELETE_FROM_config_k, "DELETE FROM config WHERE keyname=?;");
|
|
sqlite3_bind_text (stmt, 1, key, -1, SQLITE_STATIC);
|
|
state=sqlite3_step(stmt);
|
|
}
|
|
|
|
if( state != SQLITE_DONE ) {
|
|
dc_log_error(ths->m_mailbox, 0, "mrsqlite3_set_config(): Cannot change value.");
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
char* mrsqlite3_get_config__(mrsqlite3_t* ths, const char* key, const char* def) /* the returned string must be free()'d, NULL is only returned if def is NULL */
|
|
{
|
|
sqlite3_stmt* stmt;
|
|
|
|
if( !mrsqlite3_is_open(ths) || key == NULL ) {
|
|
return strdup_keep_null(def);
|
|
}
|
|
|
|
stmt = mrsqlite3_predefine__(ths, SELECT_v_FROM_config_k, SELECT_v_FROM_config_k_STATEMENT);
|
|
sqlite3_bind_text(stmt, 1, key, -1, SQLITE_STATIC);
|
|
if( sqlite3_step(stmt) == SQLITE_ROW )
|
|
{
|
|
const unsigned char* ptr = sqlite3_column_text(stmt, 0); /* Do not pass the pointers returned from sqlite3_column_text(), etc. into sqlite3_free(). */
|
|
if( ptr )
|
|
{
|
|
/* success, fall through below to free objects */
|
|
return safe_strdup((const char*)ptr);
|
|
}
|
|
}
|
|
|
|
/* return the default value */
|
|
return strdup_keep_null(def);
|
|
}
|
|
|
|
|
|
int32_t mrsqlite3_get_config_int__(mrsqlite3_t* ths, const char* key, int32_t def)
|
|
{
|
|
char* str = mrsqlite3_get_config__(ths, key, NULL);
|
|
if( str == NULL ) {
|
|
return def;
|
|
}
|
|
int32_t ret = atol(str);
|
|
free(str);
|
|
return ret;
|
|
}
|
|
|
|
|
|
int mrsqlite3_set_config_int__(mrsqlite3_t* ths, const char* key, int32_t value)
|
|
{
|
|
char* value_str = mr_mprintf("%i", (int)value);
|
|
if( value_str == NULL ) {
|
|
return 0;
|
|
}
|
|
int ret = mrsqlite3_set_config__(ths, key, value_str);
|
|
free(value_str);
|
|
return ret;
|
|
}
|
|
|
|
|
|
/*******************************************************************************
|
|
* Locking
|
|
******************************************************************************/
|
|
|
|
|
|
#ifdef MR_USE_LOCK_DEBUG
|
|
void mrsqlite3_lockNdebug(mrsqlite3_t* ths, const char* filename, int linenum) /* wait and lock */
|
|
#else
|
|
void mrsqlite3_lock(mrsqlite3_t* ths) /* wait and lock */
|
|
#endif
|
|
{
|
|
#ifdef MR_USE_LOCK_DEBUG
|
|
clock_t start = clock();
|
|
dc_log_info(ths->m_mailbox, 0, " waiting for lock at %s#L%i", filename, linenum);
|
|
#endif
|
|
|
|
pthread_mutex_lock(&ths->m_critical_);
|
|
|
|
#ifdef MR_USE_LOCK_DEBUG
|
|
dc_log_info(ths->m_mailbox, 0, "{{{ LOCK AT %s#L%i after %.3f ms", filename, linenum, (double)(clock()-start)*1000.0/CLOCKS_PER_SEC);
|
|
#endif
|
|
}
|
|
|
|
|
|
#ifdef MR_USE_LOCK_DEBUG
|
|
void mrsqlite3_unlockNdebug(mrsqlite3_t* ths, const char* filename, int linenum)
|
|
#else
|
|
void mrsqlite3_unlock(mrsqlite3_t* ths)
|
|
#endif
|
|
{
|
|
#ifdef MR_USE_LOCK_DEBUG
|
|
dc_log_info(ths->m_mailbox, 0, " UNLOCK AT %s#L%i }}}", filename, linenum);
|
|
#endif
|
|
|
|
pthread_mutex_unlock(&ths->m_critical_);
|
|
}
|
|
|
|
|
|
/*******************************************************************************
|
|
* Transactions
|
|
******************************************************************************/
|
|
|
|
|
|
void mrsqlite3_begin_transaction__(mrsqlite3_t* ths)
|
|
{
|
|
sqlite3_stmt* stmt;
|
|
|
|
ths->m_transactionCount++; /* this is safe, as the database should be locked when using a transaction */
|
|
|
|
if( ths->m_transactionCount == 1 )
|
|
{
|
|
stmt = mrsqlite3_predefine__(ths, BEGIN_transaction, "BEGIN;");
|
|
if( sqlite3_step(stmt) != SQLITE_DONE ) {
|
|
mrsqlite3_log_error(ths, "Cannot begin transaction.");
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void mrsqlite3_rollback__(mrsqlite3_t* ths)
|
|
{
|
|
sqlite3_stmt* stmt;
|
|
|
|
if( ths->m_transactionCount >= 1 )
|
|
{
|
|
if( ths->m_transactionCount == 1 )
|
|
{
|
|
stmt = mrsqlite3_predefine__(ths, ROLLBACK_transaction, "ROLLBACK;");
|
|
if( sqlite3_step(stmt) != SQLITE_DONE ) {
|
|
mrsqlite3_log_error(ths, "Cannot rollback transaction.");
|
|
}
|
|
}
|
|
|
|
ths->m_transactionCount--;
|
|
}
|
|
}
|
|
|
|
|
|
void mrsqlite3_commit__(mrsqlite3_t* ths)
|
|
{
|
|
sqlite3_stmt* stmt;
|
|
|
|
if( ths->m_transactionCount >= 1 )
|
|
{
|
|
if( ths->m_transactionCount == 1 )
|
|
{
|
|
stmt = mrsqlite3_predefine__(ths, COMMIT_transaction, "COMMIT;");
|
|
if( sqlite3_step(stmt) != SQLITE_DONE ) {
|
|
mrsqlite3_log_error(ths, "Cannot commit transaction.");
|
|
}
|
|
}
|
|
|
|
ths->m_transactionCount--;
|
|
}
|
|
}
|