mirror of
https://github.com/qTox/qTox.git
synced 2024-03-22 14:00:36 +08:00
Merge branch 'v1.17-dev'
This commit is contained in:
commit
e9570eafdf
@ -782,6 +782,10 @@ void ChatLog::checkVisibility(bool causedWheelEvent)
|
|||||||
auto upperBound = std::lower_bound(lowerBound, lines.cend(), getVisibleRect().bottom(),
|
auto upperBound = std::lower_bound(lowerBound, lines.cend(), getVisibleRect().bottom(),
|
||||||
ChatLine::lessThanBSRectTop);
|
ChatLine::lessThanBSRectTop);
|
||||||
|
|
||||||
|
const ChatLine::Ptr lastLineBeforeVisible = lowerBound == lines.cbegin()
|
||||||
|
? ChatLine::Ptr()
|
||||||
|
: *std::prev(lowerBound);
|
||||||
|
|
||||||
// set visibilty
|
// set visibilty
|
||||||
QList<ChatLine::Ptr> newVisibleLines;
|
QList<ChatLine::Ptr> newVisibleLines;
|
||||||
for (auto itr = lowerBound; itr != upperBound; ++itr) {
|
for (auto itr = lowerBound; itr != upperBound; ++itr) {
|
||||||
@ -807,7 +811,7 @@ void ChatLog::checkVisibility(bool causedWheelEvent)
|
|||||||
// visibleLines.last()->getRow() << " total " << visibleLines.size();
|
// visibleLines.last()->getRow() << " total " << visibleLines.size();
|
||||||
|
|
||||||
if (!visibleLines.isEmpty()) {
|
if (!visibleLines.isEmpty()) {
|
||||||
emit firstVisibleLineChanged(visibleLines.at(0));
|
emit firstVisibleLineChanged(lastLineBeforeVisible, visibleLines.at(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (causedWheelEvent) {
|
if (causedWheelEvent) {
|
||||||
|
@ -78,7 +78,7 @@ public:
|
|||||||
signals:
|
signals:
|
||||||
void selectionChanged();
|
void selectionChanged();
|
||||||
void workerTimeoutFinished();
|
void workerTimeoutFinished();
|
||||||
void firstVisibleLineChanged(const ChatLine::Ptr&);
|
void firstVisibleLineChanged(const ChatLine::Ptr& prevLine, const ChatLine::Ptr& firstLine);
|
||||||
void loadHistoryLower();
|
void loadHistoryLower();
|
||||||
void loadHistoryUpper();
|
void loadHistoryUpper();
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@
|
|||||||
#include "db/rawdatabase.h"
|
#include "db/rawdatabase.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
static constexpr int SCHEMA_VERSION = 3;
|
static constexpr int SCHEMA_VERSION = 4;
|
||||||
|
|
||||||
bool createCurrentSchema(RawDatabase& db)
|
bool createCurrentSchema(RawDatabase& db)
|
||||||
{
|
{
|
||||||
@ -63,6 +63,9 @@ bool createCurrentSchema(RawDatabase& db)
|
|||||||
"file_state INTEGER NOT NULL);"
|
"file_state INTEGER NOT NULL);"
|
||||||
"CREATE TABLE faux_offline_pending (id INTEGER PRIMARY KEY);"
|
"CREATE TABLE faux_offline_pending (id INTEGER PRIMARY KEY);"
|
||||||
"CREATE TABLE broken_messages (id INTEGER PRIMARY KEY);"));
|
"CREATE TABLE broken_messages (id INTEGER PRIMARY KEY);"));
|
||||||
|
// sqlite doesn't support including the index as part of the CREATE TABLE statement, so add a second query
|
||||||
|
queries += RawDatabase::Query(
|
||||||
|
"CREATE INDEX chat_id_idx on history (chat_id);");
|
||||||
queries += RawDatabase::Query(QStringLiteral("PRAGMA user_version = %1;").arg(SCHEMA_VERSION));
|
queries += RawDatabase::Query(QStringLiteral("PRAGMA user_version = %1;").arg(SCHEMA_VERSION));
|
||||||
return db.execNow(queries);
|
return db.execNow(queries);
|
||||||
}
|
}
|
||||||
@ -178,6 +181,18 @@ bool dbSchema2to3(RawDatabase& db)
|
|||||||
return db.execNow(upgradeQueries);
|
return db.execNow(upgradeQueries);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool dbSchema3to4(RawDatabase& db)
|
||||||
|
{
|
||||||
|
QVector<RawDatabase::Query> upgradeQueries;
|
||||||
|
upgradeQueries += RawDatabase::Query{QString(
|
||||||
|
"CREATE INDEX chat_id_idx on history (chat_id);")};
|
||||||
|
|
||||||
|
upgradeQueries += RawDatabase::Query(QStringLiteral("PRAGMA user_version = 4;"));
|
||||||
|
|
||||||
|
return db.execNow(upgradeQueries);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Upgrade the db schema
|
* @brief Upgrade the db schema
|
||||||
* @note On future alterations of the database all you have to do is bump the SCHEMA_VERSION
|
* @note On future alterations of the database all you have to do is bump the SCHEMA_VERSION
|
||||||
@ -252,6 +267,13 @@ void dbSchemaUpgrade(std::shared_ptr<RawDatabase>& db)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
qDebug() << "Database upgraded incrementally to schema version 3";
|
qDebug() << "Database upgraded incrementally to schema version 3";
|
||||||
|
case 3:
|
||||||
|
if (!dbSchema3to4(*db)) {
|
||||||
|
qCritical() << "Failed to upgrade db to schema version 4, aborting";
|
||||||
|
db.reset();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
qDebug() << "Database upgraded incrementally to schema version 4";
|
||||||
// etc.
|
// etc.
|
||||||
default:
|
default:
|
||||||
qInfo() << "Database upgrade finished (databaseSchemaVersion" << databaseSchemaVersion
|
qInfo() << "Database upgrade finished (databaseSchemaVersion" << databaseSchemaVersion
|
||||||
|
@ -66,14 +66,14 @@ const QString VIDEO_TOOL_TIP[] = {
|
|||||||
|
|
||||||
const QString VOL_TOOL_TIP[] = {
|
const QString VOL_TOOL_TIP[] = {
|
||||||
ChatFormHeader::tr("Sound can be disabled only during a call"),
|
ChatFormHeader::tr("Sound can be disabled only during a call"),
|
||||||
ChatFormHeader::tr("Unmute call"),
|
|
||||||
ChatFormHeader::tr("Mute call"),
|
ChatFormHeader::tr("Mute call"),
|
||||||
|
ChatFormHeader::tr("Unmute call"),
|
||||||
};
|
};
|
||||||
|
|
||||||
const QString MIC_TOOL_TIP[] = {
|
const QString MIC_TOOL_TIP[] = {
|
||||||
ChatFormHeader::tr("Microphone can be muted only during a call"),
|
ChatFormHeader::tr("Microphone can be muted only during a call"),
|
||||||
ChatFormHeader::tr("Unmute microphone"),
|
|
||||||
ChatFormHeader::tr("Mute microphone"),
|
ChatFormHeader::tr("Mute microphone"),
|
||||||
|
ChatFormHeader::tr("Unmute microphone"),
|
||||||
};
|
};
|
||||||
|
|
||||||
template <class T, class Fun>
|
template <class T, class Fun>
|
||||||
|
@ -644,7 +644,7 @@ QDateTime GenericChatForm::getTime(const ChatLine::Ptr &chatLine) const
|
|||||||
if (timestamp) {
|
if (timestamp) {
|
||||||
return timestamp->getTime();
|
return timestamp->getTime();
|
||||||
} else {
|
} else {
|
||||||
return QDateTime::currentDateTime();
|
return QDateTime();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1190,9 +1190,14 @@ void GenericChatForm::loadHistoryUpper()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GenericChatForm::updateShowDateInfo(const ChatLine::Ptr& line)
|
void GenericChatForm::updateShowDateInfo(const ChatLine::Ptr& prevLine, const ChatLine::Ptr& topLine)
|
||||||
{
|
{
|
||||||
const auto date = getTime(line);
|
// If the dateInfo is visible we need to pretend the top line is the one
|
||||||
|
// covered by the date to prevent oscillations
|
||||||
|
const auto effectiveTopLine = (dateInfo->isVisible() && prevLine)
|
||||||
|
? prevLine : topLine;
|
||||||
|
|
||||||
|
const auto date = getTime(effectiveTopLine);
|
||||||
|
|
||||||
if (date.isValid() && date.date() != QDate::currentDate()) {
|
if (date.isValid() && date.date() != QDate::currentDate()) {
|
||||||
const auto dateText = QStringLiteral("<b>%1<\b>").arg(date.toString(Settings::getInstance().getDateFormat()));
|
const auto dateText = QStringLiteral("<b>%1<\b>").arg(date.toString(Settings::getInstance().getDateFormat()));
|
||||||
|
@ -114,7 +114,7 @@ protected slots:
|
|||||||
void onExportChat();
|
void onExportChat();
|
||||||
void searchFormShow();
|
void searchFormShow();
|
||||||
void onSearchTriggered();
|
void onSearchTriggered();
|
||||||
void updateShowDateInfo(const ChatLine::Ptr& line);
|
void updateShowDateInfo(const ChatLine::Ptr& prevLine, const ChatLine::Ptr& topLine);
|
||||||
|
|
||||||
void searchInBegin(const QString& phrase, const ParameterSearch& parameter);
|
void searchInBegin(const QString& phrase, const ParameterSearch& parameter);
|
||||||
void onSearchUp(const QString& phrase, const ParameterSearch& parameter);
|
void onSearchUp(const QString& phrase, const ParameterSearch& parameter);
|
||||||
|
@ -25,8 +25,20 @@
|
|||||||
#include <QtTest/QtTest>
|
#include <QtTest/QtTest>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
|
struct SqliteMasterEntry {
|
||||||
|
QString name;
|
||||||
|
QString sql;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool operator==(const SqliteMasterEntry& lhs, const SqliteMasterEntry& rhs)
|
||||||
|
{
|
||||||
|
return lhs.name == rhs.name &&
|
||||||
|
lhs.sql == rhs.sql;
|
||||||
|
}
|
||||||
|
|
||||||
class TestDbSchema : public QObject
|
class TestDbSchema : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
@ -37,11 +49,12 @@ private slots:
|
|||||||
void test0to1();
|
void test0to1();
|
||||||
void test1to2();
|
void test1to2();
|
||||||
void test2to3();
|
void test2to3();
|
||||||
|
void test3to4();
|
||||||
void cleanupTestCase();
|
void cleanupTestCase();
|
||||||
private:
|
private:
|
||||||
bool initSucess{false};
|
bool initSucess{false};
|
||||||
void createSchemaAtVersion(std::shared_ptr<RawDatabase>, const QMap<QString, QString>& schema);
|
void createSchemaAtVersion(std::shared_ptr<RawDatabase>, const std::vector<SqliteMasterEntry>& schema);
|
||||||
void verifyDb(std::shared_ptr<RawDatabase> db, const QMap<QString, QString>& expectedSql);
|
void verifyDb(std::shared_ptr<RawDatabase> db, const std::vector<SqliteMasterEntry>& expectedSql);
|
||||||
};
|
};
|
||||||
|
|
||||||
const QString testFileList[] = {
|
const QString testFileList[] = {
|
||||||
@ -50,10 +63,13 @@ const QString testFileList[] = {
|
|||||||
"testIsNewDbFalse.db",
|
"testIsNewDbFalse.db",
|
||||||
"test0to1.db",
|
"test0to1.db",
|
||||||
"test1to2.db",
|
"test1to2.db",
|
||||||
"test2to3.db"
|
"test2to3.db",
|
||||||
|
"test3to4.db"
|
||||||
};
|
};
|
||||||
|
|
||||||
const QMap<QString, QString> schema0 {
|
// db schemas can be select with "SELECT name, sql FROM sqlite_master;" on the database.
|
||||||
|
|
||||||
|
const std::vector<SqliteMasterEntry> schema0 {
|
||||||
{"aliases", "CREATE TABLE aliases (id INTEGER PRIMARY KEY, owner INTEGER, display_name BLOB NOT NULL, UNIQUE(owner, display_name))"},
|
{"aliases", "CREATE TABLE aliases (id INTEGER PRIMARY KEY, owner INTEGER, display_name BLOB NOT NULL, UNIQUE(owner, display_name))"},
|
||||||
{"faux_offline_pending", "CREATE TABLE faux_offline_pending (id INTEGER PRIMARY KEY)"},
|
{"faux_offline_pending", "CREATE TABLE faux_offline_pending (id INTEGER PRIMARY KEY)"},
|
||||||
{"history", "CREATE TABLE history (id INTEGER PRIMARY KEY, timestamp INTEGER NOT NULL, chat_id INTEGER NOT NULL, sender_alias INTEGER NOT NULL, message BLOB NOT NULL)"},
|
{"history", "CREATE TABLE history (id INTEGER PRIMARY KEY, timestamp INTEGER NOT NULL, chat_id INTEGER NOT NULL, sender_alias INTEGER NOT NULL, message BLOB NOT NULL)"},
|
||||||
@ -61,7 +77,7 @@ const QMap<QString, QString> schema0 {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// added file transfer history
|
// added file transfer history
|
||||||
const QMap<QString, QString> schema1 {
|
const std::vector<SqliteMasterEntry> schema1 {
|
||||||
{"aliases", "CREATE TABLE aliases (id INTEGER PRIMARY KEY, owner INTEGER, display_name BLOB NOT NULL, UNIQUE(owner, display_name))"},
|
{"aliases", "CREATE TABLE aliases (id INTEGER PRIMARY KEY, owner INTEGER, display_name BLOB NOT NULL, UNIQUE(owner, display_name))"},
|
||||||
{"faux_offline_pending", "CREATE TABLE faux_offline_pending (id INTEGER PRIMARY KEY)"},
|
{"faux_offline_pending", "CREATE TABLE faux_offline_pending (id INTEGER PRIMARY KEY)"},
|
||||||
{"file_transfers", "CREATE TABLE file_transfers (id INTEGER PRIMARY KEY, chat_id INTEGER NOT NULL, file_restart_id BLOB NOT NULL, file_name BLOB NOT NULL, file_path BLOB NOT NULL, file_hash BLOB NOT NULL, file_size INTEGER NOT NULL, direction INTEGER NOT NULL, file_state INTEGER NOT NULL)"},
|
{"file_transfers", "CREATE TABLE file_transfers (id INTEGER PRIMARY KEY, chat_id INTEGER NOT NULL, file_restart_id BLOB NOT NULL, file_name BLOB NOT NULL, file_path BLOB NOT NULL, file_hash BLOB NOT NULL, file_size INTEGER NOT NULL, direction INTEGER NOT NULL, file_state INTEGER NOT NULL)"},
|
||||||
@ -70,7 +86,7 @@ const QMap<QString, QString> schema1 {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// move stuck faux offline messages do a table of "broken" messages
|
// move stuck faux offline messages do a table of "broken" messages
|
||||||
const QMap<QString, QString> schema2 {
|
const std::vector<SqliteMasterEntry> schema2 {
|
||||||
{"aliases", "CREATE TABLE aliases (id INTEGER PRIMARY KEY, owner INTEGER, display_name BLOB NOT NULL, UNIQUE(owner, display_name))"},
|
{"aliases", "CREATE TABLE aliases (id INTEGER PRIMARY KEY, owner INTEGER, display_name BLOB NOT NULL, UNIQUE(owner, display_name))"},
|
||||||
{"faux_offline_pending", "CREATE TABLE faux_offline_pending (id INTEGER PRIMARY KEY)"},
|
{"faux_offline_pending", "CREATE TABLE faux_offline_pending (id INTEGER PRIMARY KEY)"},
|
||||||
{"file_transfers", "CREATE TABLE file_transfers (id INTEGER PRIMARY KEY, chat_id INTEGER NOT NULL, file_restart_id BLOB NOT NULL, file_name BLOB NOT NULL, file_path BLOB NOT NULL, file_hash BLOB NOT NULL, file_size INTEGER NOT NULL, direction INTEGER NOT NULL, file_state INTEGER NOT NULL)"},
|
{"file_transfers", "CREATE TABLE file_transfers (id INTEGER PRIMARY KEY, chat_id INTEGER NOT NULL, file_restart_id BLOB NOT NULL, file_name BLOB NOT NULL, file_path BLOB NOT NULL, file_hash BLOB NOT NULL, file_size INTEGER NOT NULL, direction INTEGER NOT NULL, file_state INTEGER NOT NULL)"},
|
||||||
@ -82,6 +98,17 @@ const QMap<QString, QString> schema2 {
|
|||||||
// move stuck 0-length action messages to the existing "broken_messages" table. Not a real schema upgrade.
|
// move stuck 0-length action messages to the existing "broken_messages" table. Not a real schema upgrade.
|
||||||
const auto schema3 = schema2;
|
const auto schema3 = schema2;
|
||||||
|
|
||||||
|
// create index in history table on chat_id to improve query speed. Not a real schema upgrade.
|
||||||
|
const std::vector<SqliteMasterEntry> schema4 {
|
||||||
|
{"aliases", "CREATE TABLE aliases (id INTEGER PRIMARY KEY, owner INTEGER, display_name BLOB NOT NULL, UNIQUE(owner, display_name))"},
|
||||||
|
{"faux_offline_pending", "CREATE TABLE faux_offline_pending (id INTEGER PRIMARY KEY)"},
|
||||||
|
{"file_transfers", "CREATE TABLE file_transfers (id INTEGER PRIMARY KEY, chat_id INTEGER NOT NULL, file_restart_id BLOB NOT NULL, file_name BLOB NOT NULL, file_path BLOB NOT NULL, file_hash BLOB NOT NULL, file_size INTEGER NOT NULL, direction INTEGER NOT NULL, file_state INTEGER NOT NULL)"},
|
||||||
|
{"history", "CREATE TABLE history (id INTEGER PRIMARY KEY, timestamp INTEGER NOT NULL, chat_id INTEGER NOT NULL, sender_alias INTEGER NOT NULL, message BLOB NOT NULL, file_id INTEGER)"},
|
||||||
|
{"peers", "CREATE TABLE peers (id INTEGER PRIMARY KEY, public_key TEXT NOT NULL UNIQUE)"},
|
||||||
|
{"broken_messages", "CREATE TABLE broken_messages (id INTEGER PRIMARY KEY)"},
|
||||||
|
{"chat_id_idx", "CREATE INDEX chat_id_idx on history (chat_id)"}
|
||||||
|
};
|
||||||
|
|
||||||
void TestDbSchema::initTestCase()
|
void TestDbSchema::initTestCase()
|
||||||
{
|
{
|
||||||
for (const auto& path : testFileList) {
|
for (const auto& path : testFileList) {
|
||||||
@ -101,28 +128,32 @@ void TestDbSchema::cleanupTestCase()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TestDbSchema::verifyDb(std::shared_ptr<RawDatabase> db, const QMap<QString, QString>& expectedSql)
|
void TestDbSchema::verifyDb(std::shared_ptr<RawDatabase> db, const std::vector<SqliteMasterEntry>& expectedSql)
|
||||||
{
|
{
|
||||||
QVERIFY(db->execNow(RawDatabase::Query(QStringLiteral(
|
QVERIFY(db->execNow(RawDatabase::Query(QStringLiteral(
|
||||||
"SELECT name, sql FROM sqlite_master "
|
"SELECT name, sql FROM sqlite_master;"),
|
||||||
"WHERE type='table';"),
|
|
||||||
[&](const QVector<QVariant>& row) {
|
[&](const QVector<QVariant>& row) {
|
||||||
const QString tableName = row[0].toString();
|
const QString tableName = row[0].toString();
|
||||||
|
if (row[1].isNull()) {
|
||||||
|
// implicit indexes are automatically created for primary key constraints and unique constraints
|
||||||
|
// so their existence is already covered by the table creation SQL
|
||||||
|
return;
|
||||||
|
}
|
||||||
QString tableSql = row[1].toString();
|
QString tableSql = row[1].toString();
|
||||||
QVERIFY(expectedSql.contains(tableName));
|
|
||||||
// table and column names can be quoted. UPDATE TEABLE automatically quotes the new names, but this
|
// table and column names can be quoted. UPDATE TEABLE automatically quotes the new names, but this
|
||||||
// has no functional impact on the schema. Strip quotes for comparison so that our created schema
|
// has no functional impact on the schema. Strip quotes for comparison so that our created schema
|
||||||
// matches schema made from UPDATE TABLEs.
|
// matches schema made from UPDATE TABLEs.
|
||||||
const QString unquotedTableSql = tableSql.remove("\"");
|
const QString unquotedTableSql = tableSql.remove("\"");
|
||||||
QVERIFY(expectedSql.value(tableName) == unquotedTableSql);
|
SqliteMasterEntry entry{tableName, unquotedTableSql};
|
||||||
|
QVERIFY(std::find(expectedSql.begin(), expectedSql.end(), entry) != expectedSql.end());
|
||||||
})));
|
})));
|
||||||
}
|
}
|
||||||
|
|
||||||
void TestDbSchema::createSchemaAtVersion(std::shared_ptr<RawDatabase> db, const QMap<QString, QString>& schema)
|
void TestDbSchema::createSchemaAtVersion(std::shared_ptr<RawDatabase> db, const std::vector<SqliteMasterEntry>& schema)
|
||||||
{
|
{
|
||||||
QVector<RawDatabase::Query> queries;
|
QVector<RawDatabase::Query> queries;
|
||||||
for (auto const& tableCreation : schema.values()) {
|
for (auto const& entry : schema) {
|
||||||
queries += tableCreation;
|
queries += entry.sql;
|
||||||
}
|
}
|
||||||
QVERIFY(db->execNow(queries));
|
QVERIFY(db->execNow(queries));
|
||||||
}
|
}
|
||||||
@ -132,7 +163,7 @@ void TestDbSchema::testCreation()
|
|||||||
QVector<RawDatabase::Query> queries;
|
QVector<RawDatabase::Query> queries;
|
||||||
auto db = std::shared_ptr<RawDatabase>{new RawDatabase{"testCreation.db", {}, {}}};
|
auto db = std::shared_ptr<RawDatabase>{new RawDatabase{"testCreation.db", {}, {}}};
|
||||||
QVERIFY(createCurrentSchema(*db));
|
QVERIFY(createCurrentSchema(*db));
|
||||||
verifyDb(db, schema3);
|
verifyDb(db, schema4);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TestDbSchema::testIsNewDb()
|
void TestDbSchema::testIsNewDb()
|
||||||
@ -314,5 +345,13 @@ void TestDbSchema::test2to3()
|
|||||||
verifyDb(db, schema3);
|
verifyDb(db, schema3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TestDbSchema::test3to4()
|
||||||
|
{
|
||||||
|
auto db = std::shared_ptr<RawDatabase>{new RawDatabase{"test3to4.db", {}, {}}};
|
||||||
|
createSchemaAtVersion(db, schema3);
|
||||||
|
QVERIFY(dbSchema3to4(*db));
|
||||||
|
verifyDb(db, schema4);
|
||||||
|
}
|
||||||
|
|
||||||
QTEST_GUILESS_MAIN(TestDbSchema)
|
QTEST_GUILESS_MAIN(TestDbSchema)
|
||||||
#include "dbschema_test.moc"
|
#include "dbschema_test.moc"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user