2020-03-11 09:17:11 -04:00
/*
* database . c
* vim : expandtab : ts = 4 : sts = 4 : sw = 4
*
2023-07-31 10:51:58 -04:00
* Copyright ( C ) 2020 - 2023 Michael Vetter < jubalh @ iodoru . org >
2020-03-11 09:17:11 -04:00
*
* This file is part of Profanity .
*
* Profanity 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 .
*
* Profanity 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 Profanity . If not , see < https : //www.gnu.org/licenses/>.
*
* In addition , as a special exception , the copyright holders give permission to
* link the code of portions of this program with the OpenSSL library under
* certain conditions as described in each individual source file , and
* distribute linked combinations including the two .
*
* You must obey the GNU General Public License in all respects for all of the
* code used other than OpenSSL . If you modify file ( s ) with this exception , you
* may extend this exception to your version of the file ( s ) , but you are not
* obligated to do so . If you do not wish to do so , delete this exception
* statement from your version . If you delete this exception statement from all
* source files in the program , then also delete it here .
*
*/
2021-03-26 14:51:46 -04:00
# include "config.h"
2020-07-07 07:53:30 -04:00
# include <sys/stat.h>
2023-11-01 12:11:46 -04:00
# include <sys/statvfs.h>
2020-04-06 16:42:22 -04:00
# include <sqlite3.h>
2020-07-07 07:53:30 -04:00
# include <glib.h>
2020-03-19 07:10:54 -04:00
# include <stdio.h>
2020-04-06 16:42:22 -04:00
# include <stdlib.h>
# include <string.h>
2020-07-07 07:53:30 -04:00
# include <errno.h>
2020-03-11 09:17:11 -04:00
2020-07-07 07:53:30 -04:00
# include "log.h"
2020-04-18 04:01:40 -04:00
# include "common.h"
2020-03-18 13:56:22 -04:00
# include "config/files.h"
2022-07-04 17:06:04 -04:00
# include "database.h"
2023-07-03 10:06:24 -04:00
# include "config/preferences.h"
2023-10-17 05:11:10 -04:00
# include "ui/ui.h"
2022-06-28 13:55:14 -04:00
# include "xmpp/xmpp.h"
# include "xmpp/message.h"
2020-03-11 09:17:11 -04:00
2020-07-07 08:18:57 -04:00
static sqlite3 * g_chatlog_database ;
2020-03-11 09:17:11 -04:00
2020-07-07 08:18:57 -04:00
static void _add_to_db ( ProfMessage * message , char * type , const Jid * const from_jid , const Jid * const to_jid ) ;
static char * _get_db_filename ( ProfAccount * account ) ;
static prof_msg_type_t _get_message_type_type ( const char * const type ) ;
2022-06-29 09:30:54 -04:00
static prof_enc_t _get_message_enc_type ( const char * const encstr ) ;
2023-11-01 12:11:46 -04:00
static int _get_db_version ( void ) ;
static gboolean _migrate_to_v2 ( void ) ;
static gboolean _check_available_space_for_db_migration ( char * path_to_db ) ;
2020-03-27 19:18:13 -04:00
2022-12-30 13:36:53 -05:00
# define auto_sqlite __attribute__((__cleanup__(auto_free_sqlite)))
2023-11-01 12:11:46 -04:00
static const int latest_version = 2 ;
2022-12-30 13:36:53 -05:00
static void
auto_free_sqlite ( gchar * * str )
{
if ( str = = NULL )
return ;
sqlite3_free ( * str ) ;
}
2020-03-23 07:18:25 -04:00
static char *
2020-07-07 08:18:57 -04:00
_get_db_filename ( ProfAccount * account )
2020-03-23 07:18:25 -04:00
{
2022-03-13 06:58:56 -04:00
return files_file_in_account_data_path ( DIR_DATABASE , account - > jid , " chatlog.db " ) ;
2020-03-23 07:18:25 -04:00
}
2020-03-23 07:33:42 -04:00
gboolean
2020-07-07 08:18:57 -04:00
log_database_init ( ProfAccount * account )
2020-03-11 09:17:11 -04:00
{
int ret = sqlite3_initialize ( ) ;
2020-03-27 14:09:29 -04:00
if ( ret ! = SQLITE_OK ) {
2020-03-11 09:17:11 -04:00
log_error ( " Error initializing SQLite database: %d " , ret ) ;
return FALSE ;
2020-03-27 14:09:29 -04:00
}
2020-03-11 09:17:11 -04:00
2023-07-11 07:23:58 -04:00
auto_char char * filename = _get_db_filename ( account ) ;
2020-03-23 07:18:25 -04:00
if ( ! filename ) {
return FALSE ;
}
2020-03-18 13:56:22 -04:00
ret = sqlite3_open ( filename , & g_chatlog_database ) ;
2020-03-11 09:17:11 -04:00
if ( ret ! = SQLITE_OK ) {
2020-07-07 08:18:57 -04:00
const char * err_msg = sqlite3_errmsg ( g_chatlog_database ) ;
2020-03-11 09:17:11 -04:00
log_error ( " Error opening SQLite database: %s " , err_msg ) ;
return FALSE ;
}
2020-07-07 08:18:57 -04:00
char * err_msg ;
2023-11-01 12:11:46 -04:00
int db_version = _get_db_version ( ) ;
if ( db_version = = latest_version ) {
return TRUE ;
}
// ChatLogs Table
// Contains all chat messages
//
// id is primary key
// from_jid is the sender's jid
// to_jid is the receiver's jid
// from_resource is the sender's resource
// to_resource is the receiver's resource
// message is the message's text
// timestamp is the timestamp like "2020/03/24 11:12:14"
2020-03-27 14:50:24 -04:00
// type is there to distinguish: message (chat), MUC message (muc), muc pm (mucpm)
2020-07-23 09:19:13 -04:00
// stanza_id is the ID in <message>
// archive_id is the stanza-id from from XEP-0359: Unique and Stable Stanza IDs used for XEP-0313: Message Archive Management
2020-03-27 14:09:29 -04:00
// encryption is to distinguish: none, omemo, otr, pgp
// marked_read is 0/1 whether a message has been marked as read via XEP-0333: Chat Markers
2023-11-01 12:11:46 -04:00
// replace_id is the ID from XEP-0308: Last Message Correction
// replaces_db_id is ID (primary key) of the original message that LMC message corrects/replaces
// replaced_by_db_id is ID (primary key) of the last correcting (LMC) message for the original message
char * query = " CREATE TABLE IF NOT EXISTS `ChatLogs` ( "
" `id` INTEGER PRIMARY KEY AUTOINCREMENT, "
" `from_jid` TEXT NOT NULL, "
" `to_jid` TEXT NOT NULL, "
" `from_resource` TEXT, "
" `to_resource` TEXT, "
" `message` TEXT, "
" `timestamp` TEXT, "
" `type` TEXT, "
" `stanza_id` TEXT, "
" `archive_id` TEXT, "
" `encryption` TEXT, "
" `marked_read` INTEGER, "
" `replace_id` TEXT, "
" `replaces_db_id` INTEGER, "
" `replaced_by_db_id` INTEGER) " ;
2020-07-07 08:18:57 -04:00
if ( SQLITE_OK ! = sqlite3_exec ( g_chatlog_database , query , NULL , 0 , & err_msg ) ) {
2020-03-23 10:21:31 -04:00
goto out ;
2020-03-23 10:18:55 -04:00
}
2023-11-01 12:11:46 -04:00
query = " CREATE TRIGGER IF NOT EXISTS update_corrected_message "
" AFTER INSERT ON ChatLogs "
" FOR EACH ROW "
" WHEN NEW.replaces_db_id IS NOT NULL "
" BEGIN "
" UPDATE ChatLogs "
" SET replaced_by_db_id = NEW.id "
" WHERE id = NEW.replaces_db_id; "
" END; " ;
2020-07-07 08:18:57 -04:00
if ( SQLITE_OK ! = sqlite3_exec ( g_chatlog_database , query , NULL , 0 , & err_msg ) ) {
2023-11-01 12:11:46 -04:00
log_error ( " Unable to add `update_corrected_message` trigger. " ) ;
2020-03-23 10:21:31 -04:00
goto out ;
2020-03-23 10:18:55 -04:00
}
2023-11-01 12:11:46 -04:00
query = " CREATE INDEX IF NOT EXISTS ChatLogs_timestamp_IDX ON `ChatLogs` (`timestamp`) " ;
if ( SQLITE_OK ! = sqlite3_exec ( g_chatlog_database , query , NULL , 0 , & err_msg ) ) {
log_error ( " Unable to create index for timestamp. " ) ;
goto out ;
}
query = " CREATE INDEX IF NOT EXISTS ChatLogs_to_from_jid_IDX ON `ChatLogs` (`to_jid`, `from_jid`) " ;
2020-07-07 08:18:57 -04:00
if ( SQLITE_OK ! = sqlite3_exec ( g_chatlog_database , query , NULL , 0 , & err_msg ) ) {
2023-11-01 12:11:46 -04:00
log_error ( " Unable to create index for to_jid. " ) ;
goto out ;
}
query = " CREATE TABLE IF NOT EXISTS `DbVersion` (`dv_id` INTEGER PRIMARY KEY, `version` INTEGER UNIQUE) " ;
if ( SQLITE_OK ! = sqlite3_exec ( g_chatlog_database , query , NULL , 0 , & err_msg ) ) {
goto out ;
}
if ( db_version = = - 1 ) {
query = " INSERT OR IGNORE INTO `DbVersion` (`version`) VALUES ('2') " ;
if ( SQLITE_OK ! = sqlite3_exec ( g_chatlog_database , query , NULL , 0 , & err_msg ) ) {
goto out ;
}
db_version = _get_db_version ( ) ;
}
// Unlikely event, but we don't want to migrate if we are just unable to determine the DB version
if ( db_version = = - 1 ) {
cons_show_error ( " DB Initialization Error: Unable to check DB version. " ) ;
2020-03-23 10:21:31 -04:00
goto out ;
2020-03-18 13:42:16 -04:00
}
2023-11-01 12:11:46 -04:00
if ( db_version < latest_version ) {
cons_show ( " Migrating database schema. This operation may take a while... " ) ;
if ( db_version < 2 & & ( ! _check_available_space_for_db_migration ( filename ) | | ! _migrate_to_v2 ( ) ) ) {
cons_show_error ( " Database Initialization Error: Unable to migrate database to version 2. Please, check error logs for details. " ) ;
goto out ;
}
cons_show ( " Database schema migration was successful. " ) ;
}
2020-03-18 13:42:16 -04:00
log_debug ( " Initialized SQLite database: %s " , filename ) ;
2020-03-11 09:17:11 -04:00
return TRUE ;
2020-03-23 10:21:31 -04:00
out :
if ( err_msg ) {
2023-11-01 12:11:46 -04:00
log_error ( " SQLite error in log_database_init(): %s " , err_msg ) ;
2020-03-23 10:21:31 -04:00
sqlite3_free ( err_msg ) ;
} else {
2023-11-01 12:11:46 -04:00
log_error ( " Unknown SQLite error in log_database_init(). " ) ;
2020-03-23 10:21:31 -04:00
}
return FALSE ;
2020-03-11 09:17:11 -04:00
}
void
log_database_close ( void )
{
2020-03-23 07:23:21 -04:00
if ( g_chatlog_database ) {
sqlite3_close ( g_chatlog_database ) ;
sqlite3_shutdown ( ) ;
g_chatlog_database = NULL ;
}
2020-03-11 09:17:11 -04:00
}
2020-03-19 07:10:54 -04:00
2020-04-06 12:55:20 -04:00
void
2020-07-07 08:18:57 -04:00
log_database_add_incoming ( ProfMessage * message )
2020-03-27 20:16:31 -04:00
{
2020-05-27 16:06:04 -04:00
if ( message - > to_jid ) {
_add_to_db ( message , NULL , message - > from_jid , message - > to_jid ) ;
} else {
2023-07-13 09:11:30 -04:00
auto_jid Jid * myjid = jid_create ( connection_get_fulljid ( ) ) ;
2020-03-27 19:50:32 -04:00
2020-05-27 16:06:04 -04:00
_add_to_db ( message , NULL , message - > from_jid , myjid ) ;
}
2020-03-27 19:18:13 -04:00
}
2020-03-27 20:16:31 -04:00
static void
2020-07-07 08:18:57 -04:00
_log_database_add_outgoing ( char * type , const char * const id , const char * const barejid , const char * const message , const char * const replace_id , prof_enc_t enc )
2020-03-27 19:50:32 -04:00
{
2020-07-07 08:18:57 -04:00
ProfMessage * msg = message_init ( ) ;
2020-03-27 19:50:32 -04:00
msg - > id = id ? strdup ( id ) : NULL ;
2020-04-11 11:11:53 -04:00
msg - > from_jid = jid_create ( barejid ) ;
2020-03-27 19:50:32 -04:00
msg - > plain = message ? strdup ( message ) : NULL ;
msg - > replace_id = replace_id ? strdup ( replace_id ) : NULL ;
msg - > timestamp = g_date_time_new_now_local ( ) ; // TODO: get from outside. best to have whole ProfMessage from outside
2020-03-27 20:16:31 -04:00
msg - > enc = enc ;
2020-03-27 19:50:32 -04:00
2023-07-13 09:11:30 -04:00
auto_jid Jid * myjid = jid_create ( connection_get_fulljid ( ) ) ;
2020-03-27 19:50:32 -04:00
2020-04-11 11:11:53 -04:00
_add_to_db ( msg , type , myjid , msg - > from_jid ) ; // TODO: myjid now in profmessage
2020-03-27 19:50:32 -04:00
message_free ( msg ) ;
2020-03-27 19:18:13 -04:00
}
2020-03-27 20:16:31 -04:00
void
2020-07-07 08:18:57 -04:00
log_database_add_outgoing_chat ( const char * const id , const char * const barejid , const char * const message , const char * const replace_id , prof_enc_t enc )
2020-03-27 20:16:31 -04:00
{
_log_database_add_outgoing ( " chat " , id , barejid , message , replace_id , enc ) ;
}
void
2020-07-07 08:18:57 -04:00
log_database_add_outgoing_muc ( const char * const id , const char * const barejid , const char * const message , const char * const replace_id , prof_enc_t enc )
2020-03-27 20:16:31 -04:00
{
_log_database_add_outgoing ( " muc " , id , barejid , message , replace_id , enc ) ;
}
void
2020-07-07 08:18:57 -04:00
log_database_add_outgoing_muc_pm ( const char * const id , const char * const barejid , const char * const message , const char * const replace_id , prof_enc_t enc )
2020-03-27 20:16:31 -04:00
{
_log_database_add_outgoing ( " mucpm " , id , barejid , message , replace_id , enc ) ;
}
2022-07-03 14:23:07 -04:00
// Get info (timestamp and stanza_id) of the first or last message in db
ProfMessage *
log_database_get_limits_info ( const gchar * const contact_barejid , gboolean is_last )
{
sqlite3_stmt * stmt = NULL ;
const char * jid = connection_get_fulljid ( ) ;
2023-07-13 09:11:30 -04:00
auto_jid Jid * myjid = jid_create ( jid ) ;
2022-07-03 14:23:07 -04:00
if ( ! myjid )
return NULL ;
2023-11-01 12:11:46 -04:00
const char * order = is_last ? " DESC " : " ASC " ;
auto_sqlite char * query = sqlite3_mprintf ( " SELECT `archive_id`, `timestamp` FROM `ChatLogs` WHERE "
2023-11-06 12:20:39 -05:00
" (`from_jid` = %Q AND `to_jid` = %Q) OR "
" (`from_jid` = %Q AND `to_jid` = %Q) "
2023-11-01 12:11:46 -04:00
" ORDER BY `timestamp` %s LIMIT 1; " ,
contact_barejid , myjid - > barejid , myjid - > barejid , contact_barejid , order ) ;
2022-07-03 14:23:07 -04:00
if ( ! query ) {
2023-10-17 05:28:28 -04:00
log_error ( " Could not allocate memory for SQL query in log_database_get_limits_info() " ) ;
2022-07-03 14:23:07 -04:00
return NULL ;
}
int rc = sqlite3_prepare_v2 ( g_chatlog_database , query , - 1 , & stmt , NULL ) ;
if ( rc ! = SQLITE_OK ) {
2023-11-01 12:11:46 -04:00
log_error ( " Unknown SQLite error in log_database_get_last_info(). " ) ;
2022-07-03 14:23:07 -04:00
return NULL ;
}
ProfMessage * msg = message_init ( ) ;
if ( sqlite3_step ( stmt ) = = SQLITE_ROW ) {
char * archive_id = ( char * ) sqlite3_column_text ( stmt , 0 ) ;
char * date = ( char * ) sqlite3_column_text ( stmt , 1 ) ;
msg - > stanzaid = strdup ( archive_id ) ;
msg - > timestamp = g_date_time_new_from_iso8601 ( date , NULL ) ;
}
sqlite3_finalize ( stmt ) ;
return msg ;
}
2022-07-10 03:44:06 -04:00
// Query previous chats, constraints start_time and end_time. If end_time is
// null the current time is used. from_start gets first few messages if true
// otherwise the last ones. Flip flips the order of the results
2020-04-06 12:55:20 -04:00
GSList *
2023-05-11 04:52:54 -04:00
log_database_get_previous_chat ( const gchar * const contact_barejid , const char * start_time , char * end_time , gboolean from_start , gboolean flip )
2020-04-06 12:55:20 -04:00
{
2020-07-07 08:18:57 -04:00
sqlite3_stmt * stmt = NULL ;
const char * jid = connection_get_fulljid ( ) ;
2023-07-13 09:11:30 -04:00
auto_jid Jid * myjid = jid_create ( jid ) ;
2021-03-25 11:46:18 -04:00
if ( ! myjid )
return NULL ;
2020-04-06 12:55:20 -04:00
2022-07-03 14:29:36 -04:00
// Flip order when querying older pages
2022-07-10 03:44:06 -04:00
gchar * sort1 = from_start ? " ASC " : " DESC " ;
gchar * sort2 = ! flip ? " ASC " : " DESC " ;
2022-07-05 04:12:29 -04:00
GDateTime * now = g_date_time_new_now_local ( ) ;
2023-07-13 11:04:59 -04:00
auto_gchar gchar * end_date_fmt = end_time ? end_time : g_date_time_format_iso8601 ( now ) ;
2023-10-17 05:11:10 -04:00
auto_sqlite gchar * query = sqlite3_mprintf ( " SELECT * FROM ( "
" SELECT COALESCE(B.`message`, A.`message`) AS message, "
2023-11-01 12:11:46 -04:00
" A.`timestamp`, A.`from_jid`, A.`to_jid`, A.`type`, A.`encryption` FROM `ChatLogs` AS A "
" LEFT JOIN `ChatLogs` AS B ON (A.`replaced_by_db_id` = B.`id` AND A.`from_jid` = B.`from_jid`) "
2023-11-06 12:20:39 -05:00
" WHERE (A.`replaces_db_id` IS NULL) "
" AND ((A.`from_jid` = %Q AND A.`to_jid` = %Q) OR (A.`from_jid` = %Q AND A.`to_jid` = %Q)) "
" AND A.`timestamp` < %Q "
2023-10-17 05:11:10 -04:00
" AND (%Q IS NULL OR A.`timestamp` > %Q) "
" ORDER BY A.`timestamp` %s LIMIT %d) "
" ORDER BY `timestamp` %s; " ,
contact_barejid , myjid - > barejid , myjid - > barejid , contact_barejid , end_date_fmt , start_time , start_time , sort1 , MESSAGES_TO_RETRIEVE , sort2 ) ;
2022-07-05 06:09:16 -04:00
g_date_time_unref ( now ) ;
2021-03-30 11:38:13 -04:00
if ( ! query ) {
2023-10-17 05:28:28 -04:00
log_error ( " Could not allocate memory " ) ;
2020-04-06 12:55:20 -04:00
return NULL ;
}
2020-07-07 08:18:57 -04:00
int rc = sqlite3_prepare_v2 ( g_chatlog_database , query , - 1 , & stmt , NULL ) ;
if ( rc ! = SQLITE_OK ) {
2023-11-01 12:11:46 -04:00
log_error ( " SQLite error in log_database_get_previous_chat(): %s " , sqlite3_errmsg ( g_chatlog_database ) ) ;
2020-04-06 12:55:20 -04:00
return NULL ;
}
2020-07-07 08:18:57 -04:00
GSList * history = NULL ;
2020-04-06 12:55:20 -04:00
2020-07-07 08:18:57 -04:00
while ( sqlite3_step ( stmt ) = = SQLITE_ROW ) {
char * message = ( char * ) sqlite3_column_text ( stmt , 0 ) ;
char * date = ( char * ) sqlite3_column_text ( stmt , 1 ) ;
char * from = ( char * ) sqlite3_column_text ( stmt , 2 ) ;
2023-11-01 12:11:46 -04:00
char * to_jid = ( char * ) sqlite3_column_text ( stmt , 3 ) ;
char * type = ( char * ) sqlite3_column_text ( stmt , 4 ) ;
char * encryption = ( char * ) sqlite3_column_text ( stmt , 5 ) ;
2020-04-06 12:55:20 -04:00
2020-07-07 08:18:57 -04:00
ProfMessage * msg = message_init ( ) ;
2020-04-11 11:11:53 -04:00
msg - > from_jid = jid_create ( from ) ;
2023-11-01 12:11:46 -04:00
msg - > to_jid = jid_create ( to_jid ) ;
2020-04-06 12:55:20 -04:00
msg - > plain = strdup ( message ) ;
msg - > timestamp = g_date_time_new_from_iso8601 ( date , NULL ) ;
2020-04-08 06:50:23 -04:00
msg - > type = _get_message_type_type ( type ) ;
2022-06-29 09:30:54 -04:00
msg - > enc = _get_message_enc_type ( encryption ) ;
2020-04-06 12:55:20 -04:00
history = g_slist_append ( history , msg ) ;
2020-07-07 08:18:57 -04:00
}
sqlite3_finalize ( stmt ) ;
2020-04-06 12:55:20 -04:00
return history ;
}
2020-07-07 08:18:57 -04:00
static const char *
_get_message_type_str ( prof_msg_type_t type )
{
2020-04-06 12:55:20 -04:00
switch ( type ) {
case PROF_MSG_TYPE_CHAT :
return " chat " ;
case PROF_MSG_TYPE_MUC :
return " muc " ;
case PROF_MSG_TYPE_MUCPM :
return " mucpm " ;
case PROF_MSG_TYPE_UNINITIALIZED :
return NULL ;
}
return NULL ;
}
2020-07-07 08:18:57 -04:00
static prof_msg_type_t
_get_message_type_type ( const char * const type )
{
2020-04-08 06:50:23 -04:00
if ( g_strcmp0 ( type , " chat " ) = = 0 ) {
return PROF_MSG_TYPE_CHAT ;
} else if ( g_strcmp0 ( type , " muc " ) = = 0 ) {
return PROF_MSG_TYPE_MUC ;
} else if ( g_strcmp0 ( type , " mucpm " ) = = 0 ) {
return PROF_MSG_TYPE_MUCPM ;
} else {
return PROF_MSG_TYPE_UNINITIALIZED ;
}
}
2020-07-07 08:18:57 -04:00
static const char *
_get_message_enc_str ( prof_enc_t enc )
{
2020-04-06 12:55:20 -04:00
switch ( enc ) {
2020-06-21 03:43:42 -04:00
case PROF_MSG_ENC_OX :
return " ox " ;
2020-04-06 12:55:20 -04:00
case PROF_MSG_ENC_PGP :
return " pgp " ;
case PROF_MSG_ENC_OTR :
return " otr " ;
case PROF_MSG_ENC_OMEMO :
return " omemo " ;
case PROF_MSG_ENC_NONE :
return " none " ;
}
2022-06-29 09:31:37 -04:00
return " none " ;
2020-04-06 12:55:20 -04:00
}
2022-06-29 09:30:54 -04:00
static prof_enc_t
_get_message_enc_type ( const char * const encstr )
{
if ( g_strcmp0 ( encstr , " ox " ) = = 0 ) {
return PROF_MSG_ENC_OX ;
} else if ( g_strcmp0 ( encstr , " pgp " ) = = 0 ) {
return PROF_MSG_ENC_PGP ;
} else if ( g_strcmp0 ( encstr , " otr " ) = = 0 ) {
return PROF_MSG_ENC_OTR ;
} else if ( g_strcmp0 ( encstr , " omemo " ) = = 0 ) {
return PROF_MSG_ENC_OMEMO ;
}
return PROF_MSG_ENC_NONE ;
}
2020-03-27 19:18:13 -04:00
static void
2020-07-07 08:18:57 -04:00
_add_to_db ( ProfMessage * message , char * type , const Jid * const from_jid , const Jid * const to_jid )
2020-03-27 19:18:13 -04:00
{
2023-07-03 10:06:24 -04:00
auto_gchar gchar * pref_dblog = prefs_get_string ( PREF_DBLOG ) ;
2023-11-01 12:11:46 -04:00
sqlite_int64 original_message_id = - 1 ;
2023-07-03 10:06:24 -04:00
if ( g_strcmp0 ( pref_dblog , " off " ) = = 0 ) {
return ;
} else if ( g_strcmp0 ( pref_dblog , " redact " ) = = 0 ) {
if ( message - > plain ) {
free ( message - > plain ) ;
}
message - > plain = strdup ( " [REDACTED] " ) ;
}
2020-03-23 07:23:21 -04:00
if ( ! g_chatlog_database ) {
log_debug ( " log_database_add() called but db is not initialized " ) ;
return ;
}
2020-07-07 08:18:57 -04:00
char * err_msg ;
2023-07-13 11:04:59 -04:00
auto_gchar gchar * date_fmt ;
2020-04-06 06:11:08 -04:00
if ( message - > timestamp ) {
2020-09-30 13:41:18 -04:00
date_fmt = g_date_time_format_iso8601 ( message - > timestamp ) ;
2020-04-06 06:11:08 -04:00
} else {
2022-06-29 03:42:54 -04:00
GDateTime * dt = g_date_time_new_now_local ( ) ;
date_fmt = g_date_time_format_iso8601 ( dt ) ;
g_date_time_unref ( dt ) ;
2020-04-06 06:11:08 -04:00
}
2020-03-19 07:10:54 -04:00
2020-07-07 08:18:57 -04:00
const char * enc = _get_message_enc_str ( message - > enc ) ;
2020-04-06 12:55:20 -04:00
if ( ! type ) {
type = ( char * ) _get_message_type_str ( message - > type ) ;
}
2023-11-01 12:11:46 -04:00
// Apply LMC and check its validity (XEP-0308)
2023-10-17 05:11:10 -04:00
if ( message - > replace_id ) {
2023-11-01 12:11:46 -04:00
auto_sqlite char * replace_check_query = sqlite3_mprintf ( " SELECT `id`, `from_jid`, `replaces_db_id` FROM `ChatLogs` WHERE `stanza_id` = %Q ORDER BY `timestamp` DESC LIMIT 1 " ,
message - > replace_id ) ;
2023-10-17 05:11:10 -04:00
if ( ! replace_check_query ) {
2023-10-17 05:28:28 -04:00
log_error ( " Could not allocate memory for SQL replace query in log_database_add() " ) ;
2023-10-17 05:11:10 -04:00
return ;
}
sqlite3_stmt * lmc_stmt = NULL ;
2023-11-01 12:11:46 -04:00
if ( SQLITE_OK ! = sqlite3_prepare_v2 ( g_chatlog_database , replace_check_query , - 1 , & lmc_stmt , NULL ) ) {
log_error ( " SQLite error in _add_to_db() on selecting original message: %s " , sqlite3_errmsg ( g_chatlog_database ) ) ;
return ;
}
2023-10-17 05:11:10 -04:00
2023-11-01 12:11:46 -04:00
if ( sqlite3_step ( lmc_stmt ) = = SQLITE_ROW ) {
original_message_id = sqlite3_column_int64 ( lmc_stmt , 0 ) ;
const char * from_jid_orig = ( const char * ) sqlite3_column_text ( lmc_stmt , 1 ) ;
// Handle non-XEP-compliant replacement messages (edit->edit->original)
sqlite_int64 tmp = sqlite3_column_int64 ( lmc_stmt , 2 ) ;
original_message_id = tmp ? tmp : original_message_id ;
if ( g_strcmp0 ( from_jid_orig , from_jid - > barejid ) ! = 0 ) {
log_error ( " Mismatch in sender JIDs when trying to do LMC. Corrected message sender: %s. Original message sender: %s. Replace-ID: %s. Message: %s " , from_jid - > barejid , from_jid_orig , message - > replace_id , message - > plain ) ;
cons_show_error ( " %s sent a message correction with mismatched sender. See log for details. " , from_jid - > barejid ) ;
sqlite3_finalize ( lmc_stmt ) ;
return ;
2023-10-17 05:11:10 -04:00
}
2023-11-01 12:11:46 -04:00
} else {
log_warning ( " Got LMC message that does not have original message counterpart in the database from %s " , message - > from_jid - > fulljid ) ;
2023-10-17 05:11:10 -04:00
}
2023-11-01 12:11:46 -04:00
sqlite3_finalize ( lmc_stmt ) ;
2023-10-17 05:11:10 -04:00
}
2023-11-01 12:11:46 -04:00
// stanza-id (XEP-0359) doesn't have to be present in the message.
// But if it's duplicated, it's a serious server-side problem, so we better track it.
if ( message - > stanzaid ) {
2023-11-06 12:20:39 -05:00
auto_sqlite char * duplicate_check_query = sqlite3_mprintf ( " SELECT 1 FROM `ChatLogs` WHERE (`archive_id` = %Q) " ,
2023-11-01 12:11:46 -04:00
message - > stanzaid ) ;
2023-10-14 07:29:17 -04:00
2023-11-01 12:11:46 -04:00
if ( ! duplicate_check_query ) {
log_error ( " Could not allocate memory for SQL duplicate query in log_database_add() " ) ;
return ;
}
2023-10-14 07:29:17 -04:00
2023-11-01 12:11:46 -04:00
sqlite3_stmt * stmt ;
2023-10-14 07:29:17 -04:00
2023-11-01 12:11:46 -04:00
if ( SQLITE_OK = = sqlite3_prepare_v2 ( g_chatlog_database , duplicate_check_query , - 1 , & stmt , NULL ) ) {
if ( sqlite3_step ( stmt ) = = SQLITE_ROW ) {
log_error ( " Duplicate stanza-id found for the message. stanza_id: %s; archive_id: %s; sender: %s; content: %s " , message - > id , message - > stanzaid , from_jid - > barejid , message - > plain ) ;
cons_show_error ( " Got a message with duplicate (server-generated) stanza-id from %s. " , from_jid - > fulljid ) ;
}
sqlite3_finalize ( stmt ) ;
2023-10-14 07:29:17 -04:00
}
}
2023-11-01 12:11:46 -04:00
auto_sqlite char * orig_message_id = original_message_id = = - 1 ? NULL : sqlite3_mprintf ( " %d " , original_message_id ) ;
2023-10-14 07:29:17 -04:00
2023-11-01 12:11:46 -04:00
auto_sqlite char * query = sqlite3_mprintf ( " INSERT INTO `ChatLogs` "
" (`from_jid`, `from_resource`, `to_jid`, `to_resource`, "
" `message`, `timestamp`, `stanza_id`, `archive_id`, "
" `replaces_db_id`, `replace_id`, `type`, `encryption`) "
" VALUES (%Q, %Q, %Q, %Q, %Q, %Q, %Q, %Q, %Q, %Q, %Q, %Q) " ,
2023-10-14 07:29:17 -04:00
from_jid - > barejid ,
2023-11-06 12:20:39 -05:00
from_jid - > resourcepart ,
2023-10-14 07:29:17 -04:00
to_jid - > barejid ,
2023-11-06 12:20:39 -05:00
to_jid - > resourcepart ,
message - > plain ,
date_fmt ,
message - > id ,
message - > stanzaid ,
2023-11-01 12:11:46 -04:00
orig_message_id ,
2023-11-06 12:20:39 -05:00
message - > replace_id ,
type ,
enc ) ;
2021-03-30 11:38:13 -04:00
if ( ! query ) {
2023-10-17 05:28:28 -04:00
log_error ( " Could not allocate memory for SQL insert query in log_database_add() " ) ;
2020-03-19 07:10:54 -04:00
return ;
}
2023-10-14 07:29:17 -04:00
log_debug ( " Writing to DB. Query: %s " , query ) ;
2020-07-07 08:18:57 -04:00
if ( SQLITE_OK ! = sqlite3_exec ( g_chatlog_database , query , NULL , 0 , & err_msg ) ) {
2020-03-19 07:10:54 -04:00
if ( err_msg ) {
2023-11-01 12:11:46 -04:00
log_error ( " SQLite error in _add_to_db(): %s " , err_msg ) ;
2020-03-19 07:10:54 -04:00
sqlite3_free ( err_msg ) ;
} else {
2023-11-01 12:11:46 -04:00
log_error ( " Unknown SQLite error in _add_to_db(). " ) ;
2020-03-19 07:10:54 -04:00
}
2023-10-14 07:29:17 -04:00
} else {
int inserted_rows_count = sqlite3_changes ( g_chatlog_database ) ;
if ( inserted_rows_count < 1 ) {
log_error ( " SQLite did not insert message (rows: %d, id: %s, content: %s) " , inserted_rows_count , message - > id , message - > plain ) ;
}
2020-03-19 07:10:54 -04:00
}
}
2023-11-01 12:11:46 -04:00
static int
_get_db_version ( void )
{
int current_version = - 1 ;
const char * query = " SELECT `version` FROM `DbVersion` LIMIT 1 " ;
sqlite3_stmt * statement ;
if ( sqlite3_prepare_v2 ( g_chatlog_database , query , - 1 , & statement , NULL ) = = SQLITE_OK ) {
if ( sqlite3_step ( statement ) = = SQLITE_ROW ) {
current_version = sqlite3_column_int ( statement , 0 ) ;
}
sqlite3_finalize ( statement ) ;
}
return current_version ;
}
/**
* Migration to version 2 introduces new columns . Returns TRUE on success .
*
* New columns :
* ` replaces_db_id ` database ID for correcting message of the original message
* ` replaced_by_db_id ` database ID for original message of the last correcting message
*/
static gboolean
_migrate_to_v2 ( void )
{
char * err_msg = NULL ;
2023-11-06 12:20:39 -05:00
// from_resource, to_resource, message, timestamp, stanza_id, archive_id, replace_id, type, encryption
2023-11-01 12:11:46 -04:00
const char * sql_statements [ ] = {
" BEGIN TRANSACTION " ,
" ALTER TABLE `ChatLogs` ADD COLUMN `replaces_db_id` INTEGER; " ,
" ALTER TABLE `ChatLogs` ADD COLUMN `replaced_by_db_id` INTEGER; " ,
" UPDATE `ChatLogs` AS A "
" SET `replaces_db_id` = B.`id` "
" FROM `ChatLogs` AS B "
" WHERE A.`replace_id` IS NOT NULL AND A.`replace_id` != '' "
" AND A.`replace_id` = B.`stanza_id` "
" AND A.`from_jid` = B.`from_jid` AND A.`to_jid` = B.`to_jid`; " ,
" UPDATE `ChatLogs` AS A "
" SET `replaced_by_db_id` = B.`id` "
" FROM `ChatLogs` AS B "
" WHERE (A.`replace_id` IS NULL OR A.`replace_id` = '') "
" AND A.`id` = B.`replaces_db_id` "
" AND A.`from_jid` = B.`from_jid`; " ,
2023-11-06 12:20:39 -05:00
" UPDATE ChatLogs SET "
" from_resource = COALESCE(NULLIF(from_resource, ''), NULL), "
" to_resource = COALESCE(NULLIF(to_resource, ''), NULL), "
" message = COALESCE(NULLIF(message, ''), NULL), "
" timestamp = COALESCE(NULLIF(timestamp, ''), NULL), "
" stanza_id = COALESCE(NULLIF(stanza_id, ''), NULL), "
" archive_id = COALESCE(NULLIF(archive_id, ''), NULL), "
" replace_id = COALESCE(NULLIF(replace_id, ''), NULL), "
" type = COALESCE(NULLIF(type, ''), NULL), "
" encryption = COALESCE(NULLIF(encryption, ''), NULL); " ,
" UPDATE `DbVersion` SET `version` = 2; " ,
2023-11-01 12:11:46 -04:00
" END TRANSACTION "
} ;
int statements_count = sizeof ( sql_statements ) / sizeof ( sql_statements [ 0 ] ) ;
for ( int i = 0 ; i < statements_count ; i + + ) {
if ( SQLITE_OK ! = sqlite3_exec ( g_chatlog_database , sql_statements [ i ] , NULL , 0 , & err_msg ) ) {
log_error ( " SQLite error in _migrate_to_v2() on statement %d: %s " , i , err_msg ) ;
if ( err_msg ) {
sqlite3_free ( err_msg ) ;
err_msg = NULL ;
}
goto cleanup ;
}
}
return TRUE ;
cleanup :
if ( SQLITE_OK ! = sqlite3_exec ( g_chatlog_database , " ROLLBACK; " , NULL , 0 , & err_msg ) ) {
log_error ( " [DB Migration] Unable to ROLLBACK: %s " , err_msg ) ;
if ( err_msg ) {
sqlite3_free ( err_msg ) ;
}
}
return FALSE ;
}
// Checks if there is more system storage space available than current database takes + 40% (for indexing and other potential size increases)
static gboolean
_check_available_space_for_db_migration ( char * path_to_db )
{
struct stat file_stat ;
struct statvfs fs_stat ;
if ( statvfs ( path_to_db , & fs_stat ) = = 0 & & stat ( path_to_db , & file_stat ) = = 0 ) {
unsigned long long file_size = file_stat . st_size / 1024 ;
unsigned long long available_space_kb = fs_stat . f_frsize * fs_stat . f_bavail / 1024 ;
log_debug ( " _check_available_space_for_db_migration(): Available space on disk: %llu KB; DB size: %llu KB " , available_space_kb , file_size ) ;
return ( available_space_kb > = ( file_size + ( file_size * 10 / 4 ) ) ) ;
} else {
log_error ( " Error checking available space. " ) ;
return FALSE ;
}
}