[Telepathy-commits] [telepathy-qt4/master] Implement most of Message and ReceivedMessage

Simon McVittie simon.mcvittie at collabora.co.uk
Thu Feb 19 06:37:28 PST 2009


Change Message::size() to return int, consistent with QList.
---
 TelepathyQt4/Client/text-channel.cpp |  439 ++++++++++++++++++++++++++++++++++
 TelepathyQt4/Client/text-channel.h   |   69 +-----
 2 files changed, 453 insertions(+), 55 deletions(-)

diff --git a/TelepathyQt4/Client/text-channel.cpp b/TelepathyQt4/Client/text-channel.cpp
index 9970b8f..def3677 100644
--- a/TelepathyQt4/Client/text-channel.cpp
+++ b/TelepathyQt4/Client/text-channel.cpp
@@ -20,6 +20,8 @@
 
 #include <TelepathyQt4/Client/TextChannel>
 
+#include <QDateTime>
+
 #include "TelepathyQt4/Client/_gen/text-channel.moc.hpp"
 
 #include <TelepathyQt4/Client/PendingReadyChannel>
@@ -31,6 +33,443 @@ namespace Telepathy
 namespace Client
 {
 
+class Message::Private : public QSharedData
+{
+public:
+    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
+    const TextChannel *textChannel;
+    QSharedPointer<Contact> sender;
+
+    inline QVariant value(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;
+};
+
+Message::Private::Private(const MessagePartList &parts)
+    : parts(parts),
+      forceNonText(false),
+      textChannel(0),
+      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 = QString::fromAscii("");
+    }
+    return s;
+}
+
+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;
+}
+
+/**
+ * \class Message
+ * \ingroup clientchannel
+ * \headerfile TelepathyQt4/Client/text-channel.h <TelepathyQt4/Client/TextChannel>
+ *
+ * Object representing a message. These objects are implicitly shared, like
+ * QString.
+ */
+
+/**
+ * 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(QString::fromAscii("message-sent"),
+            QDBusVariant(static_cast<qlonglong>(timestamp)));
+    mPriv->parts[0].insert(QString::fromAscii("message-type"),
+            QDBusVariant(type));
+
+    mPriv->parts[1].insert(QString::fromAscii("content-type"),
+            QDBusVariant(QString::fromAscii("text/plain")));
+    mPriv->parts[1].insert(QString::fromAscii("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;
+}
+
+/**
+ * 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: Telepathy supports 64-bit time_t, but Qt only does so on
+    // ILP64 systems (e.g. sparc64, but not x86_64). If QDateTime
+    // gains a fromTimestamp64 method, we should use it instead.
+    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<uint>(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<QString> texts;
+    QSet<QString> 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<QString> 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);
+}
+
+/**
+ * \class ReceivedMessage
+ * \ingroup clientchannel
+ * \headerfile TelepathyQt4/Client/text-channel.h <TelepathyQt4/Client/TextChannel>
+ *
+ * Subclass of Message, with additional information that's generally only
+ * available on received messages.
+ */
+
+/**
+ * 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 TextChannel *channel, QSharedPointer<Contact> sender)
+    : Message(parts)
+{
+    if (!mPriv->parts[0].contains(QLatin1String("message-received"))) {
+        mPriv->parts[0].insert(QLatin1String("message-received"),
+                QDBusVariant(static_cast<qlonglong>(
+                        QDateTime::currentDateTime().toTime_t())));
+    }
+    mPriv->textChannel = channel;
+    mPriv->sender = sender;
+}
+
+/**
+ * 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: Telepathy supports 64-bit time_t, but Qt only does so on
+    // ILP64 systems (e.g. sparc64, but not x86_64). If QDateTime
+    // gains a fromTimestamp64 method, we should use it instead.
+    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
+ * QSharedPointer<Contact>(0) if unknown.
+ *
+ * \return The sender or QSharedPointer<Contact>(0)
+ */
+QSharedPointer<Contact> 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);
+}
+
 struct TextChannel::Private
 {
     inline Private();
diff --git a/TelepathyQt4/Client/text-channel.h b/TelepathyQt4/Client/text-channel.h
index 1b165f1..bf16119 100644
--- a/TelepathyQt4/Client/text-channel.h
+++ b/TelepathyQt4/Client/text-channel.h
@@ -25,6 +25,8 @@
 #error IN_TELEPATHY_QT4_HEADER
 #endif
 
+#include <QSharedDataPointer>
+
 #include <TelepathyQt4/Client/Channel>
 
 namespace Telepathy
@@ -39,103 +41,60 @@ class Message
 {
 public:
 
-#if 0
-    // shared data, like QString
     Message(const Message &other);
     Message &operator=(const Message &other);
     ~Message();
 
     // Convenient access to headers
 
-    // QDateTime() if unknown (e.g. on Text interface)
-    QDateTime sent() const; // QDateTime() if unknown
+    QDateTime sent() const;
 
-    // always known (the default is ChannelTextMessageTypeNormal)
     ChannelTextMessageType messageType() const;
 
-    // for Text, Channel_Text_Message_Flag_Truncated; for Messages, at least
-    // one part has { 'truncated': True }
     bool isTruncated() const;
 
-    // for Text, Channel_Text_Message_Flag_Non_Text_Content; for Messages,
-    // at least one part has no text/plain alternative
     bool hasNonTextContent() const;
 
-    // QString() if there is no token
     QString messageToken() const;
 
-    // false for most messages. If true, and a client doesn't understand
-    // the particular message, it should not display it (but it should still
-    // acknowledge it, if appropriate)
     bool isSpecificToDBusInterface() const;
-    // QString() if isSpecificToDBusInterface() returns false,
-    // an interface name if it returns true
     QString dbusInterface() const;
 
-    // Basic access to body
-    // For Text: the text
-    // For Messages: all text parts except lower-priority alternatives,
-    // concatenated (see telepathy-glib's TpMessageMixin for a good algorithm)
     QString text() const;
 
     // Direct access to the whole message (header and body)
 
-    inline const QVariantMap &header() const
-    {
-        return messagePart(0);
-    }
+    MessagePart header() const;
 
-    // Part 0 is the header, which has a different interpretation
-    const QVariantMap &messagePart(uint index) const;
-#endif
+    int size() const;
+    MessagePart part(uint index) const;
 
 protected:
     friend class TextChannel;
-#if 0
-    Message(QList<QVariantMap> parts);
-#endif
 
-    struct Private;
-    friend struct Private;
-    QSharedPointer<Private> mPriv;
+    Message(const MessagePartList &parts);
+    Message(uint, uint, const QString &);
+
+    class Private;
+    QSharedDataPointer<Private> mPriv;
 };
 
 class ReceivedMessage : public Message
 {
 public:
-#if 0
-    // shared data, like QString
     ReceivedMessage(const ReceivedMessage &other);
     ReceivedMessage &operator=(const ReceivedMessage &other);
+    ~ReceivedMessage();
 
-    // should always be known - use QDateTime::currentDateTime() while
-    // initially parsing the incoming message, if necessary
     QDateTime received() const;
-
-    // QSharedPointer<Contact>(null) if unknown
     QSharedPointer<Contact> sender() const;
-
-    // The incoming message was part of a replay of message history
-    //
-    // This is indicated so that loggers can use better heuristics when
-    // eliminating duplicates (a simple implementation would be to not log
-    // these messages at all).
     bool isScrollback() const;
-
-    // The incoming message has been seen in a previous channel during the
-    // lifetime of the Connection, but had not been acknowledged when that
-    // channel closed, causing an identical channel (the channel in which
-    // the message now appears) to open
-    //
-    // practical effect: loggers shouldn't log it again
     bool isRescued() const;
-#endif
 
 private:
     friend class TextChannel;
-#if 0
-    ReceivedMessage(QList<QVariantMap> parts, const TextChannel *channel);
-#endif
+    ReceivedMessage(const MessagePartList &parts, const TextChannel *channel,
+            QSharedPointer<Contact> sender);
 };
 
 class TextChannel : public Channel
-- 
1.5.6.5




More information about the telepathy-commits mailing list