~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: 2017-03-23 01:28:52 UTC
  • mfrom: (230.2.35 staging)
  • Revision ID: ci-train-bot@canonical.com-20170323012852-vzmjcare13zofbna
- Adapt to support VOIP accounts.
- Improve the notifications of participants changing
- Only start saving information events about contacts joining and leaving after the self contact is in the local list of participants.
- Improve Roles management performance by caching the retrieved data.
- Mark entire conversations as read.
- Allow pass multiple fields on sort clause.
- Reduce the dbus traffic when marking messages and threads as read.
- Use a QLockFile to ensure there will be only one instance of the daemon per user. As we now delay the registration on dbus, sometimes we ended up having two instances of the daeon running (because of dbus activation). This change makes sure that won't happen.
- Do not load the participants from threads automatically. If the client really needs it, it can use the newly added API to fetch the participants.
- Make it possible to debug sqlite commands.

Approved by: system-apps-ci-bot

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
/*
2
 
 * Copyright (C) 2013-2016 Canonical, Ltd.
 
2
 * Copyright (C) 2013-2017 Canonical, Ltd.
3
3
 *
4
4
 * Authors:
5
5
 *  Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>
85
85
{
86
86
    Q_FOREACH (QVariant participant, thread[History::FieldParticipants].toList()) {
87
87
        // found if same identifier and as member into thread info
 
88
        QVariantMap participantMap = participant.toMap();
88
89
        if (History::Utils::compareIds(thread[History::FieldAccountId].toString(),
89
90
                                       contact->id(),
90
 
                                       participant.toMap()[History::FieldIdentifier].toString()) &&
91
 
                participant.toMap()[History::FieldParticipantState].toUInt() == History::ParticipantStateRegular)
 
91
                                       participantMap[History::FieldIdentifier].toString()) &&
 
92
                participantMap[History::FieldParticipantState].toUInt() == History::ParticipantStateRegular)
92
93
        {
93
94
            return true;
94
95
        }
131
132
    History::TelepathyHelper::instance()->registerChannelObserver();
132
133
 
133
134
    connect(&mCallObserver,
134
 
            SIGNAL(callEnded(Tp::CallChannelPtr)),
135
 
            SLOT(onCallEnded(Tp::CallChannelPtr)));
 
135
            SIGNAL(callEnded(Tp::CallChannelPtr, bool)),
 
136
            SLOT(onCallEnded(Tp::CallChannelPtr, bool)));
136
137
    connect(&mTextObserver,
137
138
            SIGNAL(messageReceived(Tp::TextChannelPtr,Tp::ReceivedMessage)),
138
139
            SLOT(onMessageReceived(Tp::TextChannelPtr,Tp::ReceivedMessage)));
140
141
            SIGNAL(messageSent(Tp::TextChannelPtr,Tp::Message,QString)),
141
142
            SLOT(onMessageSent(Tp::TextChannelPtr,Tp::Message,QString)));
142
143
    connect(&mTextObserver,
143
 
            SIGNAL(messageRead(Tp::TextChannelPtr,Tp::ReceivedMessage)),
144
 
            SLOT(onMessageRead(Tp::TextChannelPtr,Tp::ReceivedMessage)));
145
 
    connect(&mTextObserver,
146
144
            SIGNAL(channelAvailable(Tp::TextChannelPtr)),
147
145
            SLOT(onTextChannelAvailable(Tp::TextChannelPtr)));
 
146
    connect(&mTextObserver,
 
147
            SIGNAL(textChannelInvalidated(Tp::TextChannelPtr)),
 
148
            SLOT(onTextChannelInvalidated(Tp::TextChannelPtr)));
148
149
 
149
150
    // FIXME: we need to do this in a better way, but for now this should do
150
151
    mProtocolFlags["ofono"] = History::MatchPhoneNumber;
163
164
 
164
165
void HistoryDaemon::onRolesChanged(const HandleRolesMap &added, const HandleRolesMap &removed)
165
166
{
166
 
    Q_UNUSED(added);
167
 
    Q_UNUSED(removed);
168
 
 
169
167
    ChannelInterfaceRolesInterface *roles_interface = qobject_cast<ChannelInterfaceRolesInterface*>(sender());
170
 
    RolesMap roles = roles_interface->getRoles();
171
 
 
172
168
    Tp::TextChannelPtr channel(qobject_cast<Tp::TextChannel*>(sender()->parent()));
 
169
    RolesMap rolesMap;
 
170
    if (!mRolesMap.contains(channel->objectPath())) {
 
171
        rolesMap = roles_interface->getRoles();
 
172
    } else {
 
173
        rolesMap = mRolesMap[channel->objectPath()];
 
174
    }
 
175
 
 
176
    QMapIterator<uint, uint> it(removed);
 
177
    while (it.hasNext()) {
 
178
        it.next();
 
179
        rolesMap.remove(it.key());
 
180
    }
 
181
 
 
182
    QMapIterator<uint, uint> it2(added);
 
183
    while (it2.hasNext()) {
 
184
        it2.next();
 
185
        rolesMap[it2.key()] = it2.value();
 
186
    }
 
187
        
 
188
    mRolesMap[channel->objectPath()] = rolesMap;
 
189
 
173
190
    QVariantMap properties = propertiesFromChannel(channel);
174
191
    QVariantMap thread = threadForProperties(channel->property(History::FieldAccountId).toString(),
175
192
                                             History::EventTypeText,
176
193
                                             properties,
177
194
                                             matchFlagsForChannel(channel),
178
195
                                             false);
179
 
 
180
 
    writeRolesInformationEvents(thread, channel, roles);
181
 
 
182
 
    updateRoomRoles(channel, roles);
 
196
    if (!thread.isEmpty()) {
 
197
        writeRolesInformationEvents(thread, channel, rolesMap);
 
198
        updateRoomRoles(channel, rolesMap);
 
199
    }
183
200
}
184
201
 
185
202
QVariantMap HistoryDaemon::propertiesFromChannel(const Tp::ChannelPtr &textChannel)
187
204
    QVariantMap properties;
188
205
    QVariantList participants;
189
206
    QStringList participantIds;
190
 
 
191
 
    ChannelInterfaceRolesInterface *roles_interface = textChannel->optionalInterface<ChannelInterfaceRolesInterface>();
192
 
    RolesMap roles;
193
 
    if (roles_interface) {
194
 
        roles = roles_interface->getRoles();
195
 
    }
196
 
 
197
 
    Q_FOREACH(const Tp::ContactPtr contact, textChannel->groupContacts(false)) {
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;
228
 
    }
229
 
 
230
 
    if (participants.isEmpty() && textChannel->targetHandleType() == Tp::HandleTypeContact &&
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;
 
207
    QString accountId = textChannel->property(History::FieldAccountId).toString();
 
208
 
 
209
    if (History::Utils::shouldIncludeParticipants(accountId, fromTelepathyHandleType(textChannel->targetHandleType()))) {
 
210
        ChannelInterfaceRolesInterface *roles_interface = textChannel->optionalInterface<ChannelInterfaceRolesInterface>();
 
211
        RolesMap roles;
 
212
        if (roles_interface) {
 
213
            if (mRolesMap.contains(textChannel->objectPath())) {
 
214
                roles = mRolesMap[textChannel->objectPath()];
 
215
            }
 
216
        }
 
217
 
 
218
        Q_FOREACH(const Tp::ContactPtr contact, textChannel->groupContacts(false)) {
 
219
            QVariantMap contactProperties;
 
220
            contactProperties[History::FieldAlias] = contact->alias();
 
221
            contactProperties[History::FieldAccountId] = accountId;
 
222
            contactProperties[History::FieldIdentifier] = contact->id();
 
223
            contactProperties[History::FieldParticipantState] = History::ParticipantStateRegular;
 
224
            contactProperties[History::FieldParticipantRoles] = roles[contact->handle().at(0)];
 
225
            participantIds << contact->id();
 
226
            participants << contactProperties;
 
227
        }
 
228
 
 
229
        Q_FOREACH(const Tp::ContactPtr contact, textChannel->groupRemotePendingContacts(false)) {
 
230
            QVariantMap contactProperties;
 
231
            contactProperties[History::FieldAlias] = contact->alias();
 
232
            contactProperties[History::FieldAccountId] = accountId;
 
233
            contactProperties[History::FieldIdentifier] = contact->id();
 
234
            contactProperties[History::FieldParticipantState] = History::ParticipantStateRemotePending;
 
235
            contactProperties[History::FieldParticipantRoles] = roles[contact->handle().at(0)];
 
236
            participantIds << contact->id();
 
237
            participants << contactProperties;
 
238
        }
 
239
 
 
240
        Q_FOREACH(const Tp::ContactPtr contact, textChannel->groupLocalPendingContacts(false)) {
 
241
            QVariantMap contactProperties;
 
242
            contactProperties[History::FieldAlias] = contact->alias();
 
243
            contactProperties[History::FieldAccountId] = accountId;
 
244
            contactProperties[History::FieldIdentifier] = contact->id();
 
245
            contactProperties[History::FieldParticipantState] = History::ParticipantStateLocalPending;
 
246
            contactProperties[History::FieldParticipantRoles] = roles[contact->handle().at(0)];
 
247
            participantIds << contact->id();
 
248
            participants << contactProperties;
 
249
        }
 
250
 
 
251
        if (participants.isEmpty() && textChannel->targetHandleType() == Tp::HandleTypeContact &&
 
252
                textChannel->targetContact() == textChannel->connection()->selfContact()) {
 
253
            QVariantMap contactProperties;
 
254
            contactProperties[History::FieldAlias] = textChannel->targetContact()->alias();
 
255
            contactProperties[History::FieldAccountId] = accountId;
 
256
            contactProperties[History::FieldIdentifier] = textChannel->targetContact()->id();
 
257
            contactProperties[History::FieldParticipantState] = History::ParticipantStateRegular;
 
258
            participantIds << textChannel->targetContact()->id();
 
259
            participants << contactProperties;
 
260
        }
239
261
    }
240
262
 
241
263
    // We map chatType directly from telepathy targetHandleType: None, Contact, Room
314
336
    return thread;
315
337
}
316
338
 
 
339
QString HistoryDaemon::threadIdForProperties(const QString &accountId, History::EventType type, const QVariantMap &properties, History::MatchFlags matchFlags, bool create)
 
340
{
 
341
    if (!mBackend) {
 
342
        return QString::null;
 
343
    }
 
344
 
 
345
    QString threadId = mBackend->threadIdForProperties(accountId,
 
346
                                                       type,
 
347
                                                       properties,
 
348
                                                       matchFlags);
 
349
    if (threadId.isEmpty() && create) {
 
350
        QVariantMap thread = mBackend->createThreadForProperties(accountId, type, properties);
 
351
        if (!thread.isEmpty()) {
 
352
            if (properties.contains("Requested") && properties[History::FieldChatType].toInt() == History::ChatTypeRoom) {
 
353
                QVariantMap map = thread[History::FieldChatRoomInfo].toMap();
 
354
                map["Requested"] = properties["Requested"];
 
355
                thread[History::FieldChatRoomInfo] = map;
 
356
            }
 
357
            mDBus.notifyThreadsAdded(QList<QVariantMap>() << thread);
 
358
            threadId = thread[History::FieldThreadId].toString();
 
359
        }
 
360
    }
 
361
    return threadId;
 
362
}
 
363
 
 
364
QList<QVariantMap> HistoryDaemon::participantsForThreads(const QList<QVariantMap> &threadIds)
 
365
{
 
366
    if (!mBackend) {
 
367
        return QList<QVariantMap>();
 
368
    }
 
369
    return mBackend->participantsForThreads(threadIds);
 
370
}
 
371
 
317
372
QString HistoryDaemon::queryThreads(int type, const QVariantMap &sort, const QVariantMap &filter, const QVariantMap &properties)
318
373
{
319
374
    if (!mBackend) {
370
425
    return mBackend->getSingleEvent((History::EventType)type, accountId, threadId, eventId);
371
426
}
372
427
 
373
 
bool HistoryDaemon::writeEvents(const QList<QVariantMap> &events, const QVariantMap &properties)
 
428
bool HistoryDaemon::writeEvents(const QList<QVariantMap> &events, const QVariantMap &properties, bool notify)
374
429
{
375
430
    if (!mBackend) {
376
431
        return false;
407
462
        threads[hash] = thread;
408
463
 
409
464
        // set the participants field in the event
410
 
        savedEvent[History::FieldParticipants] = thread[History::FieldParticipants];
 
465
        if (type == History::EventTypeVoice) {
 
466
            savedEvent[History::FieldParticipants] = thread[History::FieldParticipants];
 
467
        }
411
468
 
412
469
 
413
470
        // check if the event was a new one or a modification to an existing one
427
484
    mBackend->endBatchOperation();
428
485
 
429
486
    // and last but not least, notify the results
430
 
    if (!newEvents.isEmpty()) {
 
487
    if (!newEvents.isEmpty() && notify) {
431
488
        mDBus.notifyEventsAdded(newEvents);
432
489
    }
433
 
    if (!modifiedEvents.isEmpty()) {
 
490
    if (!modifiedEvents.isEmpty() && notify) {
434
491
        mDBus.notifyEventsModified(modifiedEvents);
435
492
    }
436
 
    if (!threads.isEmpty()) {
 
493
    if (!threads.isEmpty() && notify) {
437
494
        mDBus.notifyThreadsModified(threads.values());
438
495
    }
439
496
    return true;
512
569
    return true;
513
570
}
514
571
 
 
572
void HistoryDaemon::markThreadsAsRead(const QList<QVariantMap> &threads)
 
573
{
 
574
    if (!mBackend) {
 
575
        return;
 
576
    }
 
577
 
 
578
    QList<QVariantMap> modifiedThreads;
 
579
   
 
580
    Q_FOREACH(const QVariantMap &thread, threads) {
 
581
        mBackend->beginBatchOperation();
 
582
        QVariantMap newThread = mBackend->markThreadAsRead(thread);
 
583
        if (!newThread.isEmpty()) {
 
584
            modifiedThreads << newThread;
 
585
        }
 
586
 
 
587
        mBackend->endBatchOperation();
 
588
    }
 
589
 
 
590
    if (!modifiedThreads.isEmpty()) {
 
591
        mDBus.notifyThreadsModified(modifiedThreads);
 
592
    }
 
593
}
 
594
 
515
595
bool HistoryDaemon::removeThreads(const QList<QVariantMap> &threads)
516
596
{
517
597
    if (!mBackend) {
518
598
        return false;
519
599
    }
520
600
 
521
 
    // In order to remove a thread all we have to do is to remove all its items
522
 
    // then it is going to be removed by removeEvents() once it detects the thread is
523
 
    // empty.
524
 
    QList<QVariantMap> events;
525
 
    QMap<QString, QVariantMap> removedEmptyThreads;
 
601
    // If the thread has events
 
602
    mBackend->beginBatchOperation();
526
603
    Q_FOREACH(const QVariantMap &thread, threads) {
527
 
        QList<QVariantMap> thisEvents = mBackend->eventsForThread(thread);
528
 
        if (thisEvents.isEmpty()) {
529
 
            mBackend->beginBatchOperation();
530
 
            if (!mBackend->removeThread(thread)) {
531
 
                mBackend->rollbackBatchOperation();
532
 
                return false;
533
 
            }
534
 
            mBackend->endBatchOperation();
535
 
            QString hash = hashThread(thread);
536
 
            removedEmptyThreads[hash] = thread;
537
 
            continue;
538
 
        }
539
 
        events += thisEvents;
540
 
    }
541
 
 
542
 
    if (!removedEmptyThreads.isEmpty()) {
543
 
        mDBus.notifyThreadsRemoved(removedEmptyThreads.values());
544
 
    }
545
 
 
546
 
    if (events.size() > 0) {
547
 
        if(removeEvents(events)) {
548
 
            return true;
549
 
        }
550
 
    }
551
 
 
552
 
    return false;
 
604
        if (!mBackend->removeThread(thread)) {
 
605
            mBackend->rollbackBatchOperation();
 
606
            return false;
 
607
        }
 
608
    }
 
609
    mBackend->endBatchOperation();
 
610
    mDBus.notifyThreadsRemoved(threads);
 
611
    return true;
553
612
}
554
613
 
555
614
void HistoryDaemon::onObserverCreated()
562
621
            &mTextObserver, SLOT(onTextChannelAvailable(Tp::TextChannelPtr)));
563
622
}
564
623
 
565
 
void HistoryDaemon::onCallEnded(const Tp::CallChannelPtr &channel)
 
624
void HistoryDaemon::onCallEnded(const Tp::CallChannelPtr &channel, bool missed)
566
625
{
567
626
    QVariantMap properties = propertiesFromChannel(channel);
568
627
    QVariantList participants;
592
651
    // FIXME: check if checking for isRequested() is enough
593
652
    bool incoming = !channel->isRequested();
594
653
    int duration;
595
 
    bool missed = incoming && channel->callStateReason().reason == Tp::CallStateChangeReasonNoAnswer;
596
654
 
597
655
    if (!missed) {
598
656
        QDateTime activeTime = channel->property("activeTimestamp").toDateTime();
615
673
    writeEvents(QList<QVariantMap>() << event, properties);
616
674
}
617
675
 
 
676
void HistoryDaemon::onTextChannelInvalidated(const Tp::TextChannelPtr channel)
 
677
{
 
678
    mRolesMap.remove(channel->objectPath());
 
679
    QString accountId = channel->property(History::FieldAccountId).toString();
 
680
    QVariantMap properties = propertiesFromChannel(channel);
 
681
 
 
682
    // first try to fetch the existing thread to see if there is any.
 
683
    QVariantMap thread = threadForProperties(accountId,
 
684
                                             History::EventTypeText,
 
685
                                             properties,
 
686
                                             matchFlagsForChannel(channel),
 
687
                                             false);
 
688
 
 
689
    QVariantMap roomInfo = thread[History::FieldChatRoomInfo].toMap();
 
690
    if ((roomInfo.contains("Persistent") && !roomInfo["Persistent"].toBool()) && History::TelepathyHelper::instance()->accountForId(accountId)->protocolName() != "ofono") {
 
691
        writeInformationEvent(thread, History::InformationTypeSelfLeaving);
 
692
        // update backend
 
693
        updateRoomProperties(channel, QVariantMap{{"Joined", false}});
 
694
    }
 
695
 
 
696
    channel->disconnect(this);
 
697
}
 
698
 
618
699
void HistoryDaemon::onTextChannelAvailable(const Tp::TextChannelPtr channel)
619
700
{
620
701
    // for Rooms we need to explicitly create the thread to allow users to send messages to groups even
621
702
    // before they receive any message.
622
703
    // for other types, we can wait until messages are received
 
704
    bool notify = false;
623
705
    if (channel->targetHandleType() == Tp::HandleTypeRoom) {
624
706
        QString accountId = channel->property(History::FieldAccountId).toString();
625
707
        QVariantMap properties = propertiesFromChannel(channel);
641
723
 
642
724
            // write information event including all initial invitees
643
725
            Q_FOREACH(const Tp::ContactPtr contact, channel->groupRemotePendingContacts(false)) {
644
 
                writeInformationEvent(thread, History::InformationTypeInvitationSent, contact->alias());
 
726
                writeInformationEvent(thread, History::InformationTypeInvitationSent, contact->alias(), QString(), QString(), false);
645
727
            }
646
728
 
647
729
            // update participants only if the thread is not available previously. Otherwise we'll wait for membersChanged event
648
730
            // for reflect in conversation information events for modified participants.
649
 
            updateRoomParticipants(channel);
 
731
            updateRoomParticipants(channel, false);
 
732
            notify = true;
650
733
        }
651
734
 
652
735
        // write an entry saying you joined the group if 'joined' flag in thread is false and modify that flag.
657
740
                writeInformationEvent(thread, History::InformationTypeSelfJoined);
658
741
            }
659
742
            // update backend
660
 
            updateRoomProperties(channel, QVariantMap{{"Joined", true}});
 
743
            updateRoomProperties(channel, QVariantMap{{"Joined", true}}, false);
 
744
            notify = true;
661
745
        }
662
746
 
663
747
        Tp::AbstractInterface *room_interface = channel->optionalInterface<Tp::Client::ChannelInterfaceRoomInterface>();
685
769
 
686
770
        connect(roles_interface, SIGNAL(RolesChanged(const HandleRolesMap&, const HandleRolesMap&)), SLOT(onRolesChanged(const HandleRolesMap&, const HandleRolesMap&)));
687
771
    }
 
772
 
 
773
    if (notify) {
 
774
        updateRoomParticipants(channel, true);
 
775
    }
688
776
}
689
777
 
690
778
void HistoryDaemon::onGroupMembersChanged(const Tp::Contacts &groupMembersAdded,
702
790
    bool hasRemotePendingMembersAdded = groupRemotePendingMembersAdded.size() > 0;
703
791
    bool hasMembersAdded = groupMembersAdded.size() > 0;
704
792
    bool hasMembersRemoved = groupMembersRemoved.size() > 0;
 
793
    Tp::ContactPtr selfContact = channel->connection()->selfContact();
 
794
    bool selfContactIsPending = channel->groupRemotePendingContacts(true).contains(selfContact);
705
795
 
706
796
    if (hasRemotePendingMembersAdded || hasMembersAdded || hasMembersRemoved) {
707
797
        properties = propertiesFromChannel(channel);
710
800
                                                       properties,
711
801
                                                       matchFlagsForChannel(channel),
712
802
                                                       false);
713
 
        if (!thread.isEmpty()) {
 
803
        if (!thread.isEmpty() && !selfContactIsPending) {
 
804
            QList<QVariantMap> added;
 
805
            QList<QVariantMap> removed;
 
806
            QList<QVariantMap> modified;
714
807
            if (hasRemotePendingMembersAdded) {
715
808
                Q_FOREACH (const Tp::ContactPtr& contact, groupRemotePendingMembersAdded) {
716
809
                    if (!foundInThread(contact, thread)) {
717
 
                        writeInformationEvent(thread, History::InformationTypeInvitationSent, contact->alias());
 
810
                        writeInformationEvent(thread, History::InformationTypeInvitationSent, contact->alias(), QString(), QString(), true);
 
811
                        QVariantMap participant;
 
812
                        participant[History::FieldIdentifier] = contact->id();
 
813
                        participant[History::FieldAlias] = contact->alias();
 
814
                        participant[History::FieldParticipantState] = History::ParticipantStateRemotePending;
 
815
                        added << participant;
718
816
                    }
719
817
                }
 
818
 
720
819
            }
721
820
            if (hasMembersAdded) {
722
821
                Q_FOREACH (const Tp::ContactPtr& contact, groupMembersAdded) {
723
822
                    // 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());
 
823
                    if (!foundAsMemberInThread(contact, thread) && contact->id() != channel->groupSelfContact()->id()) {
 
824
 
 
825
                        writeInformationEvent(thread, History::InformationTypeJoined, contact->alias(), QString(), QString(), true);
 
826
 
 
827
                        QVariantMap participant;
 
828
                        participant[History::FieldIdentifier] = contact->id();
 
829
                        participant[History::FieldAlias] = contact->alias();
 
830
                        participant[History::FieldParticipantState] = History::ParticipantStateRegular;
 
831
                        added << participant;
726
832
                    }
727
833
                }
728
834
            }
741
847
                            break;
742
848
                        }
743
849
                    }
744
 
                    writeInformationEvent(thread, type);
745
 
                    // update backend
746
 
                    updateRoomProperties(channel, QVariantMap{{"Joined", false}});
 
850
                    if (thread[History::FieldChatRoomInfo].toMap()["Joined"].toBool()) {
 
851
                        writeInformationEvent(thread, type);
 
852
                        // update backend
 
853
                        updateRoomProperties(channel, QVariantMap{{"Joined", false}}, true);
 
854
                    }
747
855
                }
748
856
                else // don't notify any other group member removal if we are leaving the group
749
857
                {
750
858
                    Q_FOREACH (const Tp::ContactPtr& contact, groupMembersRemoved) {
751
859
                        // inform about removed members other than us
752
860
                        if (contact->id() != channel->groupSelfContact()->id()) {
753
 
                            writeInformationEvent(thread, History::InformationTypeLeaving, contact->alias());
 
861
                            writeInformationEvent(thread, History::InformationTypeLeaving, contact->alias(), QString(), QString(), true);
754
862
                        }
 
863
                        QVariantMap participant;
 
864
                        participant[History::FieldIdentifier] = contact->id();
 
865
                        participant[History::FieldAlias] = contact->alias();
 
866
                        removed << participant;
755
867
                    }
756
868
                }
757
869
            }
 
870
            mDBus.notifyThreadParticipantsChanged(thread, added, removed, QList<QVariantMap>());
758
871
        }
759
872
    }
760
873
 
761
 
    updateRoomParticipants(channel);
 
874
    updateRoomParticipants(channel, !selfContactIsPending);
762
875
}
763
876
 
764
 
void HistoryDaemon::updateRoomParticipants(const Tp::TextChannelPtr channel)
 
877
void HistoryDaemon::updateRoomParticipants(const Tp::TextChannelPtr channel, bool notify)
765
878
{
766
879
    if (!channel) {
767
880
        return;
772
885
 
773
886
    ChannelInterfaceRolesInterface *roles_interface = channel->optionalInterface<ChannelInterfaceRolesInterface>();
774
887
    RolesMap roles;
 
888
 
775
889
    if (roles_interface) {
776
 
        roles = roles_interface->getRoles();
 
890
        if (!mRolesMap.contains(channel->objectPath())) {
 
891
            roles = roles_interface->getRoles();
 
892
            mRolesMap[channel->objectPath()] = roles;
 
893
        } else {
 
894
            roles = mRolesMap[channel->objectPath()];
 
895
        }
777
896
    }
778
897
 
779
898
    Q_FOREACH(const Tp::ContactPtr contact, channel->groupRemotePendingContacts(false)) {
811
930
    QString accountId = channel->property(History::FieldAccountId).toString();
812
931
    QString threadId = channel->targetId();
813
932
    if (mBackend->updateRoomParticipants(accountId, threadId, History::EventTypeText, participants)) {
814
 
        QVariantMap updatedThread = getSingleThread(History::EventTypeText, accountId, threadId, QVariantMap());
815
 
        mDBus.notifyThreadsModified(QList<QVariantMap>() << updatedThread);
 
933
        if (notify) {
 
934
            QVariantMap updatedThread = getSingleThread(History::EventTypeText, accountId, threadId, QVariantMap());
 
935
            mDBus.notifyThreadsModified(QList<QVariantMap>() << updatedThread);
 
936
        }
816
937
    }
817
938
}
818
939
 
819
 
void HistoryDaemon::updateRoomRoles(const Tp::TextChannelPtr &channel, const RolesMap &rolesMap)
 
940
void HistoryDaemon::updateRoomRoles(const Tp::TextChannelPtr &channel, const RolesMap &rolesMap, bool notify)
820
941
{
821
942
    if (!channel) {
822
943
        return;
841
962
    QString accountId = channel->property(History::FieldAccountId).toString();
842
963
    QString threadId = channel->targetId();
843
964
    if (mBackend->updateRoomParticipantsRoles(accountId, threadId, History::EventTypeText, participantsRoles)) {
844
 
        QVariantMap updatedThread = getSingleThread(History::EventTypeText, accountId, threadId, QVariantMap());
845
 
        mDBus.notifyThreadsModified(QList<QVariantMap>() << updatedThread);
 
965
        if (notify) {
 
966
            QVariantMap updatedThread = getSingleThread(History::EventTypeText, accountId, threadId, QVariantMap());
 
967
            mDBus.notifyThreadsModified(QList<QVariantMap>() << updatedThread);
 
968
        }
846
969
    }
847
970
 
848
971
    // update self roles in room properties
865
988
    updateRoomProperties(accountId, threadId, type, properties, invalidated);
866
989
}
867
990
 
868
 
void HistoryDaemon::updateRoomProperties(const Tp::TextChannelPtr &channel, const QVariantMap &properties)
 
991
void HistoryDaemon::updateRoomProperties(const Tp::TextChannelPtr &channel, const QVariantMap &properties, bool notify)
869
992
{
870
993
    QString accountId = channel->property(History::FieldAccountId).toString();
871
994
    QString threadId = channel->targetId();
872
995
    History::EventType type = History::EventTypeText;
873
 
    updateRoomProperties(accountId, threadId, type, properties, QStringList());
 
996
    updateRoomProperties(accountId, threadId, type, properties, QStringList(), notify);
874
997
}
875
998
 
876
 
void HistoryDaemon::updateRoomProperties(const QString &accountId, const QString &threadId, History::EventType type, const QVariantMap &properties, const QStringList &invalidated)
 
999
void HistoryDaemon::updateRoomProperties(const QString &accountId, const QString &threadId, History::EventType type, const QVariantMap &properties, const QStringList &invalidated, bool notify)
877
1000
{
878
1001
    if (mBackend->updateRoomInfo(accountId, threadId, type, properties, invalidated)) {
879
 
        QVariantMap thread = getSingleThread(type, accountId, threadId, QVariantMap());
880
 
        mDBus.notifyThreadsModified(QList<QVariantMap>() << thread);
 
1002
        if (notify) {
 
1003
            QVariantMap thread = getSingleThread(type, accountId, threadId, QVariantMap());
 
1004
            mDBus.notifyThreadsModified(QList<QVariantMap>() << thread);
 
1005
        }
881
1006
    }
882
1007
}
883
1008
 
884
1009
void HistoryDaemon::onMessageReceived(const Tp::TextChannelPtr textChannel, const Tp::ReceivedMessage &message)
885
1010
{
886
1011
    QString eventId;
887
 
    Tp::MessagePart header = message.header();
888
1012
    QString senderId;
 
1013
 
 
1014
    QString accountId = textChannel->property(History::FieldAccountId).toString();
 
1015
    QString threadId = textChannel->property(History::FieldThreadId).toString();
889
1016
    QVariantMap properties = propertiesFromChannel(textChannel);
 
1017
 
 
1018
    if (threadId.isNull()) {
 
1019
        threadId = threadIdForProperties(accountId,
 
1020
                                         History::EventTypeText,
 
1021
                                         properties,
 
1022
                                         matchFlagsForChannel(textChannel),
 
1023
                                         true);
 
1024
    }
 
1025
 
890
1026
    History::MessageStatus status = History::MessageStatusUnknown;
891
1027
    if (!message.sender() || message.sender()->handle().at(0) == textChannel->connection()->selfHandle()) {
892
1028
        senderId = "self";
909
1045
    if (message.isDeliveryReport() && message.deliveryDetails().hasOriginalToken()) {
910
1046
        // at this point we assume the delivery report is for a message that was already
911
1047
        // sent and properly saved at our database, so we can safely get it here to update
912
 
        QVariantMap textEvent = getSingleEventFromTextChannel(textChannel, message.deliveryDetails().originalToken());
 
1048
        QVariantMap textEvent = getSingleEvent(History::EventTypeText, accountId, threadId, message.deliveryDetails().originalToken());
913
1049
        if (textEvent.isEmpty()) {
914
1050
            qWarning() << "Cound not find the original event to update with delivery details.";
915
1051
            return;
922
1058
            return;
923
1059
        }
924
1060
 
925
 
        History::MessageStatus status;
926
 
        switch (message.deliveryDetails().status()) {
927
 
        case Tp::DeliveryStatusAccepted:
928
 
            status = History::MessageStatusAccepted;
929
 
            break;
930
 
        case Tp::DeliveryStatusDeleted:
931
 
            status = History::MessageStatusDeleted;
932
 
            break;
933
 
        case Tp::DeliveryStatusDelivered:
934
 
            status = History::MessageStatusDelivered;
935
 
            break;
936
 
        case Tp::DeliveryStatusPermanentlyFailed:
937
 
            status = History::MessageStatusPermanentlyFailed;
938
 
            break;
939
 
        case Tp::DeliveryStatusRead:
940
 
            status = History::MessageStatusRead;
941
 
            break;
942
 
        case Tp::DeliveryStatusTemporarilyFailed:
943
 
            status = History::MessageStatusTemporarilyFailed;
944
 
            break;
945
 
        case Tp::DeliveryStatusUnknown:
946
 
            status = History::MessageStatusUnknown;
947
 
            break;
948
 
        }
949
 
 
950
 
        textEvent[History::FieldMessageStatus] = (int) status;
 
1061
        textEvent[History::FieldMessageStatus] = (int) fromTelepathyDeliveryStatus(message.deliveryDetails().status());
951
1062
        if (!writeEvents(QList<QVariantMap>() << textEvent, properties)) {
952
1063
            qWarning() << "Failed to save the new message status!";
953
1064
        }
954
1065
 
955
1066
        return;
956
1067
    }
957
 
 
958
 
    QVariantMap thread = threadForProperties(textChannel->property(History::FieldAccountId).toString(),
959
 
                                                                   History::EventTypeText,
960
 
                                                                   properties,
961
 
                                                                   matchFlagsForChannel(textChannel),
962
 
                                                                   true);
963
1068
    int count = 1;
964
1069
    QList<QVariantMap> attachments;
965
1070
    History::MessageType type = History::MessageTypeText;
966
1071
    QString subject;
967
1072
 
968
1073
    if (message.hasNonTextContent()) {
969
 
        QString normalizedAccountId = QString(QCryptographicHash::hash(thread[History::FieldAccountId].toString().toLatin1(), QCryptographicHash::Md5).toHex());
970
 
        QString normalizedThreadId = QString(QCryptographicHash::hash(thread[History::FieldThreadId].toString().toLatin1(), QCryptographicHash::Md5).toHex());
 
1074
        QString normalizedAccountId = QString(QCryptographicHash::hash(accountId.toLatin1(), QCryptographicHash::Md5).toHex());
 
1075
        QString normalizedThreadId = QString(QCryptographicHash::hash(threadId.toLatin1(), QCryptographicHash::Md5).toHex());
971
1076
        QString normalizedEventId = QString(QCryptographicHash::hash(eventId.toLatin1(), QCryptographicHash::Md5).toHex());
972
1077
        QString mmsStoragePath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation);
973
1078
 
1000
1105
            file.close();
1001
1106
 
1002
1107
            QVariantMap attachment;
1003
 
            attachment[History::FieldAccountId] = thread[History::FieldAccountId];
1004
 
            attachment[History::FieldThreadId] = thread[History::FieldThreadId];
 
1108
            attachment[History::FieldAccountId] = accountId;
 
1109
            attachment[History::FieldThreadId] = threadId;
1005
1110
            attachment[History::FieldEventId] = eventId;
1006
1111
            attachment[History::FieldAttachmentId] = part["identifier"].variant();
1007
1112
            attachment[History::FieldContentType] = part["content-type"].variant();
1013
1118
 
1014
1119
    QVariantMap event;
1015
1120
    event[History::FieldType] = History::EventTypeText;
1016
 
    event[History::FieldAccountId] = thread[History::FieldAccountId];
1017
 
    event[History::FieldThreadId] = thread[History::FieldThreadId];
 
1121
    event[History::FieldAccountId] = accountId;
 
1122
    event[History::FieldThreadId] = threadId;
1018
1123
    event[History::FieldEventId] = eventId;
1019
1124
    event[History::FieldSenderId] = senderId;
1020
1125
    event[History::FieldTimestamp] = message.received().toString("yyyy-MM-ddTHH:mm:ss.zzz");
1058
1163
 
1059
1164
}
1060
1165
 
1061
 
void HistoryDaemon::onMessageRead(const Tp::TextChannelPtr textChannel, const Tp::ReceivedMessage &message)
1062
 
{
1063
 
    QVariantMap textEvent = getSingleEventFromTextChannel(textChannel, message.messageToken());
1064
 
    QVariantMap properties = propertiesFromChannel(textChannel);
1065
 
 
1066
 
    if (textEvent.isEmpty()) {
1067
 
        qWarning() << "Cound not find the original event to update with newEvent = false.";
1068
 
        return;
1069
 
    }
1070
 
 
1071
 
    textEvent[History::FieldNewEvent] = false;
1072
 
    if (!writeEvents(QList<QVariantMap>() << textEvent, properties)) {
1073
 
        qWarning() << "Failed to save the new message status!";
1074
 
    }
1075
 
}
1076
 
 
1077
1166
void HistoryDaemon::onMessageSent(const Tp::TextChannelPtr textChannel, const Tp::Message &message, const QString &messageToken)
1078
1167
{
1079
1168
    QVariantMap properties = propertiesFromChannel(textChannel);
1191
1280
    return reply.value();
1192
1281
}
1193
1282
 
1194
 
void HistoryDaemon::writeInformationEvent(const QVariantMap &thread, History::InformationType type, const QString &subject, const QString &sender, const QString &text)
 
1283
void HistoryDaemon::writeInformationEvent(const QVariantMap &thread, History::InformationType type, const QString &subject, const QString &sender, const QString &text, bool notify)
1195
1284
{
1196
1285
    History::TextEvent historyEvent = History::TextEvent(thread[History::FieldAccountId].toString(),
1197
1286
                                                         thread[History::FieldThreadId].toString(),
1207
1296
                                                         QDateTime::currentDateTime(),
1208
1297
                                                         subject,
1209
1298
                                                         type);
1210
 
    writeEvents(QList<QVariantMap>() << historyEvent.properties(), thread);
 
1299
    writeEvents(QList<QVariantMap>() << historyEvent.properties(), thread, notify);
1211
1300
}
1212
1301
 
1213
1302
void HistoryDaemon::writeRoomChangesInformationEvents(const QVariantMap &thread, const QVariantMap &interfaceProperties)
1265
1354
        }
1266
1355
    }
1267
1356
}
 
1357
 
 
1358
History::MessageStatus HistoryDaemon::fromTelepathyDeliveryStatus(Tp::DeliveryStatus deliveryStatus)
 
1359
{
 
1360
    History::MessageStatus status;
 
1361
    switch (deliveryStatus) {
 
1362
    case Tp::DeliveryStatusAccepted:
 
1363
        status = History::MessageStatusAccepted;
 
1364
        break;
 
1365
    case Tp::DeliveryStatusDeleted:
 
1366
        status = History::MessageStatusDeleted;
 
1367
        break;
 
1368
    case Tp::DeliveryStatusDelivered:
 
1369
        status = History::MessageStatusDelivered;
 
1370
        break;
 
1371
    case Tp::DeliveryStatusPermanentlyFailed:
 
1372
        status = History::MessageStatusPermanentlyFailed;
 
1373
        break;
 
1374
    case Tp::DeliveryStatusRead:
 
1375
        status = History::MessageStatusRead;
 
1376
        break;
 
1377
    case Tp::DeliveryStatusTemporarilyFailed:
 
1378
        status = History::MessageStatusTemporarilyFailed;
 
1379
        break;
 
1380
    case Tp::DeliveryStatusUnknown:
 
1381
        status = History::MessageStatusUnknown;
 
1382
        break;
 
1383
    }
 
1384
 
 
1385
    return status;
 
1386
}
 
1387
 
 
1388
History::ChatType HistoryDaemon::fromTelepathyHandleType(const Tp::HandleType &type)
 
1389
{
 
1390
    History::ChatType chatType;
 
1391
    switch(type) {
 
1392
    case Tp::HandleTypeContact:
 
1393
        chatType = History::ChatTypeContact;
 
1394
        break;
 
1395
    case Tp::HandleTypeNone:
 
1396
        chatType = History::ChatTypeNone;
 
1397
        break;
 
1398
    case Tp::HandleTypeRoom:
 
1399
        chatType = History::ChatTypeRoom;
 
1400
        break;
 
1401
    }
 
1402
 
 
1403
    return chatType;
 
1404
}