~ci-train-bot/history-service/history-service-ubuntu-zesty-2629

« back to all changes in this revision

Viewing changes to daemon/historydaemon.cpp

  • Committer: Bileto Bot
  • Author(s): Gustavo Pichorim Boiko
  • Date: 2016-11-30 15:13:58 UTC
  • mfrom: (230.2.23 staging)
  • Revision ID: ci-train-bot@canonical.com-20161130151358-jy3mqj0ir0b6ncxe
Improve group chat support.

Approved by: Roberto Mier Escandón , Tiago Salem Herrmann

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
/*
2
 
 * Copyright (C) 2013-2015 Canonical, Ltd.
 
2
 * Copyright (C) 2013-2016 Canonical, Ltd.
3
3
 *
4
4
 * Authors:
5
5
 *  Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>
23
23
#include "telepathyhelper_p.h"
24
24
#include "filter.h"
25
25
#include "sort.h"
 
26
#include "utils_p.h"
26
27
 
27
28
#include "pluginmanager.h"
28
29
#include "plugin.h"
29
30
#include "pluginthreadview.h"
30
31
#include "plugineventview.h"
 
32
#include "textevent.h"
31
33
 
32
34
#include <QStandardPaths>
33
35
#include <QCryptographicHash>
34
36
#include <TelepathyQt/CallChannel>
 
37
#include <TelepathyQt/PendingVariantMap>
35
38
#include <TelepathyQt/ReferencedHandles>
36
39
 
 
40
#include <TelepathyQt/PendingVariant>
 
41
#include <TelepathyQt/PendingOperation>
 
42
 
 
43
Q_DECLARE_METATYPE(RolesMap)
 
44
 
 
45
const constexpr static int AdminRole = 2;
 
46
 
 
47
enum ChannelGroupChangeReason
 
48
{
 
49
    ChannelGroupChangeReasonNone = 0,
 
50
    ChannelGroupChangeReasonOffline = 1,
 
51
    ChannelGroupChangeReasonKicked = 2,
 
52
    ChannelGroupChangeReasonBusy = 3,
 
53
    ChannelGroupChangeReasonInvited = 4,
 
54
    ChannelGroupChangeReasonBanned = 5,
 
55
    ChannelGroupChangeReasonError = 6,
 
56
    ChannelGroupChangeReasonInvalidContact = 7,
 
57
    ChannelGroupChangeReasonNoAnswer = 8,
 
58
    ChannelGroupChangeReasonRenamed = 9,
 
59
    ChannelGroupChangeReasonPermissionDenied = 10,
 
60
    ChannelGroupChangeReasonSeparated = 11,
 
61
 
 
62
    // additional enum values not included in original ChannelGroupChangeReason
 
63
    // telepathy enumeration but needed here to provide extra info to client when group
 
64
    // is cancelled
 
65
    ChannelGroupChangeReasonGone = 12,
 
66
    ChannelGroupChangeReasonRejected = 13
 
67
};
 
68
 
 
69
const QDBusArgument &operator>>(const QDBusArgument &argument, RolesMap &roles)
 
70
{
 
71
    argument.beginMap();
 
72
    while ( !argument.atEnd() ) {
 
73
        argument.beginMapEntry();
 
74
        uint key,value;
 
75
        argument >> key >> value;
 
76
        argument.endMapEntry();
 
77
        roles[key] = value;
 
78
    }
 
79
 
 
80
    argument.endMap();
 
81
    return argument;
 
82
}
 
83
 
 
84
bool foundAsMemberInThread(const Tp::ContactPtr& contact, QVariantMap thread)
 
85
{
 
86
    Q_FOREACH (QVariant participant, thread[History::FieldParticipants].toList()) {
 
87
        // found if same identifier and as member into thread info
 
88
        if (History::Utils::compareIds(thread[History::FieldAccountId].toString(),
 
89
                                       contact->id(),
 
90
                                       participant.toMap()[History::FieldIdentifier].toString()) &&
 
91
                participant.toMap()[History::FieldParticipantState].toUInt() == History::ParticipantStateRegular)
 
92
        {
 
93
            return true;
 
94
        }
 
95
    }
 
96
    return false;
 
97
}
 
98
 
 
99
bool foundInThread(const Tp::ContactPtr& contact, QVariantMap thread)
 
100
{
 
101
    Q_FOREACH (QVariant participant, thread[History::FieldParticipants].toList()) {
 
102
        if (History::Utils::compareIds(thread[History::FieldAccountId].toString(),
 
103
                                       contact->id(),
 
104
                                       participant.toMap()[History::FieldIdentifier].toString()))
 
105
        {
 
106
            return true;
 
107
        }
 
108
    }
 
109
    return false;
 
110
}
 
111
 
37
112
HistoryDaemon::HistoryDaemon(QObject *parent)
38
113
    : QObject(parent), mCallObserver(this), mTextObserver(this)
39
114
{
 
115
    qRegisterMetaType<HandleRolesMap>();
 
116
    qDBusRegisterMetaType<HandleRolesMap>();
40
117
    // get the first plugin
41
118
    if (!History::PluginManager::instance()->plugins().isEmpty()) {
42
119
        mBackend = History::PluginManager::instance()->plugins().first();
65
142
    connect(&mTextObserver,
66
143
            SIGNAL(messageRead(Tp::TextChannelPtr,Tp::ReceivedMessage)),
67
144
            SLOT(onMessageRead(Tp::TextChannelPtr,Tp::ReceivedMessage)));
 
145
    connect(&mTextObserver,
 
146
            SIGNAL(channelAvailable(Tp::TextChannelPtr)),
 
147
            SLOT(onTextChannelAvailable(Tp::TextChannelPtr)));
68
148
 
69
149
    // FIXME: we need to do this in a better way, but for now this should do
70
150
    mProtocolFlags["ofono"] = History::MatchPhoneNumber;
81
161
    return self;
82
162
}
83
163
 
84
 
QStringList HistoryDaemon::participantsFromChannel(const Tp::TextChannelPtr &textChannel)
85
 
{
86
 
    QStringList participants;
 
164
void HistoryDaemon::onRolesChanged(const HandleRolesMap &added, const HandleRolesMap &removed)
 
165
{
 
166
    Q_UNUSED(added);
 
167
    Q_UNUSED(removed);
 
168
 
 
169
    ChannelInterfaceRolesInterface *roles_interface = qobject_cast<ChannelInterfaceRolesInterface*>(sender());
 
170
    RolesMap roles = roles_interface->getRoles();
 
171
 
 
172
    Tp::TextChannelPtr channel(qobject_cast<Tp::TextChannel*>(sender()->parent()));
 
173
    QVariantMap properties = propertiesFromChannel(channel);
 
174
    QVariantMap thread = threadForProperties(channel->property(History::FieldAccountId).toString(),
 
175
                                             History::EventTypeText,
 
176
                                             properties,
 
177
                                             matchFlagsForChannel(channel),
 
178
                                             false);
 
179
 
 
180
    writeRolesInformationEvents(thread, channel, roles);
 
181
 
 
182
    updateRoomRoles(channel, roles);
 
183
}
 
184
 
 
185
QVariantMap HistoryDaemon::propertiesFromChannel(const Tp::ChannelPtr &textChannel)
 
186
{
 
187
    QVariantMap properties;
 
188
    QVariantList participants;
 
189
    QStringList participantIds;
 
190
 
 
191
    ChannelInterfaceRolesInterface *roles_interface = textChannel->optionalInterface<ChannelInterfaceRolesInterface>();
 
192
    RolesMap roles;
 
193
    if (roles_interface) {
 
194
        roles = roles_interface->getRoles();
 
195
    }
 
196
 
87
197
    Q_FOREACH(const Tp::ContactPtr contact, textChannel->groupContacts(false)) {
88
 
        participants << contact->id();
 
198
        QVariantMap contactProperties;
 
199
        contactProperties[History::FieldAlias] = contact->alias();
 
200
        contactProperties[History::FieldAccountId] = textChannel->property(History::FieldAccountId).toString();
 
201
        contactProperties[History::FieldIdentifier] = contact->id();
 
202
        contactProperties[History::FieldParticipantState] = History::ParticipantStateRegular;
 
203
        contactProperties[History::FieldParticipantRoles] = roles[contact->handle().at(0)];
 
204
        participantIds << contact->id();
 
205
        participants << contactProperties;
 
206
    }
 
207
 
 
208
    Q_FOREACH(const Tp::ContactPtr contact, textChannel->groupRemotePendingContacts(false)) {
 
209
        QVariantMap contactProperties;
 
210
        contactProperties[History::FieldAlias] = contact->alias();
 
211
        contactProperties[History::FieldAccountId] = textChannel->property(History::FieldAccountId).toString();
 
212
        contactProperties[History::FieldIdentifier] = contact->id();
 
213
        contactProperties[History::FieldParticipantState] = History::ParticipantStateRemotePending;
 
214
        contactProperties[History::FieldParticipantRoles] = roles[contact->handle().at(0)];
 
215
        participantIds << contact->id();
 
216
        participants << contactProperties;
 
217
    }
 
218
 
 
219
    Q_FOREACH(const Tp::ContactPtr contact, textChannel->groupLocalPendingContacts(false)) {
 
220
        QVariantMap contactProperties;
 
221
        contactProperties[History::FieldAlias] = contact->alias();
 
222
        contactProperties[History::FieldAccountId] = textChannel->property(History::FieldAccountId).toString();
 
223
        contactProperties[History::FieldIdentifier] = contact->id();
 
224
        contactProperties[History::FieldParticipantState] = History::ParticipantStateLocalPending;
 
225
        contactProperties[History::FieldParticipantRoles] = roles[contact->handle().at(0)];
 
226
        participantIds << contact->id();
 
227
        participants << contactProperties;
89
228
    }
90
229
 
91
230
    if (participants.isEmpty() && textChannel->targetHandleType() == Tp::HandleTypeContact &&
92
 
           textChannel->targetContact() == textChannel->connection()->selfContact()) {
93
 
        participants << textChannel->targetContact()->id();
94
 
    }
95
 
    return participants;
 
231
            textChannel->targetContact() == textChannel->connection()->selfContact()) {
 
232
        QVariantMap contactProperties;
 
233
        contactProperties[History::FieldAlias] = textChannel->targetContact()->alias();
 
234
        contactProperties[History::FieldAccountId] = textChannel->property(History::FieldAccountId).toString();
 
235
        contactProperties[History::FieldIdentifier] = textChannel->targetContact()->id();
 
236
        contactProperties[History::FieldParticipantState] = History::ParticipantStateRegular;
 
237
        participantIds << textChannel->targetContact()->id();
 
238
        participants << contactProperties;
 
239
    }
 
240
 
 
241
    // We map chatType directly from telepathy targetHandleType: None, Contact, Room
 
242
    properties[History::FieldChatType] = textChannel->targetHandleType();
 
243
    properties[History::FieldParticipants] = participants;
 
244
    properties[History::FieldParticipantIds] = participantIds;
 
245
 
 
246
    QVariantMap roomProperties;
 
247
    switch(textChannel->targetHandleType()) {
 
248
    case Tp::HandleTypeRoom:
 
249
        if (textChannel->hasInterface(TP_QT_IFACE_CHANNEL_INTERFACE_ROOM)) {
 
250
            auto room_interface = textChannel->optionalInterface<Tp::Client::ChannelInterfaceRoomInterface>();
 
251
            QVariantMap map = getInterfaceProperties(room_interface);
 
252
            for (QVariantMap::const_iterator iter = map.begin(); iter != map.end(); ++iter) {
 
253
                if (iter.value().isValid()) {
 
254
                    roomProperties[iter.key()] = iter.value();
 
255
                }
 
256
            }
 
257
        }
 
258
        if (textChannel->hasInterface(TP_QT_IFACE_CHANNEL_INTERFACE_ROOM_CONFIG)) {
 
259
            auto room_config_interface = textChannel->optionalInterface<Tp::Client::ChannelInterfaceRoomConfigInterface>();
 
260
            QVariantMap map = getInterfaceProperties(room_config_interface);
 
261
            for (QVariantMap::const_iterator iter = map.begin(); iter != map.end(); ++iter) {
 
262
                if (iter.value().isValid()) {
 
263
                    roomProperties[iter.key()] = iter.value();
 
264
                }
 
265
            }
 
266
        }
 
267
        if (textChannel->hasInterface(TP_QT_IFACE_CHANNEL_INTERFACE_SUBJECT)) {
 
268
            auto subject_interface = textChannel->optionalInterface<Tp::Client::ChannelInterfaceSubjectInterface>();
 
269
            QVariantMap map = getInterfaceProperties(subject_interface);
 
270
            for (QVariantMap::const_iterator iter = map.begin(); iter != map.end(); ++iter) {
 
271
                if (iter.value().isValid()) {
 
272
                    roomProperties[iter.key()] = iter.value();
 
273
                }
 
274
            }
 
275
        }
 
276
 
 
277
        properties[History::FieldChatRoomInfo] = roomProperties;
 
278
        properties[History::FieldThreadId] = textChannel->targetId();
 
279
        break;
 
280
    case Tp::HandleTypeContact:
 
281
    case Tp::HandleTypeNone:
 
282
    default:
 
283
        break; 
 
284
    }
 
285
 
 
286
    return properties;
96
287
}
97
288
 
98
 
QVariantMap HistoryDaemon::threadForParticipants(const QString &accountId,
99
 
                                                 History::EventType type,
100
 
                                                 const QStringList &participants,
101
 
                                                 History::MatchFlags matchFlags,
102
 
                                                 bool create)
 
289
QVariantMap HistoryDaemon::threadForProperties(const QString &accountId,
 
290
                                               History::EventType type,
 
291
                                               const QVariantMap &properties,
 
292
                                               History::MatchFlags matchFlags,
 
293
                                               bool create)
103
294
{
104
295
    if (!mBackend) {
105
296
        return QVariantMap();
106
297
    }
107
298
 
108
 
    QVariantMap thread = mBackend->threadForParticipants(accountId,
109
 
                                                         type,
110
 
                                                         participants,
111
 
                                                         matchFlags);
 
299
    QVariantMap thread = mBackend->threadForProperties(accountId,
 
300
                                                       type,
 
301
                                                       properties,
 
302
                                                       matchFlags);
112
303
    if (thread.isEmpty() && create) {
113
 
        thread = mBackend->createThreadForParticipants(accountId, type, participants);
 
304
        thread = mBackend->createThreadForProperties(accountId, type, properties);
114
305
        if (!thread.isEmpty()) {
 
306
            if (properties.contains("Requested") && properties[History::FieldChatType].toInt() == History::ChatTypeRoom) {
 
307
                QVariantMap map = thread[History::FieldChatRoomInfo].toMap();
 
308
                map["Requested"] = properties["Requested"];
 
309
                thread[History::FieldChatRoomInfo] = map;
 
310
            }
115
311
            mDBus.notifyThreadsAdded(QList<QVariantMap>() << thread);
116
312
        }
117
313
    }
174
370
    return mBackend->getSingleEvent((History::EventType)type, accountId, threadId, eventId);
175
371
}
176
372
 
177
 
bool HistoryDaemon::writeEvents(const QList<QVariantMap> &events)
 
373
bool HistoryDaemon::writeEvents(const QList<QVariantMap> &events, const QVariantMap &properties)
178
374
{
179
375
    if (!mBackend) {
180
376
        return false;
206
402
        }
207
403
 
208
404
        // only get the thread AFTER the event is written to make sure it is up-to-date
209
 
        QVariantMap thread = getSingleThread(type, accountId, threadId, QVariantMap());
 
405
        QVariantMap thread = getSingleThread(type, accountId, threadId, properties);
210
406
        QString hash = hashThread(thread);
211
407
        threads[hash] = thread;
212
408
 
245
441
 
246
442
bool HistoryDaemon::removeEvents(const QList<QVariantMap> &events)
247
443
{
248
 
    qDebug() << __PRETTY_FUNCTION__;
249
444
    if (!mBackend) {
250
445
        return false;
251
446
    }
319
514
 
320
515
bool HistoryDaemon::removeThreads(const QList<QVariantMap> &threads)
321
516
{
322
 
    qDebug() << __PRETTY_FUNCTION__;
323
517
    if (!mBackend) {
324
518
        return false;
325
519
    }
360
554
 
361
555
void HistoryDaemon::onObserverCreated()
362
556
{
363
 
    qDebug() << __PRETTY_FUNCTION__;
364
557
    History::ChannelObserver *observer = History::TelepathyHelper::instance()->channelObserver();
365
558
 
366
559
    connect(observer, SIGNAL(callChannelAvailable(Tp::CallChannelPtr)),
371
564
 
372
565
void HistoryDaemon::onCallEnded(const Tp::CallChannelPtr &channel)
373
566
{
374
 
    qDebug() << __PRETTY_FUNCTION__;
375
 
    QStringList participants;
 
567
    QVariantMap properties = propertiesFromChannel(channel);
 
568
    QVariantList participants;
376
569
    Q_FOREACH(const Tp::ContactPtr contact, channel->remoteMembers()) {
377
 
        participants << contact->id();
 
570
        QVariantMap contactProperties;
 
571
        contactProperties[History::FieldAlias] = contact->alias();
 
572
        contactProperties[History::FieldIdentifier] = contact->id();
 
573
        contactProperties[History::FieldAccountId] = channel->property(History::FieldAccountId).toString();
 
574
        participants << contactProperties;
378
575
    }
379
576
 
380
577
    // it shouldn't happen, but in case it does, we won't crash
384
581
    }
385
582
 
386
583
    QString accountId = channel->property(History::FieldAccountId).toString();
387
 
    QVariantMap thread = threadForParticipants(accountId,
388
 
                                               History::EventTypeVoice,
389
 
                                               participants,
390
 
                                               matchFlagsForChannel(channel),
391
 
                                               true);
 
584
    QVariantMap thread = threadForProperties(accountId,
 
585
                                             History::EventTypeVoice,
 
586
                                             properties,
 
587
                                             matchFlagsForChannel(channel),
 
588
                                             true);
392
589
    // fill the call info
393
590
    QDateTime timestamp = channel->property(History::FieldTimestamp).toDateTime();
394
591
 
414
611
    event[History::FieldMissed] = missed;
415
612
    event[History::FieldDuration] = duration;
416
613
    // FIXME: check what to do when there are more than just one remote participant
417
 
    event[History::FieldRemoteParticipant] = participants[0];
418
 
    writeEvents(QList<QVariantMap>() << event);
 
614
    event[History::FieldRemoteParticipant] = participants[0].toMap()[History::FieldIdentifier];
 
615
    writeEvents(QList<QVariantMap>() << event, properties);
 
616
}
 
617
 
 
618
void HistoryDaemon::onTextChannelAvailable(const Tp::TextChannelPtr channel)
 
619
{
 
620
    // for Rooms we need to explicitly create the thread to allow users to send messages to groups even
 
621
    // before they receive any message.
 
622
    // for other types, we can wait until messages are received
 
623
    if (channel->targetHandleType() == Tp::HandleTypeRoom) {
 
624
        QString accountId = channel->property(History::FieldAccountId).toString();
 
625
        QVariantMap properties = propertiesFromChannel(channel);
 
626
 
 
627
        // first try to fetch the existing thread to see if there is any.
 
628
        QVariantMap thread = threadForProperties(accountId,
 
629
                                                 History::EventTypeText,
 
630
                                                 properties,
 
631
                                                 matchFlagsForChannel(channel),
 
632
                                                 false);
 
633
        if (thread.isEmpty()) {
 
634
            // if there no existing thread, create one
 
635
            properties["Requested"] = channel->isRequested();
 
636
            thread = threadForProperties(accountId,
 
637
                                         History::EventTypeText,
 
638
                                         properties,
 
639
                                         matchFlagsForChannel(channel),
 
640
                                         true);
 
641
 
 
642
            // write information event including all initial invitees
 
643
            Q_FOREACH(const Tp::ContactPtr contact, channel->groupRemotePendingContacts(false)) {
 
644
                writeInformationEvent(thread, History::InformationTypeInvitationSent, contact->alias());
 
645
            }
 
646
 
 
647
            // update participants only if the thread is not available previously. Otherwise we'll wait for membersChanged event
 
648
            // for reflect in conversation information events for modified participants.
 
649
            updateRoomParticipants(channel);
 
650
        }
 
651
 
 
652
        // write an entry saying you joined the group if 'joined' flag in thread is false and modify that flag.
 
653
        if (!thread[History::FieldChatRoomInfo].toMap()["Joined"].toBool()) {
 
654
            // only write self joined notification if protocol is not a phone one.
 
655
            // FIXME (rmescandon): as a first solution, let's take only ofono as phone protocol
 
656
            if (History::TelepathyHelper::instance()->accountForId(accountId)->protocolName() != "ofono") {
 
657
                writeInformationEvent(thread, History::InformationTypeSelfJoined);
 
658
            }
 
659
            // update backend
 
660
            updateRoomProperties(channel, QVariantMap{{"Joined", true}});
 
661
        }
 
662
 
 
663
        Tp::AbstractInterface *room_interface = channel->optionalInterface<Tp::Client::ChannelInterfaceRoomInterface>();
 
664
        Tp::AbstractInterface *room_config_interface = channel->optionalInterface<Tp::Client::ChannelInterfaceRoomConfigInterface>();
 
665
        Tp::AbstractInterface *subject_interface = channel->optionalInterface<Tp::Client::ChannelInterfaceSubjectInterface>();
 
666
        ChannelInterfaceRolesInterface *roles_interface = channel->optionalInterface<ChannelInterfaceRolesInterface>();
 
667
 
 
668
        QList<Tp::AbstractInterface*> interfaces;
 
669
        interfaces << room_interface << room_config_interface << subject_interface << roles_interface;
 
670
        for (auto interface : interfaces) {
 
671
            if (interface) {
 
672
                interface->setMonitorProperties(true);
 
673
                interface->setProperty(History::FieldAccountId, accountId);
 
674
                interface->setProperty(History::FieldThreadId, thread[History::FieldThreadId].toString());
 
675
                interface->setProperty(History::FieldType, thread[History::FieldType].toInt());
 
676
                connect(interface, SIGNAL(propertiesChanged(const QVariantMap &,const QStringList &)),
 
677
                                   SLOT(onRoomPropertiesChanged(const QVariantMap &,const QStringList &)));
 
678
                // update the stored info
 
679
                Q_EMIT interface->propertiesChanged(getInterfaceProperties(interface), QStringList());
 
680
            }
 
681
        }
 
682
 
 
683
        connect(channel.data(), SIGNAL(groupMembersChanged(const Tp::Contacts &, const Tp::Contacts &, const Tp::Contacts &, const Tp::Contacts &, const Tp::Channel::GroupMemberChangeDetails &)),
 
684
                SLOT(onGroupMembersChanged(const Tp::Contacts &, const Tp::Contacts &, const Tp::Contacts &, const Tp::Contacts &, const Tp::Channel::GroupMemberChangeDetails &)));
 
685
 
 
686
        connect(roles_interface, SIGNAL(RolesChanged(const HandleRolesMap&, const HandleRolesMap&)), SLOT(onRolesChanged(const HandleRolesMap&, const HandleRolesMap&)));
 
687
    }
 
688
}
 
689
 
 
690
void HistoryDaemon::onGroupMembersChanged(const Tp::Contacts &groupMembersAdded,
 
691
                                          const Tp::Contacts &groupLocalPendingMembersAdded,
 
692
                                          const Tp::Contacts &groupRemotePendingMembersAdded,
 
693
                                          const Tp::Contacts &groupMembersRemoved,
 
694
                                          const Tp::Channel::GroupMemberChangeDetails &details)
 
695
{
 
696
    Tp::TextChannelPtr channel(qobject_cast<Tp::TextChannel*>(sender()));
 
697
 
 
698
    QVariantMap properties;
 
699
    QVariantMap thread;
 
700
 
 
701
    // information events for members updates.
 
702
    bool hasRemotePendingMembersAdded = groupRemotePendingMembersAdded.size() > 0;
 
703
    bool hasMembersAdded = groupMembersAdded.size() > 0;
 
704
    bool hasMembersRemoved = groupMembersRemoved.size() > 0;
 
705
 
 
706
    if (hasRemotePendingMembersAdded || hasMembersAdded || hasMembersRemoved) {
 
707
        properties = propertiesFromChannel(channel);
 
708
        thread = threadForProperties(channel->property(History::FieldAccountId).toString(),
 
709
                                                       History::EventTypeText,
 
710
                                                       properties,
 
711
                                                       matchFlagsForChannel(channel),
 
712
                                                       false);
 
713
        if (!thread.isEmpty()) {
 
714
            if (hasRemotePendingMembersAdded) {
 
715
                Q_FOREACH (const Tp::ContactPtr& contact, groupRemotePendingMembersAdded) {
 
716
                    if (!foundInThread(contact, thread)) {
 
717
                        writeInformationEvent(thread, History::InformationTypeInvitationSent, contact->alias());
 
718
                    }
 
719
                }
 
720
            }
 
721
            if (hasMembersAdded) {
 
722
                Q_FOREACH (const Tp::ContactPtr& contact, groupMembersAdded) {
 
723
                    // if this member was not previously regular member in thread, notify about his join
 
724
                    if (!foundAsMemberInThread(contact, thread)) {
 
725
                        writeInformationEvent(thread, History::InformationTypeJoined, contact->alias());
 
726
                    }
 
727
                }
 
728
            }
 
729
 
 
730
            if (hasMembersRemoved) {
 
731
                if (channel->groupSelfContactRemoveInfo().isValid()) {
 
732
                    // evaluate if we are leaving by our own or we are kicked
 
733
                    History::InformationType type = History::InformationTypeSelfLeaving;
 
734
                    if (channel->groupSelfContactRemoveInfo().hasReason()) {
 
735
                        switch (channel->groupSelfContactRemoveInfo().reason()) {
 
736
                        case ChannelGroupChangeReasonKicked:
 
737
                            type = History::InformationTypeSelfKicked;
 
738
                            break;
 
739
                        case ChannelGroupChangeReasonGone:
 
740
                            type = History::InformationTypeGroupGone;
 
741
                            break;
 
742
                        }
 
743
                    }
 
744
                    writeInformationEvent(thread, type);
 
745
                    // update backend
 
746
                    updateRoomProperties(channel, QVariantMap{{"Joined", false}});
 
747
                }
 
748
                else // don't notify any other group member removal if we are leaving the group
 
749
                {
 
750
                    Q_FOREACH (const Tp::ContactPtr& contact, groupMembersRemoved) {
 
751
                        // inform about removed members other than us
 
752
                        if (contact->id() != channel->groupSelfContact()->id()) {
 
753
                            writeInformationEvent(thread, History::InformationTypeLeaving, contact->alias());
 
754
                        }
 
755
                    }
 
756
                }
 
757
            }
 
758
        }
 
759
    }
 
760
 
 
761
    updateRoomParticipants(channel);
 
762
}
 
763
 
 
764
void HistoryDaemon::updateRoomParticipants(const Tp::TextChannelPtr channel)
 
765
{
 
766
    if (!channel) {
 
767
        return;
 
768
    }
 
769
 
 
770
    QVariantList participants;
 
771
    QStringList contactsAdded;
 
772
 
 
773
    ChannelInterfaceRolesInterface *roles_interface = channel->optionalInterface<ChannelInterfaceRolesInterface>();
 
774
    RolesMap roles;
 
775
    if (roles_interface) {
 
776
        roles = roles_interface->getRoles();
 
777
    }
 
778
 
 
779
    Q_FOREACH(const Tp::ContactPtr contact, channel->groupRemotePendingContacts(false)) {
 
780
        QVariantMap participant;
 
781
        contactsAdded << contact->id();
 
782
        participant[History::FieldIdentifier] = contact->id();
 
783
        participant[History::FieldAlias] = contact->alias();
 
784
        participant[History::FieldParticipantState] = History::ParticipantStateRemotePending;
 
785
        participant[History::FieldParticipantRoles] = roles[contact->handle().at(0)];
 
786
        participants << QVariant::fromValue(participant);
 
787
    }
 
788
    Q_FOREACH(const Tp::ContactPtr contact, channel->groupLocalPendingContacts(false)) {
 
789
        QVariantMap participant;
 
790
        contactsAdded << contact->id();
 
791
        participant[History::FieldIdentifier] = contact->id();
 
792
        participant[History::FieldAlias] = contact->alias();
 
793
        participant[History::FieldParticipantState] = History::ParticipantStateLocalPending;
 
794
        participant[History::FieldParticipantRoles] = roles[contact->handle().at(0)];
 
795
        participants << QVariant::fromValue(participant);
 
796
    }
 
797
 
 
798
    Q_FOREACH(const Tp::ContactPtr contact, channel->groupContacts(false)) {
 
799
        // do not include remote and local pending members
 
800
        if (contactsAdded.contains(contact->id())) {
 
801
            continue;
 
802
        }
 
803
        QVariantMap participant;
 
804
        participant[History::FieldIdentifier] = contact->id();
 
805
        participant[History::FieldAlias] = contact->alias();
 
806
        participant[History::FieldParticipantState] = History::ParticipantStateRegular;
 
807
        participant[History::FieldParticipantRoles] = roles[contact->handle().at(0)];
 
808
        participants << QVariant::fromValue(participant);
 
809
    }
 
810
 
 
811
    QString accountId = channel->property(History::FieldAccountId).toString();
 
812
    QString threadId = channel->targetId();
 
813
    if (mBackend->updateRoomParticipants(accountId, threadId, History::EventTypeText, participants)) {
 
814
        QVariantMap updatedThread = getSingleThread(History::EventTypeText, accountId, threadId, QVariantMap());
 
815
        mDBus.notifyThreadsModified(QList<QVariantMap>() << updatedThread);
 
816
    }
 
817
}
 
818
 
 
819
void HistoryDaemon::updateRoomRoles(const Tp::TextChannelPtr &channel, const RolesMap &rolesMap)
 
820
{
 
821
    if (!channel) {
 
822
        return;
 
823
    }
 
824
 
 
825
    QVariantMap participantsRoles;
 
826
 
 
827
    Q_FOREACH(const Tp::ContactPtr contact, channel->groupRemotePendingContacts(false)) {
 
828
        participantsRoles[contact->id()] = rolesMap[contact->handle().at(0)];
 
829
    }
 
830
    Q_FOREACH(const Tp::ContactPtr contact, channel->groupLocalPendingContacts(false)) {
 
831
        participantsRoles[contact->id()] = rolesMap[contact->handle().at(0)];
 
832
    }
 
833
 
 
834
    Q_FOREACH(const Tp::ContactPtr contact, channel->groupContacts(false)) {
 
835
        if (!participantsRoles.contains(contact->id())) {
 
836
            participantsRoles[contact->id()] = rolesMap[contact->handle().at(0)];
 
837
        }
 
838
    }
 
839
 
 
840
    // update participants roles
 
841
    QString accountId = channel->property(History::FieldAccountId).toString();
 
842
    QString threadId = channel->targetId();
 
843
    if (mBackend->updateRoomParticipantsRoles(accountId, threadId, History::EventTypeText, participantsRoles)) {
 
844
        QVariantMap updatedThread = getSingleThread(History::EventTypeText, accountId, threadId, QVariantMap());
 
845
        mDBus.notifyThreadsModified(QList<QVariantMap>() << updatedThread);
 
846
    }
 
847
 
 
848
    // update self roles in room properties
 
849
    uint selfRoles = rolesMap[channel->groupSelfContact()->handle().at(0)];
 
850
    updateRoomProperties(channel, QVariantMap{{"SelfRoles", selfRoles}});
 
851
}
 
852
 
 
853
void HistoryDaemon::onRoomPropertiesChanged(const QVariantMap &properties,const QStringList &invalidated)
 
854
{
 
855
    QString accountId = sender()->property(History::FieldAccountId).toString();
 
856
    QString threadId = sender()->property(History::FieldThreadId).toString();
 
857
    History::EventType type = (History::EventType)sender()->property(History::FieldType).toInt();
 
858
 
 
859
    // get thread before updating to see if there are changes to insert as information events
 
860
    QVariantMap thread = getSingleThread(type, accountId, threadId, QVariantMap());
 
861
    if (!thread.empty()) {
 
862
        writeRoomChangesInformationEvents(thread, properties);
 
863
    }
 
864
 
 
865
    updateRoomProperties(accountId, threadId, type, properties, invalidated);
 
866
}
 
867
 
 
868
void HistoryDaemon::updateRoomProperties(const Tp::TextChannelPtr &channel, const QVariantMap &properties)
 
869
{
 
870
    QString accountId = channel->property(History::FieldAccountId).toString();
 
871
    QString threadId = channel->targetId();
 
872
    History::EventType type = History::EventTypeText;
 
873
    updateRoomProperties(accountId, threadId, type, properties, QStringList());
 
874
}
 
875
 
 
876
void HistoryDaemon::updateRoomProperties(const QString &accountId, const QString &threadId, History::EventType type, const QVariantMap &properties, const QStringList &invalidated)
 
877
{
 
878
    if (mBackend->updateRoomInfo(accountId, threadId, type, properties, invalidated)) {
 
879
        QVariantMap thread = getSingleThread(type, accountId, threadId, QVariantMap());
 
880
        mDBus.notifyThreadsModified(QList<QVariantMap>() << thread);
 
881
    }
419
882
}
420
883
 
421
884
void HistoryDaemon::onMessageReceived(const Tp::TextChannelPtr textChannel, const Tp::ReceivedMessage &message)
422
885
{
423
 
    qDebug() << __PRETTY_FUNCTION__;
424
886
    QString eventId;
425
887
    Tp::MessagePart header = message.header();
426
888
    QString senderId;
 
889
    QVariantMap properties = propertiesFromChannel(textChannel);
427
890
    History::MessageStatus status = History::MessageStatusUnknown;
428
 
    if (message.sender()->handle().at(0) == textChannel->connection()->selfHandle()) {
 
891
    if (!message.sender() || message.sender()->handle().at(0) == textChannel->connection()->selfHandle()) {
429
892
        senderId = "self";
430
893
        status = History::MessageStatusDelivered;
431
894
    } else {
452
915
            return;
453
916
        }
454
917
 
 
918
        // FIXME: if this message is already read, don't allow reverting the status.
 
919
        // we need to check if this is the right place to do it.
 
920
        if (textEvent[History::FieldMessageStatus].toInt() == History::MessageStatusRead) {
 
921
            qWarning() << "Skipping delivery report as it is trying to revert the Read status of an existing message to the following status:" << message.deliveryDetails().status();
 
922
            return;
 
923
        }
 
924
 
455
925
        History::MessageStatus status;
456
926
        switch (message.deliveryDetails().status()) {
457
927
        case Tp::DeliveryStatusAccepted:
478
948
        }
479
949
 
480
950
        textEvent[History::FieldMessageStatus] = (int) status;
481
 
        if (!writeEvents(QList<QVariantMap>() << textEvent)) {
 
951
        if (!writeEvents(QList<QVariantMap>() << textEvent, properties)) {
482
952
            qWarning() << "Failed to save the new message status!";
483
953
        }
484
954
 
485
955
        return;
486
956
    }
487
957
 
488
 
    QStringList participants = participantsFromChannel(textChannel);
489
 
 
490
 
    QVariantMap thread = threadForParticipants(textChannel->property(History::FieldAccountId).toString(),
491
 
                                                                     History::EventTypeText,
492
 
                                                                     participants,
493
 
                                                                     matchFlagsForChannel(textChannel),
494
 
                                                                     true);
 
958
    QVariantMap thread = threadForProperties(textChannel->property(History::FieldAccountId).toString(),
 
959
                                                                   History::EventTypeText,
 
960
                                                                   properties,
 
961
                                                                   matchFlagsForChannel(textChannel),
 
962
                                                                   true);
495
963
    int count = 1;
496
964
    QList<QVariantMap> attachments;
497
965
    History::MessageType type = History::MessageTypeText;
558
1026
    event[History::FieldSubject] = subject;
559
1027
    event[History::FieldAttachments] = QVariant::fromValue(attachments);
560
1028
 
561
 
    writeEvents(QList<QVariantMap>() << event);
 
1029
    writeEvents(QList<QVariantMap>() << event, properties);
 
1030
 
 
1031
    // if this messages supersedes another one, remove the original message
 
1032
    if (!message.supersededToken().isEmpty()) {
 
1033
        event[History::FieldEventId] = message.supersededToken();
 
1034
        removeEvents(QList<QVariantMap>() << event);
 
1035
    }
562
1036
}
563
1037
 
564
1038
QVariantMap HistoryDaemon::getSingleEventFromTextChannel(const Tp::TextChannelPtr textChannel, const QString &messageId)
565
1039
{
566
 
    QStringList participants = participantsFromChannel(textChannel);
 
1040
    QVariantMap properties = propertiesFromChannel(textChannel);
567
1041
 
568
 
    QVariantMap thread = threadForParticipants(textChannel->property(History::FieldAccountId).toString(),
 
1042
    QVariantMap thread = threadForProperties(textChannel->property(History::FieldAccountId).toString(),
569
1043
                                                                     History::EventTypeText,
570
 
                                                                     participants,
 
1044
                                                                     properties,
571
1045
                                                                     matchFlagsForChannel(textChannel),
572
1046
                                                                     false);
573
1047
    if (thread.isEmpty()) {
587
1061
void HistoryDaemon::onMessageRead(const Tp::TextChannelPtr textChannel, const Tp::ReceivedMessage &message)
588
1062
{
589
1063
    QVariantMap textEvent = getSingleEventFromTextChannel(textChannel, message.messageToken());
 
1064
    QVariantMap properties = propertiesFromChannel(textChannel);
590
1065
 
591
1066
    if (textEvent.isEmpty()) {
592
1067
        qWarning() << "Cound not find the original event to update with newEvent = false.";
594
1069
    }
595
1070
 
596
1071
    textEvent[History::FieldNewEvent] = false;
597
 
    if (!writeEvents(QList<QVariantMap>() << textEvent)) {
 
1072
    if (!writeEvents(QList<QVariantMap>() << textEvent, properties)) {
598
1073
        qWarning() << "Failed to save the new message status!";
599
1074
    }
600
1075
}
601
1076
 
602
1077
void HistoryDaemon::onMessageSent(const Tp::TextChannelPtr textChannel, const Tp::Message &message, const QString &messageToken)
603
1078
{
604
 
    qDebug() << __PRETTY_FUNCTION__;
605
 
    QStringList participants = participantsFromChannel(textChannel);
 
1079
    QVariantMap properties = propertiesFromChannel(textChannel);
606
1080
    QList<QVariantMap> attachments;
607
1081
    History::MessageType type = History::MessageTypeText;
608
1082
    int count = 1;
615
1089
        eventId = messageToken;
616
1090
    }
617
1091
 
618
 
    QVariantMap thread = threadForParticipants(textChannel->property(History::FieldAccountId).toString(),
 
1092
    QVariantMap thread = threadForProperties(textChannel->property(History::FieldAccountId).toString(),
619
1093
                                              History::EventTypeText,
620
 
                                              participants,
 
1094
                                              properties,
621
1095
                                              matchFlagsForChannel(textChannel),
622
1096
                                              true);
623
1097
    if (message.hasNonTextContent()) {
666
1140
        }
667
1141
    }
668
1142
 
669
 
 
670
1143
    QVariantMap event;
671
1144
    event[History::FieldType] = History::EventTypeText;
672
1145
    event[History::FieldAccountId] = thread[History::FieldAccountId];
686
1159
    event[History::FieldSubject] = "";
687
1160
    event[History::FieldAttachments] = QVariant::fromValue(attachments);
688
1161
 
689
 
    writeEvents(QList<QVariantMap>() << event);
 
1162
    writeEvents(QList<QVariantMap>() << event, properties);
690
1163
}
691
1164
 
692
1165
History::MatchFlags HistoryDaemon::matchFlagsForChannel(const Tp::ChannelPtr &channel)
707
1180
    hash += "#-#" + thread[History::FieldThreadId].toString();
708
1181
    return hash;
709
1182
}
 
1183
 
 
1184
QVariantMap HistoryDaemon::getInterfaceProperties(const Tp::AbstractInterface *interface)
 
1185
{
 
1186
    QDBusInterface propsInterface(interface->service(), interface->path(), "org.freedesktop.DBus.Properties");
 
1187
    QDBusReply<QVariantMap> reply = propsInterface.call("GetAll", interface->interface());
 
1188
    if (!reply.isValid()) {
 
1189
        qWarning() << "Failed to fetch channel properties for interface" << interface->interface() << reply.error().message();
 
1190
    }
 
1191
    return reply.value();
 
1192
}
 
1193
 
 
1194
void HistoryDaemon::writeInformationEvent(const QVariantMap &thread, History::InformationType type, const QString &subject, const QString &sender, const QString &text)
 
1195
{
 
1196
    History::TextEvent historyEvent = History::TextEvent(thread[History::FieldAccountId].toString(),
 
1197
                                                         thread[History::FieldThreadId].toString(),
 
1198
                                                         QString(QCryptographicHash::hash(QByteArray(
 
1199
                                                                 (QDateTime::currentDateTime().toString("yyyy-MM-ddTHH:mm:ss.zzz") + subject + text).toLatin1()),
 
1200
                                                                 QCryptographicHash::Md5).toHex()),
 
1201
                                                         sender,
 
1202
                                                         QDateTime::currentDateTime(),
 
1203
                                                         false,
 
1204
                                                         text,
 
1205
                                                         History::MessageTypeInformation,
 
1206
                                                         History::MessageStatusUnknown,
 
1207
                                                         QDateTime::currentDateTime(),
 
1208
                                                         subject,
 
1209
                                                         type);
 
1210
    writeEvents(QList<QVariantMap>() << historyEvent.properties(), thread);
 
1211
}
 
1212
 
 
1213
void HistoryDaemon::writeRoomChangesInformationEvents(const QVariantMap &thread, const QVariantMap &interfaceProperties)
 
1214
{
 
1215
    if (!thread.isEmpty()) {
 
1216
        // group subject
 
1217
        QString storedSubject = thread[History::FieldChatRoomInfo].toMap()["Subject"].toString();
 
1218
        QString newSubject = interfaceProperties["Subject"].toString();
 
1219
        if (!newSubject.isEmpty() && storedSubject != newSubject) {
 
1220
            //see if we have an actor. If actor is 'me', we have changed that subject
 
1221
            QString actor = thread[History::FieldChatRoomInfo].toMap()["Actor"].toString();
 
1222
            if (actor == "me") {
 
1223
                actor = "self";
 
1224
            }
 
1225
            writeInformationEvent(thread, History::InformationTypeTitleChanged, newSubject, actor);
 
1226
        }
 
1227
    }
 
1228
}
 
1229
 
 
1230
void HistoryDaemon::writeRolesInformationEvents(const QVariantMap &thread, const Tp::ChannelPtr &channel, const RolesMap &rolesMap)
 
1231
{
 
1232
    if (thread.isEmpty()) {
 
1233
        return;
 
1234
    }
 
1235
 
 
1236
    if (!thread[History::FieldChatRoomInfo].toMap()["Joined"].toBool()) {
 
1237
        return;
 
1238
    }
 
1239
 
 
1240
    // list of identifiers for current channel admins
 
1241
    QStringList adminIds;
 
1242
 
 
1243
    Q_FOREACH(const Tp::ContactPtr contact, channel->groupContacts(false)) {
 
1244
        // see if admin role (ChannelAdminRole == 2)
 
1245
        if (rolesMap[contact->handle().at(0)] & AdminRole) {
 
1246
            adminIds << contact->id();
 
1247
        }
 
1248
    }
 
1249
 
 
1250
    Q_FOREACH (QVariant participant, thread[History::FieldParticipants].toList()) {
 
1251
        QString participantId = participant.toMap()[History::FieldIdentifier].toString();
 
1252
        if (adminIds.contains(participantId)) {
 
1253
            // see if already was admin or not (ChannelAdminRole == 2)
 
1254
            if (! (participant.toMap()[History::FieldParticipantRoles].toUInt() & AdminRole)) {
 
1255
                writeInformationEvent(thread, History::InformationTypeAdminGranted, participantId);
 
1256
            }
 
1257
        }
 
1258
    }
 
1259
 
 
1260
    //evaluate now self roles
 
1261
    if (rolesMap[channel->groupSelfContact()->handle().at(0)] & AdminRole) {
 
1262
        uint selfRoles = thread[History::FieldChatRoomInfo].toMap()["SelfRoles"].toUInt();
 
1263
        if (! (selfRoles & AdminRole)) {
 
1264
            writeInformationEvent(thread, History::InformationTypeSelfAdminGranted);
 
1265
        }
 
1266
    }
 
1267
}