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)
|
|
|
|
{
|
|
|
|
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-17 17:26:31 +08:00
|
|
|
dbIntf = new EncryptedDb(path);
|
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-15 22:46:36 +08:00
|
|
|
dbIntf = new PlainDb(path);
|
|
|
|
historyInstance = new HistoryKeeper(dbIntf);
|
2014-10-02 03:03:10 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return historyInstance;
|
|
|
|
}
|
|
|
|
|
2014-10-19 01:38:47 +08:00
|
|
|
bool HistoryKeeper::checkPassword()
|
|
|
|
{
|
|
|
|
if (Settings::getInstance().getEnableLogging())
|
|
|
|
{
|
|
|
|
if (Settings::getInstance().getEncryptLogs())
|
|
|
|
{
|
2014-10-19 01:52:43 +08:00
|
|
|
QString dbpath = getHistoryPath();
|
2014-10-19 01:38:47 +08:00
|
|
|
return EncryptedDb::check(dbpath);
|
|
|
|
} else {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
profile_id -- profile ID (resolves from aliases table)
|
|
|
|
chat_id -- current chat ID (resolves from chats table)
|
|
|
|
sender -- sender's ID (resolves from aliases table)
|
|
|
|
message
|
|
|
|
*/
|
|
|
|
|
2014-10-15 22:46:36 +08:00
|
|
|
db->exec(QString("CREATE TABLE IF NOT EXISTS history (id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp INTEGER NOT NULL, ") +
|
|
|
|
QString("profile_id INTEGER NOT NULL, chat_id INTEGER NOT NULL, sender INTERGER NOT NULL, message TEXT NOT NULL);"));
|
|
|
|
db->exec(QString("CREATE TABLE IF NOT EXISTS aliases (id INTEGER PRIMARY KEY AUTOINCREMENT, user_id TEXT UNIQUE NOT NULL);"));
|
|
|
|
db->exec(QString("CREATE TABLE IF NOT EXISTS chats (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT UNIQUE NOT NULL, ctype INTEGER NOT NULL);"));
|
2014-10-02 03:03:10 +08:00
|
|
|
|
|
|
|
updateChatsID();
|
|
|
|
updateAliases();
|
|
|
|
}
|
|
|
|
|
|
|
|
HistoryKeeper::~HistoryKeeper()
|
|
|
|
{
|
2014-10-15 22:46:36 +08:00
|
|
|
delete db;
|
2014-10-02 03:03:10 +08:00
|
|
|
}
|
|
|
|
|
2014-10-14 22:36:17 +08:00
|
|
|
void HistoryKeeper::addChatEntry(const QString& chat, const QString& message, const QString& sender, const QDateTime &dt)
|
2014-10-02 03:03:10 +08:00
|
|
|
{
|
|
|
|
int chat_id = getChatID(chat, ctSingle).first;
|
|
|
|
int sender_id = getAliasID(sender);
|
2014-10-10 23:45:58 +08:00
|
|
|
int profile_id = getCurrentProfileID();
|
2014-10-02 03:03:10 +08:00
|
|
|
|
2014-10-15 22:46:36 +08:00
|
|
|
db->exec(QString("INSERT INTO history (profile_id, timestamp, chat_id, sender, message)") +
|
|
|
|
QString("VALUES (%1, %2, %3, %4, '%5');")
|
|
|
|
.arg(profile_id).arg(dt.toMSecsSinceEpoch()).arg(chat_id).arg(sender_id).arg(wrapMessage(message)));
|
2014-10-02 03:03:10 +08:00
|
|
|
}
|
|
|
|
|
2014-10-10 23:45:58 +08:00
|
|
|
QList<HistoryKeeper::HistMessage> HistoryKeeper::getChatHistory(HistoryKeeper::ChatType ct, const QString &profile,
|
|
|
|
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;
|
2014-10-14 21:52:23 +08:00
|
|
|
int profile_id = getAliasID(profile);
|
2014-10-02 03:03:10 +08:00
|
|
|
|
|
|
|
QSqlQuery dbAnswer;
|
|
|
|
if (ct == ctSingle)
|
|
|
|
{
|
2014-10-15 22:46:36 +08:00
|
|
|
dbAnswer = db->exec(QString("SELECT timestamp, user_id, message FROM history INNER JOIN aliases ON history.sender = aliases.id ") +
|
|
|
|
QString("AND timestamp BETWEEN %1 AND %2 AND chat_id = %3 AND profile_id = %4;")
|
|
|
|
.arg(time64_from).arg(time64_to).arg(chat_id).arg(profile_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())
|
|
|
|
{
|
|
|
|
QString sender = dbAnswer.value(1).toString();
|
|
|
|
QString message = unWrapMessage(dbAnswer.value(2).toString());
|
2014-10-14 13:49:27 +08:00
|
|
|
qint64 timeInt = dbAnswer.value(0).toLongLong();
|
|
|
|
QDateTime time = QDateTime::fromMSecsSinceEpoch(timeInt);
|
2014-10-02 03:03:10 +08:00
|
|
|
|
2014-10-14 22:36:17 +08:00
|
|
|
res.push_back({sender,message,time});
|
2014-10-02 03:03:10 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
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-10-14 13:49:27 +08:00
|
|
|
void 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
|
|
|
|
}
|
|
|
|
|
|
|
|
int HistoryKeeper::getCurrentProfileID()
|
|
|
|
{
|
|
|
|
// for many profiles
|
|
|
|
return getAliasID(Core::getInstance()->getSelfId().publicKey);
|
2014-10-02 03:03:10 +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
|
|
|
|
|
|
|
QString HistoryKeeper::getHistoryPath()
|
|
|
|
{
|
|
|
|
QDir baseDir(Settings::getInstance().getSettingsDirPath());
|
|
|
|
QString currentProfile = Settings::getInstance().getCurrentProfile();
|
|
|
|
|
|
|
|
if (Settings::getInstance().getEncryptLogs())
|
|
|
|
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"));
|
|
|
|
}
|