2014-10-02 03:03:10 +08:00
/*
Copyright ( C ) 2014 by Project Tox < https : //tox.im>
This file is part of qTox , a Qt - based graphical interface for Tox .
This program is libre 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 COPYING file for more details .
*/
# include "historykeeper.h"
# include "misc/settings.h"
2014-10-10 23:45:58 +08:00
# include "core.h"
2014-10-02 03:03:10 +08:00
# include <QSqlError>
# include <QFile>
# include <QDir>
# include <QSqlQuery>
# include <QVariant>
# include <QDebug>
2014-10-10 23:45:58 +08:00
# include <QTemporaryFile>
2014-10-02 03:03:10 +08:00
2014-10-15 22:46:36 +08:00
# include "misc/db/plaindb.h"
# include "misc/db/encrypteddb.h"
2014-10-02 03:03:10 +08:00
static HistoryKeeper * historyInstance = nullptr ;
HistoryKeeper * HistoryKeeper : : getInstance ( )
{
if ( historyInstance = = nullptr )
{
2014-10-19 16:48:10 +08:00
QList < QString > initLst ;
initLst . push_back ( QString ( " CREATE TABLE IF NOT EXISTS history (id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp INTEGER NOT NULL, " ) +
2014-11-10 22:58:07 +08:00
QString ( " chat_id INTEGER NOT NULL, sender INTEGER NOT NULL, message TEXT NOT NULL); " ) ) ;
2014-10-19 16:48:10 +08:00
initLst . push_back ( QString ( " CREATE TABLE IF NOT EXISTS aliases (id INTEGER PRIMARY KEY AUTOINCREMENT, user_id TEXT UNIQUE NOT NULL); " ) ) ;
initLst . push_back ( QString ( " CREATE TABLE IF NOT EXISTS chats (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT UNIQUE NOT NULL, ctype INTEGER NOT NULL); " ) ) ;
2014-11-10 22:58:07 +08:00
initLst . push_back ( QString ( " CREATE TABLE IF NOT EXISTS sent_status (id INTEGER PRIMARY KEY AUTOINCREMENT, status INTEGER NOT NULL DEFAULT 0); " ) ) ;
2014-10-19 16:48:10 +08:00
2014-10-02 03:03:10 +08:00
QString path ( " :memory: " ) ;
2014-10-15 22:46:36 +08:00
GenericDdInterface * dbIntf ;
2014-10-02 03:03:10 +08:00
if ( Settings : : getInstance ( ) . getEnableLogging ( ) )
{
2014-10-15 22:46:36 +08:00
bool encrypted = Settings : : getInstance ( ) . getEncryptLogs ( ) ;
if ( encrypted )
{
2014-10-19 01:38:47 +08:00
path = getHistoryPath ( ) ;
2014-10-19 16:48:10 +08:00
dbIntf = new EncryptedDb ( path , initLst ) ;
2014-10-15 22:46:36 +08:00
historyInstance = new HistoryKeeper ( dbIntf ) ;
return historyInstance ;
} else {
2014-10-19 01:38:47 +08:00
path = getHistoryPath ( ) ;
2014-10-15 22:46:36 +08:00
}
2014-10-02 03:03:10 +08:00
}
2014-10-19 16:48:10 +08:00
dbIntf = new PlainDb ( path , initLst ) ;
2014-10-15 22:46:36 +08:00
historyInstance = new HistoryKeeper ( dbIntf ) ;
2014-10-02 03:03:10 +08:00
}
return historyInstance ;
}
2014-12-04 23:22:17 +08:00
bool HistoryKeeper : : checkPassword ( int encrypted )
2014-10-19 01:38:47 +08:00
{
2014-12-04 23:22:17 +08:00
if ( ! Settings : : getInstance ( ) . getEnableLogging ( ) & & ( encrypted = = - 1 ) )
return true ;
if ( ( encrypted = = 1 ) | | ( encrypted = = - 1 & & Settings : : getInstance ( ) . getEncryptLogs ( ) ) )
return EncryptedDb : : check ( getHistoryPath ( Settings : : getInstance ( ) . getCurrentProfile ( ) , encrypted ) ) ;
2014-11-27 23:38:23 +08:00
return true ;
2014-10-19 01:38:47 +08:00
}
2014-10-15 22:46:36 +08:00
HistoryKeeper : : HistoryKeeper ( GenericDdInterface * db_ ) :
db ( db_ )
2014-10-02 03:03:10 +08:00
{
2014-10-11 17:53:25 +08:00
/*
DB format
chats :
* name - > id map
id - - auto - incrementing number
name - - chat ' s name ( for user to user conversation it is opposite user public key )
ctype - - chat type , reserved for group chats
alisases :
* user_id - > id map
id - - auto - incrementing number
name - - user ' s public key
history :
id - - auto - incrementing number
timestamp
chat_id - - current chat ID ( resolves from chats table )
sender - - sender ' s ID ( resolves from aliases table )
message
*/
2014-11-10 22:58:07 +08:00
// for old tables:
QSqlQuery ans = db - > exec ( " select seq from sqlite_sequence where name= \" history \" ; " ) ;
if ( ans . first ( ) )
{
2014-11-11 09:57:06 +08:00
int idMax = ans . value ( 0 ) . toInt ( ) ;
2014-11-10 22:58:07 +08:00
QSqlQuery ret = db - > exec ( " select seq from sqlite_sequence where name= \" sent_status \" ; " ) ;
int idCur = 0 ;
if ( ret . first ( ) )
{
idCur = ret . value ( 0 ) . toInt ( ) ;
}
2014-11-11 09:57:06 +08:00
if ( idCur ! = idMax )
2014-11-10 22:58:07 +08:00
{
2014-11-19 21:46:50 +08:00
QString cmd = QString ( " INSERT INTO sent_status (id, status) VALUES (%1, 1); " ) . arg ( idMax ) ;
2014-11-10 22:58:07 +08:00
db - > exec ( cmd ) ;
}
}
2014-10-02 03:03:10 +08:00
updateChatsID ( ) ;
updateAliases ( ) ;
2014-11-09 20:32:19 +08:00
2014-11-12 20:56:24 +08:00
setSyncType ( Settings : : getInstance ( ) . getDbSyncType ( ) ) ;
2014-11-17 18:57:23 +08:00
messageID = 0 ;
2014-11-09 20:32:19 +08:00
QSqlQuery sqlAnswer = db - > exec ( " select seq from sqlite_sequence where name= \" history \" ; " ) ;
2014-11-17 18:57:23 +08:00
if ( sqlAnswer . first ( ) )
2014-12-04 23:22:17 +08:00
messageID = sqlAnswer . value ( 0 ) . toLongLong ( ) ;
2014-10-02 03:03:10 +08:00
}
HistoryKeeper : : ~ HistoryKeeper ( )
{
2014-10-15 22:46:36 +08:00
delete db ;
2014-10-02 03:03:10 +08:00
}
2014-11-27 23:38:23 +08:00
void HistoryKeeper : : reencrypt ( QString newpw )
{
2014-11-28 00:58:33 +08:00
// TODO: this needs to appropriately set the core password as well
2014-11-27 23:38:23 +08:00
// if newpw.isEmpty(), then use the other core password
}
2014-12-04 23:22:17 +08:00
qint64 HistoryKeeper : : addChatEntry ( const QString & chat , const QString & message , const QString & sender , const QDateTime & dt , bool isSent )
2014-10-02 03:03:10 +08:00
{
2014-12-04 23:22:17 +08:00
QList < QString > cmds = generateAddChatEntryCmd ( chat , message , sender , dt , isSent ) ;
2014-10-02 03:03:10 +08:00
2014-11-19 21:46:50 +08:00
db - > exec ( " BEGIN TRANSACTION; " ) ;
2014-12-04 23:22:17 +08:00
for ( auto & it : cmds )
db - > exec ( it ) ;
2014-11-19 21:46:50 +08:00
db - > exec ( " COMMIT TRANSACTION; " ) ;
2014-11-09 20:32:19 +08:00
messageID + + ;
return messageID ;
2014-10-02 03:03:10 +08:00
}
2014-10-22 22:14:42 +08:00
QList < HistoryKeeper : : HistMessage > HistoryKeeper : : getChatHistory ( HistoryKeeper : : ChatType ct , const QString & chat ,
const QDateTime & time_from , const QDateTime & time_to )
2014-10-02 03:03:10 +08:00
{
QList < HistMessage > res ;
2014-10-14 13:49:27 +08:00
qint64 time64_from = time_from . toMSecsSinceEpoch ( ) ;
qint64 time64_to = time_to . toMSecsSinceEpoch ( ) ;
2014-10-02 03:03:10 +08:00
int chat_id = getChatID ( chat , ct ) . first ;
QSqlQuery dbAnswer ;
if ( ct = = ctSingle )
{
2014-11-11 21:16:00 +08:00
dbAnswer = db - > exec ( QString ( " SELECT history.id, timestamp, user_id, message, status FROM history LEFT JOIN sent_status ON history.id = sent_status.id " ) +
QString ( " INNER JOIN aliases ON history.sender = aliases.id AND timestamp BETWEEN %1 AND %2 AND chat_id = %3; " )
2014-10-22 22:14:42 +08:00
. arg ( time64_from ) . arg ( time64_to ) . arg ( chat_id ) ) ;
2014-10-02 03:03:10 +08:00
} else {
2014-10-10 23:45:58 +08:00
// no groupchats yet
2014-10-02 03:03:10 +08:00
}
while ( dbAnswer . next ( ) )
{
2014-11-10 19:30:56 +08:00
qint64 id = dbAnswer . value ( 0 ) . toLongLong ( ) ;
qint64 timeInt = dbAnswer . value ( 1 ) . toLongLong ( ) ;
QString sender = dbAnswer . value ( 2 ) . toString ( ) ;
QString message = unWrapMessage ( dbAnswer . value ( 3 ) . toString ( ) ) ;
2014-11-10 22:58:07 +08:00
bool isSent = true ;
if ( ! dbAnswer . value ( 4 ) . isNull ( ) )
isSent = dbAnswer . value ( 4 ) . toBool ( ) ;
2014-11-10 19:30:56 +08:00
2014-10-14 13:49:27 +08:00
QDateTime time = QDateTime : : fromMSecsSinceEpoch ( timeInt ) ;
2014-10-02 03:03:10 +08:00
2014-12-04 23:22:17 +08:00
res . push_back ( HistMessage ( id , " " , sender , message , time , isSent ) ) ;
2014-10-02 03:03:10 +08:00
}
return res ;
}
2014-12-04 23:22:17 +08:00
QList < HistoryKeeper : : HistMessage > HistoryKeeper : : exportMessages ( )
{
QSqlQuery dbAnswer ;
dbAnswer = db - > exec ( QString ( " SELECT history.id, timestamp, user_id, message, status, name FROM history LEFT JOIN sent_status ON history.id = sent_status.id " ) +
QString ( " INNER JOIN aliases ON history.sender = aliases.id INNER JOIN chats ON history.chat_id = chats.id; " ) ) ;
QList < HistMessage > res ;
while ( dbAnswer . next ( ) )
{
qint64 id = dbAnswer . value ( 0 ) . toLongLong ( ) ;
qint64 timeInt = dbAnswer . value ( 1 ) . toLongLong ( ) ;
QString sender = dbAnswer . value ( 2 ) . toString ( ) ;
QString message = unWrapMessage ( dbAnswer . value ( 3 ) . toString ( ) ) ;
bool isSent = true ;
if ( ! dbAnswer . value ( 4 ) . isNull ( ) )
isSent = dbAnswer . value ( 4 ) . toBool ( ) ;
QString chat = dbAnswer . value ( 5 ) . toString ( ) ;
QDateTime time = QDateTime : : fromMSecsSinceEpoch ( timeInt ) ;
res . push_back ( HistMessage ( id , chat , sender , message , time , isSent ) ) ;
}
return res ;
}
void HistoryKeeper : : importMessages ( const QList < HistoryKeeper : : HistMessage > & lst )
{
db - > exec ( " BEGIN TRANSACTION; " ) ;
for ( const HistMessage & msg : lst )
{
QList < QString > cmds = generateAddChatEntryCmd ( msg . chat , msg . message , msg . sender , msg . timestamp , msg . isSent ) ;
for ( auto & it : cmds )
db - > exec ( it ) ;
messageID + + ;
}
db - > exec ( " COMMIT TRANSACTION; " ) ;
}
QList < QString > HistoryKeeper : : generateAddChatEntryCmd ( const QString & chat , const QString & message , const QString & sender , const QDateTime & dt , bool isSent )
{
QList < QString > cmds ;
int chat_id = getChatID ( chat , ctSingle ) . first ;
int sender_id = getAliasID ( sender ) ;
cmds . push_back ( QString ( " INSERT INTO history (timestamp, chat_id, sender, message) VALUES (%1, %2, %3, '%4'); " )
. arg ( dt . toMSecsSinceEpoch ( ) ) . arg ( chat_id ) . arg ( sender_id ) . arg ( wrapMessage ( message ) ) ) ;
cmds . push_back ( QString ( " INSERT INTO sent_status (status) VALUES (%1); " ) . arg ( isSent ) ) ;
return cmds ;
}
2014-10-02 03:03:10 +08:00
QString HistoryKeeper : : wrapMessage ( const QString & str )
{
QString wrappedMessage ( str ) ;
wrappedMessage . replace ( " ' " , " '' " ) ;
return wrappedMessage ;
}
QString HistoryKeeper : : unWrapMessage ( const QString & str )
{
QString unWrappedMessage ( str ) ;
unWrappedMessage . replace ( " '' " , " ' " ) ;
return unWrappedMessage ;
}
void HistoryKeeper : : updateChatsID ( )
{
2014-10-15 22:46:36 +08:00
auto dbAnswer = db - > exec ( QString ( " SELECT * FROM chats; " ) ) ;
2014-10-02 03:03:10 +08:00
chats . clear ( ) ;
while ( dbAnswer . next ( ) )
{
QString name = dbAnswer . value ( 1 ) . toString ( ) ;
int id = dbAnswer . value ( 0 ) . toInt ( ) ;
2014-10-14 21:43:03 +08:00
ChatType ctype = convertToChatType ( dbAnswer . value ( 2 ) . toInt ( ) ) ;
2014-10-02 03:03:10 +08:00
chats [ name ] = { id , ctype } ;
}
}
void HistoryKeeper : : updateAliases ( )
{
2014-10-15 22:46:36 +08:00
auto dbAnswer = db - > exec ( QString ( " SELECT * FROM aliases; " ) ) ;
2014-10-02 03:03:10 +08:00
aliases . clear ( ) ;
while ( dbAnswer . next ( ) )
{
QString user_id = dbAnswer . value ( 1 ) . toString ( ) ;
int id = dbAnswer . value ( 0 ) . toInt ( ) ;
aliases [ user_id ] = id ;
}
}
QPair < int , HistoryKeeper : : ChatType > HistoryKeeper : : getChatID ( const QString & id_str , ChatType ct )
{
auto it = chats . find ( id_str ) ;
if ( it ! = chats . end ( ) )
return it . value ( ) ;
2014-10-15 22:46:36 +08:00
db - > exec ( QString ( " INSERT INTO chats (name, ctype) VALUES ('%1', '%2'); " ) . arg ( id_str ) . arg ( ct ) ) ;
2014-10-02 03:03:10 +08:00
updateChatsID ( ) ;
return getChatID ( id_str , ct ) ;
}
int HistoryKeeper : : getAliasID ( const QString & id_str )
{
auto it = aliases . find ( id_str ) ;
if ( it ! = aliases . end ( ) )
return it . value ( ) ;
2014-10-15 22:46:36 +08:00
db - > exec ( QString ( " INSERT INTO aliases (user_id) VALUES ('%1'); " ) . arg ( id_str ) ) ;
2014-10-02 03:03:10 +08:00
updateAliases ( ) ;
return getAliasID ( id_str ) ;
}
void HistoryKeeper : : resetInstance ( )
{
if ( historyInstance = = nullptr )
return ;
delete historyInstance ;
historyInstance = nullptr ;
}
2014-12-04 23:22:17 +08:00
qint64 HistoryKeeper : : addGroupChatEntry ( const QString & chat , const QString & message , const QString & sender , const QDateTime & dt )
2014-10-02 03:03:10 +08:00
{
Q_UNUSED ( chat )
Q_UNUSED ( message )
Q_UNUSED ( sender )
2014-10-14 13:49:27 +08:00
Q_UNUSED ( dt )
2014-10-10 23:45:58 +08:00
// no groupchats yet
2014-11-09 20:32:19 +08:00
return - 1 ;
2014-10-10 23:45:58 +08:00
}
2014-10-14 21:43:03 +08:00
HistoryKeeper : : ChatType HistoryKeeper : : convertToChatType ( int ct )
{
if ( ct < 0 | | ct > 1 )
return ctSingle ;
return static_cast < ChatType > ( ct ) ;
}
2014-10-19 01:38:47 +08:00
2014-12-03 08:48:50 +08:00
QString HistoryKeeper : : getHistoryPath ( QString currentProfile , int encrypted )
2014-10-19 01:38:47 +08:00
{
2014-12-03 08:48:50 +08:00
QDir baseDir ( Settings : : getSettingsDirPath ( ) ) ;
if ( currentProfile . isEmpty ( ) )
currentProfile = Settings : : getInstance ( ) . getCurrentProfile ( ) ;
2014-10-19 01:38:47 +08:00
2014-12-03 08:48:50 +08:00
if ( encrypted = = 1 | | ( encrypted = = - 1 & & Settings : : getInstance ( ) . getEncryptLogs ( ) ) )
2014-10-19 01:38:47 +08:00
return baseDir . filePath ( currentProfile + " .qtox_history.encrypted " ) ;
else
return baseDir . filePath ( currentProfile + " .qtox_history " ) ;
}
void HistoryKeeper : : renameHistory ( QString from , QString to )
{
resetInstance ( ) ;
QFile fileEnc ( QDir ( Settings : : getInstance ( ) . getSettingsDirPath ( ) ) . filePath ( from + " .qtox_history.encrypted " ) ) ;
if ( fileEnc . exists ( ) )
fileEnc . rename ( QDir ( Settings : : getInstance ( ) . getSettingsDirPath ( ) ) . filePath ( to + " .qtox_history.encrypted " ) ) ;
QFile filePlain ( QDir ( Settings : : getInstance ( ) . getSettingsDirPath ( ) ) . filePath ( from + " .qtox_history " ) ) ;
if ( filePlain . exists ( ) )
filePlain . rename ( QDir ( Settings : : getInstance ( ) . getSettingsDirPath ( ) ) . filePath ( to + " .qtox_history " ) ) ;
}
2014-11-09 20:32:19 +08:00
void HistoryKeeper : : markAsSent ( int m_id )
{
2014-11-10 22:58:07 +08:00
db - > exec ( QString ( " UPDATE sent_status SET status = 1 WHERE id = %1; " ) . arg ( m_id ) ) ;
2014-11-09 20:32:19 +08:00
}
2014-11-12 20:56:24 +08:00
void HistoryKeeper : : setSyncType ( Db : : syncType sType )
{
QString syncCmd ;
switch ( sType ) {
case Db : : syncType : : stFull :
syncCmd = " FULL " ;
break ;
case Db : : syncType : : stNormal :
syncCmd = " NORMAL " ;
break ;
case Db : : syncType : : stOff :
syncCmd = " OFF " ;
break ;
default :
syncCmd = " FULL " ;
break ;
}
db - > exec ( QString ( " PRAGMA synchronous=%1; " ) . arg ( syncCmd ) ) ;
}
2014-11-04 00:44:01 +08:00
bool HistoryKeeper : : isFileExist ( )
{
QString path = getHistoryPath ( ) ;
QFile file ( path ) ;
return file . exists ( ) ;
}
2014-12-04 23:22:17 +08:00
bool HistoryKeeper : : removeHistory ( int encrypted )
{
Q_UNUSED ( encrypted ) ;
resetInstance ( ) ;
QString path = getHistoryPath ( ) ;
QFile DbFile ( path ) ;
return DbFile . remove ( ) ;
}