30 static constexpr
int SCHEMA_VERSION = 9;
34 QVector<RawDatabase::Query> queries;
36 "CREATE TABLE peers (id INTEGER PRIMARY KEY, "
37 "public_key TEXT NOT NULL UNIQUE);"
38 "CREATE TABLE aliases (id INTEGER PRIMARY KEY, "
40 "display_name BLOB NOT NULL, "
41 "UNIQUE(owner, display_name), "
42 "FOREIGN KEY (owner) REFERENCES peers(id));"
43 "CREATE TABLE history "
44 "(id INTEGER PRIMARY KEY, "
45 "message_type CHAR(1) NOT NULL DEFAULT 'T' CHECK (message_type in ('T','F','S')), "
46 "timestamp INTEGER NOT NULL, "
47 "chat_id INTEGER NOT NULL, "
50 "UNIQUE (id, message_type), "
51 "FOREIGN KEY (chat_id) REFERENCES peers(id)); "
52 "CREATE TABLE text_messages "
53 "(id INTEGER PRIMARY KEY, "
54 "message_type CHAR(1) NOT NULL CHECK (message_type = 'T'), "
55 "sender_alias INTEGER NOT NULL, "
62 "message BLOB NOT NULL, "
63 "FOREIGN KEY (id, message_type) REFERENCES history(id, message_type), "
64 "FOREIGN KEY (sender_alias) REFERENCES aliases(id)); "
65 "CREATE TABLE file_transfers "
66 "(id INTEGER PRIMARY KEY, "
67 "message_type CHAR(1) NOT NULL CHECK (message_type = 'F'), "
68 "sender_alias INTEGER NOT NULL, "
69 "file_restart_id BLOB NOT NULL, "
70 "file_name BLOB NOT NULL, "
71 "file_path BLOB NOT NULL, "
72 "file_hash BLOB NOT NULL, "
73 "file_size INTEGER NOT NULL, "
74 "direction INTEGER NOT NULL, "
75 "file_state INTEGER NOT NULL, "
76 "FOREIGN KEY (id, message_type) REFERENCES history(id, message_type), "
77 "FOREIGN KEY (sender_alias) REFERENCES aliases(id)); "
78 "CREATE TABLE system_messages "
79 "(id INTEGER PRIMARY KEY, "
80 "message_type CHAR(1) NOT NULL CHECK (message_type = 'S'), "
81 "system_message_type INTEGER NOT NULL, "
86 "FOREIGN KEY (id, message_type) REFERENCES history(id, message_type)); "
87 "CREATE TABLE faux_offline_pending (id INTEGER PRIMARY KEY, "
88 "required_extensions INTEGER NOT NULL DEFAULT 0, "
89 "FOREIGN KEY (id) REFERENCES history(id));"
90 "CREATE TABLE broken_messages (id INTEGER PRIMARY KEY, "
91 "reason INTEGER NOT NULL DEFAULT 0, "
92 "FOREIGN KEY (id) REFERENCES history(id));"));
95 "CREATE INDEX chat_id_idx on history (chat_id);");
96 queries +=
RawDatabase::Query(QStringLiteral(
"PRAGMA user_version = %1;").arg(SCHEMA_VERSION));
100 bool isNewDb(std::shared_ptr<RawDatabase>& db,
bool& success)
104 [&](
const QVector<QVariant>& row) {
105 newDb = row[0].toLongLong() == 0;
117 QVector<RawDatabase::Query> queries;
119 "(id INTEGER PRIMARY KEY, "
120 "chat_id INTEGER NOT NULL, "
121 "file_restart_id BLOB NOT NULL, "
122 "file_name BLOB NOT NULL, "
123 "file_path BLOB NOT NULL, "
124 "file_hash BLOB NOT NULL, "
125 "file_size INTEGER NOT NULL, "
126 "direction INTEGER NOT NULL, "
127 "file_state INTEGER NOT NULL);"));
128 queries +=
RawDatabase::Query(QStringLiteral(
"ALTER TABLE history ADD file_id INTEGER;"));
140 QString lastDeliveredQuery =
141 QString(
"SELECT chat_id, MAX(history.id) FROM "
142 "history JOIN peers chat ON chat_id = chat.id "
143 "LEFT JOIN faux_offline_pending ON history.id = faux_offline_pending.id "
144 "WHERE faux_offline_pending.id IS NULL "
145 "GROUP BY chat_id;");
147 QVector<RawDatabase::Query> upgradeQueries;
149 "(id INTEGER PRIMARY KEY);"));
151 auto rowCallback = [&upgradeQueries](
const QVector<QVariant>& row) {
152 auto chatId = row[0].toLongLong();
153 auto lastDeliveredHistoryId = row[1].toLongLong();
155 upgradeQueries += QString(
"INSERT INTO broken_messages "
156 "SELECT faux_offline_pending.id FROM "
157 "history JOIN faux_offline_pending "
158 "ON faux_offline_pending.id = history.id "
159 "WHERE history.chat_id=%1 "
160 "AND history.id < %2;")
162 .arg(lastDeliveredHistoryId);
166 if (!db.
execNow({lastDeliveredQuery, rowCallback})) {
170 upgradeQueries += QString(
"DELETE FROM faux_offline_pending "
172 "SELECT id FROM broken_messages);");
176 return db.
execNow(upgradeQueries);
187 const QString emptyActionMessageString =
"/me ";
189 QVector<RawDatabase::Query> upgradeQueries;
191 "SELECT faux_offline_pending.id FROM "
192 "history JOIN faux_offline_pending "
193 "ON faux_offline_pending.id = history.id "
194 "WHERE history.message = ?;"),
195 {emptyActionMessageString.toUtf8()}};
197 upgradeQueries += QString(
"DELETE FROM faux_offline_pending "
199 "SELECT id FROM broken_messages);");
203 return db.
execNow(upgradeQueries);
208 QVector<RawDatabase::Query> upgradeQueries;
210 "CREATE INDEX chat_id_idx on history (chat_id);")};
214 return db.
execNow(upgradeQueries);
217 void addForeignKeyToAlias(QVector<RawDatabase::Query>& queries)
220 "CREATE TABLE aliases_new (id INTEGER PRIMARY KEY, owner INTEGER, "
221 "display_name BLOB NOT NULL, UNIQUE(owner, display_name), "
222 "FOREIGN KEY (owner) REFERENCES peers(id));"));
224 "INSERT INTO aliases_new (id, owner, display_name) "
225 "SELECT id, owner, display_name "
228 queries +=
RawDatabase::Query(QStringLiteral(
"ALTER TABLE aliases_new RENAME TO aliases;"));
231 void addForeignKeyToHistory(QVector<RawDatabase::Query>& queries)
234 "CREATE TABLE history_new "
235 "(id INTEGER PRIMARY KEY, "
236 "timestamp INTEGER NOT NULL, "
237 "chat_id INTEGER NOT NULL, "
238 "sender_alias INTEGER NOT NULL, "
239 "message BLOB NOT NULL, "
241 "FOREIGN KEY (file_id) REFERENCES file_transfers(id), "
242 "FOREIGN KEY (chat_id) REFERENCES peers(id), "
243 "FOREIGN KEY (sender_alias) REFERENCES aliases(id));"));
245 "INSERT INTO history_new (id, timestamp, chat_id, sender_alias, message, file_id) "
246 "SELECT id, timestamp, chat_id, sender_alias, message, file_id "
249 queries +=
RawDatabase::Query(QStringLiteral(
"ALTER TABLE history_new RENAME TO history;"));
252 void addForeignKeyToFauxOfflinePending(QVector<RawDatabase::Query>& queries)
255 "CREATE TABLE new_faux_offline_pending (id INTEGER PRIMARY KEY, "
256 "FOREIGN KEY (id) REFERENCES history(id));"));
258 "INSERT INTO new_faux_offline_pending (id) "
260 "FROM faux_offline_pending;"));
262 queries +=
RawDatabase::Query(QStringLiteral(
"ALTER TABLE new_faux_offline_pending RENAME TO faux_offline_pending;"));
265 void addForeignKeyToBrokenMessages(QVector<RawDatabase::Query>& queries)
268 "CREATE TABLE new_broken_messages (id INTEGER PRIMARY KEY, "
269 "FOREIGN KEY (id) REFERENCES history(id));"));
271 "INSERT INTO new_broken_messages (id) "
273 "FROM broken_messages;"));
275 queries +=
RawDatabase::Query(QStringLiteral(
"ALTER TABLE new_broken_messages RENAME TO broken_messages;"));
282 QVector<RawDatabase::Query> upgradeQueries;
283 addForeignKeyToAlias(upgradeQueries);
284 addForeignKeyToHistory(upgradeQueries);
285 addForeignKeyToFauxOfflinePending(upgradeQueries);
286 addForeignKeyToBrokenMessages(upgradeQueries);
288 auto transactionPass = db.
execNow(upgradeQueries);
289 if (transactionPass) {
292 return transactionPass;
297 QVector<RawDatabase::Query> upgradeQueries;
300 "ADD COLUMN required_extensions INTEGER NOT NULL "
304 "ADD COLUMN reason INTEGER NOT NULL "
308 return db.
execNow(upgradeQueries);
313 QVector<RawDatabase::Query> upgradeQueries;
317 "CREATE TABLE history_new (id INTEGER PRIMARY KEY, message_type CHAR(1) NOT NULL DEFAULT "
318 "'T' CHECK (message_type in ('T','F','S')), timestamp INTEGER NOT NULL, chat_id INTEGER "
319 "NOT NULL, UNIQUE (id, message_type), FOREIGN KEY (chat_id) REFERENCES peers(id))");
323 "CREATE TABLE text_messages (id INTEGER PRIMARY KEY, message_type CHAR(1) NOT NULL CHECK "
324 "(message_type = 'T'), sender_alias INTEGER NOT NULL, message BLOB NOT NULL, FOREIGN KEY "
325 "(id, message_type) REFERENCES history_new(id, message_type), FOREIGN KEY (sender_alias) "
326 "REFERENCES aliases(id))");
330 "CREATE TABLE file_transfers_new (id INTEGER PRIMARY KEY, message_type CHAR(1) NOT NULL "
331 "CHECK (message_type = 'F'), sender_alias INTEGER NOT NULL, file_restart_id BLOB NOT NULL, "
332 "file_name BLOB NOT NULL, file_path BLOB NOT NULL, file_hash BLOB NOT NULL, file_size "
333 "INTEGER NOT NULL, direction INTEGER NOT NULL, file_state INTEGER NOT NULL, FOREIGN KEY "
334 "(id, message_type) REFERENCES history_new(id, message_type), FOREIGN KEY (sender_alias) "
335 "REFERENCES aliases(id))");
338 RawDatabase::Query(
"INSERT INTO history_new SELECT id, 'T' AS message_type, timestamp, "
339 "chat_id FROM history WHERE history.file_id IS NULL");
343 "sender_alias, message FROM history WHERE history.file_id IS NULL");
346 RawDatabase::Query(
"INSERT INTO history_new SELECT id, 'F' AS message_type, timestamp, "
347 "chat_id FROM history WHERE history.file_id IS NOT NULL");
350 "INSERT INTO file_transfers_new (id, message_type, sender_alias, file_restart_id, "
351 "file_name, file_path, file_hash, file_size, direction, file_state) SELECT history.id, 'F' "
352 "as message_type, history.sender_alias, file_transfers.file_restart_id, "
353 "file_transfers.file_name, file_transfers.file_path, file_transfers.file_hash, "
354 "file_transfers.file_size, file_transfers.direction, file_transfers.file_state FROM "
355 "history INNER JOIN file_transfers on history.file_id = file_transfers.id WHERE "
356 "history.file_id IS NOT NULL");
359 "CREATE TABLE system_messages (id INTEGER PRIMARY KEY, message_type CHAR(1) NOT NULL CHECK "
360 "(message_type = 'S'), system_message_type INTEGER NOT NULL, arg1 BLOB, arg2 BLOB, arg3 BLOB, arg4 BLOB, "
361 "FOREIGN KEY (id, message_type) REFERENCES history_new(id, message_type))");
365 "CREATE TABLE faux_offline_pending_new (id INTEGER PRIMARY KEY, required_extensions "
366 "INTEGER NOT NULL DEFAULT 0, FOREIGN KEY (id) REFERENCES history_new(id))");
367 upgradeQueries +=
RawDatabase::Query(
"INSERT INTO faux_offline_pending_new SELECT id, "
368 "required_extensions FROM faux_offline_pending");
371 RawDatabase::Query(
"ALTER TABLE faux_offline_pending_new RENAME TO faux_offline_pending");
375 "CREATE TABLE broken_messages_new (id INTEGER PRIMARY KEY, reason INTEGER NOT NULL DEFAULT "
376 "0, FOREIGN KEY (id) REFERENCES history_new(id))");
378 "INSERT INTO broken_messages_new SELECT id, reason FROM broken_messages");
389 upgradeQueries +=
RawDatabase::Query(
"ALTER TABLE file_transfers_new RENAME TO file_transfers");
391 upgradeQueries +=
RawDatabase::Query(
"CREATE INDEX chat_id_idx on history (chat_id);");
395 return db.
execNow(upgradeQueries);
405 QVector<RawDatabase::Query> upgradeQueries;
408 return db.
execNow(upgradeQueries);
412 BadEntry(int64_t row, QString toxId) :
419 std::vector<BadEntry> getInvalidPeers(
RawDatabase& db)
421 std::vector<BadEntry> badPeerIds;
422 db.
execNow(
RawDatabase::Query(
"SELECT id, public_key FROM peers WHERE LENGTH(public_key) != 64", [&](
const QVector<QVariant>& row) {
423 badPeerIds.emplace_back(BadEntry{row[0].toInt(), row[1].toString()});
430 bool validPeerExists{
false};
433 .arg(friendPk.
toString()), [&](
const QVector<QVariant>& row) {
434 validPeerRow = RowId{row[0].toLongLong()};
435 validPeerExists =
true;
437 if (validPeerExists) {
442 int64_t maxPeerId = row[0].toInt();
443 validPeerRow =
RowId{maxPeerId + 1};
449 struct DuplicateAlias {
450 DuplicateAlias(
RowId goodAliasRow, std::vector<RowId> badAliasRows) :
451 goodAliasRow{goodAliasRow},
452 badAliasRows{badAliasRows} {}
454 RowId goodAliasRow{-1};
455 std::vector<RowId> badAliasRows;
460 std::vector<RowId> badAliasRows;
462 bool hasGoodEntry{
false};
463 db.
execNow(
RawDatabase::Query(QStringLiteral(
"SELECT good.id, bad.id FROM aliases good INNER JOIN aliases bad ON good.display_name=bad.display_name WHERE good.owner=%1 AND bad.owner=%2;").arg(goodPeerRow.get()).arg(badPeerRow.get()),
464 [&](
const QVector<QVariant>& row) {
466 goodAliasRow = RowId{row[0].toInt()};
467 badAliasRows.emplace_back(
RowId{row[1].toLongLong()});
471 return {goodAliasRow, badAliasRows};
477 void mergeAndDeleteAlias(QVector<RawDatabase::Query>& upgradeQueries,
RowId goodAlias, std::vector<RowId> badAliases)
479 for (
const auto badAliasId : badAliases) {
480 upgradeQueries +=
RawDatabase::Query(QStringLiteral(
"UPDATE text_messages SET sender_alias = %1 WHERE sender_alias = %2;").arg(goodAlias.get()).arg(badAliasId.get()));
481 upgradeQueries +=
RawDatabase::Query(QStringLiteral(
"UPDATE file_transfers SET sender_alias = %1 WHERE sender_alias = %2;").arg(goodAlias.get()).arg(badAliasId.get()));
482 upgradeQueries +=
RawDatabase::Query(QStringLiteral(
"DELETE FROM aliases WHERE id = %1;").arg(badAliasId.get()));
486 void mergeAndDeletePeer(QVector<RawDatabase::Query>& upgradeQueries,
RowId goodPeerId,
RowId badPeerId)
488 upgradeQueries +=
RawDatabase::Query(QStringLiteral(
"UPDATE aliases SET owner = %1 WHERE owner = %2").arg(goodPeerId.get()).arg(badPeerId.get()));
489 upgradeQueries +=
RawDatabase::Query(QStringLiteral(
"UPDATE history SET chat_id = %1 WHERE chat_id = %2;").arg(goodPeerId.get()).arg(badPeerId.get()));
490 upgradeQueries +=
RawDatabase::Query(QStringLiteral(
"DELETE FROM peers WHERE id = %1").arg(badPeerId.get()));
493 void mergeDuplicatePeers(QVector<RawDatabase::Query>& upgradeQueries,
RawDatabase& db, std::vector<BadEntry> badPeers)
495 for (
const auto& badPeer : badPeers) {
496 const RowId goodPeerId = getValidPeerRow(db,
ToxPk{badPeer.toxId.left(64)});
497 const auto aliasDuplicates = getDuplicateAliasRows(db, goodPeerId, badPeer.row);
498 mergeAndDeleteAlias(upgradeQueries, aliasDuplicates.goodAliasRow, aliasDuplicates.badAliasRows);
499 mergeAndDeletePeer(upgradeQueries, goodPeerId, badPeer.row);
507 QVector<RawDatabase::Query> upgradeQueries;
508 const auto badPeers = getInvalidPeers(db);
509 mergeDuplicatePeers(upgradeQueries, db, badPeers);
511 return db.
execNow(upgradeQueries);
519 bool dbSchemaUpgrade(std::shared_ptr<RawDatabase>& db)
522 bool success =
false;
523 const bool newDb = isNewDb(db, success);
525 qCritical() <<
"Failed to create current db schema";
530 if (!createCurrentSchema(*db)) {
531 qCritical() <<
"Failed to create current db schema";
534 qDebug() <<
"Database created at schema version" << SCHEMA_VERSION;
540 int64_t databaseSchemaVersion;
542 if (!db->execNow(
RawDatabase::Query(
"PRAGMA user_version", [&](
const QVector<QVariant>& row) {
543 databaseSchemaVersion = row[0].toLongLong();
545 qCritical() <<
"History failed to read user_version";
549 if (databaseSchemaVersion > SCHEMA_VERSION) {
550 qWarning().nospace() <<
"Database version (" << databaseSchemaVersion
551 <<
") is newer than we currently support (" << SCHEMA_VERSION
552 <<
"). Please upgrade qTox";
555 }
else if (databaseSchemaVersion == SCHEMA_VERSION) {
561 std::vector<DbSchemaUpgradeFn> upgradeFns = {dbSchema0to1, dbSchema1to2, dbSchema2to3,
562 dbSchema3to4, dbSchema4to5, dbSchema5to6,
563 dbSchema6to7, dbSchema7to8, dbSchema8to9};
565 assert(databaseSchemaVersion <
static_cast<int>(upgradeFns.size()));
566 assert(upgradeFns.size() == SCHEMA_VERSION);
568 for (int64_t i = databaseSchemaVersion; i < static_cast<int>(upgradeFns.size()); ++i) {
569 auto const newDbVersion = i + 1;
570 if (!upgradeFns[i](*db)) {
571 qCritical() <<
"Failed to upgrade db to schema version " << newDbVersion <<
" aborting";
574 qDebug() <<
"Database upgraded incrementally to schema version " << newDbVersion;
577 qInfo() <<
"Database upgrade finished (databaseSchemaVersion" << databaseSchemaVersion <<
"->"
578 << SCHEMA_VERSION <<
")";
582 MessageState getMessageState(
bool isPending,
bool isBroken)
584 assert(!(isPending && isBroken));
589 }
else if (isBroken) {
597 QString generatePeerIdString(
ToxPk const& pk)
599 return QString(
"(SELECT id FROM peers WHERE public_key = '%1')").arg(pk.
toString());
606 "VALUES ('%1')").arg(pk.
toString())};
612 QString(
"INSERT OR IGNORE INTO aliases (owner, display_name) VALUES (%1, ?);").arg(generatePeerIdString(pk)),
613 {dispName.toUtf8()});
618 return RawDatabase::Query(QString(
"INSERT INTO history (message_type, timestamp, chat_id) "
619 "VALUES ('%1', %2, %3);")
621 .arg(time.toMSecsSinceEpoch())
622 .arg(generatePeerIdString(friendPk)));
635 QVector<RawDatabase::Query>
636 generateNewTextMessageQueries(
const ToxPk& friendPk,
const QString& message,
const ToxPk& sender,
637 const QDateTime& time,
bool isDelivered,
ExtensionSet extensionSet,
638 QString dispName, std::function<
void(
RowId)> insertIdCallback)
640 QVector<RawDatabase::Query> queries;
642 queries += generateEnsurePkInPeers(friendPk);
643 queries += generateEnsurePkInPeers(sender);
644 queries += generateUpdateAlias(sender, dispName);
645 queries += generateHistoryTableInsertion(
'T', time, friendPk);
648 QString(
"INSERT INTO text_messages (id, message_type, sender_alias, message) "
650 " last_insert_rowid(), "
652 " (SELECT id FROM aliases WHERE owner=%1 and display_name=?), "
655 .arg(generatePeerIdString(sender)),
656 {dispName.toUtf8(),
message.toUtf8()}, insertIdCallback);
660 QString(
"INSERT INTO faux_offline_pending (id, required_extensions) VALUES ("
661 " last_insert_rowid(), %1"
663 .arg(extensionSet.to_ulong())};
669 QVector<RawDatabase::Query> generateNewSystemMessageQueries(
const ToxPk& friendPk,
672 QVector<RawDatabase::Query> queries;
674 queries += generateEnsurePkInPeers(friendPk);
675 queries += generateHistoryTableInsertion(
'S', systemMessage.
timestamp, friendPk);
677 QVector<QByteArray> blobs;
678 std::transform(systemMessage.
args.begin(), systemMessage.
args.end(), std::back_inserter(blobs),
679 [](
const QString& s) { return s.toUtf8(); });
681 queries +=
RawDatabase::Query(QString(
"INSERT INTO system_messages (id, message_type, "
682 "system_message_type, arg1, arg2, arg3, arg4)"
683 "VALUES (last_insert_rowid(), 'S', %1, ?, ?, ?, ?)")
702 static int id = qRegisterMetaType<FileDbInsertionData>();
714 qWarning() <<
"Database not open, init failed";
721 "PRAGMA foreign_keys = ON;");
723 const auto upgradeSucceeded = dbSchemaUpgrade(
db);
726 if (!upgradeSucceeded) {
751 return db &&
db->isOpen();
777 db->execNow(
"DELETE FROM faux_offline_pending;"
778 "DELETE FROM broken_messages;"
779 "DELETE FROM text_messages;"
780 "DELETE FROM file_transfers;"
781 "DELETE FROM system_messages;"
782 "DELETE FROM history;"
783 "DELETE FROM aliases;"
798 QString queryText = QString(
"DELETE FROM faux_offline_pending "
799 "WHERE faux_offline_pending.id IN ( "
800 " SELECT faux_offline_pending.id FROM faux_offline_pending "
801 " LEFT JOIN history ON faux_offline_pending.id = history.id "
804 "DELETE FROM broken_messages "
805 "WHERE broken_messages.id IN ( "
806 " SELECT broken_messages.id FROM broken_messages "
807 " LEFT JOIN history ON broken_messages.id = history.id "
810 "DELETE FROM text_messages "
812 " SELECT id from history "
813 " WHERE message_type = 'T' AND chat_id=%1);"
814 "DELETE FROM file_transfers "
816 " SELECT id from history "
817 " WHERE message_type = 'F' AND chat_id=%1);"
818 "DELETE FROM system_messages "
820 " SELECT id from history "
821 " WHERE message_type = 'S' AND chat_id=%1);"
822 "DELETE FROM history WHERE chat_id=%1; "
823 "DELETE FROM aliases WHERE owner=%1; "
824 "DELETE FROM peers WHERE id=%1; "
826 .arg(generatePeerIdString(friendPk));
828 if (!
db->execNow(queryText)) {
829 qWarning() <<
"Failed to remove friend's history";
836 if (fileInfo.finished) {
841 fileInfo.finished =
false;
842 fileInfo.fileId = dbId;
846 QVector<RawDatabase::Query>
848 const QDateTime& time,
const QString& dispName,
851 QVector<RawDatabase::Query> queries;
853 queries += generateEnsurePkInPeers(friendPk);
854 queries += generateEnsurePkInPeers(sender);
855 queries += generateUpdateAlias(sender, dispName);
856 queries += generateHistoryTableInsertion(
'F', time, friendPk);
858 std::weak_ptr<History> weakThis = shared_from_this();
859 auto fileId = insertionData.
fileId;
863 "INSERT INTO file_transfers "
864 " (id, message_type, sender_alias, "
865 " file_restart_id, file_name, file_path, "
866 " file_hash, file_size, direction, file_state) "
868 " last_insert_rowid(), "
870 " (SELECT id FROM aliases WHERE owner=%1 AND display_name=?), "
879 .arg(generatePeerIdString(sender))
880 .arg(insertionData.
size)
883 {dispName.toUtf8(), insertionData.fileId.toUtf8(),
884 insertionData.fileName.toUtf8(), insertionData.filePath.toUtf8(),
886 [weakThis, fileId](
RowId id) {
887 auto pThis = weakThis.lock();
889 emit pThis->fileInserted(id, fileId);
896 const QByteArray& fileHash)
899 if (filePath.length()) {
901 "SET file_state = %1, file_path = ?, file_hash = ?"
905 {filePath.toUtf8(), fileHash});
916 const QString& fileName,
const QString& filePath, int64_t size,
917 const ToxPk& sender,
const QDateTime& time, QString
const& dispName)
937 if (sender == friendPk) {
943 std::weak_ptr<History> weakThis = shared_from_this();
946 insertionData.
fileId = fileId;
949 insertionData.
size = size;
954 db->execLater(queries);
962 const auto queries = generateNewSystemMessageQueries(friendPk, systemMessage);
964 db->execLater(queries);
978 const QDateTime& time,
bool isDelivered,
ExtensionSet extensionSet,
979 QString dispName,
const std::function<
void(
RowId)>& insertIdCallback)
985 db->execLater(generateNewTextMessageQueries(friendPk,
message, sender, time, isDelivered,
986 extensionSet, dispName, insertIdCallback));
990 const QByteArray& fileHash)
997 if (fileInfo.fileId.get() == -1) {
998 fileInfo.finished =
true;
999 fileInfo.success = success;
1000 fileInfo.filePath = filePath;
1001 fileInfo.fileHash = fileHash;
1024 QString queryText = QString(
"SELECT COUNT(history.id) "
1026 "JOIN peers chat ON chat_id = chat.id "
1027 "WHERE chat.public_key='%1'")
1030 if (date.isNull()) {
1033 queryText += QString(
" AND timestamp < %1;").arg(date.toMSecsSinceEpoch());
1036 size_t numMessages = 0;
1037 auto rowCallback = [&numMessages](
const QVector<QVariant>& row) {
1038 numMessages = row[0].toLongLong();
1041 db->execNow({queryText, rowCallback});
1058 "SELECT history.id, history.message_type, history.timestamp, faux_offline_pending.id, "
1059 " faux_offline_pending.required_extensions, broken_messages.id, text_messages.message, "
1060 " file_restart_id, file_name, file_path, file_size, file_transfers.direction, "
1061 " file_state, peers.public_key as sender_key, aliases.display_name, "
1062 " system_messages.system_message_type, system_messages.arg1, system_messages.arg2, "
1063 " system_messages.arg3, system_messages.arg4 "
1065 "LEFT JOIN text_messages ON history.id = text_messages.id "
1066 "LEFT JOIN file_transfers ON history.id = file_transfers.id "
1067 "LEFT JOIN system_messages ON system_messages.id == history.id "
1068 "LEFT JOIN aliases ON text_messages.sender_alias = aliases.id OR "
1069 "file_transfers.sender_alias = aliases.id "
1070 "LEFT JOIN peers ON aliases.owner = peers.id "
1071 "LEFT JOIN faux_offline_pending ON faux_offline_pending.id = history.id "
1072 "LEFT JOIN broken_messages ON broken_messages.id = history.id "
1073 "WHERE history.chat_id = %1 "
1074 "LIMIT %2 OFFSET %3;")
1075 .arg(generatePeerIdString(friendPk))
1076 .arg(lastIdx - firstIdx)
1079 auto rowCallback = [&friendPk, &messages](
const QVector<QVariant>& row) {
1081 constexpr
auto messageOffset = 6;
1082 constexpr
auto fileOffset = 7;
1083 constexpr
auto senderOffset = 13;
1084 constexpr
auto systemOffset = 15;
1086 auto it = row.begin();
1088 const auto id =
RowId{(*it++).toLongLong()};
1089 const auto messageType = (*it++).toString();
1090 const auto timestamp = QDateTime::fromMSecsSinceEpoch((*it++).toLongLong());
1091 const auto isPending = !(*it++).isNull();
1093 const auto requiredExtensions =
ExtensionSet((*it++).toLongLong());
1094 const auto isBroken = !(*it++).isNull();
1095 const auto messageState = getMessageState(isPending, isBroken);
1099 assert(messageType.size() == 1);
1100 switch (messageType[0].toLatin1()) {
1102 it = std::next(row.begin(), messageOffset);
1103 assert(!it->isNull());
1104 const auto messageContent = (*it++).toString();
1105 it = std::next(row.begin(), senderOffset);
1106 const auto senderKey = (*it++).toString();
1107 const auto senderName = QString::fromUtf8((*it++).toByteArray().replace(
'\0',
""));
1108 messages +=
HistMessage(
id, messageState, requiredExtensions, timestamp,
1109 friendPk.
toString(), senderName, senderKey, messageContent);
1113 it = std::next(row.begin(), fileOffset);
1114 assert(!it->isNull());
1115 const auto fileKind = TOX_FILE_KIND_DATA;
1116 const auto resumeFileId = (*it++).toString().toUtf8();
1117 const auto fileName = (*it++).toString();
1118 const auto filePath = (*it++).toString();
1119 const auto filesize = (*it++).toLongLong();
1123 ToxFile file(0, 0, fileName, filePath, filesize, direction);
1124 file.fileKind = fileKind;
1125 file.resumeFileId = resumeFileId;
1126 file.status = status;
1128 it = std::next(row.begin(), senderOffset);
1129 const auto senderKey = (*it++).toString();
1130 const auto senderName = QString::fromUtf8((*it++).toByteArray().replace(
'\0',
""));
1137 it = std::next(row.begin(), systemOffset);
1138 assert(!it->isNull());
1142 auto argEnd = std::next(it, systemMessage.
args.size());
1143 std::transform(it, argEnd, systemMessage.
args.begin(), [](
const QVariant& arg) {
1144 return QString::fromUtf8(arg.toByteArray().replace(
'\0',
""));
1153 db->execNow({queryText, rowCallback});
1166 "SELECT history.id, history.timestamp, faux_offline_pending.id, "
1167 " faux_offline_pending.required_extensions, broken_messages.id, text_messages.message, "
1168 " peers.public_key as sender_key, aliases.display_name "
1170 "JOIN text_messages ON history.id = text_messages.id "
1171 "JOIN aliases ON text_messages.sender_alias = aliases.id "
1172 "JOIN peers ON aliases.owner = peers.id "
1173 "JOIN faux_offline_pending ON faux_offline_pending.id = history.id "
1174 "LEFT JOIN broken_messages ON broken_messages.id = history.id "
1175 "WHERE history.chat_id = %1 AND history.message_type = 'T';")
1176 .arg(generatePeerIdString(friendPk));
1179 auto rowCallback = [&friendPk, &ret](
const QVector<QVariant>& row) {
1180 auto it = row.begin();
1183 auto id =
RowId{(*it++).toLongLong()};
1184 auto timestamp = QDateTime::fromMSecsSinceEpoch((*it++).toLongLong());
1185 auto isPending = !(*it++).isNull();
1186 auto extensionSet =
ExtensionSet((*it++).toLongLong());
1187 auto isBroken = !(*it++).isNull();
1188 auto messageContent = (*it++).toString();
1189 auto senderKey = (*it++).toString();
1190 auto displayName = QString::fromUtf8((*it++).toByteArray().replace(
'\0',
""));
1192 MessageState messageState = getMessageState(isPending, isBroken);
1194 ret += {id, messageState, extensionSet, timestamp, friendPk.
toString(),
1195 displayName, senderKey, messageContent};
1198 db->execNow({queryText, rowCallback});
1219 auto rowCallback = [&result](
const QVector<QVariant>& row) {
1220 result = QDateTime::fromMSecsSinceEpoch(row[0].toLongLong());
1223 phrase.replace(
"'",
"''");
1227 switch (parameter.
filter) {
1229 message = QStringLiteral(
"text_messages.message LIKE '%%1%'").arg(phrase);
1232 message = QStringLiteral(
"text_messages.message REGEXP '%1'")
1236 message = QStringLiteral(
"REGEXPSENSITIVE(text_messages.message, '%1')")
1240 message = QStringLiteral(
"text_messages.message REGEXP '%1'").arg(phrase);
1243 message = QStringLiteral(
"REGEXPSENSITIVE(text_messages.message '%1')").arg(phrase);
1246 message = QStringLiteral(
"LOWER(text_messages.message) LIKE '%%1%'").arg(phrase.toLower());
1250 QDateTime date = from;
1252 if (!date.isValid()) {
1253 date = QDateTime::currentDateTime();
1258 #if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
1259 date = parameter.
date.startOfDay();
1261 date = QDateTime(parameter.
date);
1266 switch (parameter.
period) {
1268 period = QStringLiteral(
"ORDER BY timestamp ASC LIMIT 1;");
1271 period = QStringLiteral(
"AND timestamp > '%1' ORDER BY timestamp ASC LIMIT 1;")
1272 .arg(date.toMSecsSinceEpoch());
1275 period = QStringLiteral(
"AND timestamp < '%1' ORDER BY timestamp DESC LIMIT 1;")
1276 .arg(date.toMSecsSinceEpoch());
1279 period = QStringLiteral(
"AND timestamp < '%1' ORDER BY timestamp DESC LIMIT 1;")
1280 .arg(date.toMSecsSinceEpoch());
1285 QStringLiteral(
"SELECT timestamp "
1287 "JOIN peers chat ON chat_id = chat.id "
1288 "JOIN text_messages ON history.id = text_messages.id "
1289 "WHERE chat.public_key='%1' "
1296 db->execNow({queryText, rowCallback});
1321 auto friendPkString = friendPk.
toString();
1328 auto countMessagesForFriend =
1329 QString(
"SELECT COUNT(*) - 1 "
1330 "FROM history countHistory "
1331 "JOIN peers chat ON chat_id = chat.id "
1332 "WHERE chat.public_key = '%1'"
1333 "AND countHistory.id <= history.id")
1334 .arg(friendPkString);
1336 auto limitString = (maxNum) ? QString(
"LIMIT %1").arg(maxNum) : QString(
"");
1338 auto queryString = QString(
"SELECT (%1), (timestamp / 1000 / 60 / 60 / 24) AS day "
1340 "JOIN peers chat ON chat_id = chat.id "
1341 "WHERE chat.public_key = '%2' "
1342 "AND timestamp >= %3 "
1345 .arg(countMessagesForFriend)
1346 .arg(friendPkString)
1347 #if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
1348 .arg(QDateTime(from.startOfDay()).toMSecsSinceEpoch())
1350 .arg(QDateTime(from).toMSecsSinceEpoch())
1355 auto rowCallback = [&dateIdxs](
const QVector<QVariant>& row) {
1359 QDateTime::fromMSecsSinceEpoch(row[1].toLongLong() * 24 * 60 * 60 * 1000).date();
1360 dateIdxs.append(dateIdx);
1363 db->execNow({queryString, rowCallback});
1380 db->execLater(QString(
"DELETE FROM faux_offline_pending WHERE id=%1;").arg(messageId.get()));
1391 qCritical() <<
"Blocked history access while history is disabled";
1408 QVector<RawDatabase::Query> queries;
1409 queries +=
RawDatabase::Query(QString(
"DELETE FROM faux_offline_pending WHERE id=%1;").arg(messageId.get()));
1412 .arg(messageId.get())
1413 .arg(
static_cast<int64_t
>(reason)));
1415 db->execLater(queries);