[network/ruqola] src/core: MAke sure to delete all connection
Laurent Montel <[email protected]>
| Newsgroups | gmane.comp.kde.cvs |
|---|---|
| Message-ID | <[email protected]> |
Git commit c1ced96a786b7d03d9abd0ce31ac4469228d0099 by Laurent Montel.
Committed on 16/08/2026 at 13:11.
Pushed by mlaurent into branch 'master'.
MAke sure to delete all connection
M +5 -0 src/core/accountmanager.cpp
M +48 -0 src/core/localdatabase/autotests/localdatabasebasetest.cpp
M +1 -0 src/core/localdatabase/autotests/localdatabasebasetest.h
M +57 -4 src/core/localdatabase/localdatabasebase.cpp
M +5 -0 src/core/localdatabase/localdatabasebase.h
https://invent.kde.org/network/ruqola/-/commit/c1ced96a786b7d03d9abd0ce31ac4469228d0099
diff --git a/src/core/accountmanager.cpp b/src/core/accountmanager.cpp
index 08e98894a2..7d666440af 100644
--- a/src/core/accountmanager.cpp
+++ b/src/core/accountmanager.cpp
@@ -6,6 +6,7 @@
#include "accountmanager.h"
#include "job/validateinviteserverjob.h"
+#include "localdatabase/localdatabasebase.h"
#include "localdatabase/localdatabaseutils.h"
#include "managerdatapaths.h"
#include "model/rocketchataccountfilterproxymodel.h"
@@ -474,6 +475,10 @@ void AccountManager::removeLogs(const QString &accountName)
void AccountManager::removeDatabaseAccount(const QString &accountName)
{
+ // Sqlite connections are process-wide and outlive the account: close them before removing the
+ // files, otherwise re-adding an account with the same name would reuse a connection pointing at
+ // a deleted file (writes lost, tables gone).
+ LocalDatabaseBase::removeDataBaseConnections(accountName);
{
const QString directory = LocalDatabaseUtils::localAccountsDatabasePath() + accountName;
removeDirectory(directory);
diff --git a/src/core/localdatabase/autotests/localdatabasebasetest.cpp b/src/core/localdatabase/autotests/localdatabasebasetest.cpp
index 7e98be867e..53f11fc8cf 100644
--- a/src/core/localdatabase/autotests/localdatabasebasetest.cpp
+++ b/src/core/localdatabase/autotests/localdatabasebasetest.cpp
@@ -7,6 +7,8 @@
#include "localdatabasebasetest.h"
#include "localdatabase/localdatabasebase.h"
+#include <QSqlDatabase>
+#include <QTemporaryDir>
#include <QTest>
using namespace Qt::Literals::StringLiterals;
@@ -22,6 +24,20 @@ public:
{
return databaseName(name);
}
+ [[nodiscard]] QString schemaDataBase() const override
+ {
+ return u"CREATE TABLE TESTTABLE (identifier TEXT PRIMARY KEY NOT NULL)"_s;
+ }
+ [[nodiscard]] bool createDataBase(const QString &accountName)
+ {
+ QSqlDatabase db;
+ return initializeDataBase(accountName, db);
+ }
+ [[nodiscard]] bool createDataBase(const QString &accountName, const QByteArray &roomId)
+ {
+ QSqlDatabase db;
+ return initializeDataBase(accountName, roomId, db);
+ }
};
QTEST_MAIN(LocalDatabaseBaseTest)
@@ -69,4 +85,36 @@ void LocalDatabaseBaseTest::shouldHaveDefaultValues()
QCOMPARE(w.currentDatabaseName(u"kde"_s), u"e2e-rooms-kde"_s);
}
}
+
+void LocalDatabaseBaseTest::shouldRemoveDataBaseConnections()
+{
+ QTemporaryDir tmpDir;
+ QVERIFY(tmpDir.isValid());
+ const QString basePath = tmpDir.path() + u'/';
+
+ TestLocalDatabaseBase rooms(basePath + u"rooms/"_s, LocalDatabaseBase::DatabaseType::Rooms);
+ TestLocalDatabaseBase messages(basePath + u"messages/"_s, LocalDatabaseBase::DatabaseType::Messages);
+ QVERIFY(rooms.createDataBase(u"kde"_s));
+ QVERIFY(rooms.createDataBase(u"kde2"_s));
+ QVERIFY(messages.createDataBase(u"kde"_s, "roomId1"));
+
+ QVERIFY(QSqlDatabase::contains(u"rooms-kde"_s));
+ QVERIFY(QSqlDatabase::contains(u"rooms-kde2"_s));
+ QVERIFY(QSqlDatabase::contains(u"messages-kde-roomId1"_s));
+
+ LocalDatabaseBase::removeDataBaseConnections(u"kde"_s);
+
+ QVERIFY(!QSqlDatabase::contains(u"rooms-kde"_s));
+ QVERIFY(!QSqlDatabase::contains(u"messages-kde-roomId1"_s));
+ // An account whose name merely starts with the removed one must be left alone
+ QVERIFY(QSqlDatabase::contains(u"rooms-kde2"_s));
+
+ // The connection is gone but the file is still there: reopening must find the existing schema
+ QVERIFY(rooms.createDataBase(u"kde"_s));
+ QVERIFY(QSqlDatabase::contains(u"rooms-kde"_s));
+
+ LocalDatabaseBase::removeDataBaseConnections(u"kde"_s);
+ LocalDatabaseBase::removeDataBaseConnections(u"kde2"_s);
+}
+
#include "moc_localdatabasebasetest.cpp"
diff --git a/src/core/localdatabase/autotests/localdatabasebasetest.h b/src/core/localdatabase/autotests/localdatabasebasetest.h
index 7065fb82c1..8d60923e23 100644
--- a/src/core/localdatabase/autotests/localdatabasebasetest.h
+++ b/src/core/localdatabase/autotests/localdatabasebasetest.h
@@ -16,4 +16,5 @@ public:
~LocalDatabaseBaseTest() override = default;
private Q_SLOTS:
void shouldHaveDefaultValues();
+ void shouldRemoveDataBaseConnections();
};
diff --git a/src/core/localdatabase/localdatabasebase.cpp b/src/core/localdatabase/localdatabasebase.cpp
index bfeb975105..7d49aff197 100644
--- a/src/core/localdatabase/localdatabasebase.cpp
+++ b/src/core/localdatabase/localdatabasebase.cpp
@@ -52,12 +52,11 @@ QString LocalDatabaseBase::schemaDataBase() const
return {};
}
-QString LocalDatabaseBase::databaseName(const QString &name) const
+QString LocalDatabaseBase::databaseNamePrefix(DatabaseType type)
{
QString prefix;
- switch (mDatabaseType) {
+ switch (type) {
case DatabaseType::Unknown:
- qCWarning(RUQOLA_DATABASE_LOG) << "Unknown data base it's a bug" << name;
break;
case DatabaseType::Accounts:
prefix = u"accounts-"_s;
@@ -86,7 +85,61 @@ QString LocalDatabaseBase::databaseName(const QString &name) const
case DatabaseType::Logger:
break;
}
- return prefix + name;
+ return prefix;
+}
+
+QString LocalDatabaseBase::databaseName(const QString &name) const
+{
+ if (mDatabaseType == DatabaseType::Unknown) {
+ qCWarning(RUQOLA_DATABASE_LOG) << "Unknown data base it's a bug" << name;
+ }
+ return databaseNamePrefix(mDatabaseType) + name;
+}
+
+void LocalDatabaseBase::removeDataBaseConnections(const QString &accountName)
+{
+ if (accountName.isEmpty()) {
+ return;
+ }
+ // All types: the caller removes the account as a whole, and each database class registers its
+ // connections under its own prefix.
+ static constexpr DatabaseType allTypes[] = {
+ DatabaseType::Accounts,
+ DatabaseType::Rooms,
+ DatabaseType::Messages,
+ DatabaseType::Logger,
+ DatabaseType::Global,
+ DatabaseType::E2E,
+ DatabaseType::E2ERooms,
+ DatabaseType::PendingTypedInfo,
+ DatabaseType::RoomSubscriptions,
+ };
+
+ const QStringList connectionNames = QSqlDatabase::connectionNames();
+ QStringList namesToRemove;
+ for (const DatabaseType type : allTypes) {
+ const QString accountConnection = databaseNamePrefix(type) + accountName;
+ // Per-room databases are registered as "<prefix><accountName>-<roomId>"
+ const QString roomConnectionPrefix = accountConnection + u'-';
+ for (const QString &name : connectionNames) {
+ if (name == accountConnection || name.startsWith(roomConnectionPrefix)) {
+ namesToRemove.append(name);
+ }
+ }
+ }
+ namesToRemove.removeDuplicates();
+
+ for (const QString &name : std::as_const(namesToRemove)) {
+ {
+ // Don't reopen it just to close it, and let the copy die before removeDatabase()
+ QSqlDatabase db = QSqlDatabase::database(name, false);
+ if (db.isOpen()) {
+ db.close();
+ }
+ }
+ QSqlDatabase::removeDatabase(name);
+ }
+ qCDebug(RUQOLA_DATABASE_LOG) << "Removed" << namesToRemove.count() << "database connection(s) for account" << accountName;
}
void LocalDatabaseBase::setDatabaseLogger(RocketChatRestApi::AbstractLogger *logger)
diff --git a/src/core/localdatabase/localdatabasebase.h b/src/core/localdatabase/localdatabasebase.h
index f9c3e7567b..d6c19ce1f7 100644
--- a/src/core/localdatabase/localdatabasebase.h
+++ b/src/core/localdatabase/localdatabasebase.h
@@ -35,6 +35,10 @@ public:
[[nodiscard]] QString schemaDatabaseStr() const;
void setDatabaseLogger(RocketChatRestApi::AbstractLogger *logger);
+ // Closes and unregisters every sqlite connection belonging to this account, whatever the
+ // database type. Must be called before the account files are deleted from disk.
+ static void removeDataBaseConnections(const QString &accountName);
+
protected:
[[nodiscard]] virtual QString schemaDataBase() const;
[[nodiscard]] bool initializeDataBase(const QString &accountName, const QByteArray &roomId, QSqlDatabase &db);
@@ -42,6 +46,7 @@ protected:
[[nodiscard]] bool checkDataBase(const QString &accountName, const QByteArray &roomId, QSqlDatabase &db);
[[nodiscard]] bool checkDataBase(const QString &accountName, QSqlDatabase &db);
[[nodiscard]] QString databaseName(const QString &name) const;
+ [[nodiscard]] static QString databaseNamePrefix(DatabaseType type);
const QString mBasePath;
const DatabaseType mDatabaseType = DatabaseType::Unknown;