mirror of
https://github.com/deltachat/deltachat-core.git
synced 2025-10-05 10:39:27 +02:00
701 lines
26 KiB
C
701 lines
26 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.
|
|
|
|
We use a single handle for the database connections, mainly because
|
|
we do not know from which threads the UI calls the dc_*() functions.
|
|
|
|
As the open the Database in serialized mode explicitly, in general, this is
|
|
safe. However, there are some points to keep in mind:
|
|
|
|
1. Reading can be done at the same time from several threads, howver, only
|
|
one thread can write. If a seconds thread tries to write, this thread
|
|
is halted until the first has finished writing, at most the timespan set
|
|
by sqlite3_busy_timeout().
|
|
|
|
2. Transactions are possible using `BEGIN IMMEDIATE` (this causes the first
|
|
thread trying to write to block the others as described in 1.
|
|
Transaction cannot be nested, we recommend to use them only in the
|
|
top-level functions or not to use them.
|
|
|
|
3. Using sqlite3_last_insert_rowid() causes race conditions. If you need
|
|
this function, you have to wrap *all* INSERTs by a critical section.
|
|
We recommend not to use this function. */
|
|
|
|
|
|
/*******************************************************************************
|
|
* Tools
|
|
******************************************************************************/
|
|
|
|
|
|
void dc_sqlite3_log_error(dc_sqlite3_t* sql, const char* msg_format, ...)
|
|
{
|
|
char* msg = NULL;
|
|
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(sql->context, 0, "Bad log format string \"%s\".", msg_format); }
|
|
dc_log_error(sql->context, 0, "%s SQLite says: %s", msg, sql->cobj? sqlite3_errmsg(sql->cobj) : notSetUp);
|
|
sqlite3_free(msg);
|
|
va_end(va);
|
|
}
|
|
|
|
|
|
sqlite3_stmt* dc_sqlite3_prepare(dc_sqlite3_t* sql, const char* querystr)
|
|
{
|
|
sqlite3_stmt* stmt = NULL;
|
|
|
|
if (sql == NULL || querystr == NULL || sql->cobj == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
if (sqlite3_prepare_v2(sql->cobj,
|
|
querystr, -1 /*read `querystr` up to the first null-byte*/,
|
|
&stmt,
|
|
NULL /*tail not interesting, we use only single statements*/) != SQLITE_OK)
|
|
{
|
|
dc_sqlite3_log_error(sql, "Query failed: %s", querystr);
|
|
return NULL;
|
|
}
|
|
|
|
/* success - the result must be freed using sqlite3_finalize() */
|
|
return stmt;
|
|
}
|
|
|
|
|
|
int dc_sqlite3_execute(dc_sqlite3_t* sql, const char* querystr)
|
|
{
|
|
int success = 0;
|
|
sqlite3_stmt* stmt = NULL;
|
|
int sqlState = 0;
|
|
|
|
stmt = dc_sqlite3_prepare(sql, querystr);
|
|
if (stmt == NULL) {
|
|
goto cleanup;
|
|
}
|
|
|
|
sqlState = sqlite3_step(stmt);
|
|
if (sqlState != SQLITE_DONE && sqlState != SQLITE_ROW) {
|
|
dc_sqlite3_log_error(sql, "Cannot excecute \"%s\".", querystr);
|
|
goto cleanup;
|
|
}
|
|
|
|
success = 1;
|
|
|
|
cleanup:
|
|
if (stmt) {
|
|
sqlite3_finalize(stmt);
|
|
}
|
|
return success;
|
|
}
|
|
|
|
|
|
uint32_t dc_sqlite3_get_rowid(dc_sqlite3_t* sql, const char* table, const char* field, const char* value)
|
|
{
|
|
uint32_t id = 0;
|
|
char* q3 = sqlite3_mprintf("SELECT id FROM %s WHERE %s=%Q;", table, field, value);
|
|
sqlite3_stmt* stmt = dc_sqlite3_prepare(sql, q3);
|
|
if (SQLITE_ROW==sqlite3_step(stmt)) {
|
|
id = sqlite3_column_int(stmt, 0);
|
|
}
|
|
sqlite3_finalize(stmt);
|
|
sqlite3_free(q3);
|
|
return id;
|
|
}
|
|
|
|
|
|
/*******************************************************************************
|
|
* Main interface
|
|
******************************************************************************/
|
|
|
|
|
|
dc_sqlite3_t* dc_sqlite3_new(dc_context_t* context)
|
|
{
|
|
dc_sqlite3_t* sql = NULL;
|
|
|
|
if ((sql=calloc(1, sizeof(dc_sqlite3_t)))==NULL) {
|
|
exit(24); /* cannot allocate little memory, unrecoverable error */
|
|
}
|
|
|
|
sql->context = context;
|
|
|
|
return sql;
|
|
}
|
|
|
|
|
|
void dc_sqlite3_unref(dc_sqlite3_t* sql)
|
|
{
|
|
if (sql == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (sql->cobj) {
|
|
dc_sqlite3_close(sql);
|
|
}
|
|
|
|
free(sql);
|
|
}
|
|
|
|
|
|
int dc_sqlite3_open(dc_sqlite3_t* sql, const char* dbfile, int flags)
|
|
{
|
|
if (sql == NULL || dbfile == NULL) {
|
|
goto cleanup;
|
|
}
|
|
|
|
if (sqlite3_threadsafe() == 0) {
|
|
dc_log_error(sql->context, 0, "Sqlite3 compiled thread-unsafe; this is not supported.");
|
|
goto cleanup;
|
|
}
|
|
|
|
if (sql->cobj) {
|
|
dc_log_error(sql->context, 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 dc_sqlite3_t object are no longer needed.
|
|
// However, locking is _also_ used for dc_context_t which _is_ still needed, so, we
|
|
// should remove locks only if we're really sure.
|
|
//
|
|
// `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)
|
|
if (sqlite3_open_v2(dbfile, &sql->cobj,
|
|
SQLITE_OPEN_FULLMUTEX | ((flags&DC_OPEN_READONLY)? SQLITE_OPEN_READONLY : (SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE)),
|
|
NULL) != SQLITE_OK) {
|
|
dc_sqlite3_log_error(sql, "Cannot open database \"%s\".", dbfile); /* ususally, even for errors, the pointer is set up (if not, this is also checked by dc_sqlite3_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(sql->cobj, 10*1000);
|
|
|
|
if (!(flags&DC_OPEN_READONLY))
|
|
{
|
|
int dbversion_before_update = 0;
|
|
|
|
/* Init tables to dbversion=0 */
|
|
if (!dc_sqlite3_table_exists(sql, "config"))
|
|
{
|
|
dc_log_info(sql->context, 0, "First time init: creating tables in \"%s\".", dbfile);
|
|
|
|
// the row with the type `INTEGER PRIMARY KEY` is an alias to the 64-bit-ROWID present in every table
|
|
// we re-use this ID for our own purposes.
|
|
// (the last inserted ROWID can be accessed using sqlite3_last_insert_rowid(), which, however, is
|
|
// not recommended as not thread-safe, see above)
|
|
dc_sqlite3_execute(sql, "CREATE TABLE config (id INTEGER PRIMARY KEY, keyname TEXT, value TEXT);");
|
|
dc_sqlite3_execute(sql, "CREATE INDEX config_index1 ON config (keyname);");
|
|
|
|
dc_sqlite3_execute(sql, "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 */
|
|
dc_sqlite3_execute(sql, "CREATE INDEX contacts_index1 ON contacts (name COLLATE NOCASE);"); /* needed for query contacts */
|
|
dc_sqlite3_execute(sql, "CREATE INDEX contacts_index2 ON contacts (addr COLLATE NOCASE);"); /* needed for query and on receiving mails */
|
|
dc_sqlite3_execute(sql, "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(DC_ORIGIN_INTERNAL) || DC_ORIGIN_INTERNAL!=262144
|
|
#error
|
|
#endif
|
|
|
|
dc_sqlite3_execute(sql, "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 dc_chat.c for details */
|
|
" param TEXT DEFAULT '');");
|
|
dc_sqlite3_execute(sql, "CREATE INDEX chats_index1 ON chats (grpid);");
|
|
dc_sqlite3_execute(sql, "CREATE TABLE chats_contacts (chat_id INTEGER, contact_id INTEGER);");
|
|
dc_sqlite3_execute(sql, "CREATE INDEX chats_contacts_index1 ON chats_contacts (chat_id);");
|
|
dc_sqlite3_execute(sql, "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(DC_CHAT_TYPE_SINGLE) || DC_CHAT_TYPE_SINGLE!=100 || DC_CHAT_TYPE_GROUP!=120 || \
|
|
DC_CHAT_ID_DEADDROP!=1 || DC_CHAT_ID_TRASH!=3 || \
|
|
DC_CHAT_ID_MSGS_IN_CREATION!=4 || DC_CHAT_ID_STARRED!=5 || DC_CHAT_ID_ARCHIVED_LINK!=6 || \
|
|
DC_CHAT_NOT_BLOCKED!=0 || DC_CHAT_MANUALLY_BLOCKED!=1 || DC_CHAT_DEADDROP_BLOCKED!=2
|
|
#error
|
|
#endif
|
|
|
|
dc_sqlite3_execute(sql, "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 '');");
|
|
dc_sqlite3_execute(sql, "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 */
|
|
dc_sqlite3_execute(sql, "CREATE INDEX msgs_index2 ON msgs (chat_id);");
|
|
dc_sqlite3_execute(sql, "CREATE INDEX msgs_index3 ON msgs (timestamp);"); /* for sorting */
|
|
dc_sqlite3_execute(sql, "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 */
|
|
dc_sqlite3_execute(sql, "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 */
|
|
|
|
dc_sqlite3_execute(sql, "CREATE TABLE jobs (id INTEGER PRIMARY KEY,"
|
|
" added_timestamp INTEGER,"
|
|
" desired_timestamp INTEGER DEFAULT 0,"
|
|
" action INTEGER,"
|
|
" foreign_id INTEGER,"
|
|
" param TEXT DEFAULT '');");
|
|
dc_sqlite3_execute(sql, "CREATE INDEX jobs_index1 ON jobs (desired_timestamp);");
|
|
|
|
if (!dc_sqlite3_table_exists(sql, "config") || !dc_sqlite3_table_exists(sql, "contacts")
|
|
|| !dc_sqlite3_table_exists(sql, "chats") || !dc_sqlite3_table_exists(sql, "chats_contacts")
|
|
|| !dc_sqlite3_table_exists(sql, "msgs") || !dc_sqlite3_table_exists(sql, "jobs"))
|
|
{
|
|
dc_sqlite3_log_error(sql, "Cannot create tables in new database \"%s\".", dbfile);
|
|
goto cleanup; /* cannot create the tables - maybe we cannot write? */
|
|
}
|
|
|
|
dc_sqlite3_set_config_int(sql, "dbversion", 0);
|
|
}
|
|
else
|
|
{
|
|
dbversion_before_update = dc_sqlite3_get_config_int(sql, "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)
|
|
{
|
|
dc_sqlite3_execute(sql, "CREATE TABLE leftgrps ("
|
|
" id INTEGER PRIMARY KEY,"
|
|
" grpid TEXT DEFAULT '');");
|
|
dc_sqlite3_execute(sql, "CREATE INDEX leftgrps_index1 ON leftgrps (grpid);");
|
|
|
|
dbversion = NEW_DB_VERSION;
|
|
dc_sqlite3_set_config_int(sql, "dbversion", NEW_DB_VERSION);
|
|
}
|
|
#undef NEW_DB_VERSION
|
|
|
|
#define NEW_DB_VERSION 2
|
|
if (dbversion < NEW_DB_VERSION)
|
|
{
|
|
dc_sqlite3_execute(sql, "ALTER TABLE contacts ADD COLUMN authname TEXT DEFAULT '';");
|
|
|
|
dbversion = NEW_DB_VERSION;
|
|
dc_sqlite3_set_config_int(sql, "dbversion", NEW_DB_VERSION);
|
|
}
|
|
#undef NEW_DB_VERSION
|
|
|
|
#define NEW_DB_VERSION 7
|
|
if (dbversion < NEW_DB_VERSION)
|
|
{
|
|
dc_sqlite3_execute(sql, "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;
|
|
dc_sqlite3_set_config_int(sql, "dbversion", NEW_DB_VERSION);
|
|
}
|
|
#undef NEW_DB_VERSION
|
|
|
|
#define NEW_DB_VERSION 10
|
|
if (dbversion < NEW_DB_VERSION)
|
|
{
|
|
dc_sqlite3_execute(sql, "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);");
|
|
dc_sqlite3_execute(sql, "CREATE INDEX acpeerstates_index1 ON acpeerstates (addr);");
|
|
|
|
dbversion = NEW_DB_VERSION;
|
|
dc_sqlite3_set_config_int(sql, "dbversion", NEW_DB_VERSION);
|
|
}
|
|
#undef NEW_DB_VERSION
|
|
|
|
#define NEW_DB_VERSION 12
|
|
if (dbversion < NEW_DB_VERSION)
|
|
{
|
|
dc_sqlite3_execute(sql, "CREATE TABLE msgs_mdns ("
|
|
" msg_id INTEGER, "
|
|
" contact_id INTEGER);");
|
|
dc_sqlite3_execute(sql, "CREATE INDEX msgs_mdns_index1 ON msgs_mdns (msg_id);");
|
|
|
|
dbversion = NEW_DB_VERSION;
|
|
dc_sqlite3_set_config_int(sql, "dbversion", NEW_DB_VERSION);
|
|
}
|
|
#undef NEW_DB_VERSION
|
|
|
|
#define NEW_DB_VERSION 17
|
|
if (dbversion < NEW_DB_VERSION)
|
|
{
|
|
dc_sqlite3_execute(sql, "ALTER TABLE chats ADD COLUMN archived INTEGER DEFAULT 0;");
|
|
dc_sqlite3_execute(sql, "CREATE INDEX chats_index2 ON chats (archived);");
|
|
dc_sqlite3_execute(sql, "ALTER TABLE msgs ADD COLUMN starred INTEGER DEFAULT 0;");
|
|
dc_sqlite3_execute(sql, "CREATE INDEX msgs_index5 ON msgs (starred);");
|
|
|
|
dbversion = NEW_DB_VERSION;
|
|
dc_sqlite3_set_config_int(sql, "dbversion", NEW_DB_VERSION);
|
|
}
|
|
#undef NEW_DB_VERSION
|
|
|
|
#define NEW_DB_VERSION 18
|
|
if (dbversion < NEW_DB_VERSION)
|
|
{
|
|
dc_sqlite3_execute(sql, "ALTER TABLE acpeerstates ADD COLUMN gossip_timestamp INTEGER DEFAULT 0;");
|
|
dc_sqlite3_execute(sql, "ALTER TABLE acpeerstates ADD COLUMN gossip_key;");
|
|
|
|
dbversion = NEW_DB_VERSION;
|
|
dc_sqlite3_set_config_int(sql, "dbversion", NEW_DB_VERSION);
|
|
}
|
|
#undef NEW_DB_VERSION
|
|
|
|
#define NEW_DB_VERSION 27
|
|
if (dbversion < NEW_DB_VERSION)
|
|
{
|
|
dc_sqlite3_execute(sql, "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 */
|
|
dc_sqlite3_execute(sql, "CREATE INDEX chats_contacts_index2 ON chats_contacts (contact_id);"); /* needed to find chat by contact list */
|
|
dc_sqlite3_execute(sql, "ALTER TABLE msgs ADD COLUMN timestamp_sent INTEGER DEFAULT 0;");
|
|
dc_sqlite3_execute(sql, "ALTER TABLE msgs ADD COLUMN timestamp_rcvd INTEGER DEFAULT 0;");
|
|
|
|
dbversion = NEW_DB_VERSION;
|
|
dc_sqlite3_set_config_int(sql, "dbversion", NEW_DB_VERSION);
|
|
}
|
|
#undef NEW_DB_VERSION
|
|
|
|
#define NEW_DB_VERSION 34
|
|
if (dbversion < NEW_DB_VERSION)
|
|
{
|
|
dc_sqlite3_execute(sql, "ALTER TABLE msgs ADD COLUMN hidden INTEGER DEFAULT 0;");
|
|
dc_sqlite3_execute(sql, "ALTER TABLE msgs_mdns ADD COLUMN timestamp_sent INTEGER DEFAULT 0;");
|
|
dc_sqlite3_execute(sql, "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 */
|
|
dc_sqlite3_execute(sql, "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 */
|
|
dc_sqlite3_execute(sql, "CREATE INDEX acpeerstates_index3 ON acpeerstates (public_key_fingerprint);");
|
|
dc_sqlite3_execute(sql, "CREATE INDEX acpeerstates_index4 ON acpeerstates (gossip_key_fingerprint);");
|
|
recalc_fingerprints = 1;
|
|
|
|
dbversion = NEW_DB_VERSION;
|
|
dc_sqlite3_set_config_int(sql, "dbversion", NEW_DB_VERSION);
|
|
}
|
|
#undef NEW_DB_VERSION
|
|
|
|
#define NEW_DB_VERSION 39
|
|
if (dbversion < NEW_DB_VERSION)
|
|
{
|
|
dc_sqlite3_execute(sql, "CREATE TABLE tokens ("
|
|
" id INTEGER PRIMARY KEY,"
|
|
" namespc INTEGER DEFAULT 0,"
|
|
" foreign_id INTEGER DEFAULT 0,"
|
|
" token TEXT DEFAULT '',"
|
|
" timestamp INTEGER DEFAULT 0);");
|
|
dc_sqlite3_execute(sql, "ALTER TABLE acpeerstates ADD COLUMN verified_key;");
|
|
dc_sqlite3_execute(sql, "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 */
|
|
dc_sqlite3_execute(sql, "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)
|
|
dc_sqlite3_execute(sql, "UPDATE acpeerstates SET verified_key=gossip_key, verified_key_fingerprint=gossip_key_fingerprint WHERE gossip_key_verified=2;");
|
|
dc_sqlite3_execute(sql, "UPDATE acpeerstates SET verified_key=public_key, verified_key_fingerprint=public_key_fingerprint WHERE public_key_verified=2;");
|
|
}
|
|
|
|
dbversion = NEW_DB_VERSION;
|
|
dc_sqlite3_set_config_int(sql, "dbversion", NEW_DB_VERSION);
|
|
}
|
|
#undef NEW_DB_VERSION
|
|
|
|
#define NEW_DB_VERSION 40
|
|
if (dbversion < NEW_DB_VERSION)
|
|
{
|
|
dc_sqlite3_execute(sql, "ALTER TABLE jobs ADD COLUMN thread INTEGER DEFAULT 0;");
|
|
|
|
dbversion = NEW_DB_VERSION;
|
|
dc_sqlite3_set_config_int(sql, "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 = dc_sqlite3_prepare(sql, "SELECT addr FROM acpeerstates;");
|
|
while (sqlite3_step(stmt) == SQLITE_ROW) {
|
|
dc_apeerstate_t* peerstate = dc_apeerstate_new(sql->context);
|
|
if (dc_apeerstate_load_by_addr(peerstate, sql, (const char*)sqlite3_column_text(stmt, 0))
|
|
&& dc_apeerstate_recalc_fingerprint(peerstate)) {
|
|
dc_apeerstate_save_to_db(peerstate, sql, 0/*don't create*/);
|
|
}
|
|
dc_apeerstate_unref(peerstate);
|
|
}
|
|
sqlite3_finalize(stmt);
|
|
}
|
|
}
|
|
|
|
dc_log_info(sql->context, 0, "Opened \"%s\".", dbfile);
|
|
return 1;
|
|
|
|
cleanup:
|
|
dc_sqlite3_close(sql);
|
|
return 0;
|
|
}
|
|
|
|
|
|
void dc_sqlite3_close(dc_sqlite3_t* sql)
|
|
{
|
|
if (sql == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (sql->cobj)
|
|
{
|
|
sqlite3_close(sql->cobj);
|
|
sql->cobj = NULL;
|
|
}
|
|
|
|
dc_log_info(sql->context, 0, "Database closed."); /* We log the information even if not real closing took place; this is to detect logic errors. */
|
|
}
|
|
|
|
|
|
int dc_sqlite3_is_open(const dc_sqlite3_t* sql)
|
|
{
|
|
if (sql == NULL || sql->cobj == NULL) {
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
|
|
int dc_sqlite3_table_exists(dc_sqlite3_t* sql, const char* name)
|
|
{
|
|
int ret = 0;
|
|
char* querystr = NULL;
|
|
sqlite3_stmt* stmt = NULL;
|
|
int sqlState = 0;
|
|
|
|
if ((querystr=sqlite3_mprintf("PRAGMA table_info(%s)", name)) == NULL) { /* this statement cannot be used with binded variables */
|
|
dc_log_error(sql->context, 0, "dc_sqlite3_table_exists_(): Out of memory.");
|
|
goto cleanup;
|
|
}
|
|
|
|
if ((stmt=dc_sqlite3_prepare(sql, 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 dc_sqlite3_set_config(dc_sqlite3_t* sql, const char* key, const char* value)
|
|
{
|
|
int state = 0;
|
|
sqlite3_stmt* stmt = NULL;
|
|
|
|
if (key == NULL) {
|
|
dc_log_error(sql->context, 0, "dc_sqlite3_set_config(): Bad parameter.");
|
|
return 0;
|
|
}
|
|
|
|
if (!dc_sqlite3_is_open(sql)) {
|
|
dc_log_error(sql->context, 0, "dc_sqlite3_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 = dc_sqlite3_prepare(sql, SELECT_v_FROM_config_k_STATEMENT);
|
|
sqlite3_bind_text (stmt, 1, key, -1, SQLITE_STATIC);
|
|
state = sqlite3_step(stmt);
|
|
sqlite3_finalize(stmt);
|
|
|
|
if (state == SQLITE_DONE) {
|
|
stmt = dc_sqlite3_prepare(sql, "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);
|
|
sqlite3_finalize(stmt);
|
|
}
|
|
else if (state == SQLITE_ROW) {
|
|
stmt = dc_sqlite3_prepare(sql, "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);
|
|
sqlite3_finalize(stmt);
|
|
}
|
|
else {
|
|
dc_log_error(sql->context, 0, "dc_sqlite3_set_config(): Cannot read value.");
|
|
return 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* delete key */
|
|
stmt = dc_sqlite3_prepare(sql, "DELETE FROM config WHERE keyname=?;");
|
|
sqlite3_bind_text (stmt, 1, key, -1, SQLITE_STATIC);
|
|
state = sqlite3_step(stmt);
|
|
sqlite3_finalize(stmt);
|
|
}
|
|
|
|
if (state != SQLITE_DONE) {
|
|
dc_log_error(sql->context, 0, "dc_sqlite3_set_config(): Cannot change value.");
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
char* dc_sqlite3_get_config(dc_sqlite3_t* sql, const char* key, const char* def) /* the returned string must be free()'d, NULL is only returned if def is NULL */
|
|
{
|
|
sqlite3_stmt* stmt = NULL;
|
|
|
|
if (!dc_sqlite3_is_open(sql) || key == NULL) {
|
|
return dc_strdup_keep_null(def);
|
|
}
|
|
|
|
stmt = dc_sqlite3_prepare(sql, 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 */
|
|
char* ret = dc_strdup((const char*)ptr);
|
|
sqlite3_finalize(stmt);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
/* return the default value */
|
|
sqlite3_finalize(stmt);
|
|
return dc_strdup_keep_null(def);
|
|
}
|
|
|
|
|
|
int32_t dc_sqlite3_get_config_int(dc_sqlite3_t* sql, const char* key, int32_t def)
|
|
{
|
|
char* str = dc_sqlite3_get_config(sql, key, NULL);
|
|
if (str == NULL) {
|
|
return def;
|
|
}
|
|
int32_t ret = atol(str);
|
|
free(str);
|
|
return ret;
|
|
}
|
|
|
|
|
|
int dc_sqlite3_set_config_int(dc_sqlite3_t* sql, const char* key, int32_t value)
|
|
{
|
|
char* value_str = dc_mprintf("%i", (int)value);
|
|
if (value_str == NULL) {
|
|
return 0;
|
|
}
|
|
int ret = dc_sqlite3_set_config(sql, key, value_str);
|
|
free(value_str);
|
|
return ret;
|
|
}
|
|
|
|
|
|
/*******************************************************************************
|
|
* Transactions
|
|
******************************************************************************/
|
|
|
|
|
|
void dc_sqlite3_begin_transaction(dc_sqlite3_t* sql)
|
|
{
|
|
// `BEGIN IMMEDIATE` ensures, only one thread may write.
|
|
// all other calls to `BEGIN IMMEDIATE` will try over until sqlite3_busy_timeout() is reached.
|
|
// CAVE: This also implies that transactions MUST NOT be nested.
|
|
sqlite3_stmt* stmt = dc_sqlite3_prepare(sql, "BEGIN IMMEDIATE;");
|
|
if (sqlite3_step(stmt) != SQLITE_DONE) {
|
|
dc_sqlite3_log_error(sql, "Cannot begin transaction.");
|
|
}
|
|
sqlite3_finalize(stmt);
|
|
}
|
|
|
|
|
|
void dc_sqlite3_rollback(dc_sqlite3_t* sql)
|
|
{
|
|
sqlite3_stmt* stmt = dc_sqlite3_prepare(sql, "ROLLBACK;");
|
|
if (sqlite3_step(stmt) != SQLITE_DONE) {
|
|
dc_sqlite3_log_error(sql, "Cannot rollback transaction.");
|
|
}
|
|
sqlite3_finalize(stmt);
|
|
}
|
|
|
|
|
|
void dc_sqlite3_commit(dc_sqlite3_t* sql)
|
|
{
|
|
sqlite3_stmt* stmt = dc_sqlite3_prepare(sql, "COMMIT;");
|
|
if (sqlite3_step(stmt) != SQLITE_DONE) {
|
|
dc_sqlite3_log_error(sql, "Cannot commit transaction.");
|
|
}
|
|
sqlite3_finalize(stmt);
|
|
}
|