/* Message object used by text channel client-side proxy * * Copyright (C) 2009 Collabora Ltd. * Copyright (C) 2009 Nokia Corporation * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include "TelepathyQt4/debug-internal.h" namespace Tp { struct TELEPATHY_QT4_NO_EXPORT Message::Private : public QSharedData { Private(const MessagePartList &parts); ~Private(); MessagePartList parts; // if the Text interface says "non-text" we still only have the text, // because the interface can't tell us anything else... bool forceNonText; // for received messages only QWeakPointer textChannel; ContactPtr sender; inline QVariant value(uint index, const char *key) const; inline uint getUIntOrZero(uint index, const char *key) const; inline QString getStringOrEmpty(uint index, const char *key) const; inline bool getBoolean(uint index, const char *key, bool assumeIfAbsent) const; inline uint senderHandle() const; inline uint pendingId() const; void clearSenderHandle(); }; Message::Private::Private(const MessagePartList &parts) : parts(parts), forceNonText(false), sender(0) { } Message::Private::~Private() { } inline QVariant Message::Private::value(uint index, const char *key) const { return parts.at(index).value(QLatin1String(key)).variant(); } inline QString Message::Private::getStringOrEmpty(uint index, const char *key) const { QString s = value(index, key).toString(); if (s.isNull()) { s = QLatin1String(""); } return s; } inline uint Message::Private::getUIntOrZero(uint index, const char *key) const { return value(index, key).toUInt(); } inline bool Message::Private::getBoolean(uint index, const char *key, bool assumeIfAbsent) const { QVariant v = value(index, key); if (v.isValid() && v.type() == QVariant::Bool) { return v.toBool(); } return assumeIfAbsent; } inline uint Message::Private::senderHandle() const { return getUIntOrZero(0, "message-sender"); } inline uint Message::Private::pendingId() const { return getUIntOrZero(0, "pending-message-id"); } void Message::Private::clearSenderHandle() { parts[0].remove(QLatin1String("message-sender")); } /** * \class Message * \ingroup clientchannel * \headerfile TelepathyQt4/text-channel.h * * \brief The Message class represents a Telepathy message in a text channel. * These objects are implicitly shared, like QString. */ /** * Default constructor, only used internally. */ Message::Message() { } /** * Constructor. * * \param parts The parts of a message as defined by the Telepathy D-Bus * specification. This list must have length at least 1. */ Message::Message(const MessagePartList &parts) : mPriv(new Private(parts)) { Q_ASSERT(parts.size() > 0); } /** * Constructor, from the parameters of the old Sent signal. * * \param timestamp The time the message was sent * \param type The message type * \param text The text of the message */ Message::Message(uint timestamp, uint type, const QString &text) : mPriv(new Private(MessagePartList() << MessagePart() << MessagePart())) { mPriv->parts[0].insert(QLatin1String("message-sent"), QDBusVariant(static_cast(timestamp))); mPriv->parts[0].insert(QLatin1String("message-type"), QDBusVariant(type)); mPriv->parts[1].insert(QLatin1String("content-type"), QDBusVariant(QLatin1String("text/plain"))); mPriv->parts[1].insert(QLatin1String("content"), QDBusVariant(text)); } /** * Constructor, from the parameters of the old Send method. * * \param type The message type * \param text The text of the message */ Message::Message(ChannelTextMessageType type, const QString &text) : mPriv(new Private(MessagePartList() << MessagePart() << MessagePart())) { mPriv->parts[0].insert(QLatin1String("message-type"), QDBusVariant(static_cast(type))); mPriv->parts[1].insert(QLatin1String("content-type"), QDBusVariant(QLatin1String("text/plain"))); mPriv->parts[1].insert(QLatin1String("content"), QDBusVariant(text)); } /** * Copy constructor. */ Message::Message(const Message &other) : mPriv(other.mPriv) { } /** * Assignment operator. */ Message &Message::operator=(const Message &other) { if (this != &other) { mPriv = other.mPriv; } return *this; } /** * Equality operator. */ bool Message::operator==(const Message &other) const { return this->mPriv == other.mPriv; } /** * Class destructor. */ Message::~Message() { } /** * Return the time the message was sent, or QDateTime() if that time is * unknown. * * \return A timestamp */ QDateTime Message::sent() const { // FIXME See http://bugs.freedesktop.org/show_bug.cgi?id=21690 uint stamp = mPriv->value(0, "message-sent").toUInt(); if (stamp != 0) { return QDateTime::fromTime_t(stamp); } else { return QDateTime(); } } /** * Return the type of message this is, or ChannelTextMessageTypeNormal * if the type is not recognised. * * \return The ChannelTextMessageType for this message */ ChannelTextMessageType Message::messageType() const { uint raw = mPriv->value(0, "message-type").toUInt(); if (raw < static_cast(NUM_CHANNEL_TEXT_MESSAGE_TYPES)) { return ChannelTextMessageType(raw); } else { return ChannelTextMessageTypeNormal; } } /** * Return whether this message was truncated during delivery. */ bool Message::isTruncated() const { for (int i = 1; i < size(); i++) { if (mPriv->getBoolean(i, "truncated", false)) { return true; } } return false; } /** * Return whether this message contains parts not representable as plain * text. * * \return true if this message cannot completely be represented as plain text */ bool Message::hasNonTextContent() const { if (mPriv->forceNonText || size() <= 1 || isSpecificToDBusInterface()) { return true; } QSet texts; QSet textNeeded; for (int i = 1; i < size(); i++) { QString altGroup = mPriv->getStringOrEmpty(i, "alternative"); QString contentType = mPriv->getStringOrEmpty(i, "content-type"); if (contentType == QLatin1String("text/plain")) { if (!altGroup.isEmpty()) { // we can use this as an alternative for a non-text part // with the same altGroup texts << altGroup; } } else { QString alt = mPriv->getStringOrEmpty(i, "alternative"); if (altGroup.isEmpty()) { // we can't possibly rescue this part by using a text/plain // alternative, because it's not in any alternative group return true; } else { // maybe we'll find a text/plain alternative for this textNeeded << altGroup; } } } textNeeded -= texts; return !textNeeded.isEmpty(); } /** * Return the unique token identifying this message (e.g. the id attribute * for XMPP messages), or an empty string if there is no suitable token. * * \return A non-empty message identifier, or an empty string if none */ QString Message::messageToken() const { return mPriv->getStringOrEmpty(0, "message-token"); } /** * Return whether this message is specific to a D-Bus interface. This is * false in almost all cases. * * If this function returns true, the message is specific to the interface * indicated by dbusInterface. Clients that don't understand that interface * should not display the message. However, if the client would acknowledge * an ordinary message, it must also acknowledge this interface-specific * message. * * \return true if dbusInterface would return a non-empty string */ bool Message::isSpecificToDBusInterface() const { return !dbusInterface().isEmpty(); } /** * Return the D-Bus interface to which this message is specific, or an * empty string for normal messages. */ QString Message::dbusInterface() const { return mPriv->getStringOrEmpty(0, "interface"); } QString Message::text() const { // Alternative-groups for which we've already emitted an alternative QSet altGroupsUsed; QString text; for (int i = 1; i < size(); i++) { QString altGroup = mPriv->getStringOrEmpty(i, "alternative"); QString contentType = mPriv->getStringOrEmpty(i, "content-type"); if (contentType == QLatin1String("text/plain")) { if (!altGroup.isEmpty()) { if (altGroupsUsed.contains(altGroup)) { continue; } else { altGroupsUsed << altGroup; } } QVariant content = mPriv->value(i, "content"); if (content.type() == QVariant::String) { text += content.toString(); } else { // O RLY? debug() << "allegedly text/plain part wasn't"; } } } return text; } /** * Return the message's header part, as defined by the Telepathy D-Bus API * specification. This is provided for advanced clients that need to access * additional information not available through the normal Message API. * * \return The same thing as messagepart(0) */ MessagePart Message::header() const { return part(0); } /** * Return the number of parts in this message. * * \return 1 greater than the largest valid argument to part */ int Message::size() const { return mPriv->parts.size(); } /** * Return the message's header part, as defined by the Telepathy D-Bus API * specification. This is provided for advanced clients that need to access * additional information not available through the normal Message API. * * \param index The part to access, which must be strictly less than size(); * part number 0 is the header, parts numbered 1 or greater * are the body of the message. * \return Part of the message */ MessagePart Message::part(uint index) const { return mPriv->parts.at(index); } MessagePartList Message::parts() const { return mPriv->parts; } /** * \class ReceivedMessage * \ingroup clientchannel * \headerfile TelepathyQt4/text-channel.h * * \brief The ReceivedMessage class is a subclass of Message, representing a * received message. * * It contains additional information that's generally only * available on received messages. */ /** * Default constructor, only used internally. */ ReceivedMessage::ReceivedMessage() { } /** * Constructor. * * \param parts The parts of a message as defined by the Telepathy D-Bus * specification. This list must have length at least 1. */ ReceivedMessage::ReceivedMessage(const MessagePartList &parts, const TextChannelPtr &channel) : Message(parts) { if (!mPriv->parts[0].contains(QLatin1String("message-received"))) { mPriv->parts[0].insert(QLatin1String("message-received"), QDBusVariant(static_cast( QDateTime::currentDateTime().toTime_t()))); } mPriv->textChannel = QWeakPointer(channel.data()); } /** * Copy constructor. */ ReceivedMessage::ReceivedMessage(const ReceivedMessage &other) : Message(other) { } /** * Assignment operator. */ ReceivedMessage &ReceivedMessage::operator=(const ReceivedMessage &other) { if (this != &other) { mPriv = other.mPriv; } return *this; } /** * Destructor. */ ReceivedMessage::~ReceivedMessage() { } /** * Return the time the message was received. * * \return A timestamp */ QDateTime ReceivedMessage::received() const { // FIXME See http://bugs.freedesktop.org/show_bug.cgi?id=21690 uint stamp = mPriv->value(0, "message-received").toUInt(); if (stamp != 0) { return QDateTime::fromTime_t(stamp); } else { return QDateTime(); } } /** * Return the Contact who sent the message, or * ContactPtr(0) if unknown. * * \return The sender or ContactPtr(0) */ ContactPtr ReceivedMessage::sender() const { return mPriv->sender; } /** * Return whether the incoming message was part of a replay of message * history. * * If true, loggers can use this to improve their heuristics for elimination * of duplicate messages (a simple, correct implementation would be to avoid * logging any message that has this flag). * * \return whether the scrollback flag is set */ bool ReceivedMessage::isScrollback() const { return mPriv->getBoolean(0, "scrollback", false); } /** * Return whether the incoming message was seen in a previous channel during * the lifetime of this Connection, but was not acknowledged before that * chanenl closed, causing the channel in which it now appears to open. * * If true, loggers should not log this message again. * * \return whether the rescued flag is set */ bool ReceivedMessage::isRescued() const { return mPriv->getBoolean(0, "rescued", false); } bool ReceivedMessage::isFromChannel(const TextChannelPtr &channel) const { return TextChannelPtr(mPriv->textChannel) == channel; } uint ReceivedMessage::pendingId() const { return mPriv->pendingId(); } uint ReceivedMessage::senderHandle() const { return mPriv->senderHandle(); } void ReceivedMessage::setForceNonText() { mPriv->forceNonText = true; } void ReceivedMessage::clearSenderHandle() { mPriv->clearSenderHandle(); } void ReceivedMessage::setSender(const ContactPtr &sender) { mPriv->sender = sender; } } // Tp