Merge lp:history-service/staging into lp:history-service

Proposed by Gustavo Pichorim Boiko
Status: Approved
Approved by: Gustavo Pichorim Boiko
Approved revision: 265
Proposed branch: lp:history-service/staging
Merge into: lp:history-service
Diff against target: 3363 lines (+1270/-447)
49 files modified
CMakeLists.txt (+5/-0)
Ubuntu/History/historyeventmodel.cpp (+39/-42)
Ubuntu/History/historyeventmodel.h (+2/-5)
Ubuntu/History/historygroupedthreadsmodel.cpp (+54/-2)
Ubuntu/History/historygroupedthreadsmodel.h (+5/-0)
Ubuntu/History/historymodel.cpp (+106/-10)
Ubuntu/History/historymodel.h (+11/-0)
Ubuntu/History/historythreadmodel.cpp (+48/-9)
Ubuntu/History/historythreadmodel.h (+2/-0)
cmake/modules/GenerateTest.cmake (+1/-0)
daemon/HistoryService.xml (+29/-0)
daemon/callchannelobserver.cpp (+16/-2)
daemon/callchannelobserver.h (+2/-1)
daemon/historydaemon.cpp (+323/-186)
daemon/historydaemon.h (+21/-10)
daemon/historyservicedbus.cpp (+102/-8)
daemon/historyservicedbus.h (+25/-0)
daemon/main.cpp (+15/-5)
daemon/textchannelobserver.cpp (+1/-14)
daemon/textchannelobserver.h (+1/-2)
plugins/sqlite/schema/v18.sql (+14/-0)
plugins/sqlite/sqlitedatabase.cpp (+10/-0)
plugins/sqlite/sqlitehistoryeventview.cpp (+8/-1)
plugins/sqlite/sqlitehistoryplugin.cpp (+136/-53)
plugins/sqlite/sqlitehistoryplugin.h (+6/-1)
plugins/sqlite/sqlitehistorythreadview.cpp (+8/-1)
src/contactmatcher.cpp (+57/-22)
src/contactmatcher_p.h (+2/-0)
src/eventview.cpp (+6/-2)
src/eventview.h (+3/-1)
src/manager.cpp (+24/-0)
src/manager.h (+4/-1)
src/managerdbus.cpp (+56/-10)
src/managerdbus_p.h (+10/-2)
src/participant.cpp (+9/-0)
src/participant.h (+1/-0)
src/plugin.h (+6/-0)
src/thread.cpp (+16/-0)
src/thread.h (+2/-0)
src/threadview.cpp (+15/-0)
src/threadview.h (+8/-0)
src/threadview_p.h (+4/-0)
src/utils.cpp (+16/-1)
src/utils_p.h (+2/-0)
tests/Ubuntu.History/HistoryEventModelTest.cpp (+1/-1)
tests/daemon/DaemonTest.cpp (+0/-8)
tests/libhistoryservice/ManagerTest.cpp (+16/-43)
tests/plugins/sqlite/SqliteEventViewTest.cpp (+22/-2)
tests/plugins/sqlite/SqlitePluginTest.cpp (+0/-2)
To merge this branch: bzr merge lp:history-service/staging
Reviewer Review Type Date Requested Status
Gustavo Pichorim Boiko (community) Approve
system-apps-ci-bot continuous-integration Approve
Review via email: mp+320728@code.launchpad.net

Commit message

- 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.

Description of the change

- 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.

To post a comment you must log in.
Revision history for this message
system-apps-ci-bot (system-apps-ci-bot) wrote :

PASSED: Continuous integration, rev:264
https://jenkins.canonical.com/system-apps/job/lp-history-service-ci/2/
Executed test runs:
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build/2344
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-0-fetch/2344
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=xenial+overlay/2162
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=xenial+overlay/2162/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=zesty/2162
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=zesty/2162/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=xenial+overlay/2162
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=xenial+overlay/2162/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=zesty/2162
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=zesty/2162/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=xenial+overlay/2162
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=xenial+overlay/2162/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=zesty/2162
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=zesty/2162/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://jenkins.canonical.com/system-apps/job/lp-history-service-ci/2/rebuild

review: Approve (continuous-integration)
lp:history-service/staging updated
265. By Gustavo Pichorim Boiko

Fix return value of a function that is now asynchronous

Revision history for this message
system-apps-ci-bot (system-apps-ci-bot) wrote :

PASSED: Continuous integration, rev:265
https://jenkins.canonical.com/system-apps/job/lp-history-service-ci/3/
Executed test runs:
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build/2349
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-0-fetch/2349
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=xenial+overlay/2167
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=xenial+overlay/2167/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=zesty/2167
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=zesty/2167/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=xenial+overlay/2167
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=xenial+overlay/2167/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=zesty/2167
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=zesty/2167/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=xenial+overlay/2167
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=xenial+overlay/2167/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=zesty/2167
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=zesty/2167/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://jenkins.canonical.com/system-apps/job/lp-history-service-ci/3/rebuild

review: Approve (continuous-integration)
Revision history for this message
Gustavo Pichorim Boiko (boiko) wrote :

The changes were reviewed individually and tested from this branch, so approving.

review: Approve

Unmerged revisions

265. By Gustavo Pichorim Boiko

Fix return value of a function that is now asynchronous

264. By Gustavo Pichorim Boiko

Make it possible to debug sqlite commands.

263. By Gustavo Pichorim Boiko

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.

262. By Gustavo Pichorim Boiko

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.

261. By Gustavo Pichorim Boiko

Reduce the dbus traffic when marking messages and threads as read.

260. By Gustavo Pichorim Boiko

Allow pass multiple fields on sort clause.

259. By Gustavo Pichorim Boiko

Mark entire conversations as read.

258. By Gustavo Pichorim Boiko

Improve Roles management performance by caching the retrieved data.

257. By Gustavo Pichorim Boiko

Only start saving information events about contacts joining and leaving after the self contact is in the local list of participants.

256. By Gustavo Pichorim Boiko

Improve the notifications of participants changing

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'CMakeLists.txt'
--- CMakeLists.txt 2016-08-16 21:06:10 +0000
+++ CMakeLists.txt 2017-03-23 01:25:43 +0000
@@ -50,6 +50,11 @@
5050
51find_program(DBUS_RUNNER dbus-test-runner)51find_program(DBUS_RUNNER dbus-test-runner)
5252
53option(TRACE_SQLITE "Print Sqlite commants to the log." off)
54if (${TRACE_SQLITE})
55 add_definitions(-DTRACE_SQLITE)
56endif()
57
53add_definitions(-DQT_NO_KEYWORDS)58add_definitions(-DQT_NO_KEYWORDS)
5459
55include_directories(60include_directories(
5661
=== modified file 'Ubuntu/History/historyeventmodel.cpp'
--- Ubuntu/History/historyeventmodel.cpp 2016-09-16 11:59:52 +0000
+++ Ubuntu/History/historyeventmodel.cpp 2017-03-23 01:25:43 +0000
@@ -1,5 +1,5 @@
1/*1/*
2 * Copyright (C) 2013-2015 Canonical, Ltd.2 * Copyright (C) 2013-2017 Canonical, Ltd.
3 *3 *
4 * Authors:4 * Authors:
5 * Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>5 * Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>
@@ -29,7 +29,7 @@
29#include <QTimerEvent>29#include <QTimerEvent>
3030
31HistoryEventModel::HistoryEventModel(QObject *parent) :31HistoryEventModel::HistoryEventModel(QObject *parent) :
32 HistoryModel(parent), mCanFetchMore(true), mEventWritingTimer(0)32 HistoryModel(parent), mCanFetchMore(true)
33{33{
34 // configure the roles34 // configure the roles
35 mRoles = HistoryModel::roleNames();35 mRoles = HistoryModel::roleNames();
@@ -95,10 +95,17 @@
95 result = event.eventId();95 result = event.eventId();
96 break;96 break;
97 case SenderIdRole:97 case SenderIdRole:
98 result = event.senderId();98 result = History::ContactMatcher::normalizeId(event.senderId());
99 break;99 break;
100 case SenderRole:100 case SenderRole:
101 result = History::ContactMatcher::instance()->contactInfo(event.accountId(), event.senderId());101 if (mMatchContacts) {
102 result = History::ContactMatcher::instance()->contactInfo(event.accountId(), event.senderId());
103 } else {
104 QVariantMap map;
105 map[History::FieldIdentifier] = event.senderId();
106 map[History::FieldAccountId] = event.accountId();
107 result = map;
108 }
102 break;109 break;
103 case TimestampRole:110 case TimestampRole:
104 result = event.timestamp();111 result = event.timestamp();
@@ -168,17 +175,21 @@
168 break;175 break;
169 case RemoteParticipantRole:176 case RemoteParticipantRole:
170 if (!voiceEvent.isNull()) {177 if (!voiceEvent.isNull()) {
171 result = voiceEvent.remoteParticipant();178 result = History::ContactMatcher::normalizeId(voiceEvent.remoteParticipant());
172 }179 }
173 break;180 break;
174 case SubjectAsAliasRole:181 case SubjectAsAliasRole:
175 if (!textEvent.isNull()) {182 if (!textEvent.isNull()) {
176 QVariantMap contactInfo = History::ContactMatcher::instance()->contactInfo(event.accountId(), textEvent.subject());183 if (mMatchContacts) {
177 QString returnValue = contactInfo[History::FieldAlias].toString();184 QVariantMap contactInfo = History::ContactMatcher::instance()->contactInfo(event.accountId(), textEvent.subject());
178 if (returnValue.isEmpty()) {185 QString returnValue = contactInfo[History::FieldAlias].toString();
179 returnValue = contactInfo[History::FieldIdentifier].toString();186 if (returnValue.isEmpty()) {
187 returnValue = contactInfo[History::FieldIdentifier].toString();
188 }
189 return returnValue;
190
180 }191 }
181 return returnValue;192 return textEvent.subject();
182 }193 }
183 break;194 break;
184 }195 }
@@ -307,23 +318,6 @@
307 return History::Manager::instance()->writeEvents(History::Events() << textEvent);318 return History::Manager::instance()->writeEvents(History::Events() << textEvent);
308}319}
309320
310bool HistoryEventModel::markEventAsRead(const QString &accountId, const QString &threadId, const QString &eventId, int eventType)
311{
312 History::Event event = History::Manager::instance()->getSingleEvent((History::EventType)eventType, accountId, threadId, eventId);
313 event.setNewEvent(false);
314 if (event.type() == History::EventTypeText) {
315 History::TextEvent textEvent = event;
316 textEvent.setReadTimestamp(QDateTime::currentDateTime());
317 event = textEvent;
318 }
319 mEventWritingQueue << event;
320 if (mEventWritingTimer != 0) {
321 killTimer(mEventWritingTimer);
322 }
323 mEventWritingTimer = startTimer(500);
324 return true;
325}
326
327void HistoryEventModel::updateQuery()321void HistoryEventModel::updateQuery()
328{322{
329 // remove all events from the model323 // remove all events from the model
@@ -341,7 +335,7 @@
341 mView->disconnect(this);335 mView->disconnect(this);
342 }336 }
343337
344 if (mFilter) {338 if (mFilter && mFilter->filter().isValid()) {
345 queryFilter = mFilter->filter();339 queryFilter = mFilter->filter();
346 } else {340 } else {
347 // we should not return anything if there is no filter341 // we should not return anything if there is no filter
@@ -363,6 +357,9 @@
363 SIGNAL(eventsRemoved(History::Events)),357 SIGNAL(eventsRemoved(History::Events)),
364 SLOT(onEventsRemoved(History::Events)));358 SLOT(onEventsRemoved(History::Events)));
365 connect(mView.data(),359 connect(mView.data(),
360 SIGNAL(threadsRemoved(History::Threads)),
361 SLOT(onThreadsRemoved(History::Threads)));
362 connect(mView.data(),
366 SIGNAL(invalidated()),363 SIGNAL(invalidated()),
367 SLOT(triggerQueryUpdate()));364 SLOT(triggerQueryUpdate()));
368365
@@ -439,21 +436,21 @@
439 // should be handle internally in History::EventView?436 // should be handle internally in History::EventView?
440}437}
441438
442void HistoryEventModel::timerEvent(QTimerEvent *event)439void HistoryEventModel::onThreadsRemoved(const History::Threads &threads)
443{440{
444 HistoryModel::timerEvent(event);441 // When a thread is removed we don't get event removed signals,
445 if (event->timerId() == mEventWritingTimer) {442 // so we compare and find if we have an event matching that thread.
446 killTimer(mEventWritingTimer);443 // in case we find it, we invalidate the whole view as there might be
447 mEventWritingTimer = 0;444 // out of date cached data on the daemon side
448445 int count = rowCount();
449 if (mEventWritingQueue.isEmpty()) {446 Q_FOREACH(const History::Thread &thread, threads) {
450 return;447 for (int i = 0; i < count; ++i) {
451 }448 QModelIndex idx = index(i);
452449 if (idx.data(AccountIdRole).toString() == thread.accountId() &&
453 qDebug() << "Goint to update" << mEventWritingQueue.count() << "events.";450 idx.data(ThreadIdRole).toString() == thread.threadId()) {
454 if (History::Manager::instance()->writeEvents(mEventWritingQueue)) {451 triggerQueryUpdate();
455 qDebug() << "... succeeded!";452 return;
456 mEventWritingQueue.clear();453 }
457 }454 }
458 }455 }
459}456}
460457
=== modified file 'Ubuntu/History/historyeventmodel.h'
--- Ubuntu/History/historyeventmodel.h 2016-09-16 12:32:37 +0000
+++ Ubuntu/History/historyeventmodel.h 2017-03-23 01:25:43 +0000
@@ -1,5 +1,5 @@
1/*1/*
2 * Copyright (C) 2013-2015 Canonical, Ltd.2 * Copyright (C) 2013-2017 Canonical, Ltd.
3 *3 *
4 * Authors:4 * Authors:
5 * Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>5 * Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>
@@ -66,7 +66,6 @@
6666
67 Q_INVOKABLE bool removeEvents(const QVariantList &eventsProperties);67 Q_INVOKABLE bool removeEvents(const QVariantList &eventsProperties);
68 Q_INVOKABLE bool writeEvents(const QVariantList &eventsProperties);68 Q_INVOKABLE bool writeEvents(const QVariantList &eventsProperties);
69 Q_INVOKABLE bool markEventAsRead(const QString &accountId, const QString &threadId, const QString &eventId, int eventType);
70 Q_INVOKABLE bool removeEventAttachment(const QString &accountId, const QString &threadId, const QString &eventId, int eventType, const QString &attachmentId);69 Q_INVOKABLE bool removeEventAttachment(const QString &accountId, const QString &threadId, const QString &eventId, int eventType, const QString &attachmentId);
7170
72protected Q_SLOTS:71protected Q_SLOTS:
@@ -74,9 +73,9 @@
74 virtual void onEventsAdded(const History::Events &events);73 virtual void onEventsAdded(const History::Events &events);
75 virtual void onEventsModified(const History::Events &events);74 virtual void onEventsModified(const History::Events &events);
76 virtual void onEventsRemoved(const History::Events &events);75 virtual void onEventsRemoved(const History::Events &events);
76 virtual void onThreadsRemoved(const History::Threads &threads);
7777
78protected:78protected:
79 void timerEvent(QTimerEvent *event);
80 History::Events fetchNextPage();79 History::Events fetchNextPage();
8180
82private:81private:
@@ -85,8 +84,6 @@
85 bool mCanFetchMore;84 bool mCanFetchMore;
86 QHash<int, QByteArray> mRoles;85 QHash<int, QByteArray> mRoles;
87 mutable QMap<History::TextEvent, QList<QVariant> > mAttachmentCache;86 mutable QMap<History::TextEvent, QList<QVariant> > mAttachmentCache;
88 History::Events mEventWritingQueue;
89 int mEventWritingTimer;
90};87};
9188
92#endif // HISTORYEVENTMODEL_H89#endif // HISTORYEVENTMODEL_H
9390
=== modified file 'Ubuntu/History/historygroupedthreadsmodel.cpp'
--- Ubuntu/History/historygroupedthreadsmodel.cpp 2015-10-08 19:35:40 +0000
+++ Ubuntu/History/historygroupedthreadsmodel.cpp 2017-03-23 01:25:43 +0000
@@ -199,6 +199,21 @@
199 }199 }
200}200}
201201
202History::Threads HistoryGroupedThreadsModel::restoreParticipants(const History::Threads &oldThreads, const History::Threads &newThreads)
203{
204 History::Threads updated = newThreads;
205 for(History::Thread &thread : updated) {
206 if (!thread.participants().isEmpty()) {
207 continue;
208 }
209 int i = oldThreads.indexOf(thread);
210 if (i >=0) {
211 thread.addParticipants(oldThreads[i].participants());
212 }
213 }
214 return updated;
215}
216
202void HistoryGroupedThreadsModel::updateQuery()217void HistoryGroupedThreadsModel::updateQuery()
203{218{
204 // remove all entries and call the query update219 // remove all entries and call the query update
@@ -217,6 +232,7 @@
217 processThreadGrouping(thread);232 processThreadGrouping(thread);
218 }233 }
219234
235 fetchParticipantsIfNeeded(threads);
220 notifyDataChanged();236 notifyDataChanged();
221}237}
222238
@@ -225,7 +241,7 @@
225 Q_FOREACH(const History::Thread &thread, threads) {241 Q_FOREACH(const History::Thread &thread, threads) {
226 processThreadGrouping(thread);242 processThreadGrouping(thread);
227 }243 }
228244 fetchParticipantsIfNeeded(threads);
229 notifyDataChanged();245 notifyDataChanged();
230}246}
231247
@@ -238,6 +254,42 @@
238 notifyDataChanged();254 notifyDataChanged();
239}255}
240256
257void HistoryGroupedThreadsModel::onThreadParticipantsChanged(const History::Thread &thread, const History::Participants &added, const History::Participants &removed, const History::Participants &modified)
258{
259 int pos = existingPositionForEntry(thread);
260 if (pos >= 0) {
261 HistoryThreadGroup &group = mGroups[pos];
262 if (group.displayedThread == thread) {
263 group.displayedThread.removeParticipants(removed);
264 group.displayedThread.removeParticipants(modified);
265 group.displayedThread.addParticipants(added);
266 group.displayedThread.addParticipants(modified);
267 }
268
269 Q_FOREACH(const History::Thread &existingThread, group.threads) {
270 if (existingThread == thread) {
271 History::Thread modifiedThread = existingThread;
272 group.threads.removeOne(existingThread);
273 modifiedThread.removeParticipants(removed);
274 modifiedThread.removeParticipants(modified);
275 modifiedThread.addParticipants(added);
276 modifiedThread.addParticipants(modified);
277 group.threads.append(modifiedThread);
278 }
279 }
280 QModelIndex idx = index(pos);
281 Q_EMIT dataChanged(idx, idx);
282 }
283
284 // watch the contact info for the received participants
285 Q_FOREACH(const History::Participant &participant, added) {
286 watchContactInfo(thread.accountId(), participant.identifier(), participant.properties());
287 }
288 Q_FOREACH(const History::Participant &participant, modified) {
289 watchContactInfo(thread.accountId(), participant.identifier(), participant.properties());
290 }
291}
292
241void HistoryGroupedThreadsModel::processThreadGrouping(const History::Thread &thread)293void HistoryGroupedThreadsModel::processThreadGrouping(const History::Thread &thread)
242{294{
243 QVariantMap queryProperties;295 QVariantMap queryProperties;
@@ -262,7 +314,7 @@
262 }314 }
263315
264 HistoryThreadGroup &group = mGroups[pos];316 HistoryThreadGroup &group = mGroups[pos];
265 group.threads = groupedThread.groupedThreads();317 group.threads = restoreParticipants(group.threads, groupedThread.groupedThreads());
266318
267 updateDisplayedThread(group);319 updateDisplayedThread(group);
268 markGroupAsChanged(group);320 markGroupAsChanged(group);
269321
=== modified file 'Ubuntu/History/historygroupedthreadsmodel.h'
--- Ubuntu/History/historygroupedthreadsmodel.h 2015-09-29 20:34:22 +0000
+++ Ubuntu/History/historygroupedthreadsmodel.h 2017-03-23 01:25:43 +0000
@@ -67,12 +67,17 @@
67 int existingPositionForEntry(const History::Thread &thread) const;67 int existingPositionForEntry(const History::Thread &thread) const;
68 void removeGroup(const HistoryThreadGroup &group);68 void removeGroup(const HistoryThreadGroup &group);
69 void updateDisplayedThread(HistoryThreadGroup &group);69 void updateDisplayedThread(HistoryThreadGroup &group);
70 History::Threads restoreParticipants(const History::Threads &oldThreads, const History::Threads &newThreads);
7071
71protected Q_SLOTS:72protected Q_SLOTS:
72 virtual void updateQuery();73 virtual void updateQuery();
73 virtual void onThreadsAdded(const History::Threads &threads);74 virtual void onThreadsAdded(const History::Threads &threads);
74 virtual void onThreadsModified(const History::Threads &threads);75 virtual void onThreadsModified(const History::Threads &threads);
75 virtual void onThreadsRemoved(const History::Threads &threads);76 virtual void onThreadsRemoved(const History::Threads &threads);
77 void onThreadParticipantsChanged(const History::Thread &thread,
78 const History::Participants &added,
79 const History::Participants &removed,
80 const History::Participants &modified) override;
7681
77private Q_SLOTS:82private Q_SLOTS:
78 void processThreadGrouping(const History::Thread &thread);83 void processThreadGrouping(const History::Thread &thread);
7984
=== modified file 'Ubuntu/History/historymodel.cpp'
--- Ubuntu/History/historymodel.cpp 2016-11-24 12:22:11 +0000
+++ Ubuntu/History/historymodel.cpp 2017-03-23 01:25:43 +0000
@@ -28,13 +28,14 @@
28#include "textevent.h"28#include "textevent.h"
29#include "manager.h"29#include "manager.h"
30#include "utils_p.h"30#include "utils_p.h"
31#include "voiceevent.h"
31#include <QTimerEvent>32#include <QTimerEvent>
32#include <QCryptographicHash>33#include <QCryptographicHash>
33#include <QDebug>34#include <QDebug>
3435
35HistoryModel::HistoryModel(QObject *parent) :36HistoryModel::HistoryModel(QObject *parent) :
36 QAbstractListModel(parent), mFilter(0), mSort(new HistoryQmlSort(this)),37 QAbstractListModel(parent), mFilter(0), mSort(new HistoryQmlSort(this)),
37 mType(EventTypeText), mMatchContacts(false), mUpdateTimer(0), mWaitingForQml(false)38 mType(EventTypeText), mMatchContacts(false), mUpdateTimer(0), mEventWritingTimer(0), mThreadWritingTimer(0), mWaitingForQml(false)
38{39{
39 // configure the roles40 // configure the roles
40 mRoles[AccountIdRole] = "accountId";41 mRoles[AccountIdRole] = "accountId";
@@ -341,6 +342,20 @@
341 return QString::null;342 return QString::null;
342}343}
343344
345void HistoryModel::requestThreadParticipants(const QVariantList &threads)
346{
347 History::Threads theThreads;
348 Q_FOREACH(const QVariant &threadVariant, threads) {
349 History::Thread theThread = History::Thread::fromProperties(threadVariant.toMap());
350 // if the given thread already has the list of participants, there is no point
351 // in fetching it again
352 if (theThread.participants().isEmpty()) {
353 theThreads << theThread;
354 }
355 }
356 History::Manager::instance()->requestThreadParticipants(theThreads);
357}
358
344bool HistoryModel::writeTextInformationEvent(const QString &accountId, const QString &threadId, const QStringList &participants, const QString &message, int informationType, const QString &subject)359bool HistoryModel::writeTextInformationEvent(const QString &accountId, const QString &threadId, const QStringList &participants, const QString &message, int informationType, const QString &subject)
345{360{
346 if (participants.isEmpty() || threadId.isEmpty() || accountId.isEmpty()) {361 if (participants.isEmpty() || threadId.isEmpty() || accountId.isEmpty()) {
@@ -385,7 +400,7 @@
385 // FIXME: right now we might be grouping threads from different accounts, so we are not enforcing400 // FIXME: right now we might be grouping threads from different accounts, so we are not enforcing
386 // the accountId to be the same as the one from the contact info, but maybe we need to do that401 // the accountId to be the same as the one from the contact info, but maybe we need to do that
387 // in the future?402 // in the future?
388 if (History::Utils::compareIds(accountId, participant.identifier(), identifier)) {403 if (History::Utils::compareIds(accountId, History::ContactMatcher::normalizeId(participant.identifier()), identifier)) {
389 changedIndexes << idx;404 changedIndexes << idx;
390 }405 }
391 }406 }
@@ -406,19 +421,50 @@
406421
407void HistoryModel::timerEvent(QTimerEvent *event)422void HistoryModel::timerEvent(QTimerEvent *event)
408{423{
409 if (event->timerId() == mUpdateTimer && !mWaitingForQml) {424 if (event->timerId() == mUpdateTimer) {
410 killTimer(mUpdateTimer);425 if (!mWaitingForQml) {
411 mUpdateTimer = 0;426 killTimer(mUpdateTimer);
412 updateQuery();427 mUpdateTimer = 0;
428 updateQuery();
429 }
430 } else if (event->timerId() == mEventWritingTimer) {
431 killTimer(mEventWritingTimer);
432 mEventWritingTimer = 0;
433
434 if (mEventWritingQueue.isEmpty()) {
435 return;
436 }
437
438 if (History::Manager::instance()->writeEvents(mEventWritingQueue)) {
439 mEventWritingQueue.clear();
440 }
441 } else if (event->timerId() == mThreadWritingTimer) {
442 killTimer(mThreadWritingTimer);
443 mThreadWritingTimer = 0;
444
445 if (mThreadWritingQueue.isEmpty()) {
446 return;
447 }
448
449 History::Manager::instance()->markThreadsAsRead(mThreadWritingQueue);
450 mThreadWritingQueue.clear();
413 }451 }
414}452}
415453
416bool HistoryModel::lessThan(const QVariantMap &left, const QVariantMap &right) const454bool HistoryModel::lessThan(const QVariantMap &left, const QVariantMap &right) const
417{455{
418 QVariant leftValue = left[sort()->sortField()];456 QStringList sortFields = sort()->sortField().split(",");
419 QVariant rightValue = right[sort()->sortField()];457
420458 while(!sortFields.isEmpty()) {
421 return leftValue < rightValue;459 QString sortField = sortFields.takeFirst().trimmed();
460 QVariant leftValue = left.value(sortField, QVariant());
461 QVariant rightValue = right.value(sortField, QVariant());
462
463 if (leftValue != rightValue) {
464 return leftValue < rightValue;
465 }
466 }
467 return false;
422}468}
423469
424int HistoryModel::positionForItem(const QVariantMap &item) const470int HistoryModel::positionForItem(const QVariantMap &item) const
@@ -470,6 +516,56 @@
470 return data;516 return data;
471}517}
472518
519bool HistoryModel::markEventAsRead(const QVariantMap &eventProperties)
520{
521 History::Event event;
522 History::EventType type = (History::EventType) eventProperties[History::FieldType].toInt();
523 switch (type) {
524 case History::EventTypeText:
525 event = History::TextEvent::fromProperties(eventProperties);
526 break;
527 case History::EventTypeVoice:
528 event = History::VoiceEvent::fromProperties(eventProperties);
529 break;
530 }
531
532 event.setNewEvent(false);
533 if (event.type() == History::EventTypeText) {
534 History::TextEvent textEvent = event;
535 textEvent.setReadTimestamp(QDateTime::currentDateTime());
536 event = textEvent;
537 }
538 // for repeated events, keep the last called one only
539 if (mEventWritingQueue.contains(event)) {
540 mEventWritingQueue.removeOne(event);
541 }
542 mEventWritingQueue << event;
543 if (mEventWritingTimer != 0) {
544 killTimer(mEventWritingTimer);
545 }
546 mEventWritingTimer = startTimer(500);
547 return true;
548}
549
550void HistoryModel::markThreadsAsRead(const QVariantList &threadsProperties)
551{
552 Q_FOREACH(const QVariant &entry, threadsProperties) {
553 QVariantMap threadProperties = entry.toMap();
554 History::Thread thread = History::Thread::fromProperties(threadProperties);
555 if (!thread.isNull()) {
556 if (mThreadWritingQueue.contains(thread)) {
557 continue;
558 }
559 mThreadWritingQueue << thread;
560 }
561 }
562
563 if (mThreadWritingTimer != 0) {
564 killTimer(mThreadWritingTimer);
565 }
566 mThreadWritingTimer = startTimer(2000);
567}
568
473void HistoryModel::classBegin()569void HistoryModel::classBegin()
474{570{
475 mWaitingForQml = true;571 mWaitingForQml = true;
476572
=== modified file 'Ubuntu/History/historymodel.h'
--- Ubuntu/History/historymodel.h 2016-11-09 17:42:27 +0000
+++ Ubuntu/History/historymodel.h 2017-03-23 01:25:43 +0000
@@ -23,6 +23,8 @@
23#define HISTORYMODEL_H23#define HISTORYMODEL_H
2424
25#include "types.h"25#include "types.h"
26#include "event.h"
27#include "thread.h"
26#include "historyqmlfilter.h"28#include "historyqmlfilter.h"
27#include "historyqmlsort.h"29#include "historyqmlsort.h"
28#include <QAbstractListModel>30#include <QAbstractListModel>
@@ -166,6 +168,7 @@
166 const QStringList &participants,168 const QStringList &participants,
167 int matchFlags = (int)History::MatchCaseSensitive,169 int matchFlags = (int)History::MatchCaseSensitive,
168 bool create = false);170 bool create = false);
171 Q_INVOKABLE void requestThreadParticipants(const QVariantList &threads);
169 Q_INVOKABLE bool writeTextInformationEvent(const QString &accountId,172 Q_INVOKABLE bool writeTextInformationEvent(const QString &accountId,
170 const QString &threadId,173 const QString &threadId,
171 const QStringList &participants,174 const QStringList &participants,
@@ -175,6 +178,10 @@
175178
176 Q_INVOKABLE virtual QVariant get(int row) const;179 Q_INVOKABLE virtual QVariant get(int row) const;
177180
181 // Marking events and threads as read
182 Q_INVOKABLE bool markEventAsRead(const QVariantMap &eventProperties);
183 Q_INVOKABLE void markThreadsAsRead(const QVariantList &threadsProperties);
184
178 // QML parser status things185 // QML parser status things
179 void classBegin();186 void classBegin();
180 void componentComplete();187 void componentComplete();
@@ -206,6 +213,10 @@
206213
207private:214private:
208 QHash<int, QByteArray> mRoles;215 QHash<int, QByteArray> mRoles;
216 History::Events mEventWritingQueue;
217 int mEventWritingTimer;
218 History::Threads mThreadWritingQueue;
219 int mThreadWritingTimer;
209 int mUpdateTimer;220 int mUpdateTimer;
210 bool mWaitingForQml;221 bool mWaitingForQml;
211};222};
212223
=== modified file 'Ubuntu/History/historythreadmodel.cpp'
--- Ubuntu/History/historythreadmodel.cpp 2016-06-17 01:49:46 +0000
+++ Ubuntu/History/historythreadmodel.cpp 2017-03-23 01:25:43 +0000
@@ -26,6 +26,8 @@
26#include "voiceevent.h"26#include "voiceevent.h"
27#include <QDBusMetaType>27#include <QDBusMetaType>
2828
29#include <QDebug>
30
29Q_DECLARE_METATYPE(History::TextEventAttachments)31Q_DECLARE_METATYPE(History::TextEventAttachments)
30Q_DECLARE_METATYPE(QList<QVariantMap>)32Q_DECLARE_METATYPE(QList<QVariantMap>)
3133
@@ -190,7 +192,6 @@
190 }192 }
191 break;193 break;
192 }194 }
193
194 return result;195 return result;
195}196}
196197
@@ -214,13 +215,6 @@
214 mCanFetchMore = false;215 mCanFetchMore = false;
215 Q_EMIT canFetchMoreChanged();216 Q_EMIT canFetchMoreChanged();
216 } else {217 } else {
217 Q_FOREACH(const History::Thread &thread, threads) {
218 // insert the identifiers in the contact map
219 Q_FOREACH(const History::Participant &participant, thread.participants()) {
220 watchContactInfo(thread.accountId(), participant.identifier(), participant.properties());
221 }
222 }
223
224 beginInsertRows(QModelIndex(), mThreads.count(), mThreads.count() + threads.count() - 1);218 beginInsertRows(QModelIndex(), mThreads.count(), mThreads.count() + threads.count() - 1);
225 mThreads << threads;219 mThreads << threads;
226 endInsertRows();220 endInsertRows();
@@ -294,6 +288,10 @@
294 SIGNAL(threadsRemoved(History::Threads)),288 SIGNAL(threadsRemoved(History::Threads)),
295 SLOT(onThreadsRemoved(History::Threads)));289 SLOT(onThreadsRemoved(History::Threads)));
296 connect(mThreadView.data(),290 connect(mThreadView.data(),
291 SIGNAL(threadParticipantsChanged(History::Thread, History::Participants, History::Participants, History::Participants)),
292 SLOT(onThreadParticipantsChanged(History::Thread, History::Participants, History::Participants, History::Participants)));
293
294 connect(mThreadView.data(),
297 SIGNAL(invalidated()),295 SIGNAL(invalidated()),
298 SLOT(triggerQueryUpdate()));296 SLOT(triggerQueryUpdate()));
299297
@@ -311,6 +309,43 @@
311 fetchMore(QModelIndex());309 fetchMore(QModelIndex());
312}310}
313311
312
313void HistoryThreadModel::onThreadParticipantsChanged(const History::Thread &thread, const History::Participants &added, const History::Participants &removed, const History::Participants &modified)
314{
315 int pos = mThreads.indexOf(thread);
316 if (pos >= 0) {
317 mThreads[pos].removeParticipants(removed);
318 mThreads[pos].removeParticipants(modified);
319 mThreads[pos].addParticipants(added);
320 mThreads[pos].addParticipants(modified);
321 QModelIndex idx = index(pos);
322 Q_EMIT dataChanged(idx, idx);
323 }
324
325 // watch the contact info for the received participants
326 Q_FOREACH(const History::Participant &participant, added) {
327 watchContactInfo(thread.accountId(), participant.identifier(), participant.properties());
328 }
329 Q_FOREACH(const History::Participant &participant, modified) {
330 watchContactInfo(thread.accountId(), participant.identifier(), participant.properties());
331 }
332}
333
334void HistoryThreadModel::fetchParticipantsIfNeeded(const History::Threads &threads)
335{
336 History::Threads filtered;
337 Q_FOREACH(const History::Thread &thread, threads) {
338 if (thread.type() == History::EventTypeText && thread.participants().isEmpty() &&
339 (thread.chatType() != History::ChatTypeRoom || thread.accountId().startsWith("ofono"))) {
340 filtered << thread;
341 }
342 }
343 if (filtered.isEmpty()) {
344 return;
345 }
346 History::Manager::instance()->requestThreadParticipants(filtered);
347}
348
314void HistoryThreadModel::onThreadsAdded(const History::Threads &threads)349void HistoryThreadModel::onThreadsAdded(const History::Threads &threads)
315{350{
316 if (threads.isEmpty()) {351 if (threads.isEmpty()) {
@@ -328,6 +363,7 @@
328 mThreads.insert(pos, thread);363 mThreads.insert(pos, thread);
329 endInsertRows();364 endInsertRows();
330 }365 }
366 fetchParticipantsIfNeeded(threads);
331}367}
332368
333void HistoryThreadModel::onThreadsModified(const History::Threads &threads)369void HistoryThreadModel::onThreadsModified(const History::Threads &threads)
@@ -349,6 +385,7 @@
349 if (!newThreads.isEmpty()) {385 if (!newThreads.isEmpty()) {
350 onThreadsAdded(newThreads);386 onThreadsAdded(newThreads);
351 }387 }
388 fetchParticipantsIfNeeded(threads);
352}389}
353390
354void HistoryThreadModel::onThreadsRemoved(const History::Threads &threads)391void HistoryThreadModel::onThreadsRemoved(const History::Threads &threads)
@@ -369,5 +406,7 @@
369406
370History::Threads HistoryThreadModel::fetchNextPage()407History::Threads HistoryThreadModel::fetchNextPage()
371{408{
372 return mThreadView->nextPage();409 History::Threads threads = mThreadView->nextPage();
410 fetchParticipantsIfNeeded(threads);
411 return threads;
373}412}
374413
=== modified file 'Ubuntu/History/historythreadmodel.h'
--- Ubuntu/History/historythreadmodel.h 2016-06-17 01:49:46 +0000
+++ Ubuntu/History/historythreadmodel.h 2017-03-23 01:25:43 +0000
@@ -76,8 +76,10 @@
76 virtual void onThreadsAdded(const History::Threads &threads);76 virtual void onThreadsAdded(const History::Threads &threads);
77 virtual void onThreadsModified(const History::Threads &threads);77 virtual void onThreadsModified(const History::Threads &threads);
78 virtual void onThreadsRemoved(const History::Threads &threads);78 virtual void onThreadsRemoved(const History::Threads &threads);
79 virtual void onThreadParticipantsChanged(const History::Thread &thread, const History::Participants &added, const History::Participants &removed, const History::Participants &modified);
7980
80protected:81protected:
82 void fetchParticipantsIfNeeded(const History::Threads &threads);
81 History::Threads fetchNextPage();83 History::Threads fetchNextPage();
82 bool mCanFetchMore;84 bool mCanFetchMore;
83 bool mGroupThreads;85 bool mGroupThreads;
8486
=== modified file 'cmake/modules/GenerateTest.cmake'
--- cmake/modules/GenerateTest.cmake 2015-09-28 14:26:09 +0000
+++ cmake/modules/GenerateTest.cmake 2017-03-23 01:25:43 +0000
@@ -79,6 +79,7 @@
79 set(ARG_ENVIRONMENT HOME=${TMPDIR}79 set(ARG_ENVIRONMENT HOME=${TMPDIR}
80 HISTORY_PLUGIN_PATH=${CMAKE_BINARY_DIR}/plugins/sqlite80 HISTORY_PLUGIN_PATH=${CMAKE_BINARY_DIR}/plugins/sqlite
81 HISTORY_SQLITE_DBPATH=:memory:81 HISTORY_SQLITE_DBPATH=:memory:
82 HISTORY_LOCK_FILE=${TMPDIR}/history-service.lock
82 MC_ACCOUNT_DIR=${TMPDIR}83 MC_ACCOUNT_DIR=${TMPDIR}
83 MC_MANAGER_DIR=${TMPDIR})84 MC_MANAGER_DIR=${TMPDIR})
84 endif ()85 endif ()
8586
=== modified file 'daemon/HistoryService.xml'
--- daemon/HistoryService.xml 2016-04-15 22:23:21 +0000
+++ daemon/HistoryService.xml 2017-03-23 01:25:43 +0000
@@ -35,6 +35,15 @@
35 <arg type="a{sv}" direction="out"/>35 <arg type="a{sv}" direction="out"/>
36 <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QVariantMap"/>36 <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QVariantMap"/>
37 </method>37 </method>
38 <method name="ParticipantsForThreads">
39 <dox:d><![CDATA[
40 Return the participants for the given threads
41 ]]></dox:d>
42 <arg name="threadIds" type="a(a{sv})" direction="in"/>
43 <annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QList &lt; QVariantMap &gt;"/>
44 <arg name="participants" type="a(a{sv})" direction="out"/>
45 <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QList &lt; QVariantMap &gt;"/>
46 </method>
38 <method name="WriteEvents">47 <method name="WriteEvents">
39 <dox:d><![CDATA[48 <dox:d><![CDATA[
40 Write the given events to the storage.49 Write the given events to the storage.
@@ -62,6 +71,13 @@
62 <arg type="b" direction="out"/>71 <arg type="b" direction="out"/>
63 <annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QList &lt; QVariantMap &gt;"/>72 <annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QList &lt; QVariantMap &gt;"/>
64 </method>73 </method>
74 <method name="MarkThreadsAsRead">
75 <dox:d><![CDATA[
76 Mark an entire thread as read
77 ]]></dox:d>
78 <arg name="threads" type="a(a{sv})" direction="in"/>
79 <annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QList &lt; QVariantMap &gt;"/>
80 </method>
65 <method name="QueryThreads">81 <method name="QueryThreads">
66 <dox:d><![CDATA[82 <dox:d><![CDATA[
67 Creates a threads view with the given filter and sort order.83 Creates a threads view with the given filter and sort order.
@@ -159,5 +175,18 @@
159 <arg name="events" type="a(a{sv})"/>175 <arg name="events" type="a(a{sv})"/>
160 <annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QList &lt; QVariantMap &gt;"/>176 <annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QList &lt; QVariantMap &gt;"/>
161 </signal>177 </signal>
178 <signal name="ThreadParticipantsChanged">
179 <dox:d><![CDATA[
180 Participants changed in a certain thread changed.
181 ]]></dox:d>
182 <arg name="thread" type="a{sv}"/>
183 <arg name="added" type="a(a{sv})"/>
184 <arg name="removed" type="a(a{sv})"/>
185 <arg name="modified" type="a(a{sv})"/>
186 <annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QVariantMap"/>
187 <annotation name="org.qtproject.QtDBus.QtTypeName.In1" value="QList &lt; QVariantMap &gt;"/>
188 <annotation name="org.qtproject.QtDBus.QtTypeName.In2" value="QList &lt; QVariantMap &gt;"/>
189 <annotation name="org.qtproject.QtDBus.QtTypeName.In3" value="QList &lt; QVariantMap &gt;"/>
190 </signal>
162 </interface>191 </interface>
163</node>192</node>
164193
=== modified file 'daemon/callchannelobserver.cpp'
--- daemon/callchannelobserver.cpp 2013-07-12 14:30:18 +0000
+++ daemon/callchannelobserver.cpp 2017-03-23 01:25:43 +0000
@@ -40,6 +40,7 @@
40 SLOT(onCallStateChanged(Tp::CallState)));40 SLOT(onCallStateChanged(Tp::CallState)));
4141
42 mChannels.append(callChannel);42 mChannels.append(callChannel);
43 mCallStates[callChannel.data()] = callChannel->callState();
43}44}
4445
4546
@@ -51,11 +52,24 @@
51 }52 }
5253
53 switch (state) {54 switch (state) {
54 case Tp::CallStateEnded:55 case Tp::CallStateEnded: {
55 Q_EMIT callEnded(Tp::CallChannelPtr(channel));56 bool incoming = !channel->isRequested();
57 bool missed = incoming && channel->callStateReason().reason == Tp::CallStateChangeReasonNoAnswer;
58
59 // If the call state is not missed at this point, we need to check from which state we transitioned to ended,
60 // if from Initialised, it means it was indeed missed
61 if (incoming && !missed) {
62 missed = mCallStates[channel] == Tp::CallStateInitialised;
63 }
64 mCallStates.remove(channel);
65 mChannels.removeOne(Tp::CallChannelPtr(channel));
66 Q_EMIT callEnded(Tp::CallChannelPtr(channel), missed);
56 break;67 break;
68 }
57 case Tp::CallStateActive:69 case Tp::CallStateActive:
58 channel->setProperty("activeTimestamp", QDateTime::currentDateTime());70 channel->setProperty("activeTimestamp", QDateTime::currentDateTime());
71 default:
72 mCallStates[channel] = state;
59 break;73 break;
60 }74 }
61}75}
6276
=== modified file 'daemon/callchannelobserver.h'
--- daemon/callchannelobserver.h 2013-07-12 14:30:18 +0000
+++ daemon/callchannelobserver.h 2017-03-23 01:25:43 +0000
@@ -35,13 +35,14 @@
35 void onCallChannelAvailable(Tp::CallChannelPtr callChannel);35 void onCallChannelAvailable(Tp::CallChannelPtr callChannel);
3636
37Q_SIGNALS:37Q_SIGNALS:
38 void callEnded(Tp::CallChannelPtr callChannel);38 void callEnded(Tp::CallChannelPtr callChannel, bool missed);
3939
40protected Q_SLOTS:40protected Q_SLOTS:
41 void onCallStateChanged(Tp::CallState state);41 void onCallStateChanged(Tp::CallState state);
4242
43private:43private:
44 QList<Tp::CallChannelPtr> mChannels;44 QList<Tp::CallChannelPtr> mChannels;
45 QMap<Tp::CallChannel*,Tp::CallState> mCallStates;
45};46};
4647
47#endif // CALLCHANNELOBSERVER_H48#endif // CALLCHANNELOBSERVER_H
4849
=== modified file 'daemon/historydaemon.cpp'
--- daemon/historydaemon.cpp 2016-11-24 02:02:32 +0000
+++ daemon/historydaemon.cpp 2017-03-23 01:25:43 +0000
@@ -1,5 +1,5 @@
1/*1/*
2 * Copyright (C) 2013-2016 Canonical, Ltd.2 * Copyright (C) 2013-2017 Canonical, Ltd.
3 *3 *
4 * Authors:4 * Authors:
5 * Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>5 * Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>
@@ -85,10 +85,11 @@
85{85{
86 Q_FOREACH (QVariant participant, thread[History::FieldParticipants].toList()) {86 Q_FOREACH (QVariant participant, thread[History::FieldParticipants].toList()) {
87 // found if same identifier and as member into thread info87 // found if same identifier and as member into thread info
88 QVariantMap participantMap = participant.toMap();
88 if (History::Utils::compareIds(thread[History::FieldAccountId].toString(),89 if (History::Utils::compareIds(thread[History::FieldAccountId].toString(),
89 contact->id(),90 contact->id(),
90 participant.toMap()[History::FieldIdentifier].toString()) &&91 participantMap[History::FieldIdentifier].toString()) &&
91 participant.toMap()[History::FieldParticipantState].toUInt() == History::ParticipantStateRegular)92 participantMap[History::FieldParticipantState].toUInt() == History::ParticipantStateRegular)
92 {93 {
93 return true;94 return true;
94 }95 }
@@ -131,8 +132,8 @@
131 History::TelepathyHelper::instance()->registerChannelObserver();132 History::TelepathyHelper::instance()->registerChannelObserver();
132133
133 connect(&mCallObserver,134 connect(&mCallObserver,
134 SIGNAL(callEnded(Tp::CallChannelPtr)),135 SIGNAL(callEnded(Tp::CallChannelPtr, bool)),
135 SLOT(onCallEnded(Tp::CallChannelPtr)));136 SLOT(onCallEnded(Tp::CallChannelPtr, bool)));
136 connect(&mTextObserver,137 connect(&mTextObserver,
137 SIGNAL(messageReceived(Tp::TextChannelPtr,Tp::ReceivedMessage)),138 SIGNAL(messageReceived(Tp::TextChannelPtr,Tp::ReceivedMessage)),
138 SLOT(onMessageReceived(Tp::TextChannelPtr,Tp::ReceivedMessage)));139 SLOT(onMessageReceived(Tp::TextChannelPtr,Tp::ReceivedMessage)));
@@ -140,11 +141,11 @@
140 SIGNAL(messageSent(Tp::TextChannelPtr,Tp::Message,QString)),141 SIGNAL(messageSent(Tp::TextChannelPtr,Tp::Message,QString)),
141 SLOT(onMessageSent(Tp::TextChannelPtr,Tp::Message,QString)));142 SLOT(onMessageSent(Tp::TextChannelPtr,Tp::Message,QString)));
142 connect(&mTextObserver,143 connect(&mTextObserver,
143 SIGNAL(messageRead(Tp::TextChannelPtr,Tp::ReceivedMessage)),
144 SLOT(onMessageRead(Tp::TextChannelPtr,Tp::ReceivedMessage)));
145 connect(&mTextObserver,
146 SIGNAL(channelAvailable(Tp::TextChannelPtr)),144 SIGNAL(channelAvailable(Tp::TextChannelPtr)),
147 SLOT(onTextChannelAvailable(Tp::TextChannelPtr)));145 SLOT(onTextChannelAvailable(Tp::TextChannelPtr)));
146 connect(&mTextObserver,
147 SIGNAL(textChannelInvalidated(Tp::TextChannelPtr)),
148 SLOT(onTextChannelInvalidated(Tp::TextChannelPtr)));
148149
149 // FIXME: we need to do this in a better way, but for now this should do150 // FIXME: we need to do this in a better way, but for now this should do
150 mProtocolFlags["ofono"] = History::MatchPhoneNumber;151 mProtocolFlags["ofono"] = History::MatchPhoneNumber;
@@ -163,23 +164,39 @@
163164
164void HistoryDaemon::onRolesChanged(const HandleRolesMap &added, const HandleRolesMap &removed)165void HistoryDaemon::onRolesChanged(const HandleRolesMap &added, const HandleRolesMap &removed)
165{166{
166 Q_UNUSED(added);
167 Q_UNUSED(removed);
168
169 ChannelInterfaceRolesInterface *roles_interface = qobject_cast<ChannelInterfaceRolesInterface*>(sender());167 ChannelInterfaceRolesInterface *roles_interface = qobject_cast<ChannelInterfaceRolesInterface*>(sender());
170 RolesMap roles = roles_interface->getRoles();
171
172 Tp::TextChannelPtr channel(qobject_cast<Tp::TextChannel*>(sender()->parent()));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 QVariantMap properties = propertiesFromChannel(channel);190 QVariantMap properties = propertiesFromChannel(channel);
174 QVariantMap thread = threadForProperties(channel->property(History::FieldAccountId).toString(),191 QVariantMap thread = threadForProperties(channel->property(History::FieldAccountId).toString(),
175 History::EventTypeText,192 History::EventTypeText,
176 properties,193 properties,
177 matchFlagsForChannel(channel),194 matchFlagsForChannel(channel),
178 false);195 false);
179196 if (!thread.isEmpty()) {
180 writeRolesInformationEvents(thread, channel, roles);197 writeRolesInformationEvents(thread, channel, rolesMap);
181198 updateRoomRoles(channel, rolesMap);
182 updateRoomRoles(channel, roles);199 }
183}200}
184201
185QVariantMap HistoryDaemon::propertiesFromChannel(const Tp::ChannelPtr &textChannel)202QVariantMap HistoryDaemon::propertiesFromChannel(const Tp::ChannelPtr &textChannel)
@@ -187,55 +204,60 @@
187 QVariantMap properties;204 QVariantMap properties;
188 QVariantList participants;205 QVariantList participants;
189 QStringList participantIds;206 QStringList participantIds;
190207 QString accountId = textChannel->property(History::FieldAccountId).toString();
191 ChannelInterfaceRolesInterface *roles_interface = textChannel->optionalInterface<ChannelInterfaceRolesInterface>();208
192 RolesMap roles;209 if (History::Utils::shouldIncludeParticipants(accountId, fromTelepathyHandleType(textChannel->targetHandleType()))) {
193 if (roles_interface) {210 ChannelInterfaceRolesInterface *roles_interface = textChannel->optionalInterface<ChannelInterfaceRolesInterface>();
194 roles = roles_interface->getRoles();211 RolesMap roles;
195 }212 if (roles_interface) {
196213 if (mRolesMap.contains(textChannel->objectPath())) {
197 Q_FOREACH(const Tp::ContactPtr contact, textChannel->groupContacts(false)) {214 roles = mRolesMap[textChannel->objectPath()];
198 QVariantMap contactProperties;215 }
199 contactProperties[History::FieldAlias] = contact->alias();216 }
200 contactProperties[History::FieldAccountId] = textChannel->property(History::FieldAccountId).toString();217
201 contactProperties[History::FieldIdentifier] = contact->id();218 Q_FOREACH(const Tp::ContactPtr contact, textChannel->groupContacts(false)) {
202 contactProperties[History::FieldParticipantState] = History::ParticipantStateRegular;219 QVariantMap contactProperties;
203 contactProperties[History::FieldParticipantRoles] = roles[contact->handle().at(0)];220 contactProperties[History::FieldAlias] = contact->alias();
204 participantIds << contact->id();221 contactProperties[History::FieldAccountId] = accountId;
205 participants << contactProperties;222 contactProperties[History::FieldIdentifier] = contact->id();
206 }223 contactProperties[History::FieldParticipantState] = History::ParticipantStateRegular;
207224 contactProperties[History::FieldParticipantRoles] = roles[contact->handle().at(0)];
208 Q_FOREACH(const Tp::ContactPtr contact, textChannel->groupRemotePendingContacts(false)) {225 participantIds << contact->id();
209 QVariantMap contactProperties;226 participants << contactProperties;
210 contactProperties[History::FieldAlias] = contact->alias();227 }
211 contactProperties[History::FieldAccountId] = textChannel->property(History::FieldAccountId).toString();228
212 contactProperties[History::FieldIdentifier] = contact->id();229 Q_FOREACH(const Tp::ContactPtr contact, textChannel->groupRemotePendingContacts(false)) {
213 contactProperties[History::FieldParticipantState] = History::ParticipantStateRemotePending;230 QVariantMap contactProperties;
214 contactProperties[History::FieldParticipantRoles] = roles[contact->handle().at(0)];231 contactProperties[History::FieldAlias] = contact->alias();
215 participantIds << contact->id();232 contactProperties[History::FieldAccountId] = accountId;
216 participants << contactProperties;233 contactProperties[History::FieldIdentifier] = contact->id();
217 }234 contactProperties[History::FieldParticipantState] = History::ParticipantStateRemotePending;
218235 contactProperties[History::FieldParticipantRoles] = roles[contact->handle().at(0)];
219 Q_FOREACH(const Tp::ContactPtr contact, textChannel->groupLocalPendingContacts(false)) {236 participantIds << contact->id();
220 QVariantMap contactProperties;237 participants << contactProperties;
221 contactProperties[History::FieldAlias] = contact->alias();238 }
222 contactProperties[History::FieldAccountId] = textChannel->property(History::FieldAccountId).toString();239
223 contactProperties[History::FieldIdentifier] = contact->id();240 Q_FOREACH(const Tp::ContactPtr contact, textChannel->groupLocalPendingContacts(false)) {
224 contactProperties[History::FieldParticipantState] = History::ParticipantStateLocalPending;241 QVariantMap contactProperties;
225 contactProperties[History::FieldParticipantRoles] = roles[contact->handle().at(0)];242 contactProperties[History::FieldAlias] = contact->alias();
226 participantIds << contact->id();243 contactProperties[History::FieldAccountId] = accountId;
227 participants << contactProperties;244 contactProperties[History::FieldIdentifier] = contact->id();
228 }245 contactProperties[History::FieldParticipantState] = History::ParticipantStateLocalPending;
229246 contactProperties[History::FieldParticipantRoles] = roles[contact->handle().at(0)];
230 if (participants.isEmpty() && textChannel->targetHandleType() == Tp::HandleTypeContact &&247 participantIds << contact->id();
231 textChannel->targetContact() == textChannel->connection()->selfContact()) {248 participants << contactProperties;
232 QVariantMap contactProperties;249 }
233 contactProperties[History::FieldAlias] = textChannel->targetContact()->alias();250
234 contactProperties[History::FieldAccountId] = textChannel->property(History::FieldAccountId).toString();251 if (participants.isEmpty() && textChannel->targetHandleType() == Tp::HandleTypeContact &&
235 contactProperties[History::FieldIdentifier] = textChannel->targetContact()->id();252 textChannel->targetContact() == textChannel->connection()->selfContact()) {
236 contactProperties[History::FieldParticipantState] = History::ParticipantStateRegular;253 QVariantMap contactProperties;
237 participantIds << textChannel->targetContact()->id();254 contactProperties[History::FieldAlias] = textChannel->targetContact()->alias();
238 participants << contactProperties;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 }
240262
241 // We map chatType directly from telepathy targetHandleType: None, Contact, Room263 // We map chatType directly from telepathy targetHandleType: None, Contact, Room
@@ -314,6 +336,39 @@
314 return thread;336 return thread;
315}337}
316338
339QString 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
364QList<QVariantMap> HistoryDaemon::participantsForThreads(const QList<QVariantMap> &threadIds)
365{
366 if (!mBackend) {
367 return QList<QVariantMap>();
368 }
369 return mBackend->participantsForThreads(threadIds);
370}
371
317QString HistoryDaemon::queryThreads(int type, const QVariantMap &sort, const QVariantMap &filter, const QVariantMap &properties)372QString HistoryDaemon::queryThreads(int type, const QVariantMap &sort, const QVariantMap &filter, const QVariantMap &properties)
318{373{
319 if (!mBackend) {374 if (!mBackend) {
@@ -370,7 +425,7 @@
370 return mBackend->getSingleEvent((History::EventType)type, accountId, threadId, eventId);425 return mBackend->getSingleEvent((History::EventType)type, accountId, threadId, eventId);
371}426}
372427
373bool HistoryDaemon::writeEvents(const QList<QVariantMap> &events, const QVariantMap &properties)428bool HistoryDaemon::writeEvents(const QList<QVariantMap> &events, const QVariantMap &properties, bool notify)
374{429{
375 if (!mBackend) {430 if (!mBackend) {
376 return false;431 return false;
@@ -407,7 +462,9 @@
407 threads[hash] = thread;462 threads[hash] = thread;
408463
409 // set the participants field in the event464 // 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 }
411468
412469
413 // check if the event was a new one or a modification to an existing one470 // check if the event was a new one or a modification to an existing one
@@ -427,13 +484,13 @@
427 mBackend->endBatchOperation();484 mBackend->endBatchOperation();
428485
429 // and last but not least, notify the results486 // and last but not least, notify the results
430 if (!newEvents.isEmpty()) {487 if (!newEvents.isEmpty() && notify) {
431 mDBus.notifyEventsAdded(newEvents);488 mDBus.notifyEventsAdded(newEvents);
432 }489 }
433 if (!modifiedEvents.isEmpty()) {490 if (!modifiedEvents.isEmpty() && notify) {
434 mDBus.notifyEventsModified(modifiedEvents);491 mDBus.notifyEventsModified(modifiedEvents);
435 }492 }
436 if (!threads.isEmpty()) {493 if (!threads.isEmpty() && notify) {
437 mDBus.notifyThreadsModified(threads.values());494 mDBus.notifyThreadsModified(threads.values());
438 }495 }
439 return true;496 return true;
@@ -512,44 +569,46 @@
512 return true;569 return true;
513}570}
514571
572void 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
515bool HistoryDaemon::removeThreads(const QList<QVariantMap> &threads)595bool HistoryDaemon::removeThreads(const QList<QVariantMap> &threads)
516{596{
517 if (!mBackend) {597 if (!mBackend) {
518 return false;598 return false;
519 }599 }
520600
521 // In order to remove a thread all we have to do is to remove all its items601 // If the thread has events
522 // then it is going to be removed by removeEvents() once it detects the thread is602 mBackend->beginBatchOperation();
523 // empty.
524 QList<QVariantMap> events;
525 QMap<QString, QVariantMap> removedEmptyThreads;
526 Q_FOREACH(const QVariantMap &thread, threads) {603 Q_FOREACH(const QVariantMap &thread, threads) {
527 QList<QVariantMap> thisEvents = mBackend->eventsForThread(thread);604 if (!mBackend->removeThread(thread)) {
528 if (thisEvents.isEmpty()) {605 mBackend->rollbackBatchOperation();
529 mBackend->beginBatchOperation();606 return false;
530 if (!mBackend->removeThread(thread)) {607 }
531 mBackend->rollbackBatchOperation();608 }
532 return false;609 mBackend->endBatchOperation();
533 }610 mDBus.notifyThreadsRemoved(threads);
534 mBackend->endBatchOperation();611 return true;
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;
553}612}
554613
555void HistoryDaemon::onObserverCreated()614void HistoryDaemon::onObserverCreated()
@@ -562,7 +621,7 @@
562 &mTextObserver, SLOT(onTextChannelAvailable(Tp::TextChannelPtr)));621 &mTextObserver, SLOT(onTextChannelAvailable(Tp::TextChannelPtr)));
563}622}
564623
565void HistoryDaemon::onCallEnded(const Tp::CallChannelPtr &channel)624void HistoryDaemon::onCallEnded(const Tp::CallChannelPtr &channel, bool missed)
566{625{
567 QVariantMap properties = propertiesFromChannel(channel);626 QVariantMap properties = propertiesFromChannel(channel);
568 QVariantList participants;627 QVariantList participants;
@@ -592,7 +651,6 @@
592 // FIXME: check if checking for isRequested() is enough651 // FIXME: check if checking for isRequested() is enough
593 bool incoming = !channel->isRequested();652 bool incoming = !channel->isRequested();
594 int duration;653 int duration;
595 bool missed = incoming && channel->callStateReason().reason == Tp::CallStateChangeReasonNoAnswer;
596654
597 if (!missed) {655 if (!missed) {
598 QDateTime activeTime = channel->property("activeTimestamp").toDateTime();656 QDateTime activeTime = channel->property("activeTimestamp").toDateTime();
@@ -615,11 +673,35 @@
615 writeEvents(QList<QVariantMap>() << event, properties);673 writeEvents(QList<QVariantMap>() << event, properties);
616}674}
617675
676void 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
618void HistoryDaemon::onTextChannelAvailable(const Tp::TextChannelPtr channel)699void HistoryDaemon::onTextChannelAvailable(const Tp::TextChannelPtr channel)
619{700{
620 // for Rooms we need to explicitly create the thread to allow users to send messages to groups even701 // for Rooms we need to explicitly create the thread to allow users to send messages to groups even
621 // before they receive any message.702 // before they receive any message.
622 // for other types, we can wait until messages are received703 // for other types, we can wait until messages are received
704 bool notify = false;
623 if (channel->targetHandleType() == Tp::HandleTypeRoom) {705 if (channel->targetHandleType() == Tp::HandleTypeRoom) {
624 QString accountId = channel->property(History::FieldAccountId).toString();706 QString accountId = channel->property(History::FieldAccountId).toString();
625 QVariantMap properties = propertiesFromChannel(channel);707 QVariantMap properties = propertiesFromChannel(channel);
@@ -641,12 +723,13 @@
641723
642 // write information event including all initial invitees724 // write information event including all initial invitees
643 Q_FOREACH(const Tp::ContactPtr contact, channel->groupRemotePendingContacts(false)) {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 }
646728
647 // update participants only if the thread is not available previously. Otherwise we'll wait for membersChanged event729 // 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.730 // for reflect in conversation information events for modified participants.
649 updateRoomParticipants(channel);731 updateRoomParticipants(channel, false);
732 notify = true;
650 }733 }
651734
652 // write an entry saying you joined the group if 'joined' flag in thread is false and modify that flag.735 // write an entry saying you joined the group if 'joined' flag in thread is false and modify that flag.
@@ -657,7 +740,8 @@
657 writeInformationEvent(thread, History::InformationTypeSelfJoined);740 writeInformationEvent(thread, History::InformationTypeSelfJoined);
658 }741 }
659 // update backend742 // update backend
660 updateRoomProperties(channel, QVariantMap{{"Joined", true}});743 updateRoomProperties(channel, QVariantMap{{"Joined", true}}, false);
744 notify = true;
661 }745 }
662746
663 Tp::AbstractInterface *room_interface = channel->optionalInterface<Tp::Client::ChannelInterfaceRoomInterface>();747 Tp::AbstractInterface *room_interface = channel->optionalInterface<Tp::Client::ChannelInterfaceRoomInterface>();
@@ -685,6 +769,10 @@
685769
686 connect(roles_interface, SIGNAL(RolesChanged(const HandleRolesMap&, const HandleRolesMap&)), SLOT(onRolesChanged(const HandleRolesMap&, const HandleRolesMap&)));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}
689777
690void HistoryDaemon::onGroupMembersChanged(const Tp::Contacts &groupMembersAdded,778void HistoryDaemon::onGroupMembersChanged(const Tp::Contacts &groupMembersAdded,
@@ -702,6 +790,8 @@
702 bool hasRemotePendingMembersAdded = groupRemotePendingMembersAdded.size() > 0;790 bool hasRemotePendingMembersAdded = groupRemotePendingMembersAdded.size() > 0;
703 bool hasMembersAdded = groupMembersAdded.size() > 0;791 bool hasMembersAdded = groupMembersAdded.size() > 0;
704 bool hasMembersRemoved = groupMembersRemoved.size() > 0;792 bool hasMembersRemoved = groupMembersRemoved.size() > 0;
793 Tp::ContactPtr selfContact = channel->connection()->selfContact();
794 bool selfContactIsPending = channel->groupRemotePendingContacts(true).contains(selfContact);
705795
706 if (hasRemotePendingMembersAdded || hasMembersAdded || hasMembersRemoved) {796 if (hasRemotePendingMembersAdded || hasMembersAdded || hasMembersRemoved) {
707 properties = propertiesFromChannel(channel);797 properties = propertiesFromChannel(channel);
@@ -710,19 +800,35 @@
710 properties,800 properties,
711 matchFlagsForChannel(channel),801 matchFlagsForChannel(channel),
712 false);802 false);
713 if (!thread.isEmpty()) {803 if (!thread.isEmpty() && !selfContactIsPending) {
804 QList<QVariantMap> added;
805 QList<QVariantMap> removed;
806 QList<QVariantMap> modified;
714 if (hasRemotePendingMembersAdded) {807 if (hasRemotePendingMembersAdded) {
715 Q_FOREACH (const Tp::ContactPtr& contact, groupRemotePendingMembersAdded) {808 Q_FOREACH (const Tp::ContactPtr& contact, groupRemotePendingMembersAdded) {
716 if (!foundInThread(contact, thread)) {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 if (hasMembersAdded) {820 if (hasMembersAdded) {
722 Q_FOREACH (const Tp::ContactPtr& contact, groupMembersAdded) {821 Q_FOREACH (const Tp::ContactPtr& contact, groupMembersAdded) {
723 // if this member was not previously regular member in thread, notify about his join822 // if this member was not previously regular member in thread, notify about his join
724 if (!foundAsMemberInThread(contact, thread)) {823 if (!foundAsMemberInThread(contact, thread) && contact->id() != channel->groupSelfContact()->id()) {
725 writeInformationEvent(thread, History::InformationTypeJoined, contact->alias());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,27 +847,34 @@
741 break;847 break;
742 }848 }
743 }849 }
744 writeInformationEvent(thread, type);850 if (thread[History::FieldChatRoomInfo].toMap()["Joined"].toBool()) {
745 // update backend851 writeInformationEvent(thread, type);
746 updateRoomProperties(channel, QVariantMap{{"Joined", false}});852 // update backend
853 updateRoomProperties(channel, QVariantMap{{"Joined", false}}, true);
854 }
747 }855 }
748 else // don't notify any other group member removal if we are leaving the group856 else // don't notify any other group member removal if we are leaving the group
749 {857 {
750 Q_FOREACH (const Tp::ContactPtr& contact, groupMembersRemoved) {858 Q_FOREACH (const Tp::ContactPtr& contact, groupMembersRemoved) {
751 // inform about removed members other than us859 // inform about removed members other than us
752 if (contact->id() != channel->groupSelfContact()->id()) {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 }
760873
761 updateRoomParticipants(channel);874 updateRoomParticipants(channel, !selfContactIsPending);
762}875}
763876
764void HistoryDaemon::updateRoomParticipants(const Tp::TextChannelPtr channel)877void HistoryDaemon::updateRoomParticipants(const Tp::TextChannelPtr channel, bool notify)
765{878{
766 if (!channel) {879 if (!channel) {
767 return;880 return;
@@ -772,8 +885,14 @@
772885
773 ChannelInterfaceRolesInterface *roles_interface = channel->optionalInterface<ChannelInterfaceRolesInterface>();886 ChannelInterfaceRolesInterface *roles_interface = channel->optionalInterface<ChannelInterfaceRolesInterface>();
774 RolesMap roles;887 RolesMap roles;
888
775 if (roles_interface) {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 }
778897
779 Q_FOREACH(const Tp::ContactPtr contact, channel->groupRemotePendingContacts(false)) {898 Q_FOREACH(const Tp::ContactPtr contact, channel->groupRemotePendingContacts(false)) {
@@ -811,12 +930,14 @@
811 QString accountId = channel->property(History::FieldAccountId).toString();930 QString accountId = channel->property(History::FieldAccountId).toString();
812 QString threadId = channel->targetId();931 QString threadId = channel->targetId();
813 if (mBackend->updateRoomParticipants(accountId, threadId, History::EventTypeText, participants)) {932 if (mBackend->updateRoomParticipants(accountId, threadId, History::EventTypeText, participants)) {
814 QVariantMap updatedThread = getSingleThread(History::EventTypeText, accountId, threadId, QVariantMap());933 if (notify) {
815 mDBus.notifyThreadsModified(QList<QVariantMap>() << updatedThread);934 QVariantMap updatedThread = getSingleThread(History::EventTypeText, accountId, threadId, QVariantMap());
935 mDBus.notifyThreadsModified(QList<QVariantMap>() << updatedThread);
936 }
816 }937 }
817}938}
818939
819void HistoryDaemon::updateRoomRoles(const Tp::TextChannelPtr &channel, const RolesMap &rolesMap)940void HistoryDaemon::updateRoomRoles(const Tp::TextChannelPtr &channel, const RolesMap &rolesMap, bool notify)
820{941{
821 if (!channel) {942 if (!channel) {
822 return;943 return;
@@ -841,8 +962,10 @@
841 QString accountId = channel->property(History::FieldAccountId).toString();962 QString accountId = channel->property(History::FieldAccountId).toString();
842 QString threadId = channel->targetId();963 QString threadId = channel->targetId();
843 if (mBackend->updateRoomParticipantsRoles(accountId, threadId, History::EventTypeText, participantsRoles)) {964 if (mBackend->updateRoomParticipantsRoles(accountId, threadId, History::EventTypeText, participantsRoles)) {
844 QVariantMap updatedThread = getSingleThread(History::EventTypeText, accountId, threadId, QVariantMap());965 if (notify) {
845 mDBus.notifyThreadsModified(QList<QVariantMap>() << updatedThread);966 QVariantMap updatedThread = getSingleThread(History::EventTypeText, accountId, threadId, QVariantMap());
967 mDBus.notifyThreadsModified(QList<QVariantMap>() << updatedThread);
968 }
846 }969 }
847970
848 // update self roles in room properties971 // update self roles in room properties
@@ -865,28 +988,41 @@
865 updateRoomProperties(accountId, threadId, type, properties, invalidated);988 updateRoomProperties(accountId, threadId, type, properties, invalidated);
866}989}
867990
868void HistoryDaemon::updateRoomProperties(const Tp::TextChannelPtr &channel, const QVariantMap &properties)991void HistoryDaemon::updateRoomProperties(const Tp::TextChannelPtr &channel, const QVariantMap &properties, bool notify)
869{992{
870 QString accountId = channel->property(History::FieldAccountId).toString();993 QString accountId = channel->property(History::FieldAccountId).toString();
871 QString threadId = channel->targetId();994 QString threadId = channel->targetId();
872 History::EventType type = History::EventTypeText;995 History::EventType type = History::EventTypeText;
873 updateRoomProperties(accountId, threadId, type, properties, QStringList());996 updateRoomProperties(accountId, threadId, type, properties, QStringList(), notify);
874}997}
875998
876void HistoryDaemon::updateRoomProperties(const QString &accountId, const QString &threadId, History::EventType type, const QVariantMap &properties, const QStringList &invalidated)999void HistoryDaemon::updateRoomProperties(const QString &accountId, const QString &threadId, History::EventType type, const QVariantMap &properties, const QStringList &invalidated, bool notify)
877{1000{
878 if (mBackend->updateRoomInfo(accountId, threadId, type, properties, invalidated)) {1001 if (mBackend->updateRoomInfo(accountId, threadId, type, properties, invalidated)) {
879 QVariantMap thread = getSingleThread(type, accountId, threadId, QVariantMap());1002 if (notify) {
880 mDBus.notifyThreadsModified(QList<QVariantMap>() << thread);1003 QVariantMap thread = getSingleThread(type, accountId, threadId, QVariantMap());
1004 mDBus.notifyThreadsModified(QList<QVariantMap>() << thread);
1005 }
881 }1006 }
882}1007}
8831008
884void HistoryDaemon::onMessageReceived(const Tp::TextChannelPtr textChannel, const Tp::ReceivedMessage &message)1009void HistoryDaemon::onMessageReceived(const Tp::TextChannelPtr textChannel, const Tp::ReceivedMessage &message)
885{1010{
886 QString eventId;1011 QString eventId;
887 Tp::MessagePart header = message.header();
888 QString senderId;1012 QString senderId;
1013
1014 QString accountId = textChannel->property(History::FieldAccountId).toString();
1015 QString threadId = textChannel->property(History::FieldThreadId).toString();
889 QVariantMap properties = propertiesFromChannel(textChannel);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 History::MessageStatus status = History::MessageStatusUnknown;1026 History::MessageStatus status = History::MessageStatusUnknown;
891 if (!message.sender() || message.sender()->handle().at(0) == textChannel->connection()->selfHandle()) {1027 if (!message.sender() || message.sender()->handle().at(0) == textChannel->connection()->selfHandle()) {
892 senderId = "self";1028 senderId = "self";
@@ -909,7 +1045,7 @@
909 if (message.isDeliveryReport() && message.deliveryDetails().hasOriginalToken()) {1045 if (message.isDeliveryReport() && message.deliveryDetails().hasOriginalToken()) {
910 // at this point we assume the delivery report is for a message that was already1046 // at this point we assume the delivery report is for a message that was already
911 // sent and properly saved at our database, so we can safely get it here to update1047 // 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 if (textEvent.isEmpty()) {1049 if (textEvent.isEmpty()) {
914 qWarning() << "Cound not find the original event to update with delivery details.";1050 qWarning() << "Cound not find the original event to update with delivery details.";
915 return;1051 return;
@@ -922,52 +1058,21 @@
922 return;1058 return;
923 }1059 }
9241060
925 History::MessageStatus status;1061 textEvent[History::FieldMessageStatus] = (int) fromTelepathyDeliveryStatus(message.deliveryDetails().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;
951 if (!writeEvents(QList<QVariantMap>() << textEvent, properties)) {1062 if (!writeEvents(QList<QVariantMap>() << textEvent, properties)) {
952 qWarning() << "Failed to save the new message status!";1063 qWarning() << "Failed to save the new message status!";
953 }1064 }
9541065
955 return;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 int count = 1;1068 int count = 1;
964 QList<QVariantMap> attachments;1069 QList<QVariantMap> attachments;
965 History::MessageType type = History::MessageTypeText;1070 History::MessageType type = History::MessageTypeText;
966 QString subject;1071 QString subject;
9671072
968 if (message.hasNonTextContent()) {1073 if (message.hasNonTextContent()) {
969 QString normalizedAccountId = QString(QCryptographicHash::hash(thread[History::FieldAccountId].toString().toLatin1(), QCryptographicHash::Md5).toHex());1074 QString normalizedAccountId = QString(QCryptographicHash::hash(accountId.toLatin1(), QCryptographicHash::Md5).toHex());
970 QString normalizedThreadId = QString(QCryptographicHash::hash(thread[History::FieldThreadId].toString().toLatin1(), QCryptographicHash::Md5).toHex());1075 QString normalizedThreadId = QString(QCryptographicHash::hash(threadId.toLatin1(), QCryptographicHash::Md5).toHex());
971 QString normalizedEventId = QString(QCryptographicHash::hash(eventId.toLatin1(), QCryptographicHash::Md5).toHex());1076 QString normalizedEventId = QString(QCryptographicHash::hash(eventId.toLatin1(), QCryptographicHash::Md5).toHex());
972 QString mmsStoragePath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation);1077 QString mmsStoragePath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation);
9731078
@@ -1000,8 +1105,8 @@
1000 file.close();1105 file.close();
10011106
1002 QVariantMap attachment;1107 QVariantMap attachment;
1003 attachment[History::FieldAccountId] = thread[History::FieldAccountId];1108 attachment[History::FieldAccountId] = accountId;
1004 attachment[History::FieldThreadId] = thread[History::FieldThreadId];1109 attachment[History::FieldThreadId] = threadId;
1005 attachment[History::FieldEventId] = eventId;1110 attachment[History::FieldEventId] = eventId;
1006 attachment[History::FieldAttachmentId] = part["identifier"].variant();1111 attachment[History::FieldAttachmentId] = part["identifier"].variant();
1007 attachment[History::FieldContentType] = part["content-type"].variant();1112 attachment[History::FieldContentType] = part["content-type"].variant();
@@ -1013,8 +1118,8 @@
10131118
1014 QVariantMap event;1119 QVariantMap event;
1015 event[History::FieldType] = History::EventTypeText;1120 event[History::FieldType] = History::EventTypeText;
1016 event[History::FieldAccountId] = thread[History::FieldAccountId];1121 event[History::FieldAccountId] = accountId;
1017 event[History::FieldThreadId] = thread[History::FieldThreadId];1122 event[History::FieldThreadId] = threadId;
1018 event[History::FieldEventId] = eventId;1123 event[History::FieldEventId] = eventId;
1019 event[History::FieldSenderId] = senderId;1124 event[History::FieldSenderId] = senderId;
1020 event[History::FieldTimestamp] = message.received().toString("yyyy-MM-ddTHH:mm:ss.zzz");1125 event[History::FieldTimestamp] = message.received().toString("yyyy-MM-ddTHH:mm:ss.zzz");
@@ -1058,22 +1163,6 @@
10581163
1059}1164}
10601165
1061void 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
1077void HistoryDaemon::onMessageSent(const Tp::TextChannelPtr textChannel, const Tp::Message &message, const QString &messageToken)1166void HistoryDaemon::onMessageSent(const Tp::TextChannelPtr textChannel, const Tp::Message &message, const QString &messageToken)
1078{1167{
1079 QVariantMap properties = propertiesFromChannel(textChannel);1168 QVariantMap properties = propertiesFromChannel(textChannel);
@@ -1191,7 +1280,7 @@
1191 return reply.value();1280 return reply.value();
1192}1281}
11931282
1194void HistoryDaemon::writeInformationEvent(const QVariantMap &thread, History::InformationType type, const QString &subject, const QString &sender, const QString &text)1283void HistoryDaemon::writeInformationEvent(const QVariantMap &thread, History::InformationType type, const QString &subject, const QString &sender, const QString &text, bool notify)
1195{1284{
1196 History::TextEvent historyEvent = History::TextEvent(thread[History::FieldAccountId].toString(),1285 History::TextEvent historyEvent = History::TextEvent(thread[History::FieldAccountId].toString(),
1197 thread[History::FieldThreadId].toString(),1286 thread[History::FieldThreadId].toString(),
@@ -1207,7 +1296,7 @@
1207 QDateTime::currentDateTime(),1296 QDateTime::currentDateTime(),
1208 subject,1297 subject,
1209 type);1298 type);
1210 writeEvents(QList<QVariantMap>() << historyEvent.properties(), thread);1299 writeEvents(QList<QVariantMap>() << historyEvent.properties(), thread, notify);
1211}1300}
12121301
1213void HistoryDaemon::writeRoomChangesInformationEvents(const QVariantMap &thread, const QVariantMap &interfaceProperties)1302void HistoryDaemon::writeRoomChangesInformationEvents(const QVariantMap &thread, const QVariantMap &interfaceProperties)
@@ -1265,3 +1354,51 @@
1265 }1354 }
1266 }1355 }
1267}1356}
1357
1358History::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
1388History::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}
12681405
=== modified file 'daemon/historydaemon.h'
--- daemon/historydaemon.h 2016-10-20 13:56:10 +0000
+++ daemon/historydaemon.h 2017-03-23 01:25:43 +0000
@@ -1,5 +1,5 @@
1/*1/*
2 * Copyright (C) 2013-2016 Canonical, Ltd.2 * Copyright (C) 2013-2017 Canonical, Ltd.
3 *3 *
4 * Authors:4 * Authors:
5 * Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>5 * Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>
@@ -42,29 +42,36 @@
4242
43 static HistoryDaemon *instance();43 static HistoryDaemon *instance();
4444
45 static QVariantMap propertiesFromChannel(const Tp::ChannelPtr &textChannel);45 QVariantMap propertiesFromChannel(const Tp::ChannelPtr &textChannel);
46 QVariantMap threadForProperties(const QString &accountId,46 QVariantMap threadForProperties(const QString &accountId,
47 History::EventType type,47 History::EventType type,
48 const QVariantMap &properties,48 const QVariantMap &properties,
49 History::MatchFlags matchFlags = History::MatchCaseSensitive,49 History::MatchFlags matchFlags = History::MatchCaseSensitive,
50 bool create = true);50 bool create = true);
51 QString threadIdForProperties(const QString &accountId,
52 History::EventType type,
53 const QVariantMap &properties,
54 History::MatchFlags matchFlags = History::MatchCaseSensitive,
55 bool create = true);
56 QList<QVariantMap> participantsForThreads(const QList<QVariantMap> &threadIds);
51 QString queryThreads(int type, const QVariantMap &sort, const QVariantMap &filter, const QVariantMap &properties);57 QString queryThreads(int type, const QVariantMap &sort, const QVariantMap &filter, const QVariantMap &properties);
52 QString queryEvents(int type, const QVariantMap &sort, const QVariantMap &filter);58 QString queryEvents(int type, const QVariantMap &sort, const QVariantMap &filter);
53 QVariantMap getSingleThread(int type, const QString &accountId, const QString &threadId, const QVariantMap &properties);59 QVariantMap getSingleThread(int type, const QString &accountId, const QString &threadId, const QVariantMap &properties);
54 QVariantMap getSingleEvent(int type, const QString &accountId, const QString &threadId, const QString &eventId);60 QVariantMap getSingleEvent(int type, const QString &accountId, const QString &threadId, const QString &eventId);
55 QVariantMap getSingleEventFromTextChannel(const Tp::TextChannelPtr textChannel, const QString &messageId);61 QVariantMap getSingleEventFromTextChannel(const Tp::TextChannelPtr textChannel, const QString &messageId);
5662
57 bool writeEvents(const QList<QVariantMap> &events, const QVariantMap &properties);63 bool writeEvents(const QList<QVariantMap> &events, const QVariantMap &properties, bool notify = true);
58 bool removeEvents(const QList<QVariantMap> &events);64 bool removeEvents(const QList<QVariantMap> &events);
59 bool removeThreads(const QList<QVariantMap> &threads);65 bool removeThreads(const QList<QVariantMap> &threads);
66 void markThreadsAsRead(const QList<QVariantMap> &threads);
6067
61private Q_SLOTS:68private Q_SLOTS:
62 void onObserverCreated();69 void onObserverCreated();
63 void onCallEnded(const Tp::CallChannelPtr &channel);70 void onCallEnded(const Tp::CallChannelPtr &channel, bool missed);
64 void onMessageReceived(const Tp::TextChannelPtr textChannel, const Tp::ReceivedMessage &message);71 void onMessageReceived(const Tp::TextChannelPtr textChannel, const Tp::ReceivedMessage &message);
65 void onMessageRead(const Tp::TextChannelPtr textChannel, const Tp::ReceivedMessage &message);
66 void onMessageSent(const Tp::TextChannelPtr textChannel, const Tp::Message &message, const QString &messageToken);72 void onMessageSent(const Tp::TextChannelPtr textChannel, const Tp::Message &message, const QString &messageToken);
67 void onTextChannelAvailable(const Tp::TextChannelPtr channel);73 void onTextChannelAvailable(const Tp::TextChannelPtr channel);
74 void onTextChannelInvalidated(const Tp::TextChannelPtr channel);
68 void onRoomPropertiesChanged(const QVariantMap &properties,const QStringList &invalidated);75 void onRoomPropertiesChanged(const QVariantMap &properties,const QStringList &invalidated);
69 void onGroupMembersChanged(const Tp::Contacts &groupMembersAdded, const Tp::Contacts &groupLocalPendingMembersAdded,76 void onGroupMembersChanged(const Tp::Contacts &groupMembersAdded, const Tp::Contacts &groupLocalPendingMembersAdded,
70 const Tp::Contacts &groupRemotePendingMembersAdded, const Tp::Contacts &groupMembersRemoved,77 const Tp::Contacts &groupRemotePendingMembersAdded, const Tp::Contacts &groupMembersRemoved,
@@ -73,18 +80,21 @@
7380
74protected:81protected:
75 History::MatchFlags matchFlagsForChannel(const Tp::ChannelPtr &channel);82 History::MatchFlags matchFlagsForChannel(const Tp::ChannelPtr &channel);
76 void updateRoomParticipants(const Tp::TextChannelPtr channel);83 void updateRoomParticipants(const Tp::TextChannelPtr channel, bool notify = true);
77 void updateRoomRoles(const Tp::TextChannelPtr &channel, const RolesMap &rolesMap);84 void updateRoomRoles(const Tp::TextChannelPtr &channel, const RolesMap &rolesMap, bool notify = true);
78 QString hashThread(const QVariantMap &thread);85 QString hashThread(const QVariantMap &thread);
79 static QVariantMap getInterfaceProperties(const Tp::AbstractInterface *interface);86 static QVariantMap getInterfaceProperties(const Tp::AbstractInterface *interface);
80 void updateRoomProperties(const Tp::TextChannelPtr &channel, const QVariantMap &properties);87 void updateRoomProperties(const Tp::TextChannelPtr &channel, const QVariantMap &properties, bool notify = true);
81 void updateRoomProperties(const QString &accountId, const QString &threadId, History::EventType type, const QVariantMap &properties, const QStringList &invalidated);88 void updateRoomProperties(const QString &accountId, const QString &threadId, History::EventType type, const QVariantMap &properties, const QStringList &invalidated, bool notify = true);
8289
83 void writeInformationEvent(const QVariantMap &thread, History::InformationType type, const QString &subject = QString(), const QString &sender = QString("self"), const QString &text = QString());90 void writeInformationEvent(const QVariantMap &thread, History::InformationType type, const QString &subject = QString(), const QString &sender = QString("self"), const QString &text = QString(), bool notify = true);
8491
85 void writeRoomChangesInformationEvents(const QVariantMap &thread, const QVariantMap &interfaceProperties);92 void writeRoomChangesInformationEvents(const QVariantMap &thread, const QVariantMap &interfaceProperties);
86 void writeRolesInformationEvents(const QVariantMap &thread, const Tp::ChannelPtr &channel, const RolesMap &rolesMap);93 void writeRolesInformationEvents(const QVariantMap &thread, const Tp::ChannelPtr &channel, const RolesMap &rolesMap);
87 void writeRolesChangesInformationEvents(const QVariantMap &thread, const Tp::ChannelPtr &channel, const RolesMap &rolesMap);94 void writeRolesChangesInformationEvents(const QVariantMap &thread, const Tp::ChannelPtr &channel, const RolesMap &rolesMap);
95
96 static History::MessageStatus fromTelepathyDeliveryStatus(Tp::DeliveryStatus deliveryStatus);
97 static History::ChatType fromTelepathyHandleType(const Tp::HandleType &type);
88private:98private:
89 HistoryDaemon(QObject *parent = 0);99 HistoryDaemon(QObject *parent = 0);
90100
@@ -93,6 +103,7 @@
93 QMap<QString, History::MatchFlags> mProtocolFlags;103 QMap<QString, History::MatchFlags> mProtocolFlags;
94 History::PluginPtr mBackend;104 History::PluginPtr mBackend;
95 HistoryServiceDBus mDBus;105 HistoryServiceDBus mDBus;
106 QMap<QString, RolesMap> mRolesMap;
96};107};
97108
98#endif109#endif
99110
=== modified file 'daemon/historyservicedbus.cpp'
--- daemon/historyservicedbus.cpp 2016-11-24 01:56:01 +0000
+++ daemon/historyservicedbus.cpp 2017-03-23 01:25:43 +0000
@@ -27,7 +27,7 @@
27Q_DECLARE_METATYPE(QList< QVariantMap >)27Q_DECLARE_METATYPE(QList< QVariantMap >)
2828
29HistoryServiceDBus::HistoryServiceDBus(QObject *parent) :29HistoryServiceDBus::HistoryServiceDBus(QObject *parent) :
30 QObject(parent), mAdaptor(0)30 QObject(parent), mAdaptor(0), mSignalsTimer(-1)
31{31{
32 qDBusRegisterMetaType<QList<QVariantMap> >();32 qDBusRegisterMetaType<QList<QVariantMap> >();
33}33}
@@ -47,32 +47,46 @@
4747
48void HistoryServiceDBus::notifyThreadsAdded(const QList<QVariantMap> &threads)48void HistoryServiceDBus::notifyThreadsAdded(const QList<QVariantMap> &threads)
49{49{
50 Q_EMIT ThreadsAdded(threads);50 mThreadsAdded << threads;
51 triggerSignals();
51}52}
5253
53void HistoryServiceDBus::notifyThreadsModified(const QList<QVariantMap> &threads)54void HistoryServiceDBus::notifyThreadsModified(const QList<QVariantMap> &threads)
54{55{
55 Q_EMIT ThreadsModified(threads);56 mThreadsModified << threads;
57 triggerSignals();
56}58}
5759
58void HistoryServiceDBus::notifyThreadsRemoved(const QList<QVariantMap> &threads)60void HistoryServiceDBus::notifyThreadsRemoved(const QList<QVariantMap> &threads)
59{61{
60 Q_EMIT ThreadsRemoved(threads);62 mThreadsRemoved << threads;
63 triggerSignals();
61}64}
6265
63void HistoryServiceDBus::notifyEventsAdded(const QList<QVariantMap> &events)66void HistoryServiceDBus::notifyEventsAdded(const QList<QVariantMap> &events)
64{67{
65 Q_EMIT EventsAdded(events);68 mEventsAdded << events;
69 triggerSignals();
66}70}
6771
68void HistoryServiceDBus::notifyEventsModified(const QList<QVariantMap> &events)72void HistoryServiceDBus::notifyEventsModified(const QList<QVariantMap> &events)
69{73{
70 Q_EMIT EventsModified(events);74 mEventsModified << events;
75 triggerSignals();
71}76}
7277
73void HistoryServiceDBus::notifyEventsRemoved(const QList<QVariantMap> &events)78void HistoryServiceDBus::notifyEventsRemoved(const QList<QVariantMap> &events)
74{79{
75 Q_EMIT EventsRemoved(events);80 mEventsRemoved << events;
81 triggerSignals();
82}
83
84void HistoryServiceDBus::notifyThreadParticipantsChanged(const QVariantMap &thread,
85 const QList<QVariantMap> &added,
86 const QList<QVariantMap> &removed,
87 const QList<QVariantMap> &modified)
88{
89 Q_EMIT ThreadParticipantsChanged(thread, added, removed, modified);
76}90}
7791
78QVariantMap HistoryServiceDBus::ThreadForProperties(const QString &accountId,92QVariantMap HistoryServiceDBus::ThreadForProperties(const QString &accountId,
@@ -85,7 +99,12 @@
85 (History::EventType) type,99 (History::EventType) type,
86 properties,100 properties,
87 (History::MatchFlags) matchFlags,101 (History::MatchFlags) matchFlags,
88 create);102 create);
103}
104
105QList<QVariantMap> HistoryServiceDBus::ParticipantsForThreads(const QList<QVariantMap> &threadIds)
106{
107 return HistoryDaemon::instance()->participantsForThreads(threadIds);
89}108}
90109
91QVariantMap HistoryServiceDBus::ThreadForParticipants(const QString &accountId,110QVariantMap HistoryServiceDBus::ThreadForParticipants(const QString &accountId,
@@ -114,6 +133,11 @@
114 return HistoryDaemon::instance()->removeThreads(threads);133 return HistoryDaemon::instance()->removeThreads(threads);
115}134}
116135
136void HistoryServiceDBus::MarkThreadsAsRead(const QList<QVariantMap> &threads)
137{
138 return HistoryDaemon::instance()->markThreadsAsRead(threads);
139}
140
117bool HistoryServiceDBus::RemoveEvents(const QList<QVariantMap> &events)141bool HistoryServiceDBus::RemoveEvents(const QList<QVariantMap> &events)
118{142{
119 return HistoryDaemon::instance()->removeEvents(events);143 return HistoryDaemon::instance()->removeEvents(events);
@@ -139,3 +163,73 @@
139 return HistoryDaemon::instance()->getSingleEvent(type, accountId, threadId, eventId);163 return HistoryDaemon::instance()->getSingleEvent(type, accountId, threadId, eventId);
140}164}
141165
166void HistoryServiceDBus::timerEvent(QTimerEvent *event)
167{
168 if (event->timerId() == mSignalsTimer) {
169 killTimer(mSignalsTimer);
170 mSignalsTimer = -1;
171 processSignals();
172 }
173}
174
175void HistoryServiceDBus::filterDuplicatesAndAdd(QList<QVariantMap> &targetList, const QList<QVariantMap> newItems, const QStringList &propertiesToCompare)
176{
177 Q_FOREACH (const QVariantMap &item, newItems) {
178 Q_FOREACH(const QVariantMap &existing, targetList) {
179 bool found = true;
180 Q_FOREACH(const QString &prop, propertiesToCompare) {
181 if (item[prop] != existing[prop]) {
182 found = false;
183 break;
184 }
185 }
186
187 if (!found) {
188 targetList << item;
189 }
190 }
191 }
192}
193
194void HistoryServiceDBus::triggerSignals()
195{
196 if (mSignalsTimer >= 0) {
197 killTimer(mSignalsTimer);
198 }
199
200 mSignalsTimer = startTimer(100);
201}
202
203void HistoryServiceDBus::processSignals()
204{
205 if (!mThreadsAdded.isEmpty()) {
206 Q_EMIT ThreadsAdded(mThreadsAdded);
207 mThreadsAdded.clear();
208 }
209
210 if (!mThreadsModified.isEmpty()) {
211 Q_EMIT ThreadsModified(mThreadsModified);
212 mThreadsModified.clear();
213 }
214
215 if (!mThreadsRemoved.isEmpty()) {
216 Q_EMIT ThreadsRemoved(mThreadsRemoved);
217 mThreadsRemoved.clear();
218 }
219
220 if (!mEventsAdded.isEmpty()) {
221 Q_EMIT EventsAdded(mEventsAdded);
222 mEventsAdded.clear();
223 }
224
225 if (!mEventsModified.isEmpty()) {
226 Q_EMIT EventsModified(mEventsModified);
227 mEventsModified.clear();
228 }
229
230 if (!mEventsRemoved.isEmpty()) {
231 Q_EMIT EventsRemoved(mEventsRemoved);
232 mEventsRemoved.clear();
233 }
234}
235
142236
=== modified file 'daemon/historyservicedbus.h'
--- daemon/historyservicedbus.h 2016-06-17 01:49:46 +0000
+++ daemon/historyservicedbus.h 2017-03-23 01:25:43 +0000
@@ -39,6 +39,10 @@
39 void notifyThreadsAdded(const QList<QVariantMap> &threads);39 void notifyThreadsAdded(const QList<QVariantMap> &threads);
40 void notifyThreadsModified(const QList<QVariantMap> &threads);40 void notifyThreadsModified(const QList<QVariantMap> &threads);
41 void notifyThreadsRemoved(const QList<QVariantMap> &threads);41 void notifyThreadsRemoved(const QList<QVariantMap> &threads);
42 void notifyThreadParticipantsChanged(const QVariantMap &thread,
43 const QList<QVariantMap> &added,
44 const QList<QVariantMap> &removed,
45 const QList<QVariantMap> &modified);
4246
43 void notifyEventsAdded(const QList<QVariantMap> &events);47 void notifyEventsAdded(const QList<QVariantMap> &events);
44 void notifyEventsModified(const QList<QVariantMap> &events);48 void notifyEventsModified(const QList<QVariantMap> &events);
@@ -55,9 +59,11 @@
55 const QVariantMap &properties,59 const QVariantMap &properties,
56 int matchFlags,60 int matchFlags,
57 bool create);61 bool create);
62 QList<QVariantMap> ParticipantsForThreads(const QList<QVariantMap> &threadIds);
58 bool WriteEvents(const QList <QVariantMap> &events);63 bool WriteEvents(const QList <QVariantMap> &events);
59 bool RemoveThreads(const QList <QVariantMap> &threads);64 bool RemoveThreads(const QList <QVariantMap> &threads);
60 bool RemoveEvents(const QList <QVariantMap> &events);65 bool RemoveEvents(const QList <QVariantMap> &events);
66 void MarkThreadsAsRead(const QList <QVariantMap> &threads);
6167
62 // views68 // views
63 QString QueryThreads(int type, const QVariantMap &sort, const QVariantMap &filter, const QVariantMap &properties);69 QString QueryThreads(int type, const QVariantMap &sort, const QVariantMap &filter, const QVariantMap &properties);
@@ -70,13 +76,32 @@
70 void ThreadsAdded(const QList<QVariantMap> &threads);76 void ThreadsAdded(const QList<QVariantMap> &threads);
71 void ThreadsModified(const QList<QVariantMap> &threads);77 void ThreadsModified(const QList<QVariantMap> &threads);
72 void ThreadsRemoved(const QList<QVariantMap> &threads);78 void ThreadsRemoved(const QList<QVariantMap> &threads);
79 void ThreadParticipantsChanged(const QVariantMap &thread,
80 const QList<QVariantMap> &added,
81 const QList<QVariantMap> &removed,
82 const QList<QVariantMap> &modified);
7383
74 void EventsAdded(const QList<QVariantMap> &events);84 void EventsAdded(const QList<QVariantMap> &events);
75 void EventsModified(const QList<QVariantMap> &events);85 void EventsModified(const QList<QVariantMap> &events);
76 void EventsRemoved(const QList<QVariantMap> &events);86 void EventsRemoved(const QList<QVariantMap> &events);
7787
88protected:
89 void timerEvent(QTimerEvent *event) override;
90
91protected Q_SLOTS:
92 void filterDuplicatesAndAdd(QList<QVariantMap> &targetList, const QList<QVariantMap> newItems, const QStringList &propertiesToCompare);
93 void triggerSignals();
94 void processSignals();
95
78private:96private:
79 HistoryServiceAdaptor *mAdaptor;97 HistoryServiceAdaptor *mAdaptor;
98 QList<QVariantMap> mThreadsAdded;
99 QList<QVariantMap> mThreadsModified;
100 QList<QVariantMap> mThreadsRemoved;
101 QList<QVariantMap> mEventsAdded;
102 QList<QVariantMap> mEventsModified;
103 QList<QVariantMap> mEventsRemoved;
104 int mSignalsTimer;
80};105};
81106
82#endif // HISTORYSERVICEDBUS_H107#endif // HISTORYSERVICEDBUS_H
83108
=== modified file 'daemon/main.cpp'
--- daemon/main.cpp 2016-03-30 14:03:29 +0000
+++ daemon/main.cpp 2017-03-23 01:25:43 +0000
@@ -20,16 +20,26 @@
20 */20 */
2121
22#include "historydaemon.h"22#include "historydaemon.h"
23#include <QLockFile>
24#include <QDir>
2325
24bool checkApplicationRunning()26bool checkApplicationRunning()
25{27{
26 bool result = false;28 QString lockPath = qgetenv("HISTORY_LOCK_FILE");
27 QDBusReply<bool> reply = QDBusConnection::sessionBus().interface()->isServiceRegistered(History::DBusService);29 if (lockPath.isEmpty()) {
28 if (reply.isValid()) {30 lockPath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation);
29 result = reply.value();31 QDir dir(lockPath);
32 if (!dir.exists("history-service") && !dir.mkpath("history-service")) {
33 qCritical() << "Failed to create dir";
34 // in case we fail to create the lock, better not even start the application
35 return true;
36 }
37 dir.cd("history-service");
38 lockPath = dir.absoluteFilePath("history-daemon.lock");
30 }39 }
3140
32 return result;41 static QLockFile *lockFile = new QLockFile(lockPath);
42 return !lockFile->tryLock();
33}43}
34int main(int argc, char **argv)44int main(int argc, char **argv)
35{45{
3646
=== modified file 'daemon/textchannelobserver.cpp'
--- daemon/textchannelobserver.cpp 2016-08-26 14:10:25 +0000
+++ daemon/textchannelobserver.cpp 2017-03-23 01:25:43 +0000
@@ -39,10 +39,6 @@
39 connect(textChannel.data(),39 connect(textChannel.data(),
40 SIGNAL(messageSent(Tp::Message,Tp::MessageSendingFlags,QString)),40 SIGNAL(messageSent(Tp::Message,Tp::MessageSendingFlags,QString)),
41 SLOT(onMessageSent(Tp::Message,Tp::MessageSendingFlags,QString)));41 SLOT(onMessageSent(Tp::Message,Tp::MessageSendingFlags,QString)));
42 connect(textChannel.data(),
43 SIGNAL(pendingMessageRemoved(const Tp::ReceivedMessage&)),
44 SLOT(onPendingMessageRemoved(const Tp::ReceivedMessage&)));
45
46 Q_EMIT channelAvailable(textChannel);42 Q_EMIT channelAvailable(textChannel);
4743
48 // process the messages that are already pending in the channel44 // process the messages that are already pending in the channel
@@ -57,6 +53,7 @@
57{53{
58 Tp::TextChannelPtr textChannel(qobject_cast<Tp::TextChannel*>(sender()));54 Tp::TextChannelPtr textChannel(qobject_cast<Tp::TextChannel*>(sender()));
59 mChannels.removeAll(textChannel);55 mChannels.removeAll(textChannel);
56 Q_EMIT textChannelInvalidated(textChannel);
60}57}
6158
62void TextChannelObserver::onMessageReceived(const Tp::ReceivedMessage &message)59void TextChannelObserver::onMessageReceived(const Tp::ReceivedMessage &message)
@@ -82,13 +79,3 @@
82 79
83 Q_EMIT messageSent(textChannel, message, sentMessageToken);80 Q_EMIT messageSent(textChannel, message, sentMessageToken);
84}81}
85
86void TextChannelObserver::onPendingMessageRemoved(const Tp::ReceivedMessage &message)
87{
88 Tp::TextChannelPtr textChannel(qobject_cast<Tp::TextChannel*>(sender()));
89 if (textChannel.isNull()) {
90 return;
91 }
92
93 Q_EMIT messageRead(textChannel, message);
94}
9582
=== modified file 'daemon/textchannelobserver.h'
--- daemon/textchannelobserver.h 2016-08-26 14:10:25 +0000
+++ daemon/textchannelobserver.h 2017-03-23 01:25:43 +0000
@@ -37,8 +37,8 @@
3737
38Q_SIGNALS:38Q_SIGNALS:
39 void channelAvailable(const Tp::TextChannelPtr textChannel);39 void channelAvailable(const Tp::TextChannelPtr textChannel);
40 void textChannelInvalidated(const Tp::TextChannelPtr textChannel);
40 void messageReceived(const Tp::TextChannelPtr textChannel, const Tp::ReceivedMessage &message);41 void messageReceived(const Tp::TextChannelPtr textChannel, const Tp::ReceivedMessage &message);
41 void messageRead(const Tp::TextChannelPtr textChannel, const Tp::ReceivedMessage &message);
42 void messageSent(const Tp::TextChannelPtr textChannel, const Tp::Message &message, const QString &messageToken);42 void messageSent(const Tp::TextChannelPtr textChannel, const Tp::Message &message, const QString &messageToken);
4343
44protected:44protected:
@@ -49,7 +49,6 @@
49 void onTextChannelInvalidated();49 void onTextChannelInvalidated();
50 void onMessageReceived(const Tp::ReceivedMessage &message);50 void onMessageReceived(const Tp::ReceivedMessage &message);
51 void onMessageSent(const Tp::Message &message, Tp::MessageSendingFlags flags, const QString &sentMessageToken);51 void onMessageSent(const Tp::Message &message, Tp::MessageSendingFlags flags, const QString &sentMessageToken);
52 void onPendingMessageRemoved(const Tp::ReceivedMessage &message);
5352
54private:53private:
55 QList<Tp::TextChannelPtr> mChannels;54 QList<Tp::TextChannelPtr> mChannels;
5655
=== added file 'plugins/sqlite/schema/v18.sql'
--- plugins/sqlite/schema/v18.sql 1970-01-01 00:00:00 +0000
+++ plugins/sqlite/schema/v18.sql 2017-03-23 01:25:43 +0000
@@ -0,0 +1,14 @@
1CREATE TRIGGER text_threads_delete_trigger AFTER DELETE ON threads
2FOR EACH ROW WHEN old.type=0
3BEGIN
4 DELETE FROM text_events WHERE
5 accountId=old.accountId AND
6 threadId=old.threadId;
7END;
8CREATE TRIGGER voice_threads_delete_trigger AFTER DELETE ON threads
9FOR EACH ROW WHEN old.type=1
10BEGIN
11 DELETE FROM voice_events WHERE
12 accountId=old.accountId AND
13 threadId=old.threadId;
14END;
015
=== modified file 'plugins/sqlite/sqlitedatabase.cpp'
--- plugins/sqlite/sqlitedatabase.cpp 2016-11-08 19:43:53 +0000
+++ plugins/sqlite/sqlitedatabase.cpp 2017-03-23 01:25:43 +0000
@@ -183,6 +183,12 @@
183 return true;183 return true;
184}184}
185185
186
187void trace(void *something, const char *query)
188{
189 qDebug() << "SQLITE TRACE:" << query;
190}
191
186bool SQLiteDatabase::createOrUpdateDatabase()192bool SQLiteDatabase::createOrUpdateDatabase()
187{193{
188 bool create = !QFile(mDatabasePath).exists();194 bool create = !QFile(mDatabasePath).exists();
@@ -199,6 +205,10 @@
199 // and also create the normalizeId function205 // and also create the normalizeId function
200 sqlite3_create_function(handle, "normalizeId", 2, SQLITE_ANY, NULL, &normalizeId, NULL, NULL);206 sqlite3_create_function(handle, "normalizeId", 2, SQLITE_ANY, NULL, &normalizeId, NULL, NULL);
201207
208#ifdef TRACE_SQLITE
209 sqlite3_trace(handle, &trace, NULL);
210#endif
211
202 parseVersionInfo();212 parseVersionInfo();
203213
204 QSqlQuery query(mDatabase);214 QSqlQuery query(mDatabase);
205215
=== modified file 'plugins/sqlite/sqlitehistoryeventview.cpp'
--- plugins/sqlite/sqlitehistoryeventview.cpp 2015-01-28 23:08:01 +0000
+++ plugins/sqlite/sqlitehistoryeventview.cpp 2017-03-23 01:25:43 +0000
@@ -42,7 +42,14 @@
42 QString condition = mPlugin->filterToString(filter, filterValues);42 QString condition = mPlugin->filterToString(filter, filterValues);
43 QString order;43 QString order;
44 if (!sort.sortField().isNull()) {44 if (!sort.sortField().isNull()) {
45 order = QString("ORDER BY %1 %2").arg(sort.sortField(), sort.sortOrder() == Qt::AscendingOrder ? "ASC" : "DESC");45 // WORKAROUND: Supports multiple fields by split it using ','
46 Q_FOREACH(const QString& field, sort.sortField().split(",")) {
47 order += QString("%1 %2, ")
48 .arg(field.trimmed())
49 .arg(sort.sortOrder() == Qt::AscendingOrder ? "ASC" : "DESC");
50 }
51
52 order = QString("ORDER BY %1").arg(order.mid(0, order.lastIndexOf(",")));
46 // FIXME: check case sensitiviy53 // FIXME: check case sensitiviy
47 }54 }
4855
4956
=== modified file 'plugins/sqlite/sqlitehistoryplugin.cpp'
--- plugins/sqlite/sqlitehistoryplugin.cpp 2016-11-24 01:50:48 +0000
+++ plugins/sqlite/sqlitehistoryplugin.cpp 2017-03-23 01:25:43 +0000
@@ -306,6 +306,54 @@
306 return new SQLiteHistoryEventView(this, type, sort, filter);306 return new SQLiteHistoryEventView(this, type, sort, filter);
307}307}
308308
309QVariantMap SQLiteHistoryPlugin::markThreadAsRead(const QVariantMap &thread)
310{
311 QSqlQuery query(SQLiteDatabase::instance()->database());
312
313 if (thread[History::FieldAccountId].toString().isEmpty() ||
314 thread[History::FieldThreadId].toString().isEmpty()) {
315 return QVariantMap();
316 }
317
318 // first check if the thread actually has anything to change
319 query.prepare("SELECT unreadCount from threads WHERE accountId=:accountId AND threadId=:threadId AND type=:type");
320 query.bindValue(":accountId", thread[History::FieldAccountId].toString());
321 query.bindValue(":threadId", thread[History::FieldThreadId].toString());
322 query.bindValue(":type", (uint)History::EventTypeText);
323 if (!query.exec() || !query.next()) {
324 qCritical() << "Failed to verify the unread messages of the thread. Error:" << query.lastError();
325 return QVariantMap();
326 }
327
328
329 int unreadCount = query.value(0).toUInt();
330 if (unreadCount == 0) {
331 // no messages to ack, so no need to update anything
332 return QVariantMap();
333 }
334
335 query.prepare("UPDATE text_events SET newEvent=:newEvent WHERE accountId=:accountId AND threadId=:threadId AND newEvent=1");
336 query.bindValue(":accountId", thread[History::FieldAccountId].toString());
337 query.bindValue(":threadId", thread[History::FieldThreadId].toString());
338 query.bindValue(":newEvent", false);
339
340 if (!query.exec()) {
341 qCritical() << "Failed to mark thread as read: Error:" << query.lastError();
342 return QVariantMap();
343 }
344
345 QVariantMap existingThread = getSingleThread((History::EventType) thread[History::FieldType].toInt(),
346 thread[History::FieldAccountId].toString(),
347 thread[History::FieldThreadId].toString(),
348 QVariantMap());
349 if (!existingThread.isEmpty()) {
350 addThreadsToCache(QList<QVariantMap>() << existingThread);
351 return existingThread;
352 }
353
354 return QVariantMap();
355}
356
309QVariantMap SQLiteHistoryPlugin::threadForProperties(const QString &accountId,357QVariantMap SQLiteHistoryPlugin::threadForProperties(const QString &accountId,
310 History::EventType type,358 History::EventType type,
311 const QVariantMap &properties,359 const QVariantMap &properties,
@@ -315,8 +363,6 @@
315 return QVariantMap();363 return QVariantMap();
316 }364 }
317365
318 QSqlQuery query(SQLiteDatabase::instance()->database());
319
320 History::ChatType chatType = (History::ChatType)properties[History::FieldChatType].toUInt();366 History::ChatType chatType = (History::ChatType)properties[History::FieldChatType].toUInt();
321367
322 if (chatType == History::ChatTypeRoom) {368 if (chatType == History::ChatTypeRoom) {
@@ -332,6 +378,62 @@
332 return threadForParticipants(accountId, type, participants.identifiers(), matchFlags);378 return threadForParticipants(accountId, type, participants.identifiers(), matchFlags);
333}379}
334380
381QString SQLiteHistoryPlugin::threadIdForProperties(const QString &accountId, History::EventType type, const QVariantMap &properties, History::MatchFlags matchFlags)
382{
383 if (properties.isEmpty()) {
384 return QString::null;
385 }
386
387 // if chat type is room, just get the threadId directly
388 History::ChatType chatType = (History::ChatType)properties[History::FieldChatType].toUInt();
389 if (chatType == History::ChatTypeRoom) {
390 QString threadId = properties[History::FieldThreadId].toString();
391 return threadId;
392 }
393
394 // if chat type is anything else, fallback to returning the threadId from the participants list
395 History::Participants participants = History::Participants::fromVariant(properties[History::FieldParticipantIds]);
396 return threadForParticipants(accountId, type, participants.identifiers(), matchFlags)[History::FieldThreadId].toString();
397}
398
399QList<QVariantMap> SQLiteHistoryPlugin::participantsForThreads(const QList<QVariantMap> &threadIds)
400{
401 QList<QVariantMap> results;
402 Q_FOREACH(const QVariantMap &thread, threadIds) {
403 QString accountId = thread[History::FieldAccountId].toString();
404 QString threadId = thread[History::FieldThreadId].toString();
405 History::EventType type = (History::EventType)thread[History::FieldType].toUInt();
406 QVariantMap result = thread;
407
408 QSqlQuery query;
409 query.prepare("SELECT normalizedId, alias, state, roles FROM thread_participants "
410 "WHERE accountId=:accountId AND threadId=:threadId AND type=:type");
411 query.bindValue(":accountId", accountId);
412 query.bindValue(":threadId", threadId);
413 query.bindValue(":type", type);
414 QVariantList participants;
415 if (!query.exec()) {
416 qWarning() << "Failed to retrieve participants. Error:" << query.lastError().text() << query.lastQuery();
417 results << result;
418 continue;
419 }
420
421 while (query.next()) {
422 QVariantMap participant;
423 QString identifier = query.value(0).toString();
424 participant[History::FieldIdentifier] = identifier;
425 participant[History::FieldAlias] = query.value(1);
426 participant[History::FieldParticipantState] = query.value(2);
427 participant[History::FieldParticipantRoles] = query.value(3);
428 participants << History::ContactMatcher::instance()->contactInfo(accountId, identifier, true, participant);
429 }
430
431 result[History::FieldParticipants] = participants;
432 results << result;
433 }
434 return results;
435}
436
335QVariantMap SQLiteHistoryPlugin::threadForParticipants(const QString &accountId,437QVariantMap SQLiteHistoryPlugin::threadForParticipants(const QString &accountId,
336 History::EventType type,438 History::EventType type,
337 const QStringList &participants,439 const QStringList &participants,
@@ -1093,22 +1195,6 @@
1093 << "threads.unreadCount"1195 << "threads.unreadCount"
1094 << "threads.lastEventTimestamp";1196 << "threads.lastEventTimestamp";
10951197
1096 // get the participants in the query already
1097 fields << "(SELECT group_concat(thread_participants.participantId, \"|,|\") "
1098 "FROM thread_participants WHERE thread_participants.accountId=threads.accountId "
1099 "AND thread_participants.threadId=threads.threadId "
1100 "AND thread_participants.type=threads.type GROUP BY accountId,threadId,type) as participants";
1101
1102 fields << "(SELECT group_concat(thread_participants.state, \"|,|\") "
1103 "FROM thread_participants WHERE thread_participants.accountId=threads.accountId "
1104 "AND thread_participants.threadId=threads.threadId "
1105 "AND thread_participants.type=threads.type GROUP BY accountId,threadId,type) as state";
1106
1107 fields << "(SELECT group_concat(thread_participants.roles, \"|,|\") "
1108 "FROM thread_participants WHERE thread_participants.accountId=threads.accountId "
1109 "AND thread_participants.threadId=threads.threadId "
1110 "AND thread_participants.type=threads.type GROUP BY accountId,threadId,type) as roles";
1111
1112 QStringList extraFields;1198 QStringList extraFields;
1113 QString table;1199 QString table;
11141200
@@ -1136,6 +1222,7 @@
1136QList<QVariantMap> SQLiteHistoryPlugin::parseThreadResults(History::EventType type, QSqlQuery &query, const QVariantMap &properties)1222QList<QVariantMap> SQLiteHistoryPlugin::parseThreadResults(History::EventType type, QSqlQuery &query, const QVariantMap &properties)
1137{1223{
1138 QList<QVariantMap> threads;1224 QList<QVariantMap> threads;
1225 QList<QVariantMap> threadsWithoutParticipants;
1139 QSqlQuery attachmentsQuery(SQLiteDatabase::instance()->database());1226 QSqlQuery attachmentsQuery(SQLiteDatabase::instance()->database());
1140 QList<QVariantMap> attachments;1227 QList<QVariantMap> attachments;
1141 bool grouped = false;1228 bool grouped = false;
@@ -1170,31 +1257,11 @@
1170 thread[History::FieldEventId] = query.value(2);1257 thread[History::FieldEventId] = query.value(2);
1171 thread[History::FieldCount] = query.value(3);1258 thread[History::FieldCount] = query.value(3);
1172 thread[History::FieldUnreadCount] = query.value(4);1259 thread[History::FieldUnreadCount] = query.value(4);
1173 QStringList participants = query.value(6).toString().split("|,|", QString::SkipEmptyParts);
1174 QList<int> participantStatus;
1175 QStringList participantStatusString = query.value(7).toString().split("|,|", QString::SkipEmptyParts);
1176 Q_FOREACH(const QString &statusString, participantStatusString) {
1177 participantStatus << statusString.toUInt();
1178 }
1179 QStringList participantRolesString = query.value(8).toString().split("|,|", QString::SkipEmptyParts);
1180 QList<int> participantRoles;
1181 Q_FOREACH(const QString &rolesString, participantRolesString) {
1182 participantRoles << rolesString.toUInt();
1183 }
1184 QVariantList contactList;
1185 QVariantList contactInfo = History::ContactMatcher::instance()->contactInfo(accountId, participants, true);
1186 for (int i = 0; i < contactInfo.count(); ++i) {
1187 QVariantMap map = contactInfo[i].toMap();
1188 map["state"] = participantStatus[i];
1189 map["roles"] = participantRoles[i];
1190 contactList << map;
1191 }
1192 thread[History::FieldParticipants] = contactList;
11931260
1194 // the generic event fields1261 // the generic event fields
1195 thread[History::FieldSenderId] = query.value(9);1262 thread[History::FieldSenderId] = query.value(6);
1196 thread[History::FieldTimestamp] = toLocalTimeString(query.value(5).toDateTime());1263 thread[History::FieldTimestamp] = toLocalTimeString(query.value(5).toDateTime());
1197 thread[History::FieldNewEvent] = query.value(10).toBool();1264 thread[History::FieldNewEvent] = query.value(7).toBool();
11981265
1199 // the next step is to get the last event1266 // the next step is to get the last event
1200 switch (type) {1267 switch (type) {
@@ -1225,13 +1292,13 @@
1225 thread[History::FieldAttachments] = QVariant::fromValue(attachments);1292 thread[History::FieldAttachments] = QVariant::fromValue(attachments);
1226 attachments.clear();1293 attachments.clear();
1227 }1294 }
1228 thread[History::FieldMessage] = query.value(11);1295 thread[History::FieldMessage] = query.value(8);
1229 thread[History::FieldMessageType] = query.value(12);1296 thread[History::FieldMessageType] = query.value(9);
1230 thread[History::FieldMessageStatus] = query.value(13);1297 thread[History::FieldMessageStatus] = query.value(10);
1231 thread[History::FieldReadTimestamp] = toLocalTimeString(query.value(14).toDateTime());1298 thread[History::FieldReadTimestamp] = toLocalTimeString(query.value(11).toDateTime());
1232 thread[History::FieldChatType] = query.value(15).toUInt();1299 thread[History::FieldChatType] = query.value(12).toUInt();
12331300
1234 if (thread[History::FieldChatType].toInt() == 2) {1301 if (thread[History::FieldChatType].toInt() == History::ChatTypeRoom) {
1235 QVariantMap chatRoomInfo;1302 QVariantMap chatRoomInfo;
1236 QSqlQuery query1(SQLiteDatabase::instance()->database());1303 QSqlQuery query1(SQLiteDatabase::instance()->database());
12371304
@@ -1291,15 +1358,28 @@
12911358
1292 thread[History::FieldChatRoomInfo] = chatRoomInfo;1359 thread[History::FieldChatRoomInfo] = chatRoomInfo;
1293 }1360 }
1361 if (!History::Utils::shouldIncludeParticipants(History::Thread::fromProperties(thread))) {
1362 thread.remove(History::FieldParticipants);
1363 threadsWithoutParticipants << thread;
1364 } else {
1365 threads << thread;
1366 }
1294 break;1367 break;
1295 case History::EventTypeVoice:1368 case History::EventTypeVoice:
1296 thread[History::FieldMissed] = query.value(12);1369 thread[History::FieldMissed] = query.value(9);
1297 thread[History::FieldDuration] = query.value(11);1370 thread[History::FieldDuration] = query.value(8);
1298 thread[History::FieldRemoteParticipant] = History::ContactMatcher::instance()->contactInfo(accountId, query.value(13).toString(), true);1371 thread[History::FieldRemoteParticipant] = History::ContactMatcher::instance()->contactInfo(accountId, query.value(10).toString(), true);
1372 threads << thread;
1299 break;1373 break;
1300 }1374 }
1301 threads << thread;
1302 }1375 }
1376
1377 // get the participants
1378 threads = participantsForThreads(threads);
1379
1380 // and append the threads with no participants
1381 threads << threadsWithoutParticipants;
1382
1303 return threads;1383 return threads;
1304}1384}
13051385
@@ -1317,7 +1397,8 @@
1317 QString queryText;1397 QString queryText;
1318 switch (type) {1398 switch (type) {
1319 case History::EventTypeText:1399 case History::EventTypeText:
1320 participantsField = participantsField.arg("text_events", QString::number(type));1400 // for text events we don't need the participants at all
1401 participantsField = "\"\" as participants";
1321 queryText = QString("SELECT accountId, threadId, eventId, senderId, timestamp, newEvent, %1, "1402 queryText = QString("SELECT accountId, threadId, eventId, senderId, timestamp, newEvent, %1, "
1322 "message, messageType, messageStatus, readTimestamp, subject, informationType FROM text_events %2 %3").arg(participantsField, modifiedCondition, order);1403 "message, messageType, messageStatus, readTimestamp, subject, informationType FROM text_events %2 %3").arg(participantsField, modifiedCondition, order);
1323 break;1404 break;
@@ -1353,8 +1434,10 @@
1353 event[History::FieldSenderId] = query.value(3);1434 event[History::FieldSenderId] = query.value(3);
1354 event[History::FieldTimestamp] = toLocalTimeString(query.value(4).toDateTime());1435 event[History::FieldTimestamp] = toLocalTimeString(query.value(4).toDateTime());
1355 event[History::FieldNewEvent] = query.value(5).toBool();1436 event[History::FieldNewEvent] = query.value(5).toBool();
1356 QStringList participants = query.value(6).toString().split("|,|");1437 if (type != History::EventTypeText) {
1357 event[History::FieldParticipants] = History::ContactMatcher::instance()->contactInfo(accountId, participants, true);1438 QStringList participants = query.value(6).toString().split("|,|");
1439 event[History::FieldParticipants] = History::ContactMatcher::instance()->contactInfo(accountId, participants, true);
1440 }
13581441
1359 switch (type) {1442 switch (type) {
1360 case History::EventTypeText:1443 case History::EventTypeText:
13611444
=== modified file 'plugins/sqlite/sqlitehistoryplugin.h'
--- plugins/sqlite/sqlitehistoryplugin.h 2016-09-21 17:44:39 +0000
+++ plugins/sqlite/sqlitehistoryplugin.h 2017-03-23 01:25:43 +0000
@@ -59,7 +59,11 @@
59 History::EventType type,59 History::EventType type,
60 const QVariantMap &properties,60 const QVariantMap &properties,
61 History::MatchFlags matchFlags = History::MatchCaseSensitive);61 History::MatchFlags matchFlags = History::MatchCaseSensitive);
6262 QString threadIdForProperties(const QString &accountId,
63 History::EventType type,
64 const QVariantMap &properties,
65 History::MatchFlags matchFlags = History::MatchCaseSensitive) override;
66 QList<QVariantMap> participantsForThreads(const QList<QVariantMap> &threadIds) override;
63 QList<QVariantMap> eventsForThread(const QVariantMap &thread);67 QList<QVariantMap> eventsForThread(const QVariantMap &thread);
6468
65 QVariantMap getSingleThread(History::EventType type, const QString &accountId, const QString &threadId, const QVariantMap &properties = QVariantMap());69 QVariantMap getSingleThread(History::EventType type, const QString &accountId, const QString &threadId, const QVariantMap &properties = QVariantMap());
@@ -73,6 +77,7 @@
73 bool updateRoomParticipantsRoles(const QString &accountId, const QString &threadId, History::EventType type, const QVariantMap &participantsRoles);77 bool updateRoomParticipantsRoles(const QString &accountId, const QString &threadId, History::EventType type, const QVariantMap &participantsRoles);
74 bool updateRoomInfo(const QString &accountId, const QString &threadId, History::EventType type, const QVariantMap &properties, const QStringList &invalidated = QStringList());78 bool updateRoomInfo(const QString &accountId, const QString &threadId, History::EventType type, const QVariantMap &properties, const QStringList &invalidated = QStringList());
75 bool removeThread(const QVariantMap &thread);79 bool removeThread(const QVariantMap &thread);
80 QVariantMap markThreadAsRead(const QVariantMap &thread);
7681
77 History::EventWriteResult writeTextEvent(const QVariantMap &event);82 History::EventWriteResult writeTextEvent(const QVariantMap &event);
78 bool removeTextEvent(const QVariantMap &event);83 bool removeTextEvent(const QVariantMap &event);
7984
=== modified file 'plugins/sqlite/sqlitehistorythreadview.cpp'
--- plugins/sqlite/sqlitehistorythreadview.cpp 2016-11-24 01:56:01 +0000
+++ plugins/sqlite/sqlitehistorythreadview.cpp 2017-03-23 01:25:43 +0000
@@ -43,7 +43,14 @@
43 QString condition = mPlugin->filterToString(filter, filterValues);43 QString condition = mPlugin->filterToString(filter, filterValues);
44 QString order;44 QString order;
45 if (!sort.sortField().isNull()) {45 if (!sort.sortField().isNull()) {
46 order = QString("ORDER BY %1 %2").arg(sort.sortField(), sort.sortOrder() == Qt::AscendingOrder ? "ASC" : "DESC");46 // WORKAROUND: Supports multiple fields by split it using ','
47 Q_FOREACH(const QString& field, sort.sortField().split(",")) {
48 order += QString("%1 %2, ")
49 .arg(field.trimmed())
50 .arg(sort.sortOrder() == Qt::AscendingOrder ? "ASC" : "DESC");
51 }
52
53 order = QString("ORDER BY %1").arg(order.mid(0, order.lastIndexOf(",")));
47 // FIXME: check case sensitiviy54 // FIXME: check case sensitiviy
48 }55 }
4956
5057
=== modified file 'src/contactmatcher.cpp'
--- src/contactmatcher.cpp 2016-06-17 01:49:46 +0000
+++ src/contactmatcher.cpp 2017-03-23 01:25:43 +0000
@@ -99,21 +99,22 @@
99{99{
100 InternalContactMap &internalMap = mContactMap[accountId];100 InternalContactMap &internalMap = mContactMap[accountId];
101101
102
103 QString normalizedId = normalizeId(identifier);
104
105 QVariantMap map;
102 // first do a simple string match on the map106 // first do a simple string match on the map
103 if (internalMap.contains(identifier)) {107 if (internalMap.contains(normalizedId)) {
104 return internalMap[identifier];108 map = internalMap[normalizedId];
105 }109 } else if (History::TelepathyHelper::instance()->ready()) {
106 110 // and if there was no match, asynchronously request the info, and return an empty map for now
107 QVariantMap map;111 map = requestContactInfo(accountId, normalizedId, synchronous);
108
109 // and if there was no match, asynchronously request the info, and return an empty map for now
110 if (History::TelepathyHelper::instance()->ready()) {
111 map = requestContactInfo(accountId, identifier, synchronous);
112 } else if (!synchronous) {112 } else if (!synchronous) {
113 RequestInfo info{accountId, identifier};113 RequestInfo info{accountId, normalizedId};
114 mPendingRequests.append(info);114 mPendingRequests.append(info);
115 }115 }
116 map[History::FieldIdentifier] = identifier;116
117 map[History::FieldIdentifier] = normalizedId;
117 map[History::FieldAccountId] = accountId;118 map[History::FieldAccountId] = accountId;
118119
119 QMapIterator<QString, QVariant> i(properties);120 QMapIterator<QString, QVariant> i(properties);
@@ -124,7 +125,7 @@
124 }125 }
125 }126 }
126127
127 mContactMap[accountId][identifier] = map;128 mContactMap[accountId][normalizedId] = map;
128 return map;129 return map;
129}130}
130131
@@ -309,10 +310,17 @@
309 */310 */
310QVariantMap ContactMatcher::requestContactInfo(const QString &accountId, const QString &identifier, bool synchronous)311QVariantMap ContactMatcher::requestContactInfo(const QString &accountId, const QString &identifier, bool synchronous)
311{312{
313 QString normalizedId = normalizeId(identifier);
312 QStringList addressableVCardFields = addressableFields(accountId);314 QStringList addressableVCardFields = addressableFields(accountId);
315
316 QVariantMap contactInfo;
317 contactInfo[History::FieldIdentifier] = identifier;
318 contactInfo[History::FieldAccountId] = accountId;
319
313 if (addressableVCardFields.isEmpty()) {320 if (addressableVCardFields.isEmpty()) {
321 mContactMap[accountId][identifier] = contactInfo;
314 // FIXME: add support for generic accounts322 // FIXME: add support for generic accounts
315 return QVariantMap();323 return contactInfo;
316 }324 }
317325
318 bool phoneCompare = addressableVCardFields.contains("tel");326 bool phoneCompare = addressableVCardFields.contains("tel");
@@ -328,7 +336,7 @@
328 QContactUnionFilter topLevelFilter;336 QContactUnionFilter topLevelFilter;
329 Q_FOREACH(const QString &field, addressableVCardFields) {337 Q_FOREACH(const QString &field, addressableVCardFields) {
330 if (field == "tel") {338 if (field == "tel") {
331 topLevelFilter.append(QContactPhoneNumber::match(identifier));339 topLevelFilter.append(QContactPhoneNumber::match(normalizedId));
332 } else {340 } else {
333 // FIXME: handle more fields341 // FIXME: handle more fields
334 // rely on a generic field filter342 // rely on a generic field filter
@@ -340,7 +348,7 @@
340 QContactDetailFilter valueFilter = QContactDetailFilter();348 QContactDetailFilter valueFilter = QContactDetailFilter();
341 valueFilter.setDetailType(QContactExtendedDetail::Type, QContactExtendedDetail::FieldData);349 valueFilter.setDetailType(QContactExtendedDetail::Type, QContactExtendedDetail::FieldData);
342 valueFilter.setMatchFlags(QContactFilter::MatchExactly);350 valueFilter.setMatchFlags(QContactFilter::MatchExactly);
343 valueFilter.setValue(identifier);351 valueFilter.setValue(normalizedId);
344352
345 QContactIntersectionFilter intersectionFilter;353 QContactIntersectionFilter intersectionFilter;
346 intersectionFilter.append(nameFilter);354 intersectionFilter.append(nameFilter);
@@ -353,10 +361,11 @@
353 if (synchronous) {361 if (synchronous) {
354 QList<QContact> contacts = mManager->contacts(topLevelFilter, QList<QContactSortOrder>(), hint);362 QList<QContact> contacts = mManager->contacts(topLevelFilter, QList<QContactSortOrder>(), hint);
355 if (contacts.isEmpty()) {363 if (contacts.isEmpty()) {
356 return QVariantMap();364 mContactMap[accountId][identifier] = contactInfo;
365 return contactInfo;
357 }366 }
358 // for synchronous requests, return the results right away.367 // for synchronous requests, return the results right away.
359 return matchAndUpdate(accountId, identifier, contacts.first());368 return matchAndUpdate(accountId, normalizedId, contacts.first());
360 } else {369 } else {
361 // check if there is a request already going on for the given contact370 // check if there is a request already going on for the given contact
362 Q_FOREACH(const RequestInfo &info, mRequests.values()) {371 Q_FOREACH(const RequestInfo &info, mRequests.values()) {
@@ -365,7 +374,7 @@
365 continue;374 continue;
366 }375 }
367376
368 if (info.identifier == identifier) {377 if (info.identifier == normalizedId) {
369 // if so, just wait for it to finish378 // if so, just wait for it to finish
370 return QVariantMap();379 return QVariantMap();
371 }380 }
@@ -381,7 +390,7 @@
381390
382 RequestInfo info;391 RequestInfo info;
383 info.accountId = accountId;392 info.accountId = accountId;
384 info.identifier = identifier;393 info.identifier = normalizedId;
385 mRequests[request] = info;394 mRequests[request] = info;
386 request->start();395 request->start();
387 }396 }
@@ -414,7 +423,6 @@
414 QStringList fields = addressableFields(accountId);423 QStringList fields = addressableFields(accountId);
415 bool match = false;424 bool match = false;
416425
417 int fieldsCount = fields.count();
418 Q_FOREACH(const QString &field, fields) {426 Q_FOREACH(const QString &field, fields) {
419 if (field == "tel") {427 if (field == "tel") {
420 QList<QContactDetail> details = contact.details(QContactDetail::TypePhoneNumber);428 QList<QContactDetail> details = contact.details(QContactDetail::TypePhoneNumber);
@@ -463,12 +471,26 @@
463 return mAddressableFields[accountId];471 return mAddressableFields[accountId];
464 }472 }
465473
474 // FIXME: hardcoding account IDs here is not a good idea, we have to fix addressable fields on
475 // the protocols themselves
476 if (accountId.startsWith("irc/irc")) {
477 QStringList empty;
478 mAddressableFields[accountId] = empty;
479 return empty;
480 }
481
466 Tp::AccountPtr account = History::TelepathyHelper::instance()->accountForId(accountId);482 Tp::AccountPtr account = History::TelepathyHelper::instance()->accountForId(accountId);
467 QStringList fields;483 QStringList fields;
468 if (!account.isNull()) {484 if (!account.isNull()) {
469 fields = account->protocolInfo().addressableVCardFields();485 fields = account->protocolInfo().addressableVCardFields();
470 mAddressableFields[accountId] = fields;486 }
471 }487
488 // fallback to phone number matching in case everything else fails
489 if (fields.isEmpty()) {
490 fields << "tel";
491 }
492
493 mAddressableFields[accountId] = fields;
472494
473 return fields;495 return fields;
474}496}
@@ -478,4 +500,17 @@
478 return (map.contains(History::FieldContactId) && !map[History::FieldContactId].toString().isEmpty());500 return (map.contains(History::FieldContactId) && !map[History::FieldContactId].toString().isEmpty());
479}501}
480502
503QString ContactMatcher::normalizeId(const QString &id)
504{
505 QString normalizedId = id;
506
507 // FIXME: this is a hack so that SIP URIs get converted into phone numbers for contact matching
508 if (normalizedId.startsWith("sip:")) {
509 normalizedId.remove("sip:").remove(QRegularExpression("@.*$"));
510 }
511
512 return normalizedId;
513}
514
515
481}516}
482517
=== modified file 'src/contactmatcher_p.h'
--- src/contactmatcher_p.h 2016-06-17 01:49:46 +0000
+++ src/contactmatcher_p.h 2017-03-23 01:25:43 +0000
@@ -51,6 +51,8 @@
51 // this will only watch for contact changes affecting the identifier, but won't fetch contact info51 // this will only watch for contact changes affecting the identifier, but won't fetch contact info
52 void watchIdentifier(const QString &accountId, const QString &identifier, const QVariantMap &currentInfo = QVariantMap());52 void watchIdentifier(const QString &accountId, const QString &identifier, const QVariantMap &currentInfo = QVariantMap());
5353
54 static QString normalizeId(const QString &id);
55
54Q_SIGNALS:56Q_SIGNALS:
55 void contactInfoChanged(const QString &acountId, const QString &identifier, const QVariantMap &contactInfo);57 void contactInfoChanged(const QString &acountId, const QString &identifier, const QVariantMap &contactInfo);
5658
5759
=== modified file 'src/eventview.cpp'
--- src/eventview.cpp 2015-10-01 19:44:45 +0000
+++ src/eventview.cpp 2017-03-23 01:25:43 +0000
@@ -1,5 +1,5 @@
1/*1/*
2 * Copyright (C) 2013 Canonical, Ltd.2 * Copyright (C) 2013-2017 Canonical, Ltd.
3 *3 *
4 * Authors:4 * Authors:
5 * Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>5 * Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>
@@ -53,7 +53,7 @@
53 }53 }
5454
55 if (filterNull || filter.match(event.properties())) {55 if (filterNull || filter.match(event.properties())) {
56 filtered << events;56 filtered << event;
57 }57 }
58 }58 }
5959
@@ -128,6 +128,10 @@
128 connect(Manager::instance(),128 connect(Manager::instance(),
129 SIGNAL(eventsRemoved(History::Events)),129 SIGNAL(eventsRemoved(History::Events)),
130 SLOT(_d_eventsRemoved(History::Events)));130 SLOT(_d_eventsRemoved(History::Events)));
131 // we don't filter thread signals
132 connect(Manager::instance(),
133 SIGNAL(threadsRemoved(History::Threads)),
134 SIGNAL(threadsRemoved(History::Threads)));
131}135}
132136
133EventView::~EventView()137EventView::~EventView()
134138
=== modified file 'src/eventview.h'
--- src/eventview.h 2013-09-17 23:05:35 +0000
+++ src/eventview.h 2017-03-23 01:25:43 +0000
@@ -1,5 +1,5 @@
1/*1/*
2 * Copyright (C) 2013 Canonical, Ltd.2 * Copyright (C) 2013-2017 Canonical, Ltd.
3 *3 *
4 * Authors:4 * Authors:
5 * Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>5 * Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>
@@ -24,6 +24,7 @@
2424
25#include "types.h"25#include "types.h"
26#include "event.h"26#include "event.h"
27#include "thread.h"
27#include "filter.h"28#include "filter.h"
28#include "sort.h"29#include "sort.h"
29#include <QObject>30#include <QObject>
@@ -50,6 +51,7 @@
50 void eventsAdded(const History::Events &events);51 void eventsAdded(const History::Events &events);
51 void eventsModified(const History::Events &events);52 void eventsModified(const History::Events &events);
52 void eventsRemoved(const History::Events &events);53 void eventsRemoved(const History::Events &events);
54 void threadsRemoved(const History::Threads &threads);
53 void invalidated();55 void invalidated();
5456
55private:57private:
5658
=== modified file 'src/manager.cpp'
--- src/manager.cpp 2016-06-17 01:49:46 +0000
+++ src/manager.cpp 2017-03-23 01:25:43 +0000
@@ -65,6 +65,9 @@
65 SIGNAL(threadsRemoved(History::Threads)),65 SIGNAL(threadsRemoved(History::Threads)),
66 SIGNAL(threadsRemoved(History::Threads)));66 SIGNAL(threadsRemoved(History::Threads)));
67 connect(d->dbus.data(),67 connect(d->dbus.data(),
68 SIGNAL(threadParticipantsChanged(History::Thread, History::Participants, History::Participants, History::Participants)),
69 SIGNAL(threadParticipantsChanged(History::Thread, History::Participants, History::Participants, History::Participants)));
70 connect(d->dbus.data(),
68 SIGNAL(eventsAdded(History::Events)),71 SIGNAL(eventsAdded(History::Events)),
69 SIGNAL(eventsAdded(History::Events)));72 SIGNAL(eventsAdded(History::Events)));
70 connect(d->dbus.data(),73 connect(d->dbus.data(),
@@ -104,6 +107,13 @@
104 return self;107 return self;
105}108}
106109
110void Manager::markThreadsAsRead(const History::Threads &threads)
111{
112 Q_D(Manager);
113
114 d->dbus->markThreadsAsRead(threads);
115}
116
107ThreadViewPtr Manager::queryThreads(EventType type,117ThreadViewPtr Manager::queryThreads(EventType type,
108 const Sort &sort,118 const Sort &sort,
109 const Filter &filter,119 const Filter &filter,
@@ -154,6 +164,20 @@
154 return d->dbus->threadForProperties(accountId, type, properties, matchFlags, create);164 return d->dbus->threadForProperties(accountId, type, properties, matchFlags, create);
155}165}
156166
167/**
168 * @brief Request the list of participants of the given threads to the service
169 * @param threads The threads to be filled
170 *
171 * This is an asychronous request. When finished, the signal @ref threadParticipantsChanged
172 * will be emitted for the given threads.
173 */
174void Manager::requestThreadParticipants(const Threads &threads)
175{
176 Q_D(Manager);
177
178 d->dbus->requestThreadParticipants(threads);
179}
180
157Thread Manager::getSingleThread(EventType type, const QString &accountId, const QString &threadId, const QVariantMap &properties)181Thread Manager::getSingleThread(EventType type, const QString &accountId, const QString &threadId, const QVariantMap &properties)
158{182{
159 Q_D(Manager);183 Q_D(Manager);
160184
=== modified file 'src/manager.h'
--- src/manager.h 2016-06-17 01:49:46 +0000
+++ src/manager.h 2017-03-23 01:25:43 +0000
@@ -66,19 +66,22 @@
66 const QVariantMap &properties,66 const QVariantMap &properties,
67 History::MatchFlags matchFlags = History::MatchCaseSensitive,67 History::MatchFlags matchFlags = History::MatchCaseSensitive,
68 bool create = false);68 bool create = false);
6969 void requestThreadParticipants(const History::Threads &threads);
70 Thread getSingleThread(EventType type, const QString &accountId, const QString &threadId, const QVariantMap &properties = QVariantMap());70 Thread getSingleThread(EventType type, const QString &accountId, const QString &threadId, const QVariantMap &properties = QVariantMap());
7171
72 bool writeEvents(const History::Events &events);72 bool writeEvents(const History::Events &events);
73 bool removeThreads(const Threads &threads);73 bool removeThreads(const Threads &threads);
74 bool removeEvents(const Events &events);74 bool removeEvents(const Events &events);
7575
76 void markThreadsAsRead(const History::Threads &thread);
77
76 bool isServiceRunning() const;78 bool isServiceRunning() const;
7779
78Q_SIGNALS:80Q_SIGNALS:
79 void threadsAdded(const History::Threads &threads);81 void threadsAdded(const History::Threads &threads);
80 void threadsModified(const History::Threads &threads);82 void threadsModified(const History::Threads &threads);
81 void threadsRemoved(const History::Threads &threads);83 void threadsRemoved(const History::Threads &threads);
84 void threadParticipantsChanged(const History::Thread &thread, const History::Participants &added, const History::Participants &removed, const History::Participants &modified);
8285
83 void eventsAdded(const History::Events &events);86 void eventsAdded(const History::Events &events);
84 void eventsModified(const History::Events &events);87 void eventsModified(const History::Events &events);
8588
=== modified file 'src/managerdbus.cpp'
--- src/managerdbus.cpp 2016-06-17 01:49:46 +0000
+++ src/managerdbus.cpp 2017-03-23 01:25:43 +0000
@@ -28,6 +28,8 @@
28#include <QDBusReply>28#include <QDBusReply>
29#include <QDBusMetaType>29#include <QDBusMetaType>
3030
31#include <QDebug>
32
31Q_DECLARE_METATYPE(QList< QVariantMap >)33Q_DECLARE_METATYPE(QList< QVariantMap >)
3234
33namespace History35namespace History
@@ -50,6 +52,12 @@
50 connection.connect(DBusService, DBusObjectPath, DBusInterface, "ThreadsRemoved",52 connection.connect(DBusService, DBusObjectPath, DBusInterface, "ThreadsRemoved",
51 this, SLOT(onThreadsRemoved(QList<QVariantMap>)));53 this, SLOT(onThreadsRemoved(QList<QVariantMap>)));
5254
55 connection.connect(DBusService, DBusObjectPath, DBusInterface, "ThreadParticipantsChanged",
56 this, SLOT(onThreadParticipantsChanged(QVariantMap,
57 QList<QVariantMap>,
58 QList<QVariantMap>,
59 QList<QVariantMap>)));
60
53 connection.connect(DBusService, DBusObjectPath, DBusInterface, "EventsAdded",61 connection.connect(DBusService, DBusObjectPath, DBusInterface, "EventsAdded",
54 this, SLOT(onEventsAdded(QList<QVariantMap>)));62 this, SLOT(onEventsAdded(QList<QVariantMap>)));
55 connection.connect(DBusService, DBusObjectPath, DBusInterface, "EventsModified",63 connection.connect(DBusService, DBusObjectPath, DBusInterface, "EventsModified",
@@ -70,6 +78,16 @@
70 return threadForProperties(accountId, type, properties, matchFlags, create);78 return threadForProperties(accountId, type, properties, matchFlags, create);
71}79}
7280
81void ManagerDBus::markThreadsAsRead(const History::Threads &threads)
82{
83 QList<QVariantMap> threadMap = threadsToProperties(threads);
84 if (threadMap.isEmpty()) {
85 return;
86 }
87
88 mInterface.asyncCall("MarkThreadsAsRead", QVariant::fromValue(threadMap));
89}
90
73Thread ManagerDBus::threadForProperties(const QString &accountId,91Thread ManagerDBus::threadForProperties(const QString &accountId,
74 EventType type,92 EventType type,
75 const QVariantMap &properties,93 const QVariantMap &properties,
@@ -87,6 +105,29 @@
87 return thread;105 return thread;
88}106}
89107
108void ManagerDBus::requestThreadParticipants(const Threads &threads)
109{
110 QList<QVariantMap> ids;
111 Q_FOREACH(const Thread &thread, threads) {
112 QVariantMap id;
113 id[History::FieldAccountId] = thread.accountId();
114 id[History::FieldThreadId] = thread.threadId();
115 id[History::FieldType] = thread.type();
116 ids << id;
117 }
118
119 QDBusPendingCall call = mInterface.asyncCall("ParticipantsForThreads", QVariant::fromValue(ids));
120 QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this);
121 connect(watcher, &QDBusPendingCallWatcher::finished, [this, threads](QDBusPendingCallWatcher *watcher) {
122 QDBusPendingReply<QList<QVariantMap> > reply = *watcher;
123 Q_FOREACH(const QVariantMap &map, reply.value()) {
124 History::Thread thread = History::Thread::fromProperties(map);
125 Q_EMIT threadParticipantsChanged(thread, History::Participants(), History::Participants(), thread.participants());
126 watcher->deleteLater();
127 }
128 });
129}
130
90bool ManagerDBus::writeEvents(const Events &events)131bool ManagerDBus::writeEvents(const Events &events)
91{132{
92 QList<QVariantMap> eventMap = eventsToProperties(events);133 QList<QVariantMap> eventMap = eventsToProperties(events);
@@ -108,11 +149,8 @@
108 return false;149 return false;
109 }150 }
110151
111 QDBusReply<bool> reply = mInterface.call("RemoveThreads", QVariant::fromValue(threadMap));152 mInterface.asyncCall("RemoveThreads", QVariant::fromValue(threadMap));
112 if (!reply.isValid()) {153 return true;
113 return false;
114 }
115 return reply.value();
116}154}
117155
118bool ManagerDBus::removeEvents(const Events &events)156bool ManagerDBus::removeEvents(const Events &events)
@@ -122,11 +160,8 @@
122 return false;160 return false;
123 }161 }
124162
125 QDBusReply<bool> reply = mInterface.call("RemoveEvents", QVariant::fromValue(eventMap));163 mInterface.asyncCall("RemoveEvents", QVariant::fromValue(eventMap));
126 if (!reply.isValid()) {164 return true;
127 return false;
128 }
129 return reply.value();
130}165}
131166
132Thread ManagerDBus::getSingleThread(EventType type, const QString &accountId, const QString &threadId, const QVariantMap &properties)167Thread ManagerDBus::getSingleThread(EventType type, const QString &accountId, const QString &threadId, const QVariantMap &properties)
@@ -168,6 +203,17 @@
168 Q_EMIT threadsRemoved(threadsFromProperties(threads));203 Q_EMIT threadsRemoved(threadsFromProperties(threads));
169}204}
170205
206void ManagerDBus::onThreadParticipantsChanged(const QVariantMap &thread,
207 const QList<QVariantMap> &added,
208 const QList<QVariantMap> &removed,
209 const QList<QVariantMap> &modified)
210{
211 Q_EMIT threadParticipantsChanged(threadsFromProperties(QList<QVariantMap>() << thread).first(),
212 Participants::fromVariantMapList(added),
213 Participants::fromVariantMapList(removed),
214 Participants::fromVariantMapList(modified));
215}
216
171void ManagerDBus::onEventsAdded(const QList<QVariantMap> &events)217void ManagerDBus::onEventsAdded(const QList<QVariantMap> &events)
172{218{
173 Q_EMIT eventsAdded(eventsFromProperties(events));219 Q_EMIT eventsAdded(eventsFromProperties(events));
174220
=== modified file 'src/managerdbus_p.h'
--- src/managerdbus_p.h 2016-06-17 01:49:46 +0000
+++ src/managerdbus_p.h 2017-03-23 01:25:43 +0000
@@ -50,18 +50,23 @@
50 const QVariantMap &properties,50 const QVariantMap &properties,
51 History::MatchFlags matchFlags,51 History::MatchFlags matchFlags,
52 bool create);52 bool create);
5353 void requestThreadParticipants(const History::Threads &threads);
54 bool writeEvents(const History::Events &events);54 bool writeEvents(const History::Events &events);
55 bool removeThreads(const Threads &threads);55 bool removeThreads(const Threads &threads);
56 bool removeEvents(const Events &events);56 bool removeEvents(const Events &events);
57 Thread getSingleThread(EventType type, const QString &accountId, const QString &threadId, const QVariantMap &properties = QVariantMap());57 Thread getSingleThread(EventType type, const QString &accountId, const QString &threadId, const QVariantMap &properties = QVariantMap());
58 Event getSingleEvent(EventType type, const QString &accountId, const QString &threadId, const QString &eventId);58 Event getSingleEvent(EventType type, const QString &accountId, const QString &threadId, const QString &eventId);
59 void markThreadsAsRead(const History::Threads &threads);
5960
60Q_SIGNALS:61Q_SIGNALS:
61 // signals that will be triggered after processing bus signals62 // signals that will be triggered after processing bus signals
62 void threadsAdded(const History::Threads &threads);63 void threadsAdded(const History::Threads &threads);
63 void threadsModified(const History::Threads &threads);64 void threadsModified(const History::Threads &threads);
64 void threadsRemoved(const History::Threads &threads);65 void threadsRemoved(const History::Threads &threads);
66 void threadParticipantsChanged(const History::Thread &thread,
67 const History::Participants &added,
68 const History::Participants &removed,
69 const History::Participants &modified);
6570
66 void eventsAdded(const History::Events &events);71 void eventsAdded(const History::Events &events);
67 void eventsModified(const History::Events &events);72 void eventsModified(const History::Events &events);
@@ -71,7 +76,10 @@
71 void onThreadsAdded(const QList<QVariantMap> &threads);76 void onThreadsAdded(const QList<QVariantMap> &threads);
72 void onThreadsModified(const QList<QVariantMap> &threads);77 void onThreadsModified(const QList<QVariantMap> &threads);
73 void onThreadsRemoved(const QList<QVariantMap> &threads);78 void onThreadsRemoved(const QList<QVariantMap> &threads);
7479 void onThreadParticipantsChanged(const QVariantMap &thread,
80 const QList<QVariantMap> &added,
81 const QList<QVariantMap> &removed,
82 const QList<QVariantMap> &modified);
75 void onEventsAdded(const QList<QVariantMap> &events);83 void onEventsAdded(const QList<QVariantMap> &events);
76 void onEventsModified(const QList<QVariantMap> &events);84 void onEventsModified(const QList<QVariantMap> &events);
77 void onEventsRemoved(const QList<QVariantMap> &events);85 void onEventsRemoved(const QList<QVariantMap> &events);
7886
=== modified file 'src/participant.cpp'
--- src/participant.cpp 2016-11-24 01:04:37 +0000
+++ src/participant.cpp 2017-03-23 01:25:43 +0000
@@ -233,6 +233,15 @@
233 return participants;233 return participants;
234}234}
235235
236Participants Participants::fromVariantMapList(const QList<QVariantMap> &list)
237{
238 Participants participants;
239 Q_FOREACH(const QVariantMap& entry, list) {
240 participants << Participant::fromProperties(entry);
241 }
242 return participants;
243}
244
236QVariantList Participants::toVariantList() const245QVariantList Participants::toVariantList() const
237{246{
238 QVariantList list;247 QVariantList list;
239248
=== modified file 'src/participant.h'
--- src/participant.h 2016-11-24 01:04:37 +0000
+++ src/participant.h 2017-03-23 01:25:43 +0000
@@ -79,6 +79,7 @@
79 QStringList identifiers() const;79 QStringList identifiers() const;
80 static Participants fromVariant(const QVariant &variant);80 static Participants fromVariant(const QVariant &variant);
81 static Participants fromVariantList(const QVariantList &list);81 static Participants fromVariantList(const QVariantList &list);
82 static Participants fromVariantMapList(const QList<QVariantMap> &list);
82 static Participants fromStringList(const QStringList &list);83 static Participants fromStringList(const QStringList &list);
83 QVariantList toVariantList() const;84 QVariantList toVariantList() const;
84 History::Participants filterByState(uint state) const;85 History::Participants filterByState(uint state) const;
8586
=== modified file 'src/plugin.h'
--- src/plugin.h 2016-09-21 17:44:39 +0000
+++ src/plugin.h 2017-03-23 01:25:43 +0000
@@ -65,6 +65,11 @@
65 EventType type,65 EventType type,
66 const QVariantMap &properties,66 const QVariantMap &properties,
67 History::MatchFlags matchFlags = History::MatchCaseSensitive) = 0;67 History::MatchFlags matchFlags = History::MatchCaseSensitive) = 0;
68 virtual QString threadIdForProperties(const QString &accountId,
69 EventType type,
70 const QVariantMap &properties,
71 History::MatchFlags matchFlags = History::MatchCaseSensitive) = 0;
72 virtual QList<QVariantMap> participantsForThreads(const QList<QVariantMap> &threadIds) = 0;
6873
69 virtual QList<QVariantMap> eventsForThread(const QVariantMap &thread) = 0;74 virtual QList<QVariantMap> eventsForThread(const QVariantMap &thread) = 0;
7075
@@ -75,6 +80,7 @@
75 virtual bool updateRoomParticipantsRoles(const QString &accountId, const QString &threadId, History::EventType type, const QVariantMap &participantsRoles) { return false; };80 virtual bool updateRoomParticipantsRoles(const QString &accountId, const QString &threadId, History::EventType type, const QVariantMap &participantsRoles) { return false; };
76 virtual bool updateRoomInfo(const QString &accountId, const QString &threadId, EventType type, const QVariantMap &properties, const QStringList &invalidated = QStringList()) { return false; };81 virtual bool updateRoomInfo(const QString &accountId, const QString &threadId, EventType type, const QVariantMap &properties, const QStringList &invalidated = QStringList()) { return false; };
77 virtual bool removeThread(const QVariantMap &thread) { return false; }82 virtual bool removeThread(const QVariantMap &thread) { return false; }
83 virtual QVariantMap markThreadAsRead(const QVariantMap &thread) { return QVariantMap(); }
7884
79 virtual EventWriteResult writeTextEvent(const QVariantMap &event) { return EventWriteError; }85 virtual EventWriteResult writeTextEvent(const QVariantMap &event) { return EventWriteError; }
80 virtual bool removeTextEvent(const QVariantMap &event) { return false; }86 virtual bool removeTextEvent(const QVariantMap &event) { return false; }
8187
=== modified file 'src/thread.cpp'
--- src/thread.cpp 2016-07-12 02:08:11 +0000
+++ src/thread.cpp 2017-03-23 01:25:43 +0000
@@ -192,6 +192,22 @@
192 return selfData < otherData;192 return selfData < otherData;
193}193}
194194
195void Thread::removeParticipants(const Participants &participants)
196{
197 Q_D(Thread);
198 Q_FOREACH(const Participant &participant, participants) {
199 d->participants.removeAll(participant);
200 }
201}
202
203void Thread::addParticipants(const Participants &participants)
204{
205 Q_D(Thread);
206 Q_FOREACH(const Participant &participant, participants) {
207 d->participants.append(participant);
208 }
209}
210
195QVariantMap Thread::properties() const211QVariantMap Thread::properties() const
196{212{
197 Q_D(const Thread);213 Q_D(const Thread);
198214
=== modified file 'src/thread.h'
--- src/thread.h 2016-07-12 01:59:06 +0000
+++ src/thread.h 2017-03-23 01:25:43 +0000
@@ -73,6 +73,8 @@
73 ChatType chatType() const;73 ChatType chatType() const;
74 Threads groupedThreads() const;74 Threads groupedThreads() const;
75 QVariantMap chatRoomInfo() const;75 QVariantMap chatRoomInfo() const;
76 void addParticipants(const History::Participants &participants);
77 void removeParticipants(const History::Participants &participants);
7678
77 bool isNull() const;79 bool isNull() const;
78 bool operator==(const Thread &other) const;80 bool operator==(const Thread &other) const;
7981
=== modified file 'src/threadview.cpp'
--- src/threadview.cpp 2015-10-01 19:44:45 +0000
+++ src/threadview.cpp 2017-03-23 01:25:43 +0000
@@ -89,6 +89,18 @@
89 }89 }
90}90}
9191
92void ThreadViewPrivate::_d_threadParticipantsChanged(const History::Thread &thread,
93 const History::Participants &added,
94 const History::Participants &removed,
95 const History::Participants &modified)
96{
97 Q_Q(ThreadView);
98 Threads filtered = filteredThreads(History::Threads() << thread);
99 if (!filtered.isEmpty()) {
100 Q_EMIT q->threadParticipantsChanged(filtered.first(), added, removed, modified);
101 }
102}
103
92// ------------- ThreadView -------------------------------------------------------104// ------------- ThreadView -------------------------------------------------------
93105
94ThreadView::ThreadView(History::EventType type,106ThreadView::ThreadView(History::EventType type,
@@ -132,6 +144,9 @@
132 connect(Manager::instance(),144 connect(Manager::instance(),
133 SIGNAL(threadsRemoved(History::Threads)),145 SIGNAL(threadsRemoved(History::Threads)),
134 SLOT(_d_threadsRemoved(History::Threads)));146 SLOT(_d_threadsRemoved(History::Threads)));
147 connect(Manager::instance(),
148 SIGNAL(threadParticipantsChanged(History::Thread, History::Participants, History::Participants, History::Participants)),
149 SLOT(_d_threadParticipantsChanged(History::Thread, History::Participants, History::Participants, History::Participants)));
135}150}
136151
137ThreadView::~ThreadView()152ThreadView::~ThreadView()
138153
=== modified file 'src/threadview.h'
--- src/threadview.h 2015-09-21 20:05:06 +0000
+++ src/threadview.h 2017-03-23 01:25:43 +0000
@@ -52,12 +52,20 @@
52 void threadsAdded(const History::Threads &threads);52 void threadsAdded(const History::Threads &threads);
53 void threadsModified(const History::Threads &threads);53 void threadsModified(const History::Threads &threads);
54 void threadsRemoved(const History::Threads &threads);54 void threadsRemoved(const History::Threads &threads);
55 void threadParticipantsChanged(const History::Thread &thread,
56 const History::Participants &added,
57 const History::Participants &removed,
58 const History::Participants &modified);
55 void invalidated();59 void invalidated();
5660
57private:61private:
58 Q_PRIVATE_SLOT(d_func(), void _d_threadsAdded(const History::Threads &threads))62 Q_PRIVATE_SLOT(d_func(), void _d_threadsAdded(const History::Threads &threads))
59 Q_PRIVATE_SLOT(d_func(), void _d_threadsModified(const History::Threads &threads))63 Q_PRIVATE_SLOT(d_func(), void _d_threadsModified(const History::Threads &threads))
60 Q_PRIVATE_SLOT(d_func(), void _d_threadsRemoved(const History::Threads &threads))64 Q_PRIVATE_SLOT(d_func(), void _d_threadsRemoved(const History::Threads &threads))
65 Q_PRIVATE_SLOT(d_func(), void _d_threadParticipantsChanged(const History::Thread &thread,
66 const History::Participants &added,
67 const History::Participants &removed,
68 const History::Participants &modified))
61 QScopedPointer<ThreadViewPrivate> d_ptr;69 QScopedPointer<ThreadViewPrivate> d_ptr;
6270
63};71};
6472
=== modified file 'src/threadview_p.h'
--- src/threadview_p.h 2013-09-17 21:33:34 +0000
+++ src/threadview_p.h 2017-03-23 01:25:43 +0000
@@ -50,6 +50,10 @@
50 void _d_threadsAdded(const History::Threads &threads);50 void _d_threadsAdded(const History::Threads &threads);
51 void _d_threadsModified(const History::Threads &threads);51 void _d_threadsModified(const History::Threads &threads);
52 void _d_threadsRemoved(const History::Threads &threads);52 void _d_threadsRemoved(const History::Threads &threads);
53 void _d_threadParticipantsChanged(const History::Thread &thread,
54 const History::Participants &added,
55 const History::Participants &removed,
56 const History::Participants &modified);
5357
54 ThreadView *q_ptr;58 ThreadView *q_ptr;
55 };59 };
5660
=== modified file 'src/utils.cpp'
--- src/utils.cpp 2016-11-08 16:02:18 +0000
+++ src/utils.cpp 2017-03-23 01:25:43 +0000
@@ -50,6 +50,7 @@
50 if (protocolFlags.isEmpty()) {50 if (protocolFlags.isEmpty()) {
51 protocolFlags["ofono"] = MatchPhoneNumber;51 protocolFlags["ofono"] = MatchPhoneNumber;
52 protocolFlags["multimedia"] = MatchPhoneNumber;52 protocolFlags["multimedia"] = MatchPhoneNumber;
53 protocolFlags["sip"] = MatchPhoneNumber;
53 }54 }
5455
55 QString protocol = protocolFromAccountId(accountId);56 QString protocol = protocolFromAccountId(accountId);
@@ -57,7 +58,7 @@
57 return protocolFlags[protocol];58 return protocolFlags[protocol];
58 }59 }
5960
60 // default to this value61 // default to phone number matching for now
61 return History::MatchCaseSensitive;62 return History::MatchCaseSensitive;
62}63}
6364
@@ -175,4 +176,18 @@
175 return QVariant();176 return QVariant();
176}177}
177178
179bool Utils::shouldIncludeParticipants(const Thread &thread)
180{
181 return shouldIncludeParticipants(thread.accountId(), thread.chatType());
182}
183
184bool Utils::shouldIncludeParticipants(const QString &accountId, const ChatType &type)
185{
186 // FIXME: this is obviously incorrect. we have to query the protocol files as a final solution
187 if (protocolFromAccountId(accountId) == "irc") {
188 return type != ChatTypeRoom;
189 }
190 return true;
191}
192
178}193}
179194
=== modified file 'src/utils_p.h'
--- src/utils_p.h 2016-11-08 16:02:18 +0000
+++ src/utils_p.h 2017-03-23 01:25:43 +0000
@@ -36,6 +36,8 @@
36 static bool compareParticipants(const QStringList &participants1, const QStringList &participants2, MatchFlags flags);36 static bool compareParticipants(const QStringList &participants1, const QStringList &participants2, MatchFlags flags);
37 static bool compareNormalizedParticipants(const QStringList &participants1, const QStringList &participants2, MatchFlags flags);37 static bool compareNormalizedParticipants(const QStringList &participants1, const QStringList &participants2, MatchFlags flags);
38 static bool shouldGroupThread(const Thread &thread);38 static bool shouldGroupThread(const Thread &thread);
39 static bool shouldIncludeParticipants(const Thread &thread);
40 static bool shouldIncludeParticipants(const QString &accountId, const History::ChatType &type);
39 static QString normalizeId(const QString &accountId, const QString &id);41 static QString normalizeId(const QString &accountId, const QString &id);
40 static QVariant getUserValue(const QString &interface, const QString &propName);42 static QVariant getUserValue(const QString &interface, const QString &propName);
4143
4244
=== modified file 'tests/Ubuntu.History/HistoryEventModelTest.cpp'
--- tests/Ubuntu.History/HistoryEventModelTest.cpp 2016-09-09 20:00:09 +0000
+++ tests/Ubuntu.History/HistoryEventModelTest.cpp 2017-03-23 01:25:43 +0000
@@ -1,5 +1,5 @@
1/*1/*
2 * Copyright (C) 2016 Canonical, Ltd.2 * Copyright (C) 2016-2017 Canonical, Ltd.
3 *3 *
4 * This file is part of history-service.4 * This file is part of history-service.
5 *5 *
66
=== modified file 'tests/daemon/DaemonTest.cpp'
--- tests/daemon/DaemonTest.cpp 2016-11-03 13:20:17 +0000
+++ tests/daemon/DaemonTest.cpp 2017-03-23 01:25:43 +0000
@@ -141,8 +141,6 @@
141 History::Threads threads = threadsAddedSpy.first().first().value<History::Threads>();141 History::Threads threads = threadsAddedSpy.first().first().value<History::Threads>();
142 QCOMPARE(threads.count(), 1);142 QCOMPARE(threads.count(), 1);
143 History::Thread thread = threads.first();143 History::Thread thread = threads.first();
144 QCOMPARE(thread.participants().count(), 1);
145 QCOMPARE(thread.participants().first().identifier(), sender);
146144
147 QTRY_COMPARE(threadsModifiedSpy.count(), 1);145 QTRY_COMPARE(threadsModifiedSpy.count(), 1);
148 threads = threadsModifiedSpy.first().first().value<History::Threads>();146 threads = threadsModifiedSpy.first().first().value<History::Threads>();
@@ -252,8 +250,6 @@
252 History::Threads threads = threadsAddedSpy.first().first().value<History::Threads>();250 History::Threads threads = threadsAddedSpy.first().first().value<History::Threads>();
253 QCOMPARE(threads.count(), 1);251 QCOMPARE(threads.count(), 1);
254 History::Thread thread = threads.first();252 History::Thread thread = threads.first();
255 QCOMPARE(thread.participants().count(), 1);
256 QCOMPARE(thread.participants().first().identifier(), recipient);
257253
258 QTRY_COMPARE(threadsModifiedSpy.count(), 1);254 QTRY_COMPARE(threadsModifiedSpy.count(), 1);
259 threads = threadsModifiedSpy.first().first().value<History::Threads>();255 threads = threadsModifiedSpy.first().first().value<History::Threads>();
@@ -296,8 +292,6 @@
296 History::Threads threads = threadsAddedSpy.first().first().value<History::Threads>();292 History::Threads threads = threadsAddedSpy.first().first().value<History::Threads>();
297 QCOMPARE(threads.count(), 1);293 QCOMPARE(threads.count(), 1);
298 History::Thread thread = threads.first();294 History::Thread thread = threads.first();
299 QCOMPARE(thread.participants().count(), 1);
300 QCOMPARE(thread.participants().first().identifier(), callerId);
301295
302 QTRY_COMPARE(threadsModifiedSpy.count(), 1);296 QTRY_COMPARE(threadsModifiedSpy.count(), 1);
303 threads = threadsModifiedSpy.first().first().value<History::Threads>();297 threads = threadsModifiedSpy.first().first().value<History::Threads>();
@@ -357,8 +351,6 @@
357 History::Threads threads = threadsAddedSpy.first().first().value<History::Threads>();351 History::Threads threads = threadsAddedSpy.first().first().value<History::Threads>();
358 QCOMPARE(threads.count(), 1);352 QCOMPARE(threads.count(), 1);
359 History::Thread thread = threads.first();353 History::Thread thread = threads.first();
360 QCOMPARE(thread.participants().count(), 1);
361 QCOMPARE(thread.participants().first().identifier(), phoneNumber);
362354
363 QTRY_COMPARE(threadsModifiedSpy.count(), 1);355 QTRY_COMPARE(threadsModifiedSpy.count(), 1);
364 threads = threadsModifiedSpy.first().first().value<History::Threads>();356 threads = threadsModifiedSpy.first().first().value<History::Threads>();
365357
=== modified file 'tests/libhistoryservice/ManagerTest.cpp'
--- tests/libhistoryservice/ManagerTest.cpp 2015-09-23 21:52:48 +0000
+++ tests/libhistoryservice/ManagerTest.cpp 2017-03-23 01:25:43 +0000
@@ -96,7 +96,6 @@
9696
97 QCOMPARE(thread.accountId(), accountId);97 QCOMPARE(thread.accountId(), accountId);
98 QCOMPARE(thread.type(), type);98 QCOMPARE(thread.type(), type);
99 QCOMPARE(thread.participants().identifiers(), participants);
10099
101 // now try to get the thread again to see if it is returned correctly100 // now try to get the thread again to see if it is returned correctly
102 History::Thread sameThread = mManager->threadForParticipants(accountId, type, participantsToMatch, matchFlags, false);101 History::Thread sameThread = mManager->threadForParticipants(accountId, type, participantsToMatch, matchFlags, false);
@@ -137,14 +136,16 @@
137136
138void ManagerTest::testWriteEvents()137void ManagerTest::testWriteEvents()
139{138{
139 QString textParticipant("textParticipant");
140 QString voiceParticipant("voiceParticipant");
140 // create two threads, one for voice and one for text141 // create two threads, one for voice and one for text
141 History::Thread textThread = mManager->threadForParticipants("textAccountId",142 History::Thread textThread = mManager->threadForParticipants("textAccountId",
142 History::EventTypeText,143 History::EventTypeText,
143 QStringList()<< "textParticipant",144 QStringList()<< textParticipant,
144 History::MatchCaseSensitive, true);145 History::MatchCaseSensitive, true);
145 History::Thread voiceThread = mManager->threadForParticipants("voiceAccountId",146 History::Thread voiceThread = mManager->threadForParticipants("voiceAccountId",
146 History::EventTypeVoice,147 History::EventTypeVoice,
147 QStringList()<< "voiceParticipant",148 QStringList()<< voiceParticipant,
148 History::MatchCaseSensitive, true);149 History::MatchCaseSensitive, true);
149 // insert some text and voice events150 // insert some text and voice events
150 History::Events events;151 History::Events events;
@@ -152,7 +153,7 @@
152 History::TextEvent textEvent(textThread.accountId(),153 History::TextEvent textEvent(textThread.accountId(),
153 textThread.threadId(),154 textThread.threadId(),
154 QString("eventId%1").arg(i),155 QString("eventId%1").arg(i),
155 textThread.participants().first().identifier(),156 textParticipant,
156 QDateTime::currentDateTime(),157 QDateTime::currentDateTime(),
157 true,158 true,
158 QString("Hello world %1").arg(i),159 QString("Hello world %1").arg(i),
@@ -163,7 +164,7 @@
163 History::VoiceEvent voiceEvent(voiceThread.accountId(),164 History::VoiceEvent voiceEvent(voiceThread.accountId(),
164 voiceThread.threadId(),165 voiceThread.threadId(),
165 QString("eventId%1").arg(i),166 QString("eventId%1").arg(i),
166 voiceThread.participants().first().identifier(),167 voiceParticipant,
167 QDateTime::currentDateTime(),168 QDateTime::currentDateTime(),
168 true,169 true,
169 true);170 true);
@@ -214,14 +215,16 @@
214215
215void ManagerTest::testRemoveEvents()216void ManagerTest::testRemoveEvents()
216{217{
218 QString textParticipant("textParticipant");
219 QString voiceParticipant("voiceParticipant");
217 // create two threads, one for voice and one for text220 // create two threads, one for voice and one for text
218 History::Thread textThread = mManager->threadForParticipants("textRemovableAccount",221 History::Thread textThread = mManager->threadForParticipants("textRemovableAccount",
219 History::EventTypeText,222 History::EventTypeText,
220 QStringList()<< "textParticipant",223 QStringList()<< textParticipant,
221 History::MatchCaseSensitive, true);224 History::MatchCaseSensitive, true);
222 History::Thread voiceThread = mManager->threadForParticipants("voiceRemovableAccount",225 History::Thread voiceThread = mManager->threadForParticipants("voiceRemovableAccount",
223 History::EventTypeVoice,226 History::EventTypeVoice,
224 QStringList()<< "voiceParticipant",227 QStringList()<< voiceParticipant,
225 History::MatchCaseSensitive, true);228 History::MatchCaseSensitive, true);
226 // insert some text and voice events229 // insert some text and voice events
227 History::Events events;230 History::Events events;
@@ -229,7 +232,7 @@
229 History::TextEvent textEvent(textThread.accountId(),232 History::TextEvent textEvent(textThread.accountId(),
230 textThread.threadId(),233 textThread.threadId(),
231 QString("eventToBeRemoved%1").arg(i),234 QString("eventToBeRemoved%1").arg(i),
232 textThread.participants().first().identifier(),235 textParticipant,
233 QDateTime::currentDateTime(),236 QDateTime::currentDateTime(),
234 true,237 true,
235 QString("Hello world %1").arg(i),238 QString("Hello world %1").arg(i),
@@ -239,7 +242,7 @@
239 History::VoiceEvent voiceEvent(voiceThread.accountId(),242 History::VoiceEvent voiceEvent(voiceThread.accountId(),
240 voiceThread.threadId(),243 voiceThread.threadId(),
241 QString("eventToBeRemoved%1").arg(i),244 QString("eventToBeRemoved%1").arg(i),
242 voiceThread.participants().first().identifier(),245 voiceParticipant,
243 QDateTime::currentDateTime(),246 QDateTime::currentDateTime(),
244 true,247 true,
245 true);248 true);
@@ -280,14 +283,16 @@
280283
281void ManagerTest::testGetSingleEvent()284void ManagerTest::testGetSingleEvent()
282{285{
286 QString textParticipant("textSingleParticipant");
287 QString voiceParticipant("voiceSingleParticipant");
283 // create two threads, one for voice and one for text288 // create two threads, one for voice and one for text
284 History::Thread textThread = mManager->threadForParticipants("textSingleAccount",289 History::Thread textThread = mManager->threadForParticipants("textSingleAccount",
285 History::EventTypeText,290 History::EventTypeText,
286 QStringList()<< "textSingleParticipant",291 QStringList()<< textParticipant,
287 History::MatchCaseSensitive, true);292 History::MatchCaseSensitive, true);
288 History::Thread voiceThread = mManager->threadForParticipants("voiceSingleAccount",293 History::Thread voiceThread = mManager->threadForParticipants("voiceSingleAccount",
289 History::EventTypeVoice,294 History::EventTypeVoice,
290 QStringList()<< "voiceSingleParticipant",295 QStringList()<< voiceParticipant,
291 History::MatchCaseSensitive, true);296 History::MatchCaseSensitive, true);
292297
293 // now add two events298 // now add two events
@@ -348,43 +353,11 @@
348 History::Threads threads;353 History::Threads threads;
349 threads << textThread << voiceThread;354 threads << textThread << voiceThread;
350355
351 // insert some text and voice events
352 History::Events events;
353 for (int i = 0; i < 50; ++i) {
354 History::TextEvent textEvent(textThread.accountId(),
355 textThread.threadId(),
356 QString("eventToBeRemoved%1").arg(i),
357 textThread.participants().first().identifier(),
358 QDateTime::currentDateTime(),
359 true,
360 QString("Hello world %1").arg(i),
361 History::MessageTypeText);
362 events.append(textEvent);
363
364 History::VoiceEvent voiceEvent(voiceThread.accountId(),
365 voiceThread.threadId(),
366 QString("eventToBeRemoved%1").arg(i),
367 voiceThread.participants().first().identifier(),
368 QDateTime::currentDateTime(),
369 true,
370 true);
371 events.append(voiceEvent);
372 }
373
374 QVERIFY(mManager->writeEvents(events));
375
376 QSignalSpy eventsRemovedSpy(mManager, SIGNAL(eventsRemoved(History::Events)));
377 QSignalSpy threadsRemovedSpy(mManager, SIGNAL(threadsRemoved(History::Threads)));356 QSignalSpy threadsRemovedSpy(mManager, SIGNAL(threadsRemoved(History::Threads)));
378357
379 QVERIFY(mManager->removeThreads(threads));358 QVERIFY(mManager->removeThreads(threads));
380 QTRY_COMPARE(eventsRemovedSpy.count(), 1);
381 QTRY_COMPARE(threadsRemovedSpy.count(), 1);359 QTRY_COMPARE(threadsRemovedSpy.count(), 1);
382360
383 History::Events removedEvents = eventsRemovedSpy.first().first().value<History::Events>();
384 qSort(removedEvents);
385 qSort(events);
386 QCOMPARE(removedEvents, events);
387
388 History::Threads removedThreads = threadsRemovedSpy.first().first().value<History::Threads>();361 History::Threads removedThreads = threadsRemovedSpy.first().first().value<History::Threads>();
389 qSort(removedThreads);362 qSort(removedThreads);
390 qSort(threads);363 qSort(threads);
391364
=== modified file 'tests/plugins/sqlite/SqliteEventViewTest.cpp'
--- tests/plugins/sqlite/SqliteEventViewTest.cpp 2013-12-09 21:18:14 +0000
+++ tests/plugins/sqlite/SqliteEventViewTest.cpp 2017-03-23 01:25:43 +0000
@@ -39,6 +39,7 @@
39 void testNextPage();39 void testNextPage();
40 void testFilter();40 void testFilter();
41 void testSort();41 void testSort();
42 void testSortWithMultipleFields();
4243
43private:44private:
44 SQLiteHistoryPlugin *mPlugin;45 SQLiteHistoryPlugin *mPlugin;
@@ -128,7 +129,26 @@
128 QCOMPARE(allEvents.first()[History::FieldEventId].toString(), QString("event%1").arg(EVENT_COUNT-1));129 QCOMPARE(allEvents.first()[History::FieldEventId].toString(), QString("event%1").arg(EVENT_COUNT-1));
129 QCOMPARE(allEvents.last()[History::FieldEventId].toString(), QString("event00"));130 QCOMPARE(allEvents.last()[History::FieldEventId].toString(), QString("event00"));
130 delete view;131 delete view;
131132}
133
134void SqliteEventViewTest::testSortWithMultipleFields()
135{
136 History::Sort ascendingSort(QString("%1, %2").arg(History::FieldAccountId).arg(History::FieldEventId), Qt::AscendingOrder);
137 //History::Sort ascendingSort(QString("%1").arg(History::FieldEventId), Qt::AscendingOrder);
138 History::PluginEventView *view = mPlugin->queryEvents(History::EventTypeText, ascendingSort);
139 QVERIFY(view->IsValid());
140 QList<QVariantMap> allEvents;
141 QList<QVariantMap> events = view->NextPage();
142 while (!events.isEmpty()) {
143 allEvents << events;
144 events = view->NextPage();
145 }
146
147 QCOMPARE(allEvents[0][History::FieldEventId].toString(), QString("event00"));
148 QCOMPARE(allEvents[0][History::FieldAccountId].toString(), QString("account0"));
149 QCOMPARE(allEvents[1][History::FieldEventId].toString(), QString("event01"));
150 QCOMPARE(allEvents[1][History::FieldAccountId].toString(), QString("account0"));
151 delete view;
132}152}
133153
134void SqliteEventViewTest::populateDatabase()154void SqliteEventViewTest::populateDatabase()
@@ -136,7 +156,7 @@
136 mPlugin->beginBatchOperation();156 mPlugin->beginBatchOperation();
137157
138 // create two threads of each type158 // create two threads of each type
139 for (int i = 0; i < 2; ++i) {159 for (int i = 1; i >= 0; --i) {
140 QVariantMap voiceThread = mPlugin->createThreadForParticipants(QString("account%1").arg(i),160 QVariantMap voiceThread = mPlugin->createThreadForParticipants(QString("account%1").arg(i),
141 History::EventTypeVoice,161 History::EventTypeVoice,
142 QStringList() << QString("participant%1").arg(i));162 QStringList() << QString("participant%1").arg(i));
143163
=== modified file 'tests/plugins/sqlite/SqlitePluginTest.cpp'
--- tests/plugins/sqlite/SqlitePluginTest.cpp 2016-09-09 20:00:09 +0000
+++ tests/plugins/sqlite/SqlitePluginTest.cpp 2017-03-23 01:25:43 +0000
@@ -189,7 +189,6 @@
189 QCOMPARE(retrievedThread[History::FieldType], thread[History::FieldType]);189 QCOMPARE(retrievedThread[History::FieldType], thread[History::FieldType]);
190 QCOMPARE(retrievedThread[History::FieldCount], thread[History::FieldCount]);190 QCOMPARE(retrievedThread[History::FieldCount], thread[History::FieldCount]);
191 QCOMPARE(retrievedThread[History::FieldUnreadCount], thread[History::FieldUnreadCount]);191 QCOMPARE(retrievedThread[History::FieldUnreadCount], thread[History::FieldUnreadCount]);
192 QCOMPARE(retrievedThread[History::FieldParticipants], thread[History::FieldParticipants]);
193}192}
194193
195void SqlitePluginTest::testEmptyThreadForParticipants()194void SqlitePluginTest::testEmptyThreadForParticipants()
@@ -219,7 +218,6 @@
219 QCOMPARE(retrievedThread[History::FieldType], thread[History::FieldType]);218 QCOMPARE(retrievedThread[History::FieldType], thread[History::FieldType]);
220 QCOMPARE(retrievedThread[History::FieldCount], thread[History::FieldCount]);219 QCOMPARE(retrievedThread[History::FieldCount], thread[History::FieldCount]);
221 QCOMPARE(retrievedThread[History::FieldUnreadCount], thread[History::FieldUnreadCount]);220 QCOMPARE(retrievedThread[History::FieldUnreadCount], thread[History::FieldUnreadCount]);
222 QCOMPARE(retrievedThread[History::FieldParticipants], thread[History::FieldParticipants]);
223221
224 // FIXME: check that the last event data is also present222 // FIXME: check that the last event data is also present
225}223}

Subscribers

People subscribed via source and target branches

to all changes: