From 32da4de94d67d66936075d0877acfa70978498bd Mon Sep 17 00:00:00 2001 From: Martin Kleusberg Date: Mon, 16 Oct 2017 21:58:30 +0200 Subject: [PATCH] Fix collate warning after addition of multi-threaded loading The no collate function warning is triggered in a thread which is used for loading data. However, the warning is a message box and GUI elements can only be drawn in the main thread. So the old code would crash. This is fixed here by jumping to the main thread for showing the message box. --- src/MainWindow.cpp | 14 ++++++++++++++ src/MainWindow.h | 1 + src/sqlitedb.cpp | 31 +++++++++++++++++++------------ src/sqlitedb.h | 5 +++++ 4 files changed, 39 insertions(+), 12 deletions(-) diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 71c8ed20..46f76c2d 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -84,6 +84,7 @@ void MainWindow::init() connect(&db, SIGNAL(dbChanged(bool)), this, SLOT(dbState(bool))); connect(&db, SIGNAL(sqlExecuted(QString, int)), this, SLOT(logSql(QString,int))); connect(&db, SIGNAL(structureUpdated()), this, SLOT(populateStructure())); + connect(&db, &DBBrowserDB::requestCollation, this, &MainWindow::requestCollation); // Set the validator for the goto line edit ui->editGoto->setValidator(gotoValidator); @@ -2595,3 +2596,16 @@ void MainWindow::on_actionShowAllColumns_triggered() hideColumns(col, false); } } + +void MainWindow::requestCollation(const QString& name, int eTextRep) +{ + QMessageBox::StandardButton reply = QMessageBox::question( + this, + tr("Collation needed! Proceed?"), + tr("A table in this database requires a special collation function '%1' " + "that this application can't provide without further knowledge.\n" + "If you choose to proceed, be aware bad things can happen to your database.\n" + "Create a backup!").arg(name), QMessageBox::Yes | QMessageBox::No); + if(reply == QMessageBox::Yes) + sqlite3_create_collation(db._db, name.toUtf8(), eTextRep, nullptr, collCompare); +} diff --git a/src/MainWindow.h b/src/MainWindow.h index ef09510a..51d5bf56 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -267,6 +267,7 @@ private slots: void unlockViewEditing(bool unlock, QString pk = QString()); void hideColumns(int column = -1, bool hide = true); void on_actionShowAllColumns_triggered(); + void requestCollation(const QString& name, int eTextRep); }; #endif diff --git a/src/sqlitedb.cpp b/src/sqlitedb.cpp index cda6871a..b87cdfdc 100644 --- a/src/sqlitedb.cpp +++ b/src/sqlitedb.cpp @@ -13,6 +13,20 @@ #include #include #include +#include + +// Helper template to allow turning member functions into a C-style function pointer +// See https://stackoverflow.com/questions/19808054/convert-c-function-pointer-to-c-function-pointer/19809787 +template +struct Callback; +template +struct Callback { + template + static Ret callback(Args... args) { return func(args...); } + static std::function func; +}; +template +std::function Callback::func; // collation callbacks int collCompare(void* /*pArg*/, int /*eTextRepA*/, const void* sA, int /*eTextRepB*/, const void* sB) @@ -41,20 +55,11 @@ static int sqlite_compare_utf16ci( void* /*arg*/,int size1, const void *str1, in return QString::compare(string1, string2, Qt::CaseInsensitive); } -void collation_needed(void* /*pData*/, sqlite3* db, int eTextRep, const char* sCollationName) +void DBBrowserDB::collationNeeded(void* /*pData*/, sqlite3* /*db*/, int eTextRep, const char* sCollationName) { - QMessageBox::StandardButton reply = QMessageBox::question( - 0, - QObject::tr("Collation needed! Proceed?"), - QObject::tr("A table in this database requires a special collation function '%1' " - "that this application can't provide without further knowledge.\n" - "If you choose to proceed, be aware bad things can happen to your database.\n" - "Create a backup!").arg(sCollationName), QMessageBox::Yes | QMessageBox::No); - if(reply == QMessageBox::Yes) - sqlite3_create_collation(db, sCollationName, eTextRep, NULL, collCompare); + emit requestCollation(sCollationName, eTextRep); } - static void regexp(sqlite3_context* ctx, int /*argc*/, sqlite3_value* argv[]) { // Get arguments and check their values @@ -122,7 +127,9 @@ bool DBBrowserDB::open(const QString& db, bool readOnly) sqlite3_create_collation(_db, "UTF16CI", SQLITE_UTF16, 0, sqlite_compare_utf16ci); // register collation callback - sqlite3_collation_needed(_db, NULL, collation_needed); + Callback::func = std::bind(&DBBrowserDB::collationNeeded, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4); + void (*c_callback)(void*, sqlite3*, int, const char*) = static_cast(Callback::callback); + sqlite3_collation_needed(_db, NULL, c_callback); // Set foreign key settings as requested in the preferences bool foreignkeys = Settings::getValue("db", "foreignkeys").toBool(); diff --git a/src/sqlitedb.h b/src/sqlitedb.h index 137fd58d..4fd1ae87 100644 --- a/src/sqlitedb.h +++ b/src/sqlitedb.h @@ -19,6 +19,8 @@ enum typedef QMultiMap objectMap; // Maps from object type (table, index, view, trigger) to a pointer to the object representation typedef QMap schemaMap; // Maps from the schema name (main, temp, attached schemas) to the object map for that schema +int collCompare(void* pArg, int eTextRepA, const void* sA, int eTextRepB, const void* sB); + class DBBrowserDB : public QObject { Q_OBJECT @@ -117,6 +119,7 @@ signals: void sqlExecuted(QString sql, int msgtype); void dbChanged(bool dirty); void structureUpdated(); + void requestCollation(QString name, int eTextRep); private: QString curDBFilename; @@ -125,6 +128,8 @@ private: bool isEncrypted; bool isReadOnly; + void collationNeeded(void* pData, sqlite3* db, int eTextRep, const char* sCollationName); + bool tryEncryptionSettings(const QString& filename, bool* encrypted, CipherDialog*& cipherSettings); bool dontCheckForStructureUpdates;