diff --git a/src/Message.cpp b/src/Message.cpp index db8473b..e987cbc 100644 --- a/src/Message.cpp +++ b/src/Message.cpp @@ -1,198 +1,257 @@ /* * Kaidan - A user-friendly XMPP client for every device! * * Copyright (C) 2016-2019 Kaidan developers and contributors * (see the LICENSE file for a full list of copyright authors) * * Kaidan is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * In addition, as a special exception, the author of Kaidan gives * permission to link the code of its release with the OpenSSL * project's "OpenSSL" library (or with modified versions of it that * use the same license as the "OpenSSL" library), and distribute the * linked executables. You must obey the GNU General Public License in * all respects for all of the code used other than "OpenSSL". If you * modify this file, you may extend this exception to your version of * the file, but you are not obligated to do so. If you do not wish to * do so, delete this exception statement from your version. * * Kaidan is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Kaidan. If not, see . */ #include "Message.h" + +#include #include +static bool operator==(const QXmppStanza::Error &left, const QXmppStanza::Error &right) { + return left.code() == right.code() + && left.text() == right.text() + && left.condition() == right.condition() + && left.type() == right.type(); +} + +static bool operator==(const QXmppElement &left, const QXmppElement &right) { + return left.sourceDomElement() == right.sourceDomElement() + && left.attributeNames() == right.attributeNames() + && left.tagName() == right.tagName() + && left.value() == right.value(); +} + +static bool operator==(const QXmppExtendedAddress &left, const QXmppExtendedAddress &right) { + return left.description() == right.description() + && left.jid() == right.jid() + && left.type() == right.type() + && left.isDelivered() == right.isDelivered(); +} + +static bool operator==(const QXmppStanza &left, const QXmppStanza &right) { + return left.to() == right.to() + && left.from() == right.from() + && left.id() == right.id() + && left.lang() == right.lang() + && left.error() == right.error() + && left.extensions() == right.extensions() + && left.extendedAddresses() == right.extendedAddresses() + && left.isXmppStanza() == right.isXmppStanza(); +} + +static bool operator==(const QXmppMessage &left, const QXmppMessage &right) { + return operator==(static_cast(left), static_cast(right)) + && left.body() == right.body() + && left.isAttentionRequested() == right.isAttentionRequested() + && left.isReceiptRequested() == right.isReceiptRequested() + && left.mucInvitationJid() == right.mucInvitationJid() + && left.mucInvitationPassword() == right.mucInvitationPassword() + && left.mucInvitationReason() == right.mucInvitationReason() + && left.receiptId() == right.receiptId() + && left.stamp() == right.stamp() + && left.state() == right.state() + && left.subject() == right.subject() + && left.thread() == right.thread() + && left.type() == right.type() + && left.xhtml() == right.xhtml() + && left.isMarkable() == right.isMarkable() + && left.markedId() == right.markedId() + && left.markedThread() == right.markedThread() + && left.marker() == right.marker() + && left.isPrivate() == right.isPrivate() + && left.isXmppStanza() == right.isXmppStanza() +#if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 0, 0) + && left.outOfBandUrl() == right.outOfBandUrl() +#endif + && left.replaceId() == right.replaceId(); +} + MessageType Message::mediaTypeFromMimeType(const QMimeType &type) { if (type.inherits("image/jpeg") || type.inherits("image/png") || type.inherits("image/gif")) return MessageType::MessageImage; if (type.inherits("audio/flac") || type.inherits("audio/mp4") || type.inherits("audio/ogg") || type.inherits("audio/wav") || type.inherits("audio/mpeg") || type.inherits("audio/webm")) return MessageType::MessageAudio; if (type.inherits("video/mpeg") || type.inherits("video/x-msvideo") || type.inherits("video/quicktime") || type.inherits("video/mp4") || type.inherits("video/x-matroska")) return MessageType::MessageVideo; if (type.inherits("text/plain")) return MessageType::MessageDocument; return MessageType::MessageFile; } bool Message::operator==(const Message &m) const { - return m.id() == id() && - m.body() == body() && - m.from() == from() && - m.to() == to() && - m.type() == type() && - m.stamp() == stamp() && -#if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 0, 0) - m.outOfBandUrl() == outOfBandUrl() && + return ::operator==(static_cast(m), static_cast(*this)) + && m.mediaType() == mediaType() + && m.sentByMe() == sentByMe() + && m.isEdited() == isEdited() + && m.isSent() == isSent() + && m.isDelivered() == isDelivered() +#if (QXMPP_VERSION) < QT_VERSION_CHECK(1, 0, 0) + && m.outOfBandUrl() == outOfBandUrl() #endif - m.isSent() == isSent() && - m.isDelivered() == isDelivered() && - m.mediaType() == mediaType() && - m.mediaContentType() == mediaContentType() && - m.mediaLocation() == mediaLocation() && - m.isEdited() == isEdited() && - m.spoilerHint() == spoilerHint() && - m.isSpoiler() == isSpoiler(); + && m.mediaLocation() == mediaLocation() + && m.mediaContentType() == mediaContentType() + && m.mediaLastModified() == mediaLastModified() + && m.mediaSize() == mediaSize() + && m.isSpoiler() == isSpoiler() + && m.spoilerHint() == spoilerHint(); } bool Message::operator!=(const Message &m) const { return !operator==(m); } MessageType Message::mediaType() const { return m_mediaType; } void Message::setMediaType(MessageType mediaType) { m_mediaType = mediaType; } bool Message::sentByMe() const { return m_sentByMe; } void Message::setSentByMe(bool sentByMe) { m_sentByMe = sentByMe; } bool Message::isEdited() const { return m_isEdited; } void Message::setIsEdited(bool isEdited) { m_isEdited = isEdited; } bool Message::isSent() const { return m_isSent; } void Message::setIsSent(bool isSent) { m_isSent = isSent; } bool Message::isDelivered() const { return m_isDelivered; } void Message::setIsDelivered(bool isDelivered) { m_isDelivered = isDelivered; } QString Message::mediaLocation() const { return m_mediaLocation; } void Message::setMediaLocation(const QString &mediaLocation) { m_mediaLocation = mediaLocation; } QString Message::mediaContentType() const { return m_mediaContentType; } void Message::setMediaContentType(const QString &mediaContentType) { m_mediaContentType = mediaContentType; } QDateTime Message::mediaLastModified() const { return m_mediaLastModified; } void Message::setMediaLastModified(const QDateTime &mediaLastModified) { m_mediaLastModified = mediaLastModified; } qint64 Message::mediaSize() const { return m_mediaSize; } void Message::setMediaSize(const qint64 &mediaSize) { m_mediaSize = mediaSize; } bool Message::isSpoiler() const { return m_isSpoiler; } void Message::setIsSpoiler(bool isSpoiler) { m_isSpoiler = isSpoiler; } QString Message::spoilerHint() const { return m_spoilerHint; } void Message::setSpoilerHint(const QString &spoilerHint) { m_spoilerHint = spoilerHint; } #if (QXMPP_VERSION) < QT_VERSION_CHECK(1, 0, 0) QString Message::outOfBandUrl() const { return m_outOfBandUrl; } void Message::setOutOfBandUrl(const QString &outOfBandUrl) { m_outOfBandUrl = outOfBandUrl; } #endif diff --git a/src/Message.h b/src/Message.h index e2ddc68..46736ba 100644 --- a/src/Message.h +++ b/src/Message.h @@ -1,156 +1,156 @@ /* * Kaidan - A user-friendly XMPP client for every device! * * Copyright (C) 2016-2019 Kaidan developers and contributors * (see the LICENSE file for a full list of copyright authors) * * Kaidan is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * In addition, as a special exception, the author of Kaidan gives * permission to link the code of its release with the OpenSSL * project's "OpenSSL" library (or with modified versions of it that * use the same license as the "OpenSSL" library), and distribute the * linked executables. You must obey the GNU General Public License in * all respects for all of the code used other than "OpenSSL". If you * modify this file, you may extend this exception to your version of * the file, but you are not obligated to do so. If you do not wish to * do so, delete this exception statement from your version. * * Kaidan is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Kaidan. If not, see . */ #ifndef MESSAGE_H #define MESSAGE_H #include #include "Enums.h" class QMimeType; using namespace Enums; /** * @brief This class is used to load messages from the database and use them in * the @c MessageModel. The class inherits from @c QXmppMessage and most * properties are shared. */ class Message : public QXmppMessage { public: static MessageType mediaTypeFromMimeType(const QMimeType&); /** * Compares another @c Message with this. Only attributes that are saved in the * database are checked. */ bool operator==(const Message &m) const; bool operator!=(const Message &m) const; MessageType mediaType() const; void setMediaType(MessageType mediaType); bool sentByMe() const; void setSentByMe(bool sentByMe); bool isEdited() const; void setIsEdited(bool isEdited); bool isSent() const; void setIsSent(bool isSent); bool isDelivered() const; void setIsDelivered(bool isDelivered); QString mediaLocation() const; void setMediaLocation(const QString &mediaLocation); QString mediaContentType() const; void setMediaContentType(const QString &mediaContentType); QDateTime mediaLastModified() const; void setMediaLastModified(const QDateTime &mediaLastModified); qint64 mediaSize() const; void setMediaSize(const qint64 &mediaSize); bool isSpoiler() const; void setIsSpoiler(bool isSpoiler); QString spoilerHint() const; void setSpoilerHint(const QString &spoilerHint); #if (QXMPP_VERSION) < QT_VERSION_CHECK(1, 0, 0) QString outOfBandUrl() const; void setOutOfBandUrl(const QString &outOfBandUrl); #endif private: /** * Media type of the message, e.g. a text or image. */ MessageType m_mediaType = MessageType::MessageText; /** * True if the message was sent by the user. */ bool m_sentByMe = true; /** * True if the orginal message was edited. */ bool m_isEdited = false; /** * True if the message was sent. */ bool m_isSent = false; /** * True if a sent message was delivered to the contact. */ bool m_isDelivered = false; /** * Location of the media on the local storage. */ QString m_mediaLocation; /** * Media content type, e.g. "image/jpeg". */ QString m_mediaContentType; /** * Size of the file in bytes. */ - qint64 m_mediaSize; + qint64 m_mediaSize = 0; /** * Timestamp of the last modification date of the file locally on disk. */ QDateTime m_mediaLastModified; /** * True if the message is a spoiler message. */ bool m_isSpoiler = false; /** * Hint of the spoiler message. */ QString m_spoilerHint; #if (QXMPP_VERSION) < QT_VERSION_CHECK(1, 0, 0) QString m_outOfBandUrl; #endif }; #endif // MESSAGE_H diff --git a/src/MessageDb.cpp b/src/MessageDb.cpp index 8bbab9c..f5a46ee 100644 --- a/src/MessageDb.cpp +++ b/src/MessageDb.cpp @@ -1,289 +1,290 @@ /* * Kaidan - A user-friendly XMPP client for every device! * * Copyright (C) 2016-2019 Kaidan developers and contributors * (see the LICENSE file for a full list of copyright authors) * * Kaidan is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * In addition, as a special exception, the author of Kaidan gives * permission to link the code of its release with the OpenSSL * project's "OpenSSL" library (or with modified versions of it that * use the same license as the "OpenSSL" library), and distribute the * linked executables. You must obey the GNU General Public License in * all respects for all of the code used other than "OpenSSL". If you * modify this file, you may extend this exception to your version of * the file, but you are not obligated to do so. If you do not wish to * do so, delete this exception statement from your version. * * Kaidan is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Kaidan. If not, see . */ #include "MessageDb.h" // Kaidan #include "Globals.h" #include "Message.h" #include "Utils.h" // Qt #include #include #include #include #include MessageDb::MessageDb(QObject *parent) : QObject(parent) { connect(this, &MessageDb::fetchMessagesRequested, this, &MessageDb::fetchMessages); } void MessageDb::parseMessagesFromQuery(QSqlQuery &query, QVector &msgs) { // get indexes of attributes QSqlRecord rec = query.record(); int idxFrom = rec.indexOf("author"); int idxTo = rec.indexOf("recipient"); int idxStamp = rec.indexOf("timestamp"); int idxId = rec.indexOf("id"); int idxBody = rec.indexOf("message"); int idxIsSent = rec.indexOf("isSent"); int idxIsDelivered = rec.indexOf("isDelivered"); int idxMediaType = rec.indexOf("type"); int idxOutOfBandUrl = rec.indexOf("mediaUrl"); int idxMediaContentType = rec.indexOf("mediaContentType"); int idxMediaLocation = rec.indexOf("mediaLocation"); int idxMediaSize = rec.indexOf("mediaSize"); int idxMediaLastModified = rec.indexOf("mediaLastModified"); int idxIsEdited = rec.indexOf("edited"); int idxSpoilerHint = rec.indexOf("spoilerHint"); int idxIsSpoiler = rec.indexOf("isSpoiler"); while (query.next()) { Message msg; msg.setFrom(query.value(idxFrom).toString()); msg.setTo(query.value(idxTo).toString()); msg.setStamp(QDateTime::fromString( query.value(idxStamp).toString(), Qt::ISODate )); msg.setId(query.value(idxId).toString()); msg.setBody(query.value(idxBody).toString()); msg.setIsSent(query.value(idxIsSent).toBool()); msg.setIsDelivered(query.value(idxIsDelivered).toBool()); msg.setMediaType(static_cast(query.value(idxMediaType).toInt())); msg.setOutOfBandUrl(query.value(idxOutOfBandUrl).toString()); msg.setMediaContentType(query.value(idxMediaContentType).toString()); msg.setMediaLocation(query.value(idxMediaLocation).toString()); msg.setMediaSize(query.value(idxMediaSize).toLongLong()); msg.setMediaLastModified(QDateTime::fromMSecsSinceEpoch( query.value(idxMediaLastModified).toLongLong() )); msg.setIsEdited(query.value(idxIsEdited).toBool()); msg.setSpoilerHint(query.value(idxSpoilerHint).toString()); msg.setIsSpoiler(query.value(idxIsSpoiler).toBool()); msgs << msg; } } QSqlRecord MessageDb::createUpdateRecord(const Message &oldMsg, const Message &newMsg) { QSqlRecord rec; if (oldMsg.from() != newMsg.from()) rec.append(Utils::createSqlField("author", newMsg.from())); if (oldMsg.to() != newMsg.to()) rec.append(Utils::createSqlField("recipient", newMsg.to())); if (oldMsg.stamp() != newMsg.stamp()) rec.append(Utils::createSqlField( "timestamp", newMsg.stamp().toString(Qt::ISODate) )); if (oldMsg.id() != newMsg.id()) rec.append(Utils::createSqlField("id", newMsg.id())); if (oldMsg.body() != newMsg.body()) rec.append(Utils::createSqlField("message", newMsg.body())); if (oldMsg.isSent() != newMsg.isSent()) rec.append(Utils::createSqlField("isSent", newMsg.isSent())); if (oldMsg.isDelivered() != newMsg.isDelivered()) rec.append(Utils::createSqlField("isDelivered", newMsg.isDelivered())); if (oldMsg.mediaType() != newMsg.mediaType()) rec.append(Utils::createSqlField("type", int(newMsg.mediaType()))); if (oldMsg.outOfBandUrl() != newMsg.outOfBandUrl()) rec.append(Utils::createSqlField("mediaUrl", newMsg.outOfBandUrl())); if (oldMsg.mediaContentType() != newMsg.mediaContentType()) rec.append(Utils::createSqlField( "mediaContentType", newMsg.mediaContentType() )); if (oldMsg.mediaLocation() != newMsg.mediaLocation()) rec.append(Utils::createSqlField( "mediaLocation", newMsg.mediaLocation() )); if (oldMsg.mediaSize() != newMsg.mediaSize()) rec.append(Utils::createSqlField("mediaSize", newMsg.mediaSize())); if (oldMsg.mediaLastModified() != newMsg.mediaLastModified()) rec.append(Utils::createSqlField( "mediaLastModified", newMsg.mediaLastModified().toMSecsSinceEpoch() )); if (oldMsg.isEdited() != newMsg.isEdited()) rec.append(Utils::createSqlField("edited", newMsg.isEdited())); if (oldMsg.spoilerHint() != newMsg.spoilerHint()) rec.append(Utils::createSqlField("spoilerHint", newMsg.spoilerHint())); if (oldMsg.isSpoiler() != newMsg.isSpoiler()) rec.append(Utils::createSqlField("isSpoiler", newMsg.isSpoiler())); return rec; } void MessageDb::fetchMessages(const QString &user1, const QString &user2, int index) { QSqlQuery query(QSqlDatabase::database(DB_CONNECTION)); query.setForwardOnly(true); QMap bindValues; bindValues[":user1"] = user1; bindValues[":user2"] = user2; bindValues[":index"] = index; bindValues[":limit"] = DB_MSG_QUERY_LIMIT; Utils::execQuery( query, "SELECT * FROM Messages " "WHERE (author = :user1 AND recipient = :user2) OR " "(author = :user2 AND recipient = :user1) " "ORDER BY timestamp DESC " "LIMIT :index, :limit", bindValues ); QVector messages; parseMessagesFromQuery(query, messages); emit messagesFetched(messages); } void MessageDb::addMessage(const Message &msg) { QSqlDatabase db = QSqlDatabase::database(DB_CONNECTION); QSqlRecord record = db.record(DB_TABLE_MESSAGES); record.setValue("author", msg.from()); record.setValue("recipient", msg.to()); record.setValue("timestamp", msg.stamp().toString(Qt::ISODate)); record.setValue("message", msg.body()); record.setValue("id", msg.id().isEmpty() ? " " : msg.id()); record.setValue("isSent", msg.isSent()); record.setValue("isDelivered", msg.isDelivered()); record.setValue("type", int(msg.mediaType())); record.setValue("edited", msg.isEdited()); record.setValue("isSpoiler", msg.isSpoiler()); record.setValue("spoilerHint", msg.spoilerHint()); + record.setValue("mediaUrl", msg.outOfBandUrl()); record.setValue("mediaContentType", msg.mediaContentType()); record.setValue("mediaLocation", msg.mediaLocation()); record.setValue("mediaSize", msg.mediaSize()); record.setValue("mediaLastModified", msg.mediaLastModified().toMSecsSinceEpoch()); QSqlQuery query(db); Utils::execQuery(query, db.driver()->sqlStatement( QSqlDriver::InsertStatement, DB_TABLE_MESSAGES, record, false )); } void MessageDb::removeMessage(const QString &id) { QSqlQuery query(QSqlDatabase::database(DB_CONNECTION)); Utils::execQuery( query, "DELETE FROM Messages WHERE id = ?", QVector() << id ); } void MessageDb::updateMessage(const QString &id, const std::function &updateMsg) { // load current message item from db QSqlDatabase db = QSqlDatabase::database(DB_CONNECTION); QSqlQuery query(db); query.setForwardOnly(true); Utils::execQuery( query, "SELECT * FROM Messages WHERE id = ? LIMIT 1", QVector() << id ); QVector msgs; parseMessagesFromQuery(query, msgs); // update loaded item if (!msgs.isEmpty()) { Message msg = msgs.first(); updateMsg(msg); // replace old message with updated one, if message has changed if (msgs.first() != msg) { // create an SQL record with only the differences QSqlRecord rec = createUpdateRecord(msgs.first(), msg); Utils::execQuery( query, db.driver()->sqlStatement( QSqlDriver::UpdateStatement, DB_TABLE_MESSAGES, rec, false ) + Utils::simpleWhereStatement(db.driver(), "id", id) ); } } } void MessageDb::updateMessageRecord(const QString &id, const QSqlRecord &updateRecord) { QSqlDatabase db = QSqlDatabase::database(DB_CONNECTION); QSqlQuery query(db); Utils::execQuery( query, db.driver()->sqlStatement( QSqlDriver::UpdateStatement, DB_TABLE_MESSAGES, updateRecord, false ) + Utils::simpleWhereStatement(db.driver(), "id", id) ); } void MessageDb::setMessageAsSent(const QString &msgId) { QSqlRecord rec; rec.append(Utils::createSqlField("isSent", true)); updateMessageRecord(msgId, rec); } void MessageDb::setMessageAsDelivered(const QString &msgId) { QSqlRecord rec; rec.append(Utils::createSqlField("isDelivered", true)); updateMessageRecord(msgId, rec); }