diff --git a/src/core/coreav.cpp b/src/core/coreav.cpp index 2d6866118..f9cd1a1a3 100644 --- a/src/core/coreav.cpp +++ b/src/core/coreav.cpp @@ -68,18 +68,6 @@ * deadlock. */ -/** - * @brief Maps friend IDs to ToxFriendCall. - * @note Need to use STL container here, because Qt containers need a copy constructor. - */ -std::map CoreAV::calls; - -/** - * @brief Maps group IDs to ToxGroupCalls. - * @note Need to use STL container here, because Qt containers need a copy constructor. - */ -std::map CoreAV::groupCalls; - CoreAV::CoreAV(std::unique_ptr toxav) : audio{nullptr} , toxav{std::move(toxav)} @@ -788,12 +776,12 @@ void CoreAV::stateCallback(ToxAV* toxav, uint32_t friendNum, uint32_t state, voi if (state & TOXAV_FRIEND_CALL_STATE_ERROR) { qWarning() << "Call with friend" << friendNum << "died of unnatural causes!"; - calls.erase(friendNum); + self->calls.erase(friendNum); // why not self-> emit self->avEnd(friendNum, true); } else if (state & TOXAV_FRIEND_CALL_STATE_FINISHED) { qDebug() << "Call with friend" << friendNum << "finished quietly"; - calls.erase(friendNum); + self->calls.erase(friendNum); // why not self-> emit self->avEnd(friendNum); } else { @@ -874,7 +862,7 @@ void CoreAV::audioFrameCallback(ToxAV*, uint32_t friendNum, const int16_t* pcm, uint8_t channels, uint32_t samplingRate, void* vSelf) { CoreAV* self = static_cast(vSelf); - auto it = calls.find(friendNum); + auto it = self->calls.find(friendNum); if (it == self->calls.end()) { return; } @@ -890,10 +878,12 @@ void CoreAV::audioFrameCallback(ToxAV*, uint32_t friendNum, const int16_t* pcm, void CoreAV::videoFrameCallback(ToxAV*, uint32_t friendNum, uint16_t w, uint16_t h, const uint8_t* y, const uint8_t* u, const uint8_t* v, - int32_t ystride, int32_t ustride, int32_t vstride, void*) + int32_t ystride, int32_t ustride, int32_t vstride, void* vSelf) { - auto it = calls.find(friendNum); - if (it == calls.end()) { + auto self = static_cast(vSelf); + + auto it = self->calls.find(friendNum); + if (it == self->calls.end()) { return; } diff --git a/src/core/coreav.h b/src/core/coreav.h index 799a18945..3b23b2ff3 100644 --- a/src/core/coreav.h +++ b/src/core/coreav.h @@ -132,9 +132,19 @@ private: std::unique_ptr coreavThread; QTimer* iterateTimer = nullptr; using ToxFriendCallPtr = std::unique_ptr; - static std::map calls; + /** + * @brief Maps friend IDs to ToxFriendCall. + * @note Need to use STL container here, because Qt containers need a copy constructor. + */ + std::map calls; + + using ToxGroupCallPtr = std::unique_ptr; - static std::map groupCalls; + /** + * @brief Maps group IDs to ToxGroupCalls. + * @note Need to use STL container here, because Qt containers need a copy constructor. + */ + std::map groupCalls; std::atomic_flag threadSwitchLock; }; diff --git a/src/persistence/history.cpp b/src/persistence/history.cpp index 11c0c056b..542b3ea29 100644 --- a/src/persistence/history.cpp +++ b/src/persistence/history.cpp @@ -26,7 +26,7 @@ #include "db/rawdatabase.h" namespace { -static constexpr int SCHEMA_VERSION = 2; +static constexpr int SCHEMA_VERSION = 3; bool createCurrentSchema(RawDatabase& db) { @@ -150,6 +150,34 @@ bool dbSchema1to2(RawDatabase& db) return db.execNow(upgradeQueries); } +bool dbSchema2to3(RawDatabase& db) +{ + // Any faux_offline_pending message with the content "/me " are action + // messages that qTox previously let a user enter, but that will cause an + // action type message to be sent to toxcore, with 0 length, which will + // always fail. They must be be moved from faux_offline_pending to broken_messages + // to avoid qTox from erroring trying to send them on every connect + + const QString emptyActionMessageString = "/me "; + + QVector upgradeQueries; + upgradeQueries += RawDatabase::Query{QString("INSERT INTO broken_messages " + "SELECT faux_offline_pending.id FROM " + "history JOIN faux_offline_pending " + "ON faux_offline_pending.id = history.id " + "WHERE history.message = ?;"), + {emptyActionMessageString.toUtf8()}}; + + upgradeQueries += QString( + "DELETE FROM faux_offline_pending " + "WHERE id in (" + "SELECT id FROM broken_messages);"); + + upgradeQueries += RawDatabase::Query(QStringLiteral("PRAGMA user_version = 3;")); + + return db.execNow(upgradeQueries); +} + /** * @brief Upgrade the db schema * @note On future alterations of the database all you have to do is bump the SCHEMA_VERSION @@ -168,7 +196,8 @@ void dbSchemaUpgrade(std::shared_ptr& db) } if (databaseSchemaVersion > SCHEMA_VERSION) { - qWarning() << "Database version is newer than we currently support. Please upgrade qTox"; + qWarning().nospace() << "Database version (" << databaseSchemaVersion << + ") is newer than we currently support (" << SCHEMA_VERSION << "). Please upgrade qTox"; // We don't know what future versions have done, we have to disable db access until we re-upgrade db.reset(); return; @@ -216,6 +245,13 @@ void dbSchemaUpgrade(std::shared_ptr& db) } qDebug() << "Database upgraded incrementally to schema version 2"; //fallthrough + case 2: + if (!dbSchema2to3(*db)) { + qCritical() << "Failed to upgrade db to schema version 3, aborting"; + db.reset(); + return; + } + qDebug() << "Database upgraded incrementally to schema version 3"; // etc. default: qInfo() << "Database upgrade finished (databaseSchemaVersion" << databaseSchemaVersion diff --git a/src/widget/form/genericchatform.cpp b/src/widget/form/genericchatform.cpp index fa18dbdc0..c77ee8011 100644 --- a/src/widget/form/genericchatform.cpp +++ b/src/widget/form/genericchatform.cpp @@ -524,6 +524,11 @@ void GenericChatForm::onSendTriggered() { auto msg = msgEdit->toPlainText(); + bool isAction = msg.startsWith(ChatForm::ACTION_PREFIX, Qt::CaseInsensitive); + if (isAction) { + msg.remove(0, ChatForm::ACTION_PREFIX.length()); + } + if (msg.isEmpty()) { return; } @@ -531,11 +536,6 @@ void GenericChatForm::onSendTriggered() msgEdit->setLastMessage(msg); msgEdit->clear(); - bool isAction = msg.startsWith(ChatForm::ACTION_PREFIX, Qt::CaseInsensitive); - if (isAction) { - msg.remove(0, ChatForm::ACTION_PREFIX.length()); - } - messageDispatcher.sendMessage(isAction, msg); } diff --git a/test/persistence/dbschema_test.cpp b/test/persistence/dbschema_test.cpp index 95fe070e3..b2bb7e955 100644 --- a/test/persistence/dbschema_test.cpp +++ b/test/persistence/dbschema_test.cpp @@ -36,6 +36,7 @@ private slots: void testIsNewDb(); void test0to1(); void test1to2(); + void test2to3(); void cleanupTestCase(); private: bool initSucess{false}; @@ -48,7 +49,8 @@ const QString testFileList[] = { "testIsNewDbTrue.db", "testIsNewDbFalse.db", "test0to1.db", - "test1to2.db" + "test1to2.db", + "test2to3.db" }; const QMap schema0 { @@ -77,6 +79,9 @@ const QMap schema2 { {"broken_messages", "CREATE TABLE broken_messages (id INTEGER PRIMARY KEY)"} }; +// move stuck 0-length action messages to the existing "broken_messages" table. Not a real schema upgrade. +const auto schema3 = schema2; + void TestDbSchema::initTestCase() { for (const auto& path : testFileList) { @@ -127,7 +132,7 @@ void TestDbSchema::testCreation() QVector queries; auto db = std::shared_ptr{new RawDatabase{"testCreation.db", {}, {}}}; QVERIFY(createCurrentSchema(*db)); - verifyDb(db, schema2); + verifyDb(db, schema3); } void TestDbSchema::testIsNewDb() @@ -247,5 +252,67 @@ void TestDbSchema::test1to2() QVERIFY(totalHisoryCount == 6); // all messages should still be in history. } +void TestDbSchema::test2to3() +{ + auto db = std::shared_ptr{new RawDatabase{"test2to3.db", {}, {}}}; + createSchemaAtVersion(db, schema2); + + // since we don't enforce foreign key contraints in the db, we can stick in IDs to other tables + // to avoid generating proper entries for peers and aliases tables, since they aren't actually + // relevant for the test. + + QVector queries; + // pending message, should be moved out + queries += RawDatabase::Query{ + "INSERT INTO history (id, timestamp, chat_id, message, sender_alias) VALUES (1, 1, 0, ?, 0)", + {"/me "}}; + queries += {"INSERT INTO faux_offline_pending (id) VALUES (" + " last_insert_rowid()" + ");"}; + + // non pending message with the content "/me ". Maybe it was sent by a friend using a different client. + queries += RawDatabase::Query{ + "INSERT INTO history (id, timestamp, chat_id, message, sender_alias) VALUES (2, 2, 0, ?, 2)", + {"/me "}}; + + // non pending message sent by us + queries += RawDatabase::Query{ + "INSERT INTO history (id, timestamp, chat_id, message, sender_alias) VALUES (3, 3, 0, ?, 1)", + {"a normal message"}}; + + // pending normal message sent by us + queries += RawDatabase::Query{ + "INSERT INTO history (id, timestamp, chat_id, message, sender_alias) VALUES (4, 3, 0, ?, 1)", + {"a normal faux offline message"}}; + queries += {"INSERT INTO faux_offline_pending (id) VALUES (" + " last_insert_rowid()" + ");"}; + QVERIFY(db->execNow(queries)); + QVERIFY(dbSchema2to3(*db)); + + long brokenCount = -1; + RawDatabase::Query brokenCountQuery = {"SELECT COUNT(*) FROM broken_messages;", [&](const QVector& row) { + brokenCount = row[0].toLongLong(); + }}; + QVERIFY(db->execNow(brokenCountQuery)); + QVERIFY(brokenCount == 1); + + int fauxOfflineCount = -1; + RawDatabase::Query fauxOfflineCountQuery = {"SELECT COUNT(*) FROM faux_offline_pending;", [&](const QVector& row) { + fauxOfflineCount = row[0].toLongLong(); + }}; + QVERIFY(db->execNow(fauxOfflineCountQuery)); + QVERIFY(fauxOfflineCount == 1); + + int totalHisoryCount = -1; + RawDatabase::Query totalHistoryCountQuery = {"SELECT COUNT(*) FROM history;", [&](const QVector& row) { + totalHisoryCount = row[0].toLongLong(); + }}; + QVERIFY(db->execNow(totalHistoryCountQuery)); + QVERIFY(totalHisoryCount == 4); + + verifyDb(db, schema3); +} + QTEST_GUILESS_MAIN(TestDbSchema) #include "dbschema_test.moc"