+ QSqlDatabase db = logDb();
+ db.transaction();
+
+ {
+ QSqlQuery query(db);
+ query.prepare(queryString("select_buffers_for_network"));
+ query.bindValue(":networkid", networkId.toInt());
+ query.bindValue(":userid", user.toInt());
+
+ lockForRead();
+ safeExec(query);
+ watchQuery(query);
+ while (query.next()) {
+ bufferList.emplace_back(query.value(0).toInt());
+ }
+ db.commit();
+ }
+ unlock();
+
+ return bufferList;
+}
+
+bool SqliteStorage::removeBuffer(const UserId& user, const BufferId& bufferId)
+{
+ QSqlDatabase db = logDb();
+ db.transaction();
+
+ bool error = false;
+ {
+ QSqlQuery delBufferQuery(db);
+ delBufferQuery.prepare(queryString("delete_buffer_for_bufferid"));
+ delBufferQuery.bindValue(":bufferid", bufferId.toInt());
+ delBufferQuery.bindValue(":userid", user.toInt());
+
+ lockForWrite();
+ safeExec(delBufferQuery);
+
+ error = (!watchQuery(delBufferQuery) || delBufferQuery.numRowsAffected() != 1);
+ }
+
+ if (error) {
+ db.rollback();
+ unlock();
+ return false;
+ }
+
+ {
+ QSqlQuery delBacklogQuery(db);
+ delBacklogQuery.prepare(queryString("delete_backlog_for_buffer"));
+ delBacklogQuery.bindValue(":bufferid", bufferId.toInt());
+
+ safeExec(delBacklogQuery);
+ error = !watchQuery(delBacklogQuery);
+ }
+
+ if (error) {
+ db.rollback();
+ }
+ else {
+ db.commit();
+ }
+ unlock();
+ return !error;
+}
+
+bool SqliteStorage::renameBuffer(const UserId& user, const BufferId& bufferId, const QString& newName)
+{
+ QSqlDatabase db = logDb();
+ db.transaction();
+
+ bool error = false;
+ {
+ QSqlQuery query(db);
+ query.prepare(queryString("update_buffer_name"));
+ query.bindValue(":buffername", newName);
+ query.bindValue(":buffercname", newName.toLower());
+ query.bindValue(":bufferid", bufferId.toInt());
+ query.bindValue(":userid", user.toInt());
+
+ lockForWrite();
+ safeExec(query);
+
+ error = query.lastError().isValid();
+ // unexepcted error occured (19 == constraint violation)
+ if (error && query.lastError().nativeErrorCode() != QLatin1String{"19"}) {
+ watchQuery(query);
+ }
+ else {
+ error |= (query.numRowsAffected() != 1);
+ }
+ }
+ if (error) {
+ db.rollback();
+ }
+ else {
+ db.commit();
+ }
+ unlock();
+ return !error;
+}
+
+bool SqliteStorage::mergeBuffersPermanently(const UserId& user, const BufferId& bufferId1, const BufferId& bufferId2)
+{
+ QSqlDatabase db = logDb();
+ db.transaction();
+
+ bool error = false;
+ {
+ QSqlQuery checkQuery(db);
+ checkQuery.prepare(queryString("select_buffers_for_merge"));
+ checkQuery.bindValue(":oldbufferid", bufferId2.toInt());
+ checkQuery.bindValue(":newbufferid", bufferId1.toInt());
+ checkQuery.bindValue(":userid", user.toInt());
+
+ lockForRead();
+ safeExec(checkQuery);
+ error = (!checkQuery.first() || checkQuery.value(0).toInt() != 2);
+ }
+ if (error) {
+ db.rollback();
+ unlock();
+ return false;
+ }
+
+ {
+ QSqlQuery query(db);
+ query.prepare(queryString("update_backlog_bufferid"));
+ query.bindValue(":oldbufferid", bufferId2.toInt());
+ query.bindValue(":newbufferid", bufferId1.toInt());
+ safeExec(query);
+ error = !watchQuery(query);
+ }
+ if (error) {
+ db.rollback();
+ unlock();
+ return false;
+ }
+
+ {
+ QSqlQuery delBufferQuery(db);
+ delBufferQuery.prepare(queryString("delete_buffer_for_bufferid"));
+ delBufferQuery.bindValue(":bufferid", bufferId2.toInt());
+ delBufferQuery.bindValue(":userid", user.toInt());
+ safeExec(delBufferQuery);
+ error = !watchQuery(delBufferQuery);
+ }
+
+ if (error) {
+ db.rollback();
+ }
+ else {
+ db.commit();
+ }
+ unlock();
+ return !error;
+}
+
+QHash<BufferId, MsgId> SqliteStorage::bufferLastMsgIds(UserId user)
+{
+ QHash<BufferId, MsgId> 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)
+{
+ QSqlDatabase db = logDb();
+ db.transaction();
+
+ {
+ QSqlQuery query(db);
+ query.prepare(queryString("update_buffer_lastseen"));
+ query.bindValue(":userid", user.toInt());
+ query.bindValue(":bufferid", bufferId.toInt());
+ query.bindValue(":lastseenmsgid", msgId.toQint64());
+
+ lockForWrite();
+ safeExec(query);
+ watchQuery(query);
+ }
+ db.commit();
+ unlock();
+}
+
+QHash<BufferId, MsgId> SqliteStorage::bufferLastSeenMsgIds(UserId user)
+{
+ QHash<BufferId, MsgId> lastSeenHash;
+
+ QSqlDatabase db = logDb();
+ db.transaction();
+
+ bool error = false;
+ {
+ QSqlQuery query(db);
+ query.prepare(queryString("select_buffer_lastseen_messages"));
+ query.bindValue(":userid", user.toInt());
+
+ lockForRead();
+ safeExec(query);
+ error = !watchQuery(query);
+ if (!error) {
+ while (query.next()) {
+ lastSeenHash[query.value(0).toInt()] = query.value(1).toLongLong();
+ }
+ }
+ }
+
+ db.commit();
+ unlock();
+ return lastSeenHash;
+}
+
+void SqliteStorage::setBufferMarkerLineMsg(UserId user, const BufferId& bufferId, const MsgId& msgId)
+{
+ QSqlDatabase db = logDb();
+ db.transaction();
+
+ {
+ QSqlQuery query(db);
+ query.prepare(queryString("update_buffer_markerlinemsgid"));
+ query.bindValue(":userid", user.toInt());
+ query.bindValue(":bufferid", bufferId.toInt());
+ query.bindValue(":markerlinemsgid", msgId.toQint64());
+
+ lockForWrite();
+ safeExec(query);
+ watchQuery(query);
+ }
+ db.commit();
+ unlock();
+}
+
+QHash<BufferId, MsgId> SqliteStorage::bufferMarkerLineMsgIds(UserId user)
+{
+ QHash<BufferId, MsgId> markerLineHash;
+
+ QSqlDatabase db = logDb();
+ db.transaction();
+
+ bool error = false;
+ {
+ QSqlQuery query(db);
+ query.prepare(queryString("select_buffer_markerlinemsgids"));
+ query.bindValue(":userid", user.toInt());
+
+ lockForRead();
+ safeExec(query);
+ error = !watchQuery(query);
+ if (!error) {
+ while (query.next()) {
+ markerLineHash[query.value(0).toInt()] = query.value(1).toLongLong();
+ }
+ }
+ }
+
+ db.commit();
+ unlock();
+ return markerLineHash;
+}
+
+void SqliteStorage::setBufferActivity(UserId user, BufferId bufferId, Message::Types bufferActivity)
+{
+ QSqlDatabase db = logDb();
+ db.transaction();
+
+ {
+ 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(query);
+ watchQuery(query);
+ }
+ db.commit();
+ unlock();
+}
+
+QHash<BufferId, Message::Types> SqliteStorage::bufferActivities(UserId user)
+{
+ QHash<BufferId, Message::Types> 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) {
+ while (query.next()) {
+ bufferActivityHash[query.value(0).toInt()] = Message::Types(query.value(1).toInt());
+ }
+ }
+ }
+
+ db.commit();
+ unlock();
+ return bufferActivityHash;
+}
+
+Message::Types SqliteStorage::bufferActivity(BufferId bufferId, MsgId lastSeenMsgId)
+{
+ QSqlDatabase db = logDb();
+ db.transaction();
+
+ Message::Types result = Message::Types(nullptr);
+ {
+ QSqlQuery query(db);
+ query.prepare(queryString("select_buffer_bufferactivity"));
+ query.bindValue(":bufferid", bufferId.toInt());
+ query.bindValue(":lastseenmsgid", lastSeenMsgId.toQint64());
+
+ lockForRead();
+ safeExec(query);
+ if (query.first())
+ result = Message::Types(query.value(0).toInt());
+ }
+
+ db.commit();
+ unlock();
+ return result;
+}
+
+QHash<QString, QByteArray> SqliteStorage::bufferCiphers(UserId user, const NetworkId& networkId)
+{
+ QHash<QString, QByteArray> 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<BufferId, int> SqliteStorage::highlightCounts(UserId user)
+{
+ QHash<BufferId, int> 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<SenderData> 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)) {
+ error = true;
+ break;
+ }
+ else {
+ msg.setMsgId(logMessageQuery.lastInsertId().toLongLong());
+ }
+ }
+ }
+
+ if (error) {
+ db.rollback();
+ unlock();
+ // we had a rollback in the db so we need to reset all msgIds
+ for (int i = 0; i < msgs.count(); i++) {
+ msgs[i].setMsgId(MsgId());
+ }
+ }
+ else {
+ db.commit();
+ unlock();
+ }
+ return !error;
+}
+
+std::vector<Message> SqliteStorage::requestMsgs(UserId user, BufferId bufferId, MsgId first, MsgId last, int limit)
+{
+ std::vector<Message> messagelist;
+
+ QSqlDatabase db = logDb();
+ db.transaction();
+
+ bool error = false;
+ BufferInfo bufferInfo;
+ {
+ // 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"));
+ 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"));
+ }
+ else if (last == -1) {
+ query.prepare(queryString("select_messagesNewerThan"));
+ query.bindValue(":firstmsg", first.toQint64());
+ }
+ else {
+ query.prepare(queryString("select_messagesRange"));
+ query.bindValue(":lastmsg", last.toQint64());
+ query.bindValue(":firstmsg", first.toQint64());
+ }
+ query.bindValue(":bufferid", bufferId.toInt());
+ 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<Message> SqliteStorage::requestMsgsFiltered(
+ UserId user, BufferId bufferId, MsgId first, MsgId last, int limit, Message::Types type, Message::Flags flags)
+{
+ std::vector<Message> 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(),
+ 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<Message> SqliteStorage::requestMsgsForward(
+ UserId user, BufferId bufferId, MsgId first, MsgId last, int limit, Message::Types type, Message::Flags flags)
+{
+ std::vector<Message> 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<qint64>::min());
+ } else {
+ query.bindValue(":firstmsg", first.toQint64());
+ }
+
+ if (last == -1) {
+ query.bindValue(":lastmsg", std::numeric_limits<qint64>::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);
+
+ 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<Message> SqliteStorage::requestAllMsgs(UserId user, MsgId first, MsgId last, int limit)
+{
+ std::vector<Message> messagelist;
+
+ QSqlDatabase db = logDb();
+ db.transaction();
+
+ QHash<BufferId, BufferInfo> 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"));
+ }
+ else {
+ query.prepare(queryString("select_messagesAll"));
+ query.bindValue(":lastmsg", last.toQint64());
+ }
+ query.bindValue(":userid", user.toInt());
+ query.bindValue(":firstmsg", first.toQint64());
+ 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(2).toLongLong()),
+ bufferInfoHash[query.value(1).toInt()],
+ (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<Message> SqliteStorage::requestAllMsgsFiltered(UserId user, MsgId first, MsgId last, int limit, Message::Types type, Message::Flags flags)
+{
+ std::vector<Message> messagelist;
+
+ QSqlDatabase db = logDb();
+ db.transaction();
+
+ QHash<BufferId, BufferInfo> 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(),
+ 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;
+}
+
+QMap<UserId, QString> SqliteStorage::getAllAuthUserNames()
+{
+ QMap<UserId, QString> 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)
+{
+ query.exec();
+
+ if (!query.lastError().isValid())
+ return true;
+
+ 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);
+ }
+ return false;
+}
+
+// ========================================
+// SqliteMigration
+// ========================================
+SqliteMigrationReader::SqliteMigrationReader()
+ : SqliteStorage()
+{}
+
+void SqliteMigrationReader::setMaxId(MigrationObject mo)
+{
+ QString queryString;
+ switch (mo) {
+ case Sender:
+ queryString = "SELECT max(senderid) FROM sender";
+ break;
+ case Backlog:
+ queryString = "SELECT max(messageid) FROM backlog";
+ break;
+ default:
+ _maxId = 0;
+ return;
+ }
+ QSqlQuery query = logDb().exec(queryString);
+ query.first();
+ _maxId = query.value(0).toLongLong();
+}
+
+bool SqliteMigrationReader::prepareQuery(MigrationObject mo)
+{
+ setMaxId(mo);
+
+ switch (mo) {
+ case QuasselUser:
+ newQuery(queryString("migrate_read_quasseluser"), logDb());
+ break;
+ case Identity:
+ newQuery(queryString("migrate_read_identity"), logDb());
+ break;
+ case IdentityNick:
+ newQuery(queryString("migrate_read_identity_nick"), logDb());
+ break;
+ case Network:
+ newQuery(queryString("migrate_read_network"), logDb());
+ break;
+ case Buffer:
+ newQuery(queryString("migrate_read_buffer"), logDb());
+ break;
+ case Sender:
+ newQuery(queryString("migrate_read_sender"), logDb());
+ bindValue(0, 0);
+ bindValue(1, stepSize());
+ break;
+ case Backlog:
+ newQuery(queryString("migrate_read_backlog"), logDb());
+ bindValue(0, 0);
+ bindValue(1, stepSize());
+ break;
+ case IrcServer:
+ newQuery(queryString("migrate_read_ircserver"), logDb());
+ break;
+ 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)
+{
+ if (!next())
+ return false;
+
+ user.id = value(0).toInt();
+ 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)
+{
+ if (!next())
+ return false;
+
+ identity.id = value(0).toInt();
+ identity.userid = value(1).toInt();
+ identity.identityname = value(2).toString();
+ identity.realname = value(3).toString();
+ identity.awayNick = value(4).toString();
+ identity.awayNickEnabled = value(5).toInt() == 1 ? true : false;
+ identity.awayReason = value(6).toString();
+ identity.awayReasonEnabled = value(7).toInt() == 1 ? true : false;
+ identity.autoAwayEnabled = value(8).toInt() == 1 ? true : false;
+ identity.autoAwayTime = value(9).toInt();
+ identity.autoAwayReason = value(10).toString();
+ identity.autoAwayReasonEnabled = value(11).toInt() == 1 ? true : false;
+ identity.detachAwayEnabled = value(12).toInt() == 1 ? true : false;
+ identity.detachAwayReason = value(13).toString();
+ identity.detachAwayReasonEnabled = value(14).toInt() == 1 ? true : false;
+ identity.ident = value(15).toString();
+ identity.kickReason = value(16).toString();
+ identity.partReason = value(17).toString();
+ identity.quitReason = value(18).toString();
+ identity.sslCert = value(19).toByteArray();
+ identity.sslKey = value(20).toByteArray();
+ return true;
+}
+
+bool SqliteMigrationReader::readMo(IdentityNickMO& identityNick)
+{
+ if (!next())
+ return false;
+
+ identityNick.nickid = value(0).toInt();
+ identityNick.identityId = value(1).toInt();
+ identityNick.nick = value(2).toString();
+ return true;
+}
+
+bool SqliteMigrationReader::readMo(NetworkMO& network)
+{
+ if (!next())
+ return false;
+
+ network.networkid = value(0).toInt();
+ network.userid = value(1).toInt();
+ network.networkname = value(2).toString();
+ network.identityid = value(3).toInt();
+ network.encodingcodec = value(4).toString();
+ network.decodingcodec = value(5).toString();
+ network.servercodec = value(6).toString();
+ network.userandomserver = value(7).toInt() == 1 ? true : false;
+ network.perform = value(8).toString();
+ network.useautoidentify = value(9).toInt() == 1 ? true : false;
+ network.autoidentifyservice = value(10).toString();
+ network.autoidentifypassword = value(11).toString();
+ network.useautoreconnect = value(12).toInt() == 1 ? true : false;
+ network.autoreconnectinterval = value(13).toInt();
+ network.autoreconnectretries = value(14).toInt();
+ network.unlimitedconnectretries = value(15).toInt() == 1 ? true : false;
+ network.rejoinchannels = value(16).toInt() == 1 ? true : false;
+ network.connected = value(17).toInt() == 1 ? true : false;
+ network.usermode = value(18).toString();
+ network.awaymessage = value(19).toString();
+ network.attachperform = value(20).toString();
+ network.detachperform = value(21).toString();
+ 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)
+{
+ if (!next())
+ return false;
+
+ buffer.bufferid = value(0).toInt();
+ buffer.userid = value(1).toInt();
+ buffer.groupid = value(2).toInt();
+ buffer.networkid = value(3).toInt();
+ buffer.buffername = value(4).toString();
+ buffer.buffercname = value(5).toString();
+ buffer.buffertype = value(6).toInt();
+ 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)
+{
+ int skipSteps = 0;
+ while (!next()) {
+ if (sender.senderId < _maxId) {
+ bindValue(0, sender.senderId + (skipSteps * stepSize()));
+ bindValue(1, sender.senderId + ((skipSteps + 1) * stepSize()));
+ skipSteps++;
+ if (!exec())
+ return false;
+ }
+ else {
+ return false;
+ }
+ }
+
+ 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)
+{
+ qint64 skipSteps = 0;
+ while (!next()) {
+ if (backlog.messageid < _maxId) {
+ bindValue(0, backlog.messageid.toQint64() + (skipSteps * stepSize()));
+ bindValue(1, backlog.messageid.toQint64() + ((skipSteps + 1) * stepSize()));
+ skipSteps++;
+ if (!exec())
+ return false;
+ }
+ else {
+ return false;
+ }
+ }
+
+ 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).toLongLong();
+ backlog.senderprefixes = value(6).toString();
+ backlog.message = value(7).toString();
+ return true;
+}
+
+bool SqliteMigrationReader::readMo(IrcServerMO& ircserver)
+{
+ if (!next())
+ return false;
+
+ ircserver.serverid = value(0).toInt();
+ ircserver.userid = value(1).toInt();
+ ircserver.networkid = value(2).toInt();
+ ircserver.hostname = value(3).toString();
+ ircserver.port = value(4).toInt();
+ ircserver.password = value(5).toString();
+ ircserver.ssl = value(6).toInt() == 1 ? true : false;
+ ircserver.sslversion = value(7).toInt();
+ ircserver.useproxy = value(8).toInt() == 1 ? true : false;
+ ircserver.proxytype = value(9).toInt();
+ ircserver.proxyhost = value(10).toString();
+ 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)
+{
+ if (!next())
+ return false;
+
+ userSetting.userid = value(0).toInt();
+ userSetting.settingname = value(1).toString();
+ userSetting.settingvalue = value(2).toByteArray();
+
+ return true;
+}
+
+bool SqliteMigrationReader::readMo(CoreStateMO& coreState)
+{
+ if (!next())
+ return false;
+
+ coreState.key = value(0).toString();
+ coreState.value = value(1).toByteArray();
+
+ return true;
+}