X-Git-Url: https://git.quassel-irc.org/?p=quassel.git;a=blobdiff_plain;f=src%2Fcore%2Fsqlitestorage.cpp;h=b7a07fd3919293b7893ef61d0224c72421ee05c7;hp=e71c5375bed2d95f13b3b5e4567be95139c633f0;hb=99055e8e4a83c234ae424cc225d3a7aa17c1544b;hpb=8cece06596c290d69c1f32b7221c796437f5fabb diff --git a/src/core/sqlitestorage.cpp b/src/core/sqlitestorage.cpp index e71c5375..b7a07fd3 100644 --- a/src/core/sqlitestorage.cpp +++ b/src/core/sqlitestorage.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2005-2015 by the Quassel Project * + * Copyright (C) 2005-2020 by the Quassel Project * * devel@quassel-irc.org * * * * This program is free software; you can redistribute it and/or modify * @@ -20,41 +20,41 @@ #include "sqlitestorage.h" -#include +#include +#include +#include +#include -#include "logger.h" #include "network.h" #include "quassel.h" int SqliteStorage::_maxRetryCount = 150; -SqliteStorage::SqliteStorage(QObject *parent) +SqliteStorage::SqliteStorage(QObject* parent) : AbstractSqlStorage(parent) -{ -} - - -SqliteStorage::~SqliteStorage() -{ -} - +{} bool SqliteStorage::isAvailable() const { - if (!QSqlDatabase::isDriverAvailable("QSQLITE")) return false; + if (!QSqlDatabase::isDriverAvailable("QSQLITE")) + return false; return true; } +QString SqliteStorage::backendId() const +{ + return QString("SQLite"); +} QString SqliteStorage::displayName() const { + // Note: Pre-0.13 clients use the displayName property for backend idenfication // We identify the backend to use for the monolithic core by its displayname. // so only change this string if you _really_ have to and make sure the core // setup for the mono client still works ;) - return QString("SQLite"); + return backendId(); } - QString SqliteStorage::description() const { return tr("SQLite is a file-based database engine that does not require any setup. It is suitable for small and medium-sized " @@ -62,7 +62,6 @@ QString SqliteStorage::description() const "it is running on, and if you only expect a few users to use your core."); } - int SqliteStorage::installedSchemaVersion() { // only used when there is a singlethread (during startup) @@ -79,24 +78,40 @@ int SqliteStorage::installedSchemaVersion() return AbstractSqlStorage::installedSchemaVersion(); } - -bool SqliteStorage::updateSchemaVersion(int newVersion) +bool SqliteStorage::updateSchemaVersion(int newVersion, bool clearUpgradeStep) { // only used when there is a singlethread (during startup) // so we don't need locking here - QSqlQuery query(logDb()); + + QSqlDatabase db = logDb(); + + // Atomically update the schema version and clear the upgrade step, if specified + // Note: This will need reworked if "updateSchemaVersion" is ever called within a transaction. + db.transaction(); + + QSqlQuery query(db); query.prepare("UPDATE coreinfo SET value = :version WHERE key = 'schemaversion'"); query.bindValue(":version", newVersion); - query.exec(); + safeExec(query); - bool success = true; - if (query.lastError().isValid()) { - qCritical() << "SqliteStorage::updateSchemaVersion(int): Updating schema version failed!"; - success = false; + if (!watchQuery(query)) { + qCritical() << "SqliteStorage::updateSchemaVersion(int, bool): Updating schema version failed!"; + db.rollback(); + return false; + } + + if (clearUpgradeStep) { + // Try clearing the upgrade step if requested + if (!setSchemaVersionUpgradeStep("")) { + db.rollback(); + return false; + } } - return success; -} + // Successful, commit and return true + db.commit(); + return true; +} bool SqliteStorage::setupSchemaVersion(int version) { @@ -115,8 +130,53 @@ bool SqliteStorage::setupSchemaVersion(int version) return success; } +QString SqliteStorage::schemaVersionUpgradeStep() +{ + // Only used when there is a singlethread (during startup), so we don't need locking here + QSqlQuery query(logDb()); + query.prepare("SELECT value FROM coreinfo WHERE key = 'schemaupgradestep'"); + safeExec(query); + watchQuery(query); + if (query.first()) + return query.value(0).toString(); + + // Fall back to the default value + return AbstractSqlStorage::schemaVersionUpgradeStep(); +} + +bool SqliteStorage::setSchemaVersionUpgradeStep(QString upgradeQuery) +{ + // Only used when there is a singlethread (during startup), so we don't need locking here + + // Intentionally do not wrap in a transaction so other functions can include multiple operations + QSqlQuery query(logDb()); + query.prepare("UPDATE coreinfo SET value = :upgradestep WHERE key = 'schemaupgradestep'"); + query.bindValue(":upgradestep", upgradeQuery); + safeExec(query); + + // Don't wrap with watchQuery to avoid an alarming message in the log when the key is missing + // Make sure that the query didn't fail, and that some non-zero number of rows were affected + bool success = !query.lastError().isValid() && query.numRowsAffected() != 0; + + if (!success) { + // The key might not exist (Quassel 0.13.0 and older). Try inserting it... + query = QSqlQuery(logDb()); + query.prepare("INSERT INTO coreinfo (key, value) VALUES ('schemaupgradestep', :upgradestep)"); + query.bindValue(":upgradestep", upgradeQuery); + safeExec(query); + + if (!watchQuery(query)) { + qCritical() << Q_FUNC_INFO << "Setting schema upgrade step failed!"; + success = false; + } + else { + success = true; + } + } + return success; +} -UserId SqliteStorage::addUser(const QString &user, const QString &password) +UserId SqliteStorage::addUser(const QString& user, const QString& password, const QString& authenticator) { QSqlDatabase db = logDb(); UserId uid; @@ -131,9 +191,11 @@ UserId SqliteStorage::addUser(const QString &user, const QString &password) query.bindValue(":username", user); query.bindValue(":password", hashPassword(password)); query.bindValue(":hashversion", Storage::HashVersion::Latest); + query.bindValue(":authenticator", authenticator); lockForWrite(); safeExec(query); - if (query.lastError().isValid() && query.lastError().number() == 19) { // user already exists - sadly 19 seems to be the general constraint violation error... + if (query.lastError().isValid() + && query.lastError().nativeErrorCode() == QLatin1String{"19"}) { // user already exists - sadly 19 seems to be the general constraint violation error... db.rollback(); } else { @@ -148,8 +210,7 @@ UserId SqliteStorage::addUser(const QString &user, const QString &password) return uid; } - -bool SqliteStorage::updateUser(UserId user, const QString &password) +bool SqliteStorage::updateUser(UserId user, const QString& password) { QSqlDatabase db = logDb(); bool success = false; @@ -170,8 +231,7 @@ bool SqliteStorage::updateUser(UserId user, const QString &password) return success; } - -void SqliteStorage::renameUser(UserId user, const QString &newName) +void SqliteStorage::renameUser(UserId user, const QString& newName) { QSqlDatabase db = logDb(); db.transaction(); @@ -188,12 +248,11 @@ void SqliteStorage::renameUser(UserId user, const QString &newName) emit userRenamed(user, newName); } - -UserId SqliteStorage::validateUser(const QString &user, const QString &password) +UserId SqliteStorage::validateUser(const QString& user, const QString& password) { UserId userId; QString hashedPassword; - Storage::HashVersion hashVersion; + Storage::HashVersion hashVersion = Storage::HashVersion::Latest; { QSqlQuery query(logDb()); @@ -218,8 +277,7 @@ UserId SqliteStorage::validateUser(const QString &user, const QString &password) return returnUserId; } - -UserId SqliteStorage::getUserId(const QString &username) +UserId SqliteStorage::getUserId(const QString& username) { UserId userId; @@ -240,6 +298,26 @@ UserId SqliteStorage::getUserId(const QString &username) return userId; } +QString SqliteStorage::getUserAuthenticator(const UserId userid) +{ + QString authenticator = QString(""); + + { + QSqlQuery query(logDb()); + query.prepare(queryString("select_authenticator")); + query.bindValue(":userid", userid.toInt()); + + lockForRead(); + safeExec(query); + + if (query.first()) { + authenticator = query.value(0).toString(); + } + } + unlock(); + + return authenticator; +} UserId SqliteStorage::internalUser() { @@ -260,7 +338,6 @@ UserId SqliteStorage::internalUser() return userId; } - void SqliteStorage::delUser(UserId user) { QSqlDatabase db = logDb(); @@ -292,8 +369,7 @@ void SqliteStorage::delUser(UserId user) emit userRemoved(user); } - -void SqliteStorage::setUserSetting(UserId userId, const QString &settingName, const QVariant &data) +void SqliteStorage::setUserSetting(UserId userId, const QString& settingName, const QVariant& data) { QByteArray rawData; QDataStream out(&rawData, QIODevice::WriteOnly); @@ -324,8 +400,7 @@ void SqliteStorage::setUserSetting(UserId userId, const QString &settingName, co unlock(); } - -QVariant SqliteStorage::getUserSetting(UserId userId, const QString &settingName, const QVariant &defaultData) +QVariant SqliteStorage::getUserSetting(UserId userId, const QString& settingName, const QVariant& defaultData) { QVariant data = defaultData; { @@ -347,8 +422,60 @@ QVariant SqliteStorage::getUserSetting(UserId userId, const QString &settingName return data; } +void SqliteStorage::setCoreState(const QVariantList& data) +{ + QByteArray rawData; + QDataStream out(&rawData, QIODevice::WriteOnly); + out.setVersion(QDataStream::Qt_4_2); + out << data; + + QSqlDatabase db = logDb(); + db.transaction(); + { + QSqlQuery query(db); + query.prepare(queryString("insert_core_state")); + query.bindValue(":key", "active_sessions"); + query.bindValue(":value", rawData); + lockForWrite(); + safeExec(query); + + if (query.lastError().isValid()) { + QSqlQuery updateQuery(db); + updateQuery.prepare(queryString("update_core_state")); + updateQuery.bindValue(":key", "active_sessions"); + updateQuery.bindValue(":value", rawData); + safeExec(updateQuery); + } + db.commit(); + } + unlock(); +} + +QVariantList SqliteStorage::getCoreState(const QVariantList& defaultData) +{ + QVariantList data; + { + QSqlQuery query(logDb()); + query.prepare(queryString("select_core_state")); + query.bindValue(":key", "active_sessions"); + lockForRead(); + safeExec(query); + + if (query.first()) { + QByteArray rawData = query.value(0).toByteArray(); + QDataStream in(&rawData, QIODevice::ReadOnly); + in.setVersion(QDataStream::Qt_4_2); + in >> data; + } + else { + data = defaultData; + } + } + unlock(); + return data; +} -IdentityId SqliteStorage::createIdentity(UserId user, CoreIdentity &identity) +IdentityId SqliteStorage::createIdentity(UserId user, CoreIdentity& identity) { IdentityId identityId; @@ -376,13 +503,8 @@ IdentityId SqliteStorage::createIdentity(UserId user, CoreIdentity &identity) query.bindValue(":kickreason", identity.kickReason()); query.bindValue(":partreason", identity.partReason()); query.bindValue(":quitreason", identity.quitReason()); -#ifdef HAVE_SSL query.bindValue(":sslcert", identity.sslCert().toPem()); query.bindValue(":sslkey", identity.sslKey().toPem()); -#else - query.bindValue(":sslcert", QByteArray()); - query.bindValue(":sslkey", QByteArray()); -#endif lockForWrite(); safeExec(query); @@ -399,7 +521,7 @@ IdentityId SqliteStorage::createIdentity(UserId user, CoreIdentity &identity) QSqlQuery insertNickQuery(db); insertNickQuery.prepare(queryString("insert_nick")); - foreach(QString nick, identity.nicks()) { + foreach (QString nick, identity.nicks()) { insertNickQuery.bindValue(":identityid", identityId.toInt()); insertNickQuery.bindValue(":nick", nick); safeExec(insertNickQuery); @@ -412,8 +534,7 @@ IdentityId SqliteStorage::createIdentity(UserId user, CoreIdentity &identity) return identityId; } - -bool SqliteStorage::updateIdentity(UserId user, const CoreIdentity &identity) +bool SqliteStorage::updateIdentity(UserId user, const CoreIdentity& identity) { QSqlDatabase db = logDb(); bool error = false; @@ -455,13 +576,8 @@ bool SqliteStorage::updateIdentity(UserId user, const CoreIdentity &identity) query.bindValue(":kickreason", identity.kickReason()); query.bindValue(":partreason", identity.partReason()); query.bindValue(":quitreason", identity.quitReason()); -#ifdef HAVE_SSL query.bindValue(":sslcert", identity.sslCert().toPem()); query.bindValue(":sslkey", identity.sslKey().toPem()); -#else - query.bindValue(":sslcert", QByteArray()); - query.bindValue(":sslkey", QByteArray()); -#endif query.bindValue(":identityid", identity.id().toInt()); safeExec(query); watchQuery(query); @@ -474,7 +590,7 @@ bool SqliteStorage::updateIdentity(UserId user, const CoreIdentity &identity) QSqlQuery insertNickQuery(db); insertNickQuery.prepare(queryString("insert_nick")); - foreach(QString nick, identity.nicks()) { + foreach (QString nick, identity.nicks()) { insertNickQuery.bindValue(":identityid", identity.id().toInt()); insertNickQuery.bindValue(":nick", nick); safeExec(insertNickQuery); @@ -486,7 +602,6 @@ bool SqliteStorage::updateIdentity(UserId user, const CoreIdentity &identity) return true; } - void SqliteStorage::removeIdentity(UserId user, IdentityId identityId) { QSqlDatabase db = logDb(); @@ -525,10 +640,9 @@ void SqliteStorage::removeIdentity(UserId user, IdentityId identityId) unlock(); } - -QList SqliteStorage::identities(UserId user) +std::vector SqliteStorage::identities(UserId user) { - QList identities; + std::vector identities; QSqlDatabase db = logDb(); db.transaction(); @@ -563,10 +677,8 @@ QList SqliteStorage::identities(UserId user) identity.setKickReason(query.value(15).toString()); identity.setPartReason(query.value(16).toString()); identity.setQuitReason(query.value(17).toString()); -#ifdef HAVE_SSL identity.setSslCert(query.value(18).toByteArray()); identity.setSslKey(query.value(19).toByteArray()); -#endif nickQuery.bindValue(":identityid", identity.id().toInt()); QList nicks; @@ -575,8 +687,8 @@ QList SqliteStorage::identities(UserId user) while (nickQuery.next()) { nicks << nickQuery.value(0).toString(); } - identity.setNicks(nicks); - identities << identity; + identity.setNicks(std::move(nicks)); + identities.push_back(std::move(identity)); } db.commit(); } @@ -584,8 +696,7 @@ QList SqliteStorage::identities(UserId user) return identities; } - -NetworkId SqliteStorage::createNetwork(UserId user, const NetworkInfo &info) +NetworkId SqliteStorage::createNetwork(UserId user, const NetworkInfo& info) { NetworkId networkId; @@ -610,13 +721,13 @@ NetworkId SqliteStorage::createNetwork(UserId user, const NetworkInfo &info) } if (error) { unlock(); - return NetworkId(); + return {}; } { QSqlQuery insertServersQuery(db); insertServersQuery.prepare(queryString("insert_server")); - foreach(Network::Server server, info.serverList) { + foreach (Network::Server server, info.serverList) { insertServersQuery.bindValue(":userid", user.toInt()); insertServersQuery.bindValue(":networkid", networkId.toInt()); bindServerInfo(insertServersQuery, server); @@ -632,13 +743,12 @@ NetworkId SqliteStorage::createNetwork(UserId user, const NetworkInfo &info) } unlock(); if (error) - return NetworkId(); + return {}; else return networkId; } - -void SqliteStorage::bindNetworkInfo(QSqlQuery &query, const NetworkInfo &info) +void SqliteStorage::bindNetworkInfo(QSqlQuery& query, const NetworkInfo& info) { query.bindValue(":networkname", info.networkName); query.bindValue(":identityid", info.identity.toInt()); @@ -658,12 +768,17 @@ void SqliteStorage::bindNetworkInfo(QSqlQuery &query, const NetworkInfo &info) query.bindValue(":autoreconnectretries", info.autoReconnectRetries); query.bindValue(":unlimitedconnectretries", info.unlimitedReconnectRetries ? 1 : 0); query.bindValue(":rejoinchannels", info.rejoinChannels ? 1 : 0); + // Custom rate limiting + query.bindValue(":usecustomessagerate", info.useCustomMessageRate ? 1 : 0); + query.bindValue(":messagerateburstsize", info.messageRateBurstSize); + query.bindValue(":messageratedelay", info.messageRateDelay); + query.bindValue(":unlimitedmessagerate", info.unlimitedMessageRate ? 1 : 0); + query.bindValue(":skipcaps", info.skipCapsToString()); if (info.networkId.isValid()) query.bindValue(":networkid", info.networkId.toInt()); } - -void SqliteStorage::bindServerInfo(QSqlQuery &query, const Network::Server &server) +void SqliteStorage::bindServerInfo(QSqlQuery& query, const Network::Server& server) { query.bindValue(":hostname", server.host); query.bindValue(":port", server.port); @@ -676,10 +791,10 @@ void SqliteStorage::bindServerInfo(QSqlQuery &query, const Network::Server &serv query.bindValue(":proxyport", server.proxyPort); query.bindValue(":proxyuser", server.proxyUser); query.bindValue(":proxypass", server.proxyPass); + query.bindValue(":sslverify", server.sslVerify ? 1 : 0); } - -bool SqliteStorage::updateNetwork(UserId user, const NetworkInfo &info) +bool SqliteStorage::updateNetwork(UserId user, const NetworkInfo& info) { QSqlDatabase db = logDb(); bool error = false; @@ -721,7 +836,7 @@ bool SqliteStorage::updateNetwork(UserId user, const NetworkInfo &info) { QSqlQuery insertServersQuery(db); insertServersQuery.prepare(queryString("insert_server")); - foreach(Network::Server server, info.serverList) { + foreach (Network::Server server, info.serverList) { insertServersQuery.bindValue(":userid", user.toInt()); insertServersQuery.bindValue(":networkid", info.networkId.toInt()); bindServerInfo(insertServersQuery, server); @@ -739,8 +854,7 @@ bool SqliteStorage::updateNetwork(UserId user, const NetworkInfo &info) return !error; } - -bool SqliteStorage::removeNetwork(UserId user, const NetworkId &networkId) +bool SqliteStorage::removeNetwork(UserId user, const NetworkId& networkId) { QSqlDatabase db = logDb(); bool error = false; @@ -813,10 +927,9 @@ bool SqliteStorage::removeNetwork(UserId user, const NetworkId &networkId) return true; } - -QList SqliteStorage::networks(UserId user) +std::vector SqliteStorage::networks(UserId user) { - QList nets; + std::vector nets; QSqlDatabase db = logDb(); db.transaction(); @@ -853,6 +966,12 @@ QList SqliteStorage::networks(UserId user) net.useSasl = networksQuery.value(16).toInt() == 1 ? true : false; net.saslAccount = networksQuery.value(17).toString(); net.saslPassword = networksQuery.value(18).toString(); + // Custom rate limiting + net.useCustomMessageRate = networksQuery.value(19).toInt() == 1 ? true : false; + net.messageRateBurstSize = networksQuery.value(20).toUInt(); + net.messageRateDelay = networksQuery.value(21).toUInt(); + net.unlimitedMessageRate = networksQuery.value(22).toInt() == 1 ? true : false; + net.skipCapsFromString(networksQuery.value(23).toString()); serversQuery.bindValue(":networkid", net.networkId.toInt()); safeExec(serversQuery); @@ -875,10 +994,11 @@ QList SqliteStorage::networks(UserId user) server.proxyPort = serversQuery.value(8).toUInt(); server.proxyUser = serversQuery.value(9).toString(); server.proxyPass = serversQuery.value(10).toString(); + server.sslVerify = serversQuery.value(11).toInt() == 1 ? true : false; servers << server; } net.serverList = servers; - nets << net; + nets.push_back(std::move(net)); } } } @@ -888,10 +1008,9 @@ QList SqliteStorage::networks(UserId user) return nets; } - -QList SqliteStorage::connectedNetworks(UserId user) +std::vector SqliteStorage::connectedNetworks(UserId user) { - QList connectedNets; + std::vector connectedNets; QSqlDatabase db = logDb(); db.transaction(); @@ -905,7 +1024,7 @@ QList SqliteStorage::connectedNetworks(UserId user) watchQuery(query); while (query.next()) { - connectedNets << query.value(0).toInt(); + connectedNets.emplace_back(query.value(0).toInt()); } db.commit(); } @@ -913,8 +1032,7 @@ QList SqliteStorage::connectedNetworks(UserId user) return connectedNets; } - -void SqliteStorage::setNetworkConnected(UserId user, const NetworkId &networkId, bool isConnected) +void SqliteStorage::setNetworkConnected(UserId user, const NetworkId& networkId, bool isConnected) { QSqlDatabase db = logDb(); db.transaction(); @@ -934,8 +1052,7 @@ void SqliteStorage::setNetworkConnected(UserId user, const NetworkId &networkId, unlock(); } - -QHash SqliteStorage::persistentChannels(UserId user, const NetworkId &networkId) +QHash SqliteStorage::persistentChannels(UserId user, const NetworkId& networkId) { QHash persistentChans; @@ -958,8 +1075,7 @@ QHash SqliteStorage::persistentChannels(UserId user, const Net return persistentChans; } - -void SqliteStorage::setChannelPersistent(UserId user, const NetworkId &networkId, const QString &channel, bool isJoined) +void SqliteStorage::setChannelPersistent(UserId user, const NetworkId& networkId, const QString& channel, bool isJoined) { QSqlDatabase db = logDb(); db.transaction(); @@ -980,8 +1096,7 @@ void SqliteStorage::setChannelPersistent(UserId user, const NetworkId &networkId unlock(); } - -void SqliteStorage::setPersistentChannelKey(UserId user, const NetworkId &networkId, const QString &channel, const QString &key) +void SqliteStorage::setPersistentChannelKey(UserId user, const NetworkId& networkId, const QString& channel, const QString& key) { QSqlDatabase db = logDb(); db.transaction(); @@ -1002,7 +1117,6 @@ void SqliteStorage::setPersistentChannelKey(UserId user, const NetworkId &networ unlock(); } - QString SqliteStorage::awayMessage(UserId user, NetworkId networkId) { QSqlDatabase db = logDb(); @@ -1027,8 +1141,7 @@ QString SqliteStorage::awayMessage(UserId user, NetworkId networkId) return awayMsg; } - -void SqliteStorage::setAwayMessage(UserId user, NetworkId networkId, const QString &awayMsg) +void SqliteStorage::setAwayMessage(UserId user, NetworkId networkId, const QString& awayMsg) { QSqlDatabase db = logDb(); db.transaction(); @@ -1048,7 +1161,6 @@ void SqliteStorage::setAwayMessage(UserId user, NetworkId networkId, const QStri unlock(); } - QString SqliteStorage::userModes(UserId user, NetworkId networkId) { QSqlDatabase db = logDb(); @@ -1073,8 +1185,7 @@ QString SqliteStorage::userModes(UserId user, NetworkId networkId) return modes; } - -void SqliteStorage::setUserModes(UserId user, NetworkId networkId, const QString &userModes) +void SqliteStorage::setUserModes(UserId user, NetworkId networkId, const QString& userModes) { QSqlDatabase db = logDb(); db.transaction(); @@ -1094,8 +1205,7 @@ void SqliteStorage::setUserModes(UserId user, NetworkId networkId, const QString unlock(); } - -BufferInfo SqliteStorage::bufferInfo(UserId user, const NetworkId &networkId, BufferInfo::Type type, const QString &buffer, bool create) +BufferInfo SqliteStorage::bufferInfo(UserId user, const NetworkId& networkId, BufferInfo::Type type, const QString& buffer, bool create) { QSqlDatabase db = logDb(); db.transaction(); @@ -1146,8 +1256,7 @@ BufferInfo SqliteStorage::bufferInfo(UserId user, const NetworkId &networkId, Bu return bufferInfo; } - -BufferInfo SqliteStorage::getBufferInfo(UserId user, const BufferId &bufferId) +BufferInfo SqliteStorage::getBufferInfo(UserId user, const BufferId& bufferId) { QSqlDatabase db = logDb(); db.transaction(); @@ -1163,7 +1272,11 @@ BufferInfo SqliteStorage::getBufferInfo(UserId user, const BufferId &bufferId) safeExec(query); if (watchQuery(query) && query.first()) { - bufferInfo = BufferInfo(query.value(0).toInt(), query.value(1).toInt(), (BufferInfo::Type)query.value(2).toInt(), 0, query.value(4).toString()); + bufferInfo = BufferInfo(query.value(0).toInt(), + query.value(1).toInt(), + (BufferInfo::Type)query.value(2).toInt(), + 0, + query.value(4).toString()); Q_ASSERT(!query.next()); } db.commit(); @@ -1172,10 +1285,9 @@ BufferInfo SqliteStorage::getBufferInfo(UserId user, const BufferId &bufferId) return bufferInfo; } - -QList SqliteStorage::requestBuffers(UserId user) +std::vector SqliteStorage::requestBuffers(UserId user) { - QList bufferlist; + std::vector bufferlist; QSqlDatabase db = logDb(); db.transaction(); @@ -1189,7 +1301,11 @@ QList SqliteStorage::requestBuffers(UserId user) safeExec(query); watchQuery(query); while (query.next()) { - bufferlist << BufferInfo(query.value(0).toInt(), query.value(1).toInt(), (BufferInfo::Type)query.value(2).toInt(), query.value(3).toInt(), query.value(4).toString()); + bufferlist.emplace_back(query.value(0).toInt(), + query.value(1).toInt(), + (BufferInfo::Type)query.value(2).toInt(), + query.value(3).toInt(), + query.value(4).toString()); } db.commit(); } @@ -1198,10 +1314,9 @@ QList SqliteStorage::requestBuffers(UserId user) return bufferlist; } - -QList SqliteStorage::requestBufferIdsForNetwork(UserId user, NetworkId networkId) +std::vector SqliteStorage::requestBufferIdsForNetwork(UserId user, NetworkId networkId) { - QList bufferList; + std::vector bufferList; QSqlDatabase db = logDb(); db.transaction(); @@ -1216,7 +1331,7 @@ QList SqliteStorage::requestBufferIdsForNetwork(UserId user, NetworkId safeExec(query); watchQuery(query); while (query.next()) { - bufferList << BufferId(query.value(0).toInt()); + bufferList.emplace_back(query.value(0).toInt()); } db.commit(); } @@ -1225,8 +1340,7 @@ QList SqliteStorage::requestBufferIdsForNetwork(UserId user, NetworkId return bufferList; } - -bool SqliteStorage::removeBuffer(const UserId &user, const BufferId &bufferId) +bool SqliteStorage::removeBuffer(const UserId& user, const BufferId& bufferId) { QSqlDatabase db = logDb(); db.transaction(); @@ -1269,8 +1383,7 @@ bool SqliteStorage::removeBuffer(const UserId &user, const BufferId &bufferId) return !error; } - -bool SqliteStorage::renameBuffer(const UserId &user, const BufferId &bufferId, const QString &newName) +bool SqliteStorage::renameBuffer(const UserId& user, const BufferId& bufferId, const QString& newName) { QSqlDatabase db = logDb(); db.transaction(); @@ -1289,7 +1402,7 @@ bool SqliteStorage::renameBuffer(const UserId &user, const BufferId &bufferId, c error = query.lastError().isValid(); // unexepcted error occured (19 == constraint violation) - if (error && query.lastError().number() != 19) { + if (error && query.lastError().nativeErrorCode() != QLatin1String{"19"}) { watchQuery(query); } else { @@ -1306,8 +1419,7 @@ bool SqliteStorage::renameBuffer(const UserId &user, const BufferId &bufferId, c return !error; } - -bool SqliteStorage::mergeBuffersPermanently(const UserId &user, const BufferId &bufferId1, const BufferId &bufferId2) +bool SqliteStorage::mergeBuffersPermanently(const UserId& user, const BufferId& bufferId1, const BufferId& bufferId2) { QSqlDatabase db = logDb(); db.transaction(); @@ -1363,8 +1475,35 @@ bool SqliteStorage::mergeBuffersPermanently(const UserId &user, const BufferId & return !error; } +QHash SqliteStorage::bufferLastMsgIds(UserId user) +{ + QHash lastMsgHash; + + QSqlDatabase db = logDb(); + db.transaction(); + + bool error = false; + { + QSqlQuery query(db); + query.prepare(queryString("select_buffer_last_messages")); + query.bindValue(":userid", user.toInt()); + + lockForRead(); + safeExec(query); + error = !watchQuery(query); + if (!error) { + while (query.next()) { + lastMsgHash[query.value(0).toInt()] = query.value(1).toLongLong(); + } + } + } + + db.commit(); + unlock(); + return lastMsgHash; +} -void SqliteStorage::setBufferLastSeenMsg(UserId user, const BufferId &bufferId, const MsgId &msgId) +void SqliteStorage::setBufferLastSeenMsg(UserId user, const BufferId& bufferId, const MsgId& msgId) { QSqlDatabase db = logDb(); db.transaction(); @@ -1374,7 +1513,7 @@ void SqliteStorage::setBufferLastSeenMsg(UserId user, const BufferId &bufferId, query.prepare(queryString("update_buffer_lastseen")); query.bindValue(":userid", user.toInt()); query.bindValue(":bufferid", bufferId.toInt()); - query.bindValue(":lastseenmsgid", msgId.toInt()); + query.bindValue(":lastseenmsgid", msgId.toQint64()); lockForWrite(); safeExec(query); @@ -1384,7 +1523,6 @@ void SqliteStorage::setBufferLastSeenMsg(UserId user, const BufferId &bufferId, unlock(); } - QHash SqliteStorage::bufferLastSeenMsgIds(UserId user) { QHash lastSeenHash; @@ -1403,7 +1541,7 @@ QHash SqliteStorage::bufferLastSeenMsgIds(UserId user) error = !watchQuery(query); if (!error) { while (query.next()) { - lastSeenHash[query.value(0).toInt()] = query.value(1).toInt(); + lastSeenHash[query.value(0).toInt()] = query.value(1).toLongLong(); } } } @@ -1413,8 +1551,7 @@ QHash SqliteStorage::bufferLastSeenMsgIds(UserId user) return lastSeenHash; } - -void SqliteStorage::setBufferMarkerLineMsg(UserId user, const BufferId &bufferId, const MsgId &msgId) +void SqliteStorage::setBufferMarkerLineMsg(UserId user, const BufferId& bufferId, const MsgId& msgId) { QSqlDatabase db = logDb(); db.transaction(); @@ -1424,7 +1561,7 @@ void SqliteStorage::setBufferMarkerLineMsg(UserId user, const BufferId &bufferId query.prepare(queryString("update_buffer_markerlinemsgid")); query.bindValue(":userid", user.toInt()); query.bindValue(":bufferid", bufferId.toInt()); - query.bindValue(":markerlinemsgid", msgId.toInt()); + query.bindValue(":markerlinemsgid", msgId.toQint64()); lockForWrite(); safeExec(query); @@ -1434,7 +1571,6 @@ void SqliteStorage::setBufferMarkerLineMsg(UserId user, const BufferId &bufferId unlock(); } - QHash SqliteStorage::bufferMarkerLineMsgIds(UserId user) { QHash markerLineHash; @@ -1453,7 +1589,7 @@ QHash SqliteStorage::bufferMarkerLineMsgIds(UserId user) error = !watchQuery(query); if (!error) { while (query.next()) { - markerLineHash[query.value(0).toInt()] = query.value(1).toInt(); + markerLineHash[query.value(0).toInt()] = query.value(1).toLongLong(); } } } @@ -1463,98 +1599,295 @@ QHash SqliteStorage::bufferMarkerLineMsgIds(UserId user) return markerLineHash; } - -bool SqliteStorage::logMessage(Message &msg) +void SqliteStorage::setBufferActivity(UserId user, BufferId bufferId, Message::Types bufferActivity) { QSqlDatabase db = logDb(); db.transaction(); - bool error = false; { - QSqlQuery logMessageQuery(db); - logMessageQuery.prepare(queryString("insert_message")); - - logMessageQuery.bindValue(":time", msg.timestamp().toTime_t()); - logMessageQuery.bindValue(":bufferid", msg.bufferInfo().bufferId().toInt()); - logMessageQuery.bindValue(":type", msg.type()); - logMessageQuery.bindValue(":flags", (int)msg.flags()); - logMessageQuery.bindValue(":sender", msg.sender()); - logMessageQuery.bindValue(":message", msg.contents()); + QSqlQuery query(db); + query.prepare(queryString("update_buffer_bufferactivity")); + query.bindValue(":userid", user.toInt()); + query.bindValue(":bufferid", bufferId.toInt()); + query.bindValue(":bufferactivity", (int)bufferActivity); lockForWrite(); - safeExec(logMessageQuery); + safeExec(query); + watchQuery(query); + } + db.commit(); + unlock(); +} - if (logMessageQuery.lastError().isValid()) { - // constraint violation - must be NOT NULL constraint - probably the sender is missing... - if (logMessageQuery.lastError().number() == 19) { - QSqlQuery addSenderQuery(db); - addSenderQuery.prepare(queryString("insert_sender")); - addSenderQuery.bindValue(":sender", msg.sender()); - safeExec(addSenderQuery); - safeExec(logMessageQuery); - error = !watchQuery(logMessageQuery); - } - else { - watchQuery(logMessageQuery); - } - } +QHash SqliteStorage::bufferActivities(UserId user) +{ + QHash bufferActivityHash; + + QSqlDatabase db = logDb(); + db.transaction(); + + bool error = false; + { + QSqlQuery query(db); + query.prepare(queryString("select_buffer_bufferactivities")); + query.bindValue(":userid", user.toInt()); + + lockForRead(); + safeExec(query); + error = !watchQuery(query); if (!error) { - MsgId msgId = logMessageQuery.lastInsertId().toInt(); - if (msgId.isValid()) { - msg.setMsgId(msgId); - } - else { - error = true; + while (query.next()) { + bufferActivityHash[query.value(0).toInt()] = Message::Types(query.value(1).toInt()); } } } - if (error) { - db.rollback(); - } - else { - db.commit(); - } - + db.commit(); unlock(); - return !error; + return bufferActivityHash; } - -bool SqliteStorage::logMessages(MessageList &msgs) +Message::Types SqliteStorage::bufferActivity(BufferId bufferId, MsgId lastSeenMsgId) { QSqlDatabase db = logDb(); db.transaction(); + Message::Types result{}; { - QSet senders; - QSqlQuery addSenderQuery(db); - addSenderQuery.prepare(queryString("insert_sender")); - lockForWrite(); - for (int i = 0; i < msgs.count(); i++) { - const QString &sender = msgs.at(i).sender(); - if (senders.contains(sender)) - continue; - senders << sender; + QSqlQuery query(db); + query.prepare(queryString("select_buffer_bufferactivity")); + query.bindValue(":bufferid", bufferId.toInt()); + query.bindValue(":lastseenmsgid", lastSeenMsgId.toQint64()); - addSenderQuery.bindValue(":sender", sender); - safeExec(addSenderQuery); - } + lockForRead(); + safeExec(query); + if (query.first()) + result = Message::Types(query.value(0).toInt()); } - bool error = false; - { - QSqlQuery logMessageQuery(db); - logMessageQuery.prepare(queryString("insert_message")); - for (int i = 0; i < msgs.count(); i++) { - Message &msg = msgs[i]; + db.commit(); + unlock(); + return result; +} - logMessageQuery.bindValue(":time", msg.timestamp().toTime_t()); - logMessageQuery.bindValue(":bufferid", msg.bufferInfo().bufferId().toInt()); - logMessageQuery.bindValue(":type", msg.type()); - logMessageQuery.bindValue(":flags", (int)msg.flags()); - logMessageQuery.bindValue(":sender", msg.sender()); - logMessageQuery.bindValue(":message", msg.contents()); +QHash SqliteStorage::bufferCiphers(UserId user, const NetworkId& networkId) +{ + QHash bufferCiphers; + + QSqlDatabase db = logDb(); + db.transaction(); + { + QSqlQuery query(db); + query.prepare(queryString("select_buffer_ciphers")); + query.bindValue(":userid", user.toInt()); + query.bindValue(":networkid", networkId.toInt()); + + lockForRead(); + safeExec(query); + watchQuery(query); + while (query.next()) { + bufferCiphers[query.value(0).toString()] = QByteArray::fromHex(query.value(1).toString().toUtf8()); + } + } + unlock(); + return bufferCiphers; +} + +void SqliteStorage::setBufferCipher(UserId user, const NetworkId& networkId, const QString& bufferName, const QByteArray& cipher) +{ + QSqlDatabase db = logDb(); + db.transaction(); + + { + QSqlQuery query(db); + query.prepare(queryString("update_buffer_cipher")); + query.bindValue(":userid", user.toInt()); + query.bindValue(":networkid", networkId.toInt()); + query.bindValue(":buffercname", bufferName.toLower()); + query.bindValue(":cipher", QString(cipher.toHex())); + + lockForWrite(); + safeExec(query); + watchQuery(query); + db.commit(); + } + unlock(); +} + +void SqliteStorage::setHighlightCount(UserId user, BufferId bufferId, int count) +{ + QSqlDatabase db = logDb(); + db.transaction(); + + { + QSqlQuery query(db); + query.prepare(queryString("update_buffer_highlightcount")); + query.bindValue(":userid", user.toInt()); + query.bindValue(":bufferid", bufferId.toInt()); + query.bindValue(":highlightcount", count); + + lockForWrite(); + safeExec(query); + watchQuery(query); + } + db.commit(); + unlock(); +} + +QHash SqliteStorage::highlightCounts(UserId user) +{ + QHash highlightCountHash; + + QSqlDatabase db = logDb(); + db.transaction(); + + bool error = false; + { + QSqlQuery query(db); + query.prepare(queryString("select_buffer_highlightcounts")); + query.bindValue(":userid", user.toInt()); + + lockForRead(); + safeExec(query); + error = !watchQuery(query); + if (!error) { + while (query.next()) { + highlightCountHash[query.value(0).toInt()] = query.value(1).toInt(); + } + } + } + + db.commit(); + unlock(); + return highlightCountHash; +} + +int SqliteStorage::highlightCount(BufferId bufferId, MsgId lastSeenMsgId) +{ + QSqlDatabase db = logDb(); + db.transaction(); + + int result = 0; + { + QSqlQuery query(db); + query.prepare(queryString("select_buffer_highlightcount")); + query.bindValue(":bufferid", bufferId.toInt()); + query.bindValue(":lastseenmsgid", lastSeenMsgId.toQint64()); + + lockForRead(); + safeExec(query); + if (query.first()) + result = query.value(0).toInt(); + } + + db.commit(); + unlock(); + return result; +} + +bool SqliteStorage::logMessage(Message& msg) +{ + QSqlDatabase db = logDb(); + db.transaction(); + + bool error = false; + { + QSqlQuery logMessageQuery(db); + logMessageQuery.prepare(queryString("insert_message")); + // As of SQLite schema version 31, timestamps are stored in milliseconds instead of + // seconds. This nets us more precision as well as simplifying 64-bit time. + logMessageQuery.bindValue(":time", msg.timestamp().toMSecsSinceEpoch()); + logMessageQuery.bindValue(":bufferid", msg.bufferInfo().bufferId().toInt()); + logMessageQuery.bindValue(":type", msg.type()); + logMessageQuery.bindValue(":flags", (int)msg.flags()); + logMessageQuery.bindValue(":sender", msg.sender()); + logMessageQuery.bindValue(":realname", msg.realName()); + logMessageQuery.bindValue(":avatarurl", msg.avatarUrl()); + logMessageQuery.bindValue(":senderprefixes", msg.senderPrefixes()); + logMessageQuery.bindValue(":message", msg.contents()); + + lockForWrite(); + safeExec(logMessageQuery); + + if (logMessageQuery.lastError().isValid()) { + // constraint violation - must be NOT NULL constraint - probably the sender is missing... + if (logMessageQuery.lastError().nativeErrorCode() == QLatin1String{"19"}) { + QSqlQuery addSenderQuery(db); + addSenderQuery.prepare(queryString("insert_sender")); + addSenderQuery.bindValue(":sender", msg.sender()); + addSenderQuery.bindValue(":realname", msg.realName()); + addSenderQuery.bindValue(":avatarurl", msg.avatarUrl()); + safeExec(addSenderQuery); + safeExec(logMessageQuery); + error = !watchQuery(logMessageQuery); + } + else { + watchQuery(logMessageQuery); + } + } + if (!error) { + MsgId msgId = logMessageQuery.lastInsertId().toLongLong(); + if (msgId.isValid()) { + msg.setMsgId(msgId); + } + else { + error = true; + } + } + } + + if (error) { + db.rollback(); + } + else { + db.commit(); + } + + unlock(); + return !error; +} + +bool SqliteStorage::logMessages(MessageList& msgs) +{ + QSqlDatabase db = logDb(); + db.transaction(); + + { + QSet senders; + QSqlQuery addSenderQuery(db); + addSenderQuery.prepare(queryString("insert_sender")); + lockForWrite(); + for (int i = 0; i < msgs.count(); i++) { + auto& msg = msgs.at(i); + SenderData sender = {msg.sender(), msg.realName(), msg.avatarUrl()}; + if (senders.contains(sender)) + continue; + senders << sender; + + addSenderQuery.bindValue(":sender", sender.sender); + addSenderQuery.bindValue(":realname", sender.realname); + addSenderQuery.bindValue(":avatarurl", sender.avatarurl); + safeExec(addSenderQuery); + } + } + + bool error = false; + { + QSqlQuery logMessageQuery(db); + logMessageQuery.prepare(queryString("insert_message")); + for (int i = 0; i < msgs.count(); i++) { + Message& msg = msgs[i]; + // As of SQLite schema version 31, timestamps are stored in milliseconds instead of + // seconds. This nets us more precision as well as simplifying 64-bit time. + logMessageQuery.bindValue(":time", msg.timestamp().toMSecsSinceEpoch()); + logMessageQuery.bindValue(":bufferid", msg.bufferInfo().bufferId().toInt()); + logMessageQuery.bindValue(":type", msg.type()); + logMessageQuery.bindValue(":flags", (int)msg.flags()); + logMessageQuery.bindValue(":sender", msg.sender()); + logMessageQuery.bindValue(":realname", msg.realName()); + logMessageQuery.bindValue(":avatarurl", msg.avatarUrl()); + logMessageQuery.bindValue(":senderprefixes", msg.senderPrefixes()); + logMessageQuery.bindValue(":message", msg.contents()); safeExec(logMessageQuery); if (!watchQuery(logMessageQuery)) { @@ -1562,7 +1895,7 @@ bool SqliteStorage::logMessages(MessageList &msgs) break; } else { - msg.setMsgId(logMessageQuery.lastInsertId().toInt()); + msg.setMsgId(logMessageQuery.lastInsertId().toLongLong()); } } } @@ -1582,10 +1915,9 @@ bool SqliteStorage::logMessages(MessageList &msgs) return !error; } - -QList SqliteStorage::requestMsgs(UserId user, BufferId bufferId, MsgId first, MsgId last, int limit) +std::vector SqliteStorage::requestMsgs(UserId user, BufferId bufferId, MsgId first, MsgId last, int limit) { - QList messagelist; + std::vector messagelist; QSqlDatabase db = logDb(); db.transaction(); @@ -1593,7 +1925,7 @@ QList SqliteStorage::requestMsgs(UserId user, BufferId bufferId, MsgId bool error = false; BufferInfo bufferInfo; { - // code dupication from getBufferInfo: + // code duplication from getBufferInfo: // this is due to the impossibility of nesting transactions and recursive locking QSqlQuery bufferInfoQuery(db); bufferInfoQuery.prepare(queryString("select_buffer_by_id")); @@ -1604,7 +1936,11 @@ QList SqliteStorage::requestMsgs(UserId user, BufferId bufferId, MsgId safeExec(bufferInfoQuery); error = !watchQuery(bufferInfoQuery) || !bufferInfoQuery.first(); if (!error) { - bufferInfo = BufferInfo(bufferInfoQuery.value(0).toInt(), bufferInfoQuery.value(1).toInt(), (BufferInfo::Type)bufferInfoQuery.value(2).toInt(), 0, bufferInfoQuery.value(4).toString()); + bufferInfo = BufferInfo(bufferInfoQuery.value(0).toInt(), + bufferInfoQuery.value(1).toInt(), + (BufferInfo::Type)bufferInfoQuery.value(2).toInt(), + 0, + bufferInfoQuery.value(4).toString()); error = !bufferInfo.isValid(); } } @@ -1621,12 +1957,12 @@ QList SqliteStorage::requestMsgs(UserId user, BufferId bufferId, MsgId } else if (last == -1) { query.prepare(queryString("select_messagesNewerThan")); - query.bindValue(":firstmsg", first.toInt()); + query.bindValue(":firstmsg", first.toQint64()); } else { - query.prepare(queryString("select_messages")); - query.bindValue(":lastmsg", last.toInt()); - query.bindValue(":firstmsg", first.toInt()); + query.prepare(queryString("select_messagesRange")); + query.bindValue(":lastmsg", last.toQint64()); + query.bindValue(":firstmsg", first.toQint64()); } query.bindValue(":bufferid", bufferId.toInt()); query.bindValue(":limit", limit); @@ -1635,14 +1971,104 @@ QList SqliteStorage::requestMsgs(UserId user, BufferId bufferId, MsgId watchQuery(query); while (query.next()) { - Message msg(QDateTime::fromTime_t(query.value(1).toInt()), + Message msg( + // As of SQLite schema version 31, timestamps are stored in milliseconds instead of + // seconds. This nets us more precision as well as simplifying 64-bit time. + QDateTime::fromMSecsSinceEpoch(query.value(1).toLongLong()), bufferInfo, - (Message::Type)query.value(2).toUInt(), + (Message::Type)query.value(2).toInt(), + query.value(8).toString(), + query.value(4).toString(), query.value(5).toString(), + query.value(6).toString(), + query.value(7).toString(), + (Message::Flags)query.value(3).toInt()); + msg.setMsgId(query.value(0).toLongLong()); + messagelist.push_back(std::move(msg)); + } + } + db.commit(); + unlock(); + + return messagelist; +} + +std::vector SqliteStorage::requestMsgsFiltered( + UserId user, BufferId bufferId, MsgId first, MsgId last, int limit, Message::Types type, Message::Flags flags) +{ + std::vector messagelist; + + QSqlDatabase db = logDb(); + db.transaction(); + + bool error = false; + BufferInfo bufferInfo; + { + // code dupication from getBufferInfo: + // this is due to the impossibility of nesting transactions and recursive locking + QSqlQuery bufferInfoQuery(db); + bufferInfoQuery.prepare(queryString("select_buffer_by_id")); + bufferInfoQuery.bindValue(":userid", user.toInt()); + bufferInfoQuery.bindValue(":bufferid", bufferId.toInt()); + + lockForRead(); + safeExec(bufferInfoQuery); + error = !watchQuery(bufferInfoQuery) || !bufferInfoQuery.first(); + if (!error) { + bufferInfo = BufferInfo(bufferInfoQuery.value(0).toInt(), + bufferInfoQuery.value(1).toInt(), + (BufferInfo::Type)bufferInfoQuery.value(2).toInt(), + 0, + bufferInfoQuery.value(4).toString()); + error = !bufferInfo.isValid(); + } + } + if (error) { + db.rollback(); + unlock(); + return messagelist; + } + + { + QSqlQuery query(db); + if (last == -1 && first == -1) { + query.prepare(queryString("select_messagesNewestK_filtered")); + } + else if (last == -1) { + query.prepare(queryString("select_messagesNewerThan_filtered")); + query.bindValue(":firstmsg", first.toQint64()); + } + else { + query.prepare(queryString("select_messagesRange_filtered")); + query.bindValue(":lastmsg", last.toQint64()); + query.bindValue(":firstmsg", first.toQint64()); + } + query.bindValue(":bufferid", bufferId.toInt()); + query.bindValue(":limit", limit); + int typeRaw = type; + query.bindValue(":type", typeRaw); + int flagsRaw = flags; + query.bindValue(":flags", flagsRaw); + + safeExec(query); + watchQuery(query); + + while (query.next()) { + Message msg( + // As of SQLite schema version 31, timestamps are stored in milliseconds + // instead of seconds. This nets us more precision as well as simplifying + // 64-bit time. + QDateTime::fromMSecsSinceEpoch(query.value(1).toLongLong()), + bufferInfo, + (Message::Type)query.value(2).toInt(), + query.value(8).toString(), query.value(4).toString(), - (Message::Flags)query.value(3).toUInt()); - msg.setMsgId(query.value(0).toInt()); - messagelist << msg; + query.value(5).toString(), + query.value(6).toString(), + query.value(7).toString(), + Message::Flags{query.value(3).toInt()}); + msg.setMsgId(query.value(0).toLongLong()); + messagelist.push_back(std::move(msg)); } } db.commit(); @@ -1651,10 +2077,97 @@ QList SqliteStorage::requestMsgs(UserId user, BufferId bufferId, MsgId return messagelist; } +std::vector SqliteStorage::requestMsgsForward( + UserId user, BufferId bufferId, MsgId first, MsgId last, int limit, Message::Types type, Message::Flags flags) +{ + std::vector messagelist; + + QSqlDatabase db = logDb(); + db.transaction(); + + bool error = false; + BufferInfo bufferInfo; + { + // code dupication from getBufferInfo: + // this is due to the impossibility of nesting transactions and recursive locking + QSqlQuery bufferInfoQuery(db); + bufferInfoQuery.prepare(queryString("select_buffer_by_id")); + bufferInfoQuery.bindValue(":userid", user.toInt()); + bufferInfoQuery.bindValue(":bufferid", bufferId.toInt()); + + lockForRead(); + safeExec(bufferInfoQuery); + error = !watchQuery(bufferInfoQuery) || !bufferInfoQuery.first(); + if (!error) { + bufferInfo = BufferInfo(bufferInfoQuery.value(0).toInt(), + bufferInfoQuery.value(1).toInt(), + (BufferInfo::Type)bufferInfoQuery.value(2).toInt(), + 0, + bufferInfoQuery.value(4).toString()); + error = !bufferInfo.isValid(); + } + } + if (error) { + db.rollback(); + unlock(); + return messagelist; + } + + { + QSqlQuery query(db); + query.prepare(queryString("select_messagesForward")); + + if (first == -1) { + query.bindValue(":firstmsg", std::numeric_limits::min()); + } else { + query.bindValue(":firstmsg", first.toQint64()); + } + + if (last == -1) { + query.bindValue(":lastmsg", std::numeric_limits::max()); + } else { + query.bindValue(":lastmsg", last.toQint64()); + } + + query.bindValue(":bufferid", bufferId.toInt()); + + int typeRaw = type; + int flagsRaw = flags; + query.bindValue(":type", typeRaw); + query.bindValue(":flags", flagsRaw); -QList SqliteStorage::requestAllMsgs(UserId user, MsgId first, MsgId last, int limit) + query.bindValue(":limit", limit); + + safeExec(query); + watchQuery(query); + + while (query.next()) { + Message msg( + // As of SQLite schema version 31, timestamps are stored in milliseconds + // instead of seconds. This nets us more precision as well as simplifying + // 64-bit time. + QDateTime::fromMSecsSinceEpoch(query.value(1).toLongLong()), + bufferInfo, + (Message::Type)query.value(2).toInt(), + query.value(8).toString(), + query.value(4).toString(), + query.value(5).toString(), + query.value(6).toString(), + query.value(7).toString(), + Message::Flags{query.value(3).toInt()}); + msg.setMsgId(query.value(0).toLongLong()); + messagelist.push_back(std::move(msg)); + } + } + db.commit(); + unlock(); + + return messagelist; +} + +std::vector SqliteStorage::requestAllMsgs(UserId user, MsgId first, MsgId last, int limit) { - QList messagelist; + std::vector messagelist; QSqlDatabase db = logDb(); db.transaction(); @@ -1669,7 +2182,11 @@ QList SqliteStorage::requestAllMsgs(UserId user, MsgId first, MsgId las safeExec(bufferInfoQuery); watchQuery(bufferInfoQuery); while (bufferInfoQuery.next()) { - BufferInfo bufferInfo = BufferInfo(bufferInfoQuery.value(0).toInt(), bufferInfoQuery.value(1).toInt(), (BufferInfo::Type)bufferInfoQuery.value(2).toInt(), bufferInfoQuery.value(3).toInt(), bufferInfoQuery.value(4).toString()); + BufferInfo bufferInfo = BufferInfo(bufferInfoQuery.value(0).toInt(), + bufferInfoQuery.value(1).toInt(), + (BufferInfo::Type)bufferInfoQuery.value(2).toInt(), + bufferInfoQuery.value(3).toInt(), + bufferInfoQuery.value(4).toString()); bufferInfoHash[bufferInfo.bufferId()] = bufferInfo; } @@ -1679,24 +2196,97 @@ QList SqliteStorage::requestAllMsgs(UserId user, MsgId first, MsgId las } else { query.prepare(queryString("select_messagesAll")); - query.bindValue(":lastmsg", last.toInt()); + query.bindValue(":lastmsg", last.toQint64()); } query.bindValue(":userid", user.toInt()); - query.bindValue(":firstmsg", first.toInt()); + query.bindValue(":firstmsg", first.toQint64()); query.bindValue(":limit", limit); safeExec(query); watchQuery(query); while (query.next()) { - Message msg(QDateTime::fromTime_t(query.value(2).toInt()), + Message msg( + // As of SQLite schema version 31, timestamps are stored in milliseconds instead of + // seconds. This nets us more precision as well as simplifying 64-bit time. + QDateTime::fromMSecsSinceEpoch(query.value(2).toLongLong()), bufferInfoHash[query.value(1).toInt()], - (Message::Type)query.value(3).toUInt(), + (Message::Type)query.value(3).toInt(), + query.value(9).toString(), + query.value(5).toString(), query.value(6).toString(), + query.value(7).toString(), + query.value(8).toString(), + (Message::Flags)query.value(4).toInt()); + msg.setMsgId(query.value(0).toLongLong()); + messagelist.push_back(std::move(msg)); + } + } + db.commit(); + unlock(); + return messagelist; +} + +std::vector SqliteStorage::requestAllMsgsFiltered(UserId user, MsgId first, MsgId last, int limit, Message::Types type, Message::Flags flags) +{ + std::vector messagelist; + + QSqlDatabase db = logDb(); + db.transaction(); + + QHash bufferInfoHash; + { + QSqlQuery bufferInfoQuery(db); + bufferInfoQuery.prepare(queryString("select_buffers")); + bufferInfoQuery.bindValue(":userid", user.toInt()); + + lockForRead(); + safeExec(bufferInfoQuery); + watchQuery(bufferInfoQuery); + while (bufferInfoQuery.next()) { + BufferInfo bufferInfo = BufferInfo(bufferInfoQuery.value(0).toInt(), + bufferInfoQuery.value(1).toInt(), + (BufferInfo::Type)bufferInfoQuery.value(2).toInt(), + bufferInfoQuery.value(3).toInt(), + bufferInfoQuery.value(4).toString()); + bufferInfoHash[bufferInfo.bufferId()] = bufferInfo; + } + + QSqlQuery query(db); + if (last == -1) { + query.prepare(queryString("select_messagesAllNew_filtered")); + } + else { + query.prepare(queryString("select_messagesAll_filtered")); + query.bindValue(":lastmsg", last.toQint64()); + } + query.bindValue(":userid", user.toInt()); + query.bindValue(":firstmsg", first.toQint64()); + query.bindValue(":limit", limit); + int typeRaw = type; + query.bindValue(":type", typeRaw); + int flagsRaw = flags; + query.bindValue(":flags", flagsRaw); + safeExec(query); + + watchQuery(query); + + while (query.next()) { + Message msg( + // As of SQLite schema version 31, timestamps are stored in milliseconds + // instead of seconds. This nets us more precision as well as simplifying + // 64-bit time. + QDateTime::fromMSecsSinceEpoch(query.value(2).toLongLong()), + bufferInfoHash[query.value(1).toInt()], + (Message::Type)query.value(3).toInt(), + query.value(9).toString(), query.value(5).toString(), - (Message::Flags)query.value(4).toUInt()); - msg.setMsgId(query.value(0).toInt()); - messagelist << msg; + query.value(6).toString(), + query.value(7).toString(), + query.value(8).toString(), + Message::Flags{query.value(4).toInt()}); + msg.setMsgId(query.value(0).toLongLong()); + messagelist.push_back(std::move(msg)); } } db.commit(); @@ -1704,40 +2294,57 @@ QList SqliteStorage::requestAllMsgs(UserId user, MsgId first, MsgId las return messagelist; } +QMap SqliteStorage::getAllAuthUserNames() +{ + QMap authusernames; + + QSqlDatabase db = logDb(); + db.transaction(); + { + QSqlQuery query(db); + query.prepare(queryString("select_all_authusernames")); + + lockForRead(); + safeExec(query); + watchQuery(query); + while (query.next()) { + authusernames[query.value(0).toInt()] = query.value(1).toString(); + } + } + db.commit(); + unlock(); + return authusernames; +} QString SqliteStorage::backlogFile() { return Quassel::configDirPath() + "quassel-storage.sqlite"; } - -bool SqliteStorage::safeExec(QSqlQuery &query, int retryCount) +bool SqliteStorage::safeExec(QSqlQuery& query, int retryCount) { query.exec(); if (!query.lastError().isValid()) return true; - switch (query.lastError().number()) { - case 5: // SQLITE_BUSY 5 /* The database file is locked */ - case 6: // SQLITE_LOCKED 6 /* A table in the database is locked */ + QString nativeErrorCode = query.lastError().nativeErrorCode(); + + // SQLITE_BUSY 5 /* The database file is locked */ + // SQLITE_LOCKED 6 /* A table in the database is locked */ + if (nativeErrorCode == QLatin1String{"5"} || nativeErrorCode == QLatin1String{"6"}) { if (retryCount < _maxRetryCount) return safeExec(query, retryCount + 1); - default: - return false; } + return false; } - // ======================================== // SqliteMigration // ======================================== SqliteMigrationReader::SqliteMigrationReader() - : SqliteStorage(), - _maxId(0) -{ -} - + : SqliteStorage() +{} void SqliteMigrationReader::setMaxId(MigrationObject mo) { @@ -1755,10 +2362,9 @@ void SqliteMigrationReader::setMaxId(MigrationObject mo) } QSqlQuery query = logDb().exec(queryString); query.first(); - _maxId = query.value(0).toInt(); + _maxId = query.value(0).toLongLong(); } - bool SqliteMigrationReader::prepareQuery(MigrationObject mo) { setMaxId(mo); @@ -1795,12 +2401,14 @@ bool SqliteMigrationReader::prepareQuery(MigrationObject mo) case UserSetting: newQuery(queryString("migrate_read_usersetting"), logDb()); break; + case CoreState: + newQuery(queryString("migrate_read_corestate"), logDb()); + break; } return exec(); } - -bool SqliteMigrationReader::readMo(QuasselUserMO &user) +bool SqliteMigrationReader::readMo(QuasselUserMO& user) { if (!next()) return false; @@ -1809,11 +2417,11 @@ bool SqliteMigrationReader::readMo(QuasselUserMO &user) user.username = value(1).toString(); user.password = value(2).toString(); user.hashversion = value(3).toInt(); + user.authenticator = value(4).toString(); return true; } - -bool SqliteMigrationReader::readMo(IdentityMO &identity) +bool SqliteMigrationReader::readMo(IdentityMO& identity) { if (!next()) return false; @@ -1832,7 +2440,7 @@ bool SqliteMigrationReader::readMo(IdentityMO &identity) identity.autoAwayReasonEnabled = value(11).toInt() == 1 ? true : false; identity.detachAwayEnabled = value(12).toInt() == 1 ? true : false; identity.detachAwayReason = value(13).toString(); - identity.detchAwayReasonEnabled = value(14).toInt() == 1 ? true : false; + identity.detachAwayReasonEnabled = value(14).toInt() == 1 ? true : false; identity.ident = value(15).toString(); identity.kickReason = value(16).toString(); identity.partReason = value(17).toString(); @@ -1842,8 +2450,7 @@ bool SqliteMigrationReader::readMo(IdentityMO &identity) return true; } - -bool SqliteMigrationReader::readMo(IdentityNickMO &identityNick) +bool SqliteMigrationReader::readMo(IdentityNickMO& identityNick) { if (!next()) return false; @@ -1854,8 +2461,7 @@ bool SqliteMigrationReader::readMo(IdentityNickMO &identityNick) return true; } - -bool SqliteMigrationReader::readMo(NetworkMO &network) +bool SqliteMigrationReader::readMo(NetworkMO& network) { if (!next()) return false; @@ -1885,11 +2491,17 @@ bool SqliteMigrationReader::readMo(NetworkMO &network) network.usesasl = value(22).toInt() == 1 ? true : false; network.saslaccount = value(23).toString(); network.saslpassword = value(24).toString(); + // Custom rate limiting + network.usecustommessagerate = value(25).toInt() == 1 ? true : false; + network.messagerateburstsize = value(26).toInt(); + network.messageratedelay = value(27).toUInt(); + network.unlimitedmessagerate = value(28).toInt() == 1 ? true : false; + // Skipped IRCv3 caps + network.skipcaps = value(29).toString(); return true; } - -bool SqliteMigrationReader::readMo(BufferMO &buffer) +bool SqliteMigrationReader::readMo(BufferMO& buffer) { if (!next()) return false; @@ -1901,15 +2513,18 @@ bool SqliteMigrationReader::readMo(BufferMO &buffer) buffer.buffername = value(4).toString(); buffer.buffercname = value(5).toString(); buffer.buffertype = value(6).toInt(); - buffer.lastseenmsgid = value(7).toInt(); - buffer.markerlinemsgid = value(8).toInt(); - buffer.key = value(9).toString(); - buffer.joined = value(10).toInt() == 1 ? true : false; + buffer.lastmsgid = value(7).toLongLong(); + buffer.lastseenmsgid = value(8).toLongLong(); + buffer.markerlinemsgid = value(9).toLongLong(); + buffer.bufferactivity = value(10).toInt(); + buffer.highlightcount = value(11).toInt(); + buffer.key = value(12).toString(); + buffer.joined = value(13).toInt() == 1 ? true : false; + buffer.cipher = value(14).toString(); return true; } - -bool SqliteMigrationReader::readMo(SenderMO &sender) +bool SqliteMigrationReader::readMo(SenderMO& sender) { int skipSteps = 0; while (!next()) { @@ -1925,19 +2540,20 @@ bool SqliteMigrationReader::readMo(SenderMO &sender) } } - sender.senderId = value(0).toInt(); + sender.senderId = value(0).toLongLong(); sender.sender = value(1).toString(); + sender.realname = value(2).toString(); + sender.avatarurl = value(3).toString(); return true; } - -bool SqliteMigrationReader::readMo(BacklogMO &backlog) +bool SqliteMigrationReader::readMo(BacklogMO& backlog) { - int skipSteps = 0; + qint64 skipSteps = 0; while (!next()) { if (backlog.messageid < _maxId) { - bindValue(0, backlog.messageid.toInt() + (skipSteps * stepSize())); - bindValue(1, backlog.messageid.toInt() + ((skipSteps + 1) * stepSize())); + bindValue(0, backlog.messageid.toQint64() + (skipSteps * stepSize())); + bindValue(1, backlog.messageid.toQint64() + ((skipSteps + 1) * stepSize())); skipSteps++; if (!exec()) return false; @@ -1947,18 +2563,20 @@ bool SqliteMigrationReader::readMo(BacklogMO &backlog) } } - backlog.messageid = value(0).toInt(); - backlog.time = QDateTime::fromTime_t(value(1).toInt()).toUTC(); + backlog.messageid = value(0).toLongLong(); + // As of SQLite schema version 31, timestamps are stored in milliseconds instead of + // seconds. This nets us more precision as well as simplifying 64-bit time. + backlog.time = QDateTime::fromMSecsSinceEpoch(value(1).toLongLong()).toUTC(); backlog.bufferid = value(2).toInt(); backlog.type = value(3).toInt(); backlog.flags = value(4).toInt(); - backlog.senderid = value(5).toInt(); - backlog.message = value(6).toString(); + backlog.senderid = value(5).toLongLong(); + backlog.senderprefixes = value(6).toString(); + backlog.message = value(7).toString(); return true; } - -bool SqliteMigrationReader::readMo(IrcServerMO &ircserver) +bool SqliteMigrationReader::readMo(IrcServerMO& ircserver) { if (!next()) return false; @@ -1977,11 +2595,11 @@ bool SqliteMigrationReader::readMo(IrcServerMO &ircserver) ircserver.proxyport = value(11).toInt(); ircserver.proxyuser = value(12).toString(); ircserver.proxypass = value(13).toString(); + ircserver.sslverify = value(14).toInt() == 1 ? true : false; return true; } - -bool SqliteMigrationReader::readMo(UserSettingMO &userSetting) +bool SqliteMigrationReader::readMo(UserSettingMO& userSetting) { if (!next()) return false; @@ -1992,3 +2610,14 @@ bool SqliteMigrationReader::readMo(UserSettingMO &userSetting) return true; } + +bool SqliteMigrationReader::readMo(CoreStateMO& coreState) +{ + if (!next()) + return false; + + coreState.key = value(0).toString(); + coreState.value = value(1).toByteArray(); + + return true; +}