+ return modes;
+}
+
+
+void SqliteStorage::setUserModes(UserId user, NetworkId networkId, const QString &userModes)
+{
+ QSqlDatabase db = logDb();
+ db.transaction();
+
+ {
+ QSqlQuery query(db);
+ query.prepare(queryString("update_network_set_usermode"));
+ query.bindValue(":userid", user.toInt());
+ query.bindValue(":networkid", networkId.toInt());
+ query.bindValue(":usermode", userModes);
+
+ lockForWrite();
+ safeExec(query);
+ watchQuery(query);
+ db.commit();
+ }
+ unlock();
+}
+
+
+BufferInfo SqliteStorage::bufferInfo(UserId user, const NetworkId &networkId, BufferInfo::Type type, const QString &buffer, bool create)
+{
+ QSqlDatabase db = logDb();
+ db.transaction();
+
+ BufferInfo bufferInfo;
+ {
+ QSqlQuery query(db);
+ query.prepare(queryString("select_bufferByName"));
+ query.bindValue(":networkid", networkId.toInt());
+ query.bindValue(":userid", user.toInt());
+ query.bindValue(":buffercname", buffer.toLower());
+
+ lockForRead();
+ safeExec(query);
+
+ if (query.first()) {
+ bufferInfo = BufferInfo(query.value(0).toInt(), networkId, (BufferInfo::Type)query.value(1).toInt(), 0, buffer);
+ if (query.next()) {
+ qCritical() << "SqliteStorage::getBufferInfo(): received more then one Buffer!";
+ qCritical() << " Query:" << query.lastQuery();
+ qCritical() << " bound Values:";
+ QList<QVariant> list = query.boundValues().values();
+ for (int i = 0; i < list.size(); ++i)
+ qCritical() << i << ":" << list.at(i).toString().toLatin1().data();
+ Q_ASSERT(false);
+ }
+ }
+ else if (create) {
+ // let's create the buffer
+ QSqlQuery createQuery(db);
+ createQuery.prepare(queryString("insert_buffer"));
+ createQuery.bindValue(":userid", user.toInt());
+ createQuery.bindValue(":networkid", networkId.toInt());
+ createQuery.bindValue(":buffertype", (int)type);
+ createQuery.bindValue(":buffername", buffer);
+ createQuery.bindValue(":buffercname", buffer.toLower());
+ createQuery.bindValue(":joined", type & BufferInfo::ChannelBuffer ? 1 : 0);
+
+ unlock();
+ lockForWrite();
+ safeExec(createQuery);
+ watchQuery(createQuery);
+ bufferInfo = BufferInfo(createQuery.lastInsertId().toInt(), networkId, type, 0, buffer);
+ }
+ }
+ db.commit();
+ unlock();
+ return bufferInfo;
+}
+
+
+BufferInfo SqliteStorage::getBufferInfo(UserId user, const BufferId &bufferId)
+{
+ QSqlDatabase db = logDb();
+ db.transaction();
+
+ BufferInfo bufferInfo;
+ {
+ QSqlQuery query(db);
+ query.prepare(queryString("select_buffer_by_id"));
+ query.bindValue(":userid", user.toInt());
+ query.bindValue(":bufferid", bufferId.toInt());
+
+ lockForRead();
+ 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());
+ Q_ASSERT(!query.next());
+ }
+ db.commit();
+ }
+ unlock();
+ return bufferInfo;
+}
+
+
+QList<BufferInfo> SqliteStorage::requestBuffers(UserId user)
+{
+ QList<BufferInfo> bufferlist;
+
+ QSqlDatabase db = logDb();
+ db.transaction();
+
+ {
+ QSqlQuery query(db);
+ query.prepare(queryString("select_buffers"));
+ query.bindValue(":userid", user.toInt());
+
+ lockForRead();
+ 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());
+ }
+ db.commit();
+ }
+ unlock();
+
+ return bufferlist;
+}
+
+
+QList<BufferId> SqliteStorage::requestBufferIdsForNetwork(UserId user, NetworkId networkId)
+{
+ QList<BufferId> bufferList;
+
+ 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 << BufferId(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().number() != 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;
+}
+
+
+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().number() == 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;
+}
+
+
+QList<Message> SqliteStorage::requestMsgs(UserId user, BufferId bufferId, MsgId first, MsgId last, int limit)
+{
+ QList<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 << msg;
+ }
+ }
+ db.commit();
+ unlock();
+
+ return messagelist;
+}
+
+
+QList<Message> SqliteStorage::requestMsgsFiltered(UserId user, BufferId bufferId, MsgId first, MsgId last, int limit, Message::Types type, Message::Flags flags)
+{
+ QList<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 << msg;
+ }
+ }
+ db.commit();
+ unlock();
+
+ return messagelist;
+}
+
+
+QList<Message> SqliteStorage::requestAllMsgs(UserId user, MsgId first, MsgId last, int limit)
+{
+ QList<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 << msg;
+ }
+ }
+ db.commit();
+ unlock();
+ return messagelist;
+}
+
+QList<Message> SqliteStorage::requestAllMsgsFiltered(UserId user, MsgId first, MsgId last, int limit, Message::Types type, Message::Flags flags)
+{
+ QList<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 << 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;
+
+ switch (query.lastError().number()) {
+ case 5: // SQLITE_BUSY 5 /* The database file is locked */
+ // fallthrough
+ case 6: // SQLITE_LOCKED 6 /* A table in the database is locked */
+ if (retryCount < _maxRetryCount)
+ return safeExec(query, retryCount + 1);
+ break;
+ default:
+ ;
+ }
+ return false;