2016-10-20 17:00:05 +08:00
|
|
|
/*
|
2018-04-13 04:02:28 +08:00
|
|
|
Copyright © 2015-2018 by The qTox Project Contributors
|
2016-10-20 17:00:05 +08:00
|
|
|
|
|
|
|
This file is part of qTox, a Qt-based graphical interface for Tox.
|
|
|
|
|
|
|
|
qTox 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.
|
|
|
|
|
|
|
|
qTox 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 qTox. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
2015-12-17 18:24:01 +08:00
|
|
|
#include <QDebug>
|
|
|
|
#include <cassert>
|
|
|
|
|
2016-10-20 17:00:05 +08:00
|
|
|
#include "history.h"
|
|
|
|
#include "profile.h"
|
|
|
|
#include "settings.h"
|
2017-02-26 19:52:45 +08:00
|
|
|
#include "db/rawdatabase.h"
|
2015-12-17 18:24:01 +08:00
|
|
|
|
2016-07-27 06:20:54 +08:00
|
|
|
/**
|
2016-08-01 16:21:23 +08:00
|
|
|
* @class History
|
|
|
|
* @brief Interacts with the profile database to save the chat history.
|
|
|
|
*
|
|
|
|
* @var QHash<QString, int64_t> History::peers
|
|
|
|
* @brief Maps friend public keys to unique IDs by index.
|
|
|
|
* Caches mappings to speed up message saving.
|
|
|
|
*/
|
2016-07-27 06:20:54 +08:00
|
|
|
|
2018-04-24 07:51:26 +08:00
|
|
|
static constexpr int NUM_MESSAGES_DEFAULT = 100; // arbitrary number of messages loaded when not loading by date
|
2018-09-24 16:05:43 +08:00
|
|
|
static constexpr int SCHEMA_VERSION = 0;
|
2018-04-24 07:51:26 +08:00
|
|
|
|
2016-07-27 06:20:54 +08:00
|
|
|
/**
|
2016-10-20 17:00:05 +08:00
|
|
|
* @brief Prepares the database to work with the history.
|
|
|
|
* @param db This database will be prepared for use with the history.
|
2016-08-01 16:21:23 +08:00
|
|
|
*/
|
2016-10-20 17:00:05 +08:00
|
|
|
History::History(std::shared_ptr<RawDatabase> db)
|
|
|
|
: db(db)
|
2015-12-17 18:24:01 +08:00
|
|
|
{
|
2017-02-26 19:52:45 +08:00
|
|
|
if (!isValid()) {
|
2016-10-20 17:00:05 +08:00
|
|
|
qWarning() << "Database not open, init failed";
|
|
|
|
return;
|
|
|
|
}
|
2015-12-17 18:24:01 +08:00
|
|
|
|
2018-09-24 16:05:43 +08:00
|
|
|
dbSchemaUpgrade();
|
|
|
|
|
|
|
|
// dbSchemaUpgrade may have put us in an invalid state
|
|
|
|
if (!isValid()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-02-26 19:52:45 +08:00
|
|
|
db->execLater(
|
|
|
|
"CREATE TABLE IF NOT EXISTS peers (id INTEGER PRIMARY KEY, public_key TEXT NOT NULL "
|
|
|
|
"UNIQUE);"
|
|
|
|
"CREATE TABLE IF NOT EXISTS aliases (id INTEGER PRIMARY KEY, owner INTEGER,"
|
|
|
|
"display_name BLOB NOT NULL, UNIQUE(owner, display_name));"
|
|
|
|
"CREATE TABLE IF NOT EXISTS history (id INTEGER PRIMARY KEY, timestamp INTEGER NOT NULL, "
|
|
|
|
"chat_id INTEGER NOT NULL, sender_alias INTEGER NOT NULL, "
|
|
|
|
"message BLOB NOT NULL);"
|
|
|
|
"CREATE TABLE IF NOT EXISTS faux_offline_pending (id INTEGER PRIMARY KEY);");
|
2016-10-20 17:00:05 +08:00
|
|
|
|
|
|
|
// Cache our current peers
|
2017-02-26 19:52:45 +08:00
|
|
|
db->execLater(RawDatabase::Query{"SELECT public_key, id FROM peers;",
|
|
|
|
[this](const QVector<QVariant>& row) {
|
|
|
|
peers[row[0].toString()] = row[1].toInt();
|
|
|
|
}});
|
2015-12-17 18:24:01 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
History::~History()
|
|
|
|
{
|
2017-02-26 19:52:45 +08:00
|
|
|
if (!isValid()) {
|
2016-10-20 17:00:05 +08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-12-17 18:24:01 +08:00
|
|
|
// We could have execLater requests pending with a lambda attached,
|
|
|
|
// so clear the pending transactions first
|
2016-10-20 17:00:05 +08:00
|
|
|
db->sync();
|
2015-12-17 18:24:01 +08:00
|
|
|
}
|
|
|
|
|
2016-07-27 06:20:54 +08:00
|
|
|
/**
|
2016-08-01 16:21:23 +08:00
|
|
|
* @brief Checks if the database was opened successfully
|
|
|
|
* @return True if database if opened, false otherwise.
|
|
|
|
*/
|
2015-12-17 18:24:01 +08:00
|
|
|
bool History::isValid()
|
|
|
|
{
|
2016-10-20 17:00:05 +08:00
|
|
|
return db && db->isOpen();
|
2015-12-19 23:51:15 +08:00
|
|
|
}
|
|
|
|
|
2018-10-05 10:24:39 +08:00
|
|
|
/**
|
|
|
|
* @brief Checks if a friend has chat history
|
|
|
|
* @param friendPk
|
|
|
|
* @return True if has, false otherwise.
|
|
|
|
*/
|
|
|
|
bool History::isHistoryExistence(const QString& friendPk)
|
|
|
|
{
|
|
|
|
return !getChatHistoryDefaultNum(friendPk).isEmpty();
|
|
|
|
}
|
|
|
|
|
2016-07-27 06:20:54 +08:00
|
|
|
/**
|
2016-08-01 16:21:23 +08:00
|
|
|
* @brief Erases all the chat history from the database.
|
|
|
|
*/
|
2015-12-17 18:24:01 +08:00
|
|
|
void History::eraseHistory()
|
|
|
|
{
|
2017-02-26 19:52:45 +08:00
|
|
|
if (!isValid()) {
|
2016-10-20 17:00:05 +08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
db->execNow("DELETE FROM faux_offline_pending;"
|
2017-02-26 19:52:45 +08:00
|
|
|
"DELETE FROM history;"
|
|
|
|
"DELETE FROM aliases;"
|
|
|
|
"DELETE FROM peers;"
|
|
|
|
"VACUUM;");
|
2015-12-17 18:24:01 +08:00
|
|
|
}
|
|
|
|
|
2016-07-27 06:20:54 +08:00
|
|
|
/**
|
2016-08-01 16:21:23 +08:00
|
|
|
* @brief Erases the chat history with one friend.
|
|
|
|
* @param friendPk Friend public key to erase.
|
|
|
|
*/
|
2016-10-20 17:00:05 +08:00
|
|
|
void History::removeFriendHistory(const QString& friendPk)
|
2015-12-17 18:24:01 +08:00
|
|
|
{
|
2017-02-26 19:52:45 +08:00
|
|
|
if (!isValid()) {
|
2016-10-20 17:00:05 +08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-02-26 19:52:45 +08:00
|
|
|
if (!peers.contains(friendPk)) {
|
2015-12-17 18:24:01 +08:00
|
|
|
return;
|
2016-10-20 17:00:05 +08:00
|
|
|
}
|
|
|
|
|
2015-12-17 18:24:01 +08:00
|
|
|
int64_t id = peers[friendPk];
|
|
|
|
|
2017-02-26 19:52:45 +08:00
|
|
|
QString queryText = QString("DELETE FROM faux_offline_pending "
|
|
|
|
"WHERE faux_offline_pending.id IN ( "
|
|
|
|
" SELECT faux_offline_pending.id FROM faux_offline_pending "
|
|
|
|
" LEFT JOIN history ON faux_offline_pending.id = history.id "
|
|
|
|
" WHERE chat_id=%1 "
|
|
|
|
"); "
|
|
|
|
"DELETE FROM history WHERE chat_id=%1; "
|
|
|
|
"DELETE FROM aliases WHERE owner=%1; "
|
|
|
|
"DELETE FROM peers WHERE id=%1; "
|
|
|
|
"VACUUM;")
|
|
|
|
.arg(id);
|
|
|
|
|
|
|
|
if (db->execNow(queryText)) {
|
2015-12-17 18:24:01 +08:00
|
|
|
peers.remove(friendPk);
|
2017-02-26 19:52:45 +08:00
|
|
|
} else {
|
2015-12-17 18:24:01 +08:00
|
|
|
qWarning() << "Failed to remove friend's history";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-07-27 06:20:54 +08:00
|
|
|
/**
|
2016-08-01 16:21:23 +08:00
|
|
|
* @brief Generate query to insert new message in database
|
|
|
|
* @param friendPk Friend publick key to save.
|
|
|
|
* @param message Message to save.
|
|
|
|
* @param sender Sender to save.
|
|
|
|
* @param time Time of message sending.
|
|
|
|
* @param isSent True if message was already sent.
|
|
|
|
* @param dispName Name, which should be displayed.
|
|
|
|
* @param insertIdCallback Function, called after query execution.
|
|
|
|
*/
|
2017-02-26 19:52:45 +08:00
|
|
|
QVector<RawDatabase::Query>
|
|
|
|
History::generateNewMessageQueries(const QString& friendPk, const QString& message,
|
|
|
|
const QString& sender, const QDateTime& time, bool isSent,
|
|
|
|
QString dispName, std::function<void(int64_t)> insertIdCallback)
|
2015-12-17 18:24:01 +08:00
|
|
|
{
|
|
|
|
QVector<RawDatabase::Query> queries;
|
|
|
|
|
|
|
|
// Get the db id of the peer we're chatting with
|
|
|
|
int64_t peerId;
|
2017-02-26 19:52:45 +08:00
|
|
|
if (peers.contains(friendPk)) {
|
2015-12-17 18:24:01 +08:00
|
|
|
peerId = peers[friendPk];
|
2017-02-26 19:52:45 +08:00
|
|
|
} else {
|
|
|
|
if (peers.isEmpty()) {
|
2015-12-17 18:24:01 +08:00
|
|
|
peerId = 0;
|
2017-02-26 19:52:45 +08:00
|
|
|
} else {
|
2016-10-20 17:00:05 +08:00
|
|
|
peerId = *std::max_element(peers.begin(), peers.end()) + 1;
|
|
|
|
}
|
|
|
|
|
2015-12-17 18:24:01 +08:00
|
|
|
peers[friendPk] = peerId;
|
2016-10-20 17:00:05 +08:00
|
|
|
queries += RawDatabase::Query(("INSERT INTO peers (id, public_key) "
|
2017-02-26 19:52:45 +08:00
|
|
|
"VALUES (%1, '"
|
|
|
|
+ friendPk + "');")
|
|
|
|
.arg(peerId));
|
2015-12-17 18:24:01 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Get the db id of the sender of the message
|
|
|
|
int64_t senderId;
|
2017-02-26 19:52:45 +08:00
|
|
|
if (peers.contains(sender)) {
|
2015-12-17 18:24:01 +08:00
|
|
|
senderId = peers[sender];
|
2017-02-26 19:52:45 +08:00
|
|
|
} else {
|
|
|
|
if (peers.isEmpty()) {
|
2015-12-17 18:24:01 +08:00
|
|
|
senderId = 0;
|
2017-02-26 19:52:45 +08:00
|
|
|
} else {
|
2016-10-20 17:00:05 +08:00
|
|
|
senderId = *std::max_element(peers.begin(), peers.end()) + 1;
|
|
|
|
}
|
|
|
|
|
2015-12-17 18:24:01 +08:00
|
|
|
peers[sender] = senderId;
|
2016-10-20 17:00:05 +08:00
|
|
|
queries += RawDatabase::Query{("INSERT INTO peers (id, public_key) "
|
2017-02-26 19:52:45 +08:00
|
|
|
"VALUES (%1, '"
|
|
|
|
+ sender + "');")
|
|
|
|
.arg(senderId)};
|
2015-12-17 18:24:01 +08:00
|
|
|
}
|
|
|
|
|
2017-02-26 19:52:45 +08:00
|
|
|
queries += RawDatabase::Query(
|
|
|
|
QString("INSERT OR IGNORE INTO aliases (owner, display_name) VALUES (%1, ?);").arg(senderId),
|
|
|
|
{dispName.toUtf8()});
|
2015-12-17 18:24:01 +08:00
|
|
|
|
2017-02-26 19:52:45 +08:00
|
|
|
// If the alias already existed, the insert will ignore the conflict and last_insert_rowid()
|
|
|
|
// will return garbage,
|
2015-12-17 18:24:01 +08:00
|
|
|
// so we have to check changes() and manually fetch the row ID in this case
|
2017-02-26 19:52:45 +08:00
|
|
|
queries +=
|
|
|
|
RawDatabase::Query(QString(
|
|
|
|
"INSERT INTO history (timestamp, chat_id, message, sender_alias) "
|
|
|
|
"VALUES (%1, %2, ?, ("
|
|
|
|
" CASE WHEN changes() IS 0 THEN ("
|
|
|
|
" SELECT id FROM aliases WHERE owner=%3 AND display_name=?)"
|
|
|
|
" ELSE last_insert_rowid() END"
|
|
|
|
"));")
|
|
|
|
.arg(time.toMSecsSinceEpoch())
|
|
|
|
.arg(peerId)
|
|
|
|
.arg(senderId),
|
|
|
|
{message.toUtf8(), dispName.toUtf8()}, insertIdCallback);
|
|
|
|
|
|
|
|
if (!isSent) {
|
|
|
|
queries += RawDatabase::Query{"INSERT INTO faux_offline_pending (id) VALUES ("
|
|
|
|
" last_insert_rowid()"
|
|
|
|
");"};
|
2016-10-20 17:00:05 +08:00
|
|
|
}
|
2015-12-17 18:24:01 +08:00
|
|
|
|
|
|
|
return queries;
|
|
|
|
}
|
|
|
|
|
2016-07-27 06:20:54 +08:00
|
|
|
/**
|
2016-08-01 16:21:23 +08:00
|
|
|
* @brief Saves a chat message in the database.
|
|
|
|
* @param friendPk Friend publick key to save.
|
|
|
|
* @param message Message to save.
|
|
|
|
* @param sender Sender to save.
|
|
|
|
* @param time Time of message sending.
|
|
|
|
* @param isSent True if message was already sent.
|
|
|
|
* @param dispName Name, which should be displayed.
|
|
|
|
* @param insertIdCallback Function, called after query execution.
|
|
|
|
*/
|
2017-02-26 19:52:45 +08:00
|
|
|
void History::addNewMessage(const QString& friendPk, const QString& message, const QString& sender,
|
|
|
|
const QDateTime& time, bool isSent, QString dispName,
|
2017-06-12 02:48:44 +08:00
|
|
|
const std::function<void(int64_t)>& insertIdCallback)
|
2015-12-17 18:24:01 +08:00
|
|
|
{
|
2018-03-28 15:17:51 +08:00
|
|
|
if (!Settings::getInstance().getEnableLogging()) {
|
|
|
|
qWarning() << "Blocked a message from being added to database while history is disabled";
|
|
|
|
return;
|
|
|
|
}
|
2017-02-26 19:52:45 +08:00
|
|
|
if (!isValid()) {
|
2016-10-20 17:00:05 +08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-02-26 19:52:45 +08:00
|
|
|
db->execLater(generateNewMessageQueries(friendPk, message, sender, time, isSent, dispName,
|
|
|
|
insertIdCallback));
|
2015-12-17 18:24:01 +08:00
|
|
|
}
|
|
|
|
|
2016-07-27 06:20:54 +08:00
|
|
|
/**
|
2016-08-01 16:21:23 +08:00
|
|
|
* @brief Fetches chat messages from the database.
|
|
|
|
* @param friendPk Friend publick key to fetch.
|
|
|
|
* @param from Start of period to fetch.
|
|
|
|
* @param to End of period to fetch.
|
|
|
|
* @return List of messages.
|
|
|
|
*/
|
2018-04-24 07:51:26 +08:00
|
|
|
QList<History::HistMessage> History::getChatHistoryFromDate(const QString& friendPk, const QDateTime& from,
|
2016-10-20 17:00:05 +08:00
|
|
|
const QDateTime& to)
|
2015-12-17 18:24:01 +08:00
|
|
|
{
|
2017-02-26 19:52:45 +08:00
|
|
|
if (!isValid()) {
|
2016-10-20 17:00:05 +08:00
|
|
|
return {};
|
|
|
|
}
|
2018-04-24 07:51:26 +08:00
|
|
|
return getChatHistory(friendPk, from, to, 0);
|
|
|
|
}
|
2016-10-20 17:00:05 +08:00
|
|
|
|
2018-04-24 07:51:26 +08:00
|
|
|
/**
|
|
|
|
* @brief Fetches the latest set amount of messages from the database.
|
|
|
|
* @param friendPk Friend public key to fetch.
|
|
|
|
* @return List of messages.
|
|
|
|
*/
|
|
|
|
QList<History::HistMessage> History::getChatHistoryDefaultNum(const QString& friendPk)
|
|
|
|
{
|
|
|
|
if (!isValid()) {
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
return getChatHistory(friendPk, QDateTime::fromMSecsSinceEpoch(0), QDateTime::currentDateTime(), NUM_MESSAGES_DEFAULT);
|
2015-12-17 18:24:01 +08:00
|
|
|
}
|
|
|
|
|
2018-04-24 07:51:26 +08:00
|
|
|
|
2017-06-25 05:45:47 +08:00
|
|
|
/**
|
|
|
|
* @brief Fetches chat messages counts for each day from the database.
|
|
|
|
* @param friendPk Friend public key to fetch.
|
|
|
|
* @param from Start of period to fetch.
|
|
|
|
* @param to End of period to fetch.
|
|
|
|
* @return List of structs containing days offset and message count for that day.
|
|
|
|
*/
|
|
|
|
QList<History::DateMessages> History::getChatHistoryCounts(const ToxPk& friendPk, const QDate& from,
|
|
|
|
const QDate& to)
|
|
|
|
{
|
|
|
|
if (!isValid()) {
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
QDateTime fromTime(from);
|
|
|
|
QDateTime toTime(to);
|
|
|
|
|
|
|
|
QList<DateMessages> counts;
|
|
|
|
|
|
|
|
auto rowCallback = [&counts](const QVector<QVariant>& row) {
|
|
|
|
DateMessages app;
|
|
|
|
app.count = row[0].toUInt();
|
|
|
|
app.offsetDays = row[1].toUInt();
|
|
|
|
counts.append(app);
|
|
|
|
};
|
|
|
|
|
|
|
|
QString queryText =
|
|
|
|
QString("SELECT COUNT(history.id), ((timestamp / 1000 / 60 / 60 / 24) - %4 ) AS day "
|
|
|
|
"FROM history "
|
|
|
|
"JOIN peers chat ON chat_id = chat.id "
|
|
|
|
"WHERE timestamp BETWEEN %1 AND %2 AND chat.public_key='%3'"
|
|
|
|
"GROUP BY day;")
|
|
|
|
.arg(fromTime.toMSecsSinceEpoch())
|
|
|
|
.arg(toTime.toMSecsSinceEpoch())
|
|
|
|
.arg(friendPk.toString())
|
|
|
|
.arg(QDateTime::fromMSecsSinceEpoch(0).daysTo(fromTime));
|
|
|
|
|
|
|
|
db->execNow({queryText, rowCallback});
|
|
|
|
|
|
|
|
return counts;
|
|
|
|
}
|
|
|
|
|
2018-06-30 00:58:28 +08:00
|
|
|
/**
|
|
|
|
* @brief Search phrase in chat messages
|
|
|
|
* @param friendPk Friend public key
|
|
|
|
* @param from a date message where need to start a search
|
|
|
|
* @param phrase what need to find
|
|
|
|
* @param parameter for search
|
|
|
|
* @return date of the message where the phrase was found
|
|
|
|
*/
|
2018-06-25 02:11:20 +08:00
|
|
|
QDateTime History::getDateWhereFindPhrase(const QString& friendPk, const QDateTime& from, QString phrase, const ParameterSearch& parameter)
|
2018-02-10 23:50:48 +08:00
|
|
|
{
|
2018-07-14 05:06:04 +08:00
|
|
|
QDateTime result;
|
|
|
|
auto rowCallback = [&result](const QVector<QVariant>& row) {
|
|
|
|
result = QDateTime::fromMSecsSinceEpoch(row[0].toLongLong());
|
2018-02-10 23:50:48 +08:00
|
|
|
};
|
|
|
|
|
2018-02-15 19:21:05 +08:00
|
|
|
phrase.replace("'", "''");
|
2018-02-14 17:30:38 +08:00
|
|
|
|
2018-06-25 02:11:20 +08:00
|
|
|
QString message;
|
|
|
|
|
|
|
|
switch (parameter.filter) {
|
|
|
|
case FilterSearch::Register:
|
2018-06-30 00:31:34 +08:00
|
|
|
message = QStringLiteral("message LIKE '%%1%'").arg(phrase);
|
2018-06-25 02:11:20 +08:00
|
|
|
break;
|
|
|
|
case FilterSearch::WordsOnly:
|
2018-07-08 17:33:37 +08:00
|
|
|
message = QStringLiteral("message REGEXP '%1'").arg(SearchExtraFunctions::generateFilterWordsOnly(phrase).toLower());
|
2018-06-25 02:11:20 +08:00
|
|
|
break;
|
|
|
|
case FilterSearch::RegisterAndWordsOnly:
|
2018-07-08 17:33:37 +08:00
|
|
|
message = QStringLiteral("REGEXPSENSITIVE(message, '%1')").arg(SearchExtraFunctions::generateFilterWordsOnly(phrase));
|
2018-06-25 02:11:20 +08:00
|
|
|
break;
|
|
|
|
case FilterSearch::Regular:
|
2018-06-30 00:31:34 +08:00
|
|
|
message = QStringLiteral("message REGEXP '%1'").arg(phrase);
|
2018-06-25 02:11:20 +08:00
|
|
|
break;
|
|
|
|
case FilterSearch::RegisterAndRegular:
|
2018-06-30 00:31:34 +08:00
|
|
|
message = QStringLiteral("REGEXPSENSITIVE(message '%1')").arg(phrase);
|
2018-06-25 02:11:20 +08:00
|
|
|
break;
|
|
|
|
default:
|
2018-06-30 00:31:34 +08:00
|
|
|
message = QStringLiteral("LOWER(message) LIKE '%%1%'").arg(phrase.toLower());
|
2018-06-25 02:11:20 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
QDateTime date = from;
|
2018-08-09 02:35:28 +08:00
|
|
|
if (parameter.period == PeriodSearch::AfterDate || parameter.period == PeriodSearch::BeforeDate) {
|
2018-06-25 02:11:20 +08:00
|
|
|
date = QDateTime(parameter.date);
|
|
|
|
}
|
|
|
|
|
|
|
|
QString period;
|
|
|
|
switch (parameter.period) {
|
|
|
|
case PeriodSearch::WithTheFirst:
|
2018-06-30 00:31:34 +08:00
|
|
|
period = QStringLiteral("ORDER BY timestamp ASC LIMIT 1;");
|
2018-06-25 02:11:20 +08:00
|
|
|
break;
|
|
|
|
case PeriodSearch::AfterDate:
|
2018-06-30 00:31:34 +08:00
|
|
|
period = QStringLiteral("AND timestamp > '%1' ORDER BY timestamp ASC LIMIT 1;").arg(date.toMSecsSinceEpoch());
|
2018-06-25 02:11:20 +08:00
|
|
|
break;
|
|
|
|
case PeriodSearch::BeforeDate:
|
2018-06-30 00:31:34 +08:00
|
|
|
period = QStringLiteral("AND timestamp < '%1' ORDER BY timestamp DESC LIMIT 1;").arg(date.toMSecsSinceEpoch());
|
2018-06-25 02:11:20 +08:00
|
|
|
break;
|
|
|
|
default:
|
2018-06-30 00:31:34 +08:00
|
|
|
period = QStringLiteral("AND timestamp < '%1' ORDER BY timestamp DESC LIMIT 1;").arg(date.toMSecsSinceEpoch());
|
2018-06-25 02:11:20 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2018-02-10 23:50:48 +08:00
|
|
|
QString queryText =
|
2018-06-30 00:31:34 +08:00
|
|
|
QStringLiteral("SELECT timestamp "
|
2018-02-10 23:50:48 +08:00
|
|
|
"FROM history "
|
|
|
|
"LEFT JOIN faux_offline_pending ON history.id = faux_offline_pending.id "
|
|
|
|
"JOIN peers chat ON chat_id = chat.id "
|
2018-02-12 07:02:28 +08:00
|
|
|
"WHERE chat.public_key='%1' "
|
2018-06-25 02:11:20 +08:00
|
|
|
"AND %2 "
|
|
|
|
"%3")
|
2018-02-12 07:02:28 +08:00
|
|
|
.arg(friendPk)
|
2018-06-25 02:11:20 +08:00
|
|
|
.arg(message)
|
|
|
|
.arg(period);
|
|
|
|
|
|
|
|
db->execNow({queryText, rowCallback});
|
|
|
|
|
2018-07-14 05:06:04 +08:00
|
|
|
return result;
|
2018-06-25 02:11:20 +08:00
|
|
|
}
|
2018-02-12 07:02:28 +08:00
|
|
|
|
2018-06-30 00:58:28 +08:00
|
|
|
/**
|
|
|
|
* @brief get start date of correspondence
|
|
|
|
* @param friendPk Friend public key
|
|
|
|
* @return start date of correspondence
|
|
|
|
*/
|
2018-06-25 02:11:20 +08:00
|
|
|
QDateTime History::getStartDateChatHistory(const QString &friendPk)
|
|
|
|
{
|
2018-07-14 05:06:04 +08:00
|
|
|
QDateTime result;
|
|
|
|
auto rowCallback = [&result](const QVector<QVariant>& row) {
|
|
|
|
result = QDateTime::fromMSecsSinceEpoch(row[0].toLongLong());
|
2018-06-25 02:11:20 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
QString queryText =
|
2018-06-30 00:31:34 +08:00
|
|
|
QStringLiteral("SELECT timestamp "
|
2018-06-25 02:11:20 +08:00
|
|
|
"FROM history "
|
|
|
|
"LEFT JOIN faux_offline_pending ON history.id = faux_offline_pending.id "
|
|
|
|
"JOIN peers chat ON chat_id = chat.id "
|
|
|
|
"WHERE chat.public_key='%1' ORDER BY timestamp ASC LIMIT 1;")
|
|
|
|
.arg(friendPk);
|
2018-02-10 23:50:48 +08:00
|
|
|
|
|
|
|
db->execNow({queryText, rowCallback});
|
|
|
|
|
2018-07-14 05:06:04 +08:00
|
|
|
return result;
|
2018-02-10 23:50:48 +08:00
|
|
|
}
|
|
|
|
|
2016-07-27 06:20:54 +08:00
|
|
|
/**
|
2016-08-01 16:21:23 +08:00
|
|
|
* @brief Marks a message as sent.
|
|
|
|
* Removing message from the faux-offline pending messages list.
|
|
|
|
*
|
|
|
|
* @param id Message ID.
|
|
|
|
*/
|
2016-10-20 17:00:05 +08:00
|
|
|
void History::markAsSent(qint64 messageId)
|
2015-12-17 18:24:01 +08:00
|
|
|
{
|
2017-02-26 19:52:45 +08:00
|
|
|
if (!isValid()) {
|
2015-12-17 18:24:01 +08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-02-26 19:52:45 +08:00
|
|
|
db->execLater(QString("DELETE FROM faux_offline_pending WHERE id=%1;").arg(messageId));
|
2015-12-17 18:24:01 +08:00
|
|
|
}
|
2018-04-24 07:51:26 +08:00
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief Fetches chat messages from the database.
|
|
|
|
* @param friendPk Friend publick key to fetch.
|
|
|
|
* @param from Start of period to fetch.
|
|
|
|
* @param to End of period to fetch.
|
|
|
|
* @param numMessages max number of messages to fetch.
|
|
|
|
* @return List of messages.
|
|
|
|
*/
|
|
|
|
QList<History::HistMessage> History::getChatHistory(const QString& friendPk, const QDateTime& from,
|
|
|
|
const QDateTime& to, int numMessages)
|
|
|
|
{
|
|
|
|
QList<HistMessage> messages;
|
|
|
|
|
|
|
|
auto rowCallback = [&messages](const QVector<QVariant>& row) {
|
|
|
|
// dispName and message could have null bytes, QString::fromUtf8
|
|
|
|
// truncates on null bytes so we strip them
|
|
|
|
messages += {row[0].toLongLong(),
|
|
|
|
row[1].isNull(),
|
|
|
|
QDateTime::fromMSecsSinceEpoch(row[2].toLongLong()),
|
|
|
|
row[3].toString(),
|
|
|
|
QString::fromUtf8(row[4].toByteArray().replace('\0', "")),
|
|
|
|
row[5].toString(),
|
|
|
|
QString::fromUtf8(row[6].toByteArray().replace('\0', ""))};
|
|
|
|
};
|
|
|
|
|
|
|
|
// Don't forget to update the rowCallback if you change the selected columns!
|
|
|
|
QString queryText =
|
|
|
|
QString("SELECT history.id, faux_offline_pending.id, timestamp, "
|
|
|
|
"chat.public_key, aliases.display_name, sender.public_key, "
|
|
|
|
"message FROM history "
|
|
|
|
"LEFT JOIN faux_offline_pending ON history.id = faux_offline_pending.id "
|
|
|
|
"JOIN peers chat ON chat_id = chat.id "
|
|
|
|
"JOIN aliases ON sender_alias = aliases.id "
|
|
|
|
"JOIN peers sender ON aliases.owner = sender.id "
|
|
|
|
"WHERE timestamp BETWEEN %1 AND %2 AND chat.public_key='%3'")
|
|
|
|
.arg(from.toMSecsSinceEpoch())
|
|
|
|
.arg(to.toMSecsSinceEpoch())
|
|
|
|
.arg(friendPk);
|
|
|
|
if (numMessages) {
|
|
|
|
queryText = "SELECT * FROM (" + queryText +
|
|
|
|
QString(" ORDER BY history.id DESC limit %1) AS T1 ORDER BY T1.id ASC;").arg(numMessages);
|
|
|
|
} else {
|
|
|
|
queryText = queryText + ";";
|
|
|
|
}
|
|
|
|
|
|
|
|
db->execNow({queryText, rowCallback});
|
|
|
|
|
|
|
|
return messages;
|
|
|
|
}
|
2018-09-24 16:05:43 +08:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief Upgrade the db schema
|
|
|
|
* @note On future alterations of the database all you have to do is bump the SCHEMA_VERSION
|
|
|
|
* variable and add another case to the switch statement below. Make sure to fall through on each case.
|
|
|
|
*/
|
|
|
|
void History::dbSchemaUpgrade()
|
|
|
|
{
|
|
|
|
int64_t databaseSchemaVersion;
|
|
|
|
db->execNow(RawDatabase::Query("PRAGMA user_version", [&] (const QVector<QVariant>& row){
|
|
|
|
databaseSchemaVersion = row[0].toLongLong();
|
|
|
|
}));
|
|
|
|
|
|
|
|
if (databaseSchemaVersion > SCHEMA_VERSION) {
|
|
|
|
qWarning() << "Database version is newer than we currently support. Please upgrade qTox";
|
|
|
|
// We don't know what future versions have done, we have to disable db access until we re-upgrade
|
|
|
|
db.reset();
|
|
|
|
}
|
|
|
|
else if (databaseSchemaVersion == SCHEMA_VERSION) {
|
|
|
|
// No work to do
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (databaseSchemaVersion)
|
|
|
|
{
|
|
|
|
//case 0:
|
|
|
|
// do 0 -> 1 upgrade
|
|
|
|
// //fallthrough
|
|
|
|
//case 1:
|
|
|
|
// do 1 -> 2 upgrade
|
|
|
|
// //fallthrough
|
|
|
|
// etc.
|
|
|
|
default:
|
|
|
|
db->execLater(RawDatabase::Query(QStringLiteral("PRAGMA user_version = %1;").arg(SCHEMA_VERSION)));
|
|
|
|
qDebug() << "Database upgrade finished (databaseSchemaVersion "
|
|
|
|
<< databaseSchemaVersion << " -> " << SCHEMA_VERSION << ")";
|
|
|
|
}
|
|
|
|
}
|