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
1=== modified file 'CMakeLists.txt'
2--- CMakeLists.txt 2016-08-16 21:06:10 +0000
3+++ CMakeLists.txt 2017-03-23 01:25:43 +0000
4@@ -50,6 +50,11 @@
5
6 find_program(DBUS_RUNNER dbus-test-runner)
7
8+option(TRACE_SQLITE "Print Sqlite commants to the log." off)
9+if (${TRACE_SQLITE})
10+ add_definitions(-DTRACE_SQLITE)
11+endif()
12+
13 add_definitions(-DQT_NO_KEYWORDS)
14
15 include_directories(
16
17=== modified file 'Ubuntu/History/historyeventmodel.cpp'
18--- Ubuntu/History/historyeventmodel.cpp 2016-09-16 11:59:52 +0000
19+++ Ubuntu/History/historyeventmodel.cpp 2017-03-23 01:25:43 +0000
20@@ -1,5 +1,5 @@
21 /*
22- * Copyright (C) 2013-2015 Canonical, Ltd.
23+ * Copyright (C) 2013-2017 Canonical, Ltd.
24 *
25 * Authors:
26 * Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>
27@@ -29,7 +29,7 @@
28 #include <QTimerEvent>
29
30 HistoryEventModel::HistoryEventModel(QObject *parent) :
31- HistoryModel(parent), mCanFetchMore(true), mEventWritingTimer(0)
32+ HistoryModel(parent), mCanFetchMore(true)
33 {
34 // configure the roles
35 mRoles = HistoryModel::roleNames();
36@@ -95,10 +95,17 @@
37 result = event.eventId();
38 break;
39 case SenderIdRole:
40- result = event.senderId();
41+ result = History::ContactMatcher::normalizeId(event.senderId());
42 break;
43 case SenderRole:
44- result = History::ContactMatcher::instance()->contactInfo(event.accountId(), event.senderId());
45+ if (mMatchContacts) {
46+ result = History::ContactMatcher::instance()->contactInfo(event.accountId(), event.senderId());
47+ } else {
48+ QVariantMap map;
49+ map[History::FieldIdentifier] = event.senderId();
50+ map[History::FieldAccountId] = event.accountId();
51+ result = map;
52+ }
53 break;
54 case TimestampRole:
55 result = event.timestamp();
56@@ -168,17 +175,21 @@
57 break;
58 case RemoteParticipantRole:
59 if (!voiceEvent.isNull()) {
60- result = voiceEvent.remoteParticipant();
61+ result = History::ContactMatcher::normalizeId(voiceEvent.remoteParticipant());
62 }
63 break;
64 case SubjectAsAliasRole:
65 if (!textEvent.isNull()) {
66- QVariantMap contactInfo = History::ContactMatcher::instance()->contactInfo(event.accountId(), textEvent.subject());
67- QString returnValue = contactInfo[History::FieldAlias].toString();
68- if (returnValue.isEmpty()) {
69- returnValue = contactInfo[History::FieldIdentifier].toString();
70+ if (mMatchContacts) {
71+ QVariantMap contactInfo = History::ContactMatcher::instance()->contactInfo(event.accountId(), textEvent.subject());
72+ QString returnValue = contactInfo[History::FieldAlias].toString();
73+ if (returnValue.isEmpty()) {
74+ returnValue = contactInfo[History::FieldIdentifier].toString();
75+ }
76+ return returnValue;
77+
78 }
79- return returnValue;
80+ return textEvent.subject();
81 }
82 break;
83 }
84@@ -307,23 +318,6 @@
85 return History::Manager::instance()->writeEvents(History::Events() << textEvent);
86 }
87
88-bool HistoryEventModel::markEventAsRead(const QString &accountId, const QString &threadId, const QString &eventId, int eventType)
89-{
90- History::Event event = History::Manager::instance()->getSingleEvent((History::EventType)eventType, accountId, threadId, eventId);
91- event.setNewEvent(false);
92- if (event.type() == History::EventTypeText) {
93- History::TextEvent textEvent = event;
94- textEvent.setReadTimestamp(QDateTime::currentDateTime());
95- event = textEvent;
96- }
97- mEventWritingQueue << event;
98- if (mEventWritingTimer != 0) {
99- killTimer(mEventWritingTimer);
100- }
101- mEventWritingTimer = startTimer(500);
102- return true;
103-}
104-
105 void HistoryEventModel::updateQuery()
106 {
107 // remove all events from the model
108@@ -341,7 +335,7 @@
109 mView->disconnect(this);
110 }
111
112- if (mFilter) {
113+ if (mFilter && mFilter->filter().isValid()) {
114 queryFilter = mFilter->filter();
115 } else {
116 // we should not return anything if there is no filter
117@@ -363,6 +357,9 @@
118 SIGNAL(eventsRemoved(History::Events)),
119 SLOT(onEventsRemoved(History::Events)));
120 connect(mView.data(),
121+ SIGNAL(threadsRemoved(History::Threads)),
122+ SLOT(onThreadsRemoved(History::Threads)));
123+ connect(mView.data(),
124 SIGNAL(invalidated()),
125 SLOT(triggerQueryUpdate()));
126
127@@ -439,21 +436,21 @@
128 // should be handle internally in History::EventView?
129 }
130
131-void HistoryEventModel::timerEvent(QTimerEvent *event)
132+void HistoryEventModel::onThreadsRemoved(const History::Threads &threads)
133 {
134- HistoryModel::timerEvent(event);
135- if (event->timerId() == mEventWritingTimer) {
136- killTimer(mEventWritingTimer);
137- mEventWritingTimer = 0;
138-
139- if (mEventWritingQueue.isEmpty()) {
140- return;
141- }
142-
143- qDebug() << "Goint to update" << mEventWritingQueue.count() << "events.";
144- if (History::Manager::instance()->writeEvents(mEventWritingQueue)) {
145- qDebug() << "... succeeded!";
146- mEventWritingQueue.clear();
147+ // When a thread is removed we don't get event removed signals,
148+ // so we compare and find if we have an event matching that thread.
149+ // in case we find it, we invalidate the whole view as there might be
150+ // out of date cached data on the daemon side
151+ int count = rowCount();
152+ Q_FOREACH(const History::Thread &thread, threads) {
153+ for (int i = 0; i < count; ++i) {
154+ QModelIndex idx = index(i);
155+ if (idx.data(AccountIdRole).toString() == thread.accountId() &&
156+ idx.data(ThreadIdRole).toString() == thread.threadId()) {
157+ triggerQueryUpdate();
158+ return;
159+ }
160 }
161 }
162 }
163
164=== modified file 'Ubuntu/History/historyeventmodel.h'
165--- Ubuntu/History/historyeventmodel.h 2016-09-16 12:32:37 +0000
166+++ Ubuntu/History/historyeventmodel.h 2017-03-23 01:25:43 +0000
167@@ -1,5 +1,5 @@
168 /*
169- * Copyright (C) 2013-2015 Canonical, Ltd.
170+ * Copyright (C) 2013-2017 Canonical, Ltd.
171 *
172 * Authors:
173 * Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>
174@@ -66,7 +66,6 @@
175
176 Q_INVOKABLE bool removeEvents(const QVariantList &eventsProperties);
177 Q_INVOKABLE bool writeEvents(const QVariantList &eventsProperties);
178- Q_INVOKABLE bool markEventAsRead(const QString &accountId, const QString &threadId, const QString &eventId, int eventType);
179 Q_INVOKABLE bool removeEventAttachment(const QString &accountId, const QString &threadId, const QString &eventId, int eventType, const QString &attachmentId);
180
181 protected Q_SLOTS:
182@@ -74,9 +73,9 @@
183 virtual void onEventsAdded(const History::Events &events);
184 virtual void onEventsModified(const History::Events &events);
185 virtual void onEventsRemoved(const History::Events &events);
186+ virtual void onThreadsRemoved(const History::Threads &threads);
187
188 protected:
189- void timerEvent(QTimerEvent *event);
190 History::Events fetchNextPage();
191
192 private:
193@@ -85,8 +84,6 @@
194 bool mCanFetchMore;
195 QHash<int, QByteArray> mRoles;
196 mutable QMap<History::TextEvent, QList<QVariant> > mAttachmentCache;
197- History::Events mEventWritingQueue;
198- int mEventWritingTimer;
199 };
200
201 #endif // HISTORYEVENTMODEL_H
202
203=== modified file 'Ubuntu/History/historygroupedthreadsmodel.cpp'
204--- Ubuntu/History/historygroupedthreadsmodel.cpp 2015-10-08 19:35:40 +0000
205+++ Ubuntu/History/historygroupedthreadsmodel.cpp 2017-03-23 01:25:43 +0000
206@@ -199,6 +199,21 @@
207 }
208 }
209
210+History::Threads HistoryGroupedThreadsModel::restoreParticipants(const History::Threads &oldThreads, const History::Threads &newThreads)
211+{
212+ History::Threads updated = newThreads;
213+ for(History::Thread &thread : updated) {
214+ if (!thread.participants().isEmpty()) {
215+ continue;
216+ }
217+ int i = oldThreads.indexOf(thread);
218+ if (i >=0) {
219+ thread.addParticipants(oldThreads[i].participants());
220+ }
221+ }
222+ return updated;
223+}
224+
225 void HistoryGroupedThreadsModel::updateQuery()
226 {
227 // remove all entries and call the query update
228@@ -217,6 +232,7 @@
229 processThreadGrouping(thread);
230 }
231
232+ fetchParticipantsIfNeeded(threads);
233 notifyDataChanged();
234 }
235
236@@ -225,7 +241,7 @@
237 Q_FOREACH(const History::Thread &thread, threads) {
238 processThreadGrouping(thread);
239 }
240-
241+ fetchParticipantsIfNeeded(threads);
242 notifyDataChanged();
243 }
244
245@@ -238,6 +254,42 @@
246 notifyDataChanged();
247 }
248
249+void HistoryGroupedThreadsModel::onThreadParticipantsChanged(const History::Thread &thread, const History::Participants &added, const History::Participants &removed, const History::Participants &modified)
250+{
251+ int pos = existingPositionForEntry(thread);
252+ if (pos >= 0) {
253+ HistoryThreadGroup &group = mGroups[pos];
254+ if (group.displayedThread == thread) {
255+ group.displayedThread.removeParticipants(removed);
256+ group.displayedThread.removeParticipants(modified);
257+ group.displayedThread.addParticipants(added);
258+ group.displayedThread.addParticipants(modified);
259+ }
260+
261+ Q_FOREACH(const History::Thread &existingThread, group.threads) {
262+ if (existingThread == thread) {
263+ History::Thread modifiedThread = existingThread;
264+ group.threads.removeOne(existingThread);
265+ modifiedThread.removeParticipants(removed);
266+ modifiedThread.removeParticipants(modified);
267+ modifiedThread.addParticipants(added);
268+ modifiedThread.addParticipants(modified);
269+ group.threads.append(modifiedThread);
270+ }
271+ }
272+ QModelIndex idx = index(pos);
273+ Q_EMIT dataChanged(idx, idx);
274+ }
275+
276+ // watch the contact info for the received participants
277+ Q_FOREACH(const History::Participant &participant, added) {
278+ watchContactInfo(thread.accountId(), participant.identifier(), participant.properties());
279+ }
280+ Q_FOREACH(const History::Participant &participant, modified) {
281+ watchContactInfo(thread.accountId(), participant.identifier(), participant.properties());
282+ }
283+}
284+
285 void HistoryGroupedThreadsModel::processThreadGrouping(const History::Thread &thread)
286 {
287 QVariantMap queryProperties;
288@@ -262,7 +314,7 @@
289 }
290
291 HistoryThreadGroup &group = mGroups[pos];
292- group.threads = groupedThread.groupedThreads();
293+ group.threads = restoreParticipants(group.threads, groupedThread.groupedThreads());
294
295 updateDisplayedThread(group);
296 markGroupAsChanged(group);
297
298=== modified file 'Ubuntu/History/historygroupedthreadsmodel.h'
299--- Ubuntu/History/historygroupedthreadsmodel.h 2015-09-29 20:34:22 +0000
300+++ Ubuntu/History/historygroupedthreadsmodel.h 2017-03-23 01:25:43 +0000
301@@ -67,12 +67,17 @@
302 int existingPositionForEntry(const History::Thread &thread) const;
303 void removeGroup(const HistoryThreadGroup &group);
304 void updateDisplayedThread(HistoryThreadGroup &group);
305+ History::Threads restoreParticipants(const History::Threads &oldThreads, const History::Threads &newThreads);
306
307 protected Q_SLOTS:
308 virtual void updateQuery();
309 virtual void onThreadsAdded(const History::Threads &threads);
310 virtual void onThreadsModified(const History::Threads &threads);
311 virtual void onThreadsRemoved(const History::Threads &threads);
312+ void onThreadParticipantsChanged(const History::Thread &thread,
313+ const History::Participants &added,
314+ const History::Participants &removed,
315+ const History::Participants &modified) override;
316
317 private Q_SLOTS:
318 void processThreadGrouping(const History::Thread &thread);
319
320=== modified file 'Ubuntu/History/historymodel.cpp'
321--- Ubuntu/History/historymodel.cpp 2016-11-24 12:22:11 +0000
322+++ Ubuntu/History/historymodel.cpp 2017-03-23 01:25:43 +0000
323@@ -28,13 +28,14 @@
324 #include "textevent.h"
325 #include "manager.h"
326 #include "utils_p.h"
327+#include "voiceevent.h"
328 #include <QTimerEvent>
329 #include <QCryptographicHash>
330 #include <QDebug>
331
332 HistoryModel::HistoryModel(QObject *parent) :
333 QAbstractListModel(parent), mFilter(0), mSort(new HistoryQmlSort(this)),
334- mType(EventTypeText), mMatchContacts(false), mUpdateTimer(0), mWaitingForQml(false)
335+ mType(EventTypeText), mMatchContacts(false), mUpdateTimer(0), mEventWritingTimer(0), mThreadWritingTimer(0), mWaitingForQml(false)
336 {
337 // configure the roles
338 mRoles[AccountIdRole] = "accountId";
339@@ -341,6 +342,20 @@
340 return QString::null;
341 }
342
343+void HistoryModel::requestThreadParticipants(const QVariantList &threads)
344+{
345+ History::Threads theThreads;
346+ Q_FOREACH(const QVariant &threadVariant, threads) {
347+ History::Thread theThread = History::Thread::fromProperties(threadVariant.toMap());
348+ // if the given thread already has the list of participants, there is no point
349+ // in fetching it again
350+ if (theThread.participants().isEmpty()) {
351+ theThreads << theThread;
352+ }
353+ }
354+ History::Manager::instance()->requestThreadParticipants(theThreads);
355+}
356+
357 bool HistoryModel::writeTextInformationEvent(const QString &accountId, const QString &threadId, const QStringList &participants, const QString &message, int informationType, const QString &subject)
358 {
359 if (participants.isEmpty() || threadId.isEmpty() || accountId.isEmpty()) {
360@@ -385,7 +400,7 @@
361 // FIXME: right now we might be grouping threads from different accounts, so we are not enforcing
362 // the accountId to be the same as the one from the contact info, but maybe we need to do that
363 // in the future?
364- if (History::Utils::compareIds(accountId, participant.identifier(), identifier)) {
365+ if (History::Utils::compareIds(accountId, History::ContactMatcher::normalizeId(participant.identifier()), identifier)) {
366 changedIndexes << idx;
367 }
368 }
369@@ -406,19 +421,50 @@
370
371 void HistoryModel::timerEvent(QTimerEvent *event)
372 {
373- if (event->timerId() == mUpdateTimer && !mWaitingForQml) {
374- killTimer(mUpdateTimer);
375- mUpdateTimer = 0;
376- updateQuery();
377+ if (event->timerId() == mUpdateTimer) {
378+ if (!mWaitingForQml) {
379+ killTimer(mUpdateTimer);
380+ mUpdateTimer = 0;
381+ updateQuery();
382+ }
383+ } else if (event->timerId() == mEventWritingTimer) {
384+ killTimer(mEventWritingTimer);
385+ mEventWritingTimer = 0;
386+
387+ if (mEventWritingQueue.isEmpty()) {
388+ return;
389+ }
390+
391+ if (History::Manager::instance()->writeEvents(mEventWritingQueue)) {
392+ mEventWritingQueue.clear();
393+ }
394+ } else if (event->timerId() == mThreadWritingTimer) {
395+ killTimer(mThreadWritingTimer);
396+ mThreadWritingTimer = 0;
397+
398+ if (mThreadWritingQueue.isEmpty()) {
399+ return;
400+ }
401+
402+ History::Manager::instance()->markThreadsAsRead(mThreadWritingQueue);
403+ mThreadWritingQueue.clear();
404 }
405 }
406
407 bool HistoryModel::lessThan(const QVariantMap &left, const QVariantMap &right) const
408 {
409- QVariant leftValue = left[sort()->sortField()];
410- QVariant rightValue = right[sort()->sortField()];
411-
412- return leftValue < rightValue;
413+ QStringList sortFields = sort()->sortField().split(",");
414+
415+ while(!sortFields.isEmpty()) {
416+ QString sortField = sortFields.takeFirst().trimmed();
417+ QVariant leftValue = left.value(sortField, QVariant());
418+ QVariant rightValue = right.value(sortField, QVariant());
419+
420+ if (leftValue != rightValue) {
421+ return leftValue < rightValue;
422+ }
423+ }
424+ return false;
425 }
426
427 int HistoryModel::positionForItem(const QVariantMap &item) const
428@@ -470,6 +516,56 @@
429 return data;
430 }
431
432+bool HistoryModel::markEventAsRead(const QVariantMap &eventProperties)
433+{
434+ History::Event event;
435+ History::EventType type = (History::EventType) eventProperties[History::FieldType].toInt();
436+ switch (type) {
437+ case History::EventTypeText:
438+ event = History::TextEvent::fromProperties(eventProperties);
439+ break;
440+ case History::EventTypeVoice:
441+ event = History::VoiceEvent::fromProperties(eventProperties);
442+ break;
443+ }
444+
445+ event.setNewEvent(false);
446+ if (event.type() == History::EventTypeText) {
447+ History::TextEvent textEvent = event;
448+ textEvent.setReadTimestamp(QDateTime::currentDateTime());
449+ event = textEvent;
450+ }
451+ // for repeated events, keep the last called one only
452+ if (mEventWritingQueue.contains(event)) {
453+ mEventWritingQueue.removeOne(event);
454+ }
455+ mEventWritingQueue << event;
456+ if (mEventWritingTimer != 0) {
457+ killTimer(mEventWritingTimer);
458+ }
459+ mEventWritingTimer = startTimer(500);
460+ return true;
461+}
462+
463+void HistoryModel::markThreadsAsRead(const QVariantList &threadsProperties)
464+{
465+ Q_FOREACH(const QVariant &entry, threadsProperties) {
466+ QVariantMap threadProperties = entry.toMap();
467+ History::Thread thread = History::Thread::fromProperties(threadProperties);
468+ if (!thread.isNull()) {
469+ if (mThreadWritingQueue.contains(thread)) {
470+ continue;
471+ }
472+ mThreadWritingQueue << thread;
473+ }
474+ }
475+
476+ if (mThreadWritingTimer != 0) {
477+ killTimer(mThreadWritingTimer);
478+ }
479+ mThreadWritingTimer = startTimer(2000);
480+}
481+
482 void HistoryModel::classBegin()
483 {
484 mWaitingForQml = true;
485
486=== modified file 'Ubuntu/History/historymodel.h'
487--- Ubuntu/History/historymodel.h 2016-11-09 17:42:27 +0000
488+++ Ubuntu/History/historymodel.h 2017-03-23 01:25:43 +0000
489@@ -23,6 +23,8 @@
490 #define HISTORYMODEL_H
491
492 #include "types.h"
493+#include "event.h"
494+#include "thread.h"
495 #include "historyqmlfilter.h"
496 #include "historyqmlsort.h"
497 #include <QAbstractListModel>
498@@ -166,6 +168,7 @@
499 const QStringList &participants,
500 int matchFlags = (int)History::MatchCaseSensitive,
501 bool create = false);
502+ Q_INVOKABLE void requestThreadParticipants(const QVariantList &threads);
503 Q_INVOKABLE bool writeTextInformationEvent(const QString &accountId,
504 const QString &threadId,
505 const QStringList &participants,
506@@ -175,6 +178,10 @@
507
508 Q_INVOKABLE virtual QVariant get(int row) const;
509
510+ // Marking events and threads as read
511+ Q_INVOKABLE bool markEventAsRead(const QVariantMap &eventProperties);
512+ Q_INVOKABLE void markThreadsAsRead(const QVariantList &threadsProperties);
513+
514 // QML parser status things
515 void classBegin();
516 void componentComplete();
517@@ -206,6 +213,10 @@
518
519 private:
520 QHash<int, QByteArray> mRoles;
521+ History::Events mEventWritingQueue;
522+ int mEventWritingTimer;
523+ History::Threads mThreadWritingQueue;
524+ int mThreadWritingTimer;
525 int mUpdateTimer;
526 bool mWaitingForQml;
527 };
528
529=== modified file 'Ubuntu/History/historythreadmodel.cpp'
530--- Ubuntu/History/historythreadmodel.cpp 2016-06-17 01:49:46 +0000
531+++ Ubuntu/History/historythreadmodel.cpp 2017-03-23 01:25:43 +0000
532@@ -26,6 +26,8 @@
533 #include "voiceevent.h"
534 #include <QDBusMetaType>
535
536+#include <QDebug>
537+
538 Q_DECLARE_METATYPE(History::TextEventAttachments)
539 Q_DECLARE_METATYPE(QList<QVariantMap>)
540
541@@ -190,7 +192,6 @@
542 }
543 break;
544 }
545-
546 return result;
547 }
548
549@@ -214,13 +215,6 @@
550 mCanFetchMore = false;
551 Q_EMIT canFetchMoreChanged();
552 } else {
553- Q_FOREACH(const History::Thread &thread, threads) {
554- // insert the identifiers in the contact map
555- Q_FOREACH(const History::Participant &participant, thread.participants()) {
556- watchContactInfo(thread.accountId(), participant.identifier(), participant.properties());
557- }
558- }
559-
560 beginInsertRows(QModelIndex(), mThreads.count(), mThreads.count() + threads.count() - 1);
561 mThreads << threads;
562 endInsertRows();
563@@ -294,6 +288,10 @@
564 SIGNAL(threadsRemoved(History::Threads)),
565 SLOT(onThreadsRemoved(History::Threads)));
566 connect(mThreadView.data(),
567+ SIGNAL(threadParticipantsChanged(History::Thread, History::Participants, History::Participants, History::Participants)),
568+ SLOT(onThreadParticipantsChanged(History::Thread, History::Participants, History::Participants, History::Participants)));
569+
570+ connect(mThreadView.data(),
571 SIGNAL(invalidated()),
572 SLOT(triggerQueryUpdate()));
573
574@@ -311,6 +309,43 @@
575 fetchMore(QModelIndex());
576 }
577
578+
579+void HistoryThreadModel::onThreadParticipantsChanged(const History::Thread &thread, const History::Participants &added, const History::Participants &removed, const History::Participants &modified)
580+{
581+ int pos = mThreads.indexOf(thread);
582+ if (pos >= 0) {
583+ mThreads[pos].removeParticipants(removed);
584+ mThreads[pos].removeParticipants(modified);
585+ mThreads[pos].addParticipants(added);
586+ mThreads[pos].addParticipants(modified);
587+ QModelIndex idx = index(pos);
588+ Q_EMIT dataChanged(idx, idx);
589+ }
590+
591+ // watch the contact info for the received participants
592+ Q_FOREACH(const History::Participant &participant, added) {
593+ watchContactInfo(thread.accountId(), participant.identifier(), participant.properties());
594+ }
595+ Q_FOREACH(const History::Participant &participant, modified) {
596+ watchContactInfo(thread.accountId(), participant.identifier(), participant.properties());
597+ }
598+}
599+
600+void HistoryThreadModel::fetchParticipantsIfNeeded(const History::Threads &threads)
601+{
602+ History::Threads filtered;
603+ Q_FOREACH(const History::Thread &thread, threads) {
604+ if (thread.type() == History::EventTypeText && thread.participants().isEmpty() &&
605+ (thread.chatType() != History::ChatTypeRoom || thread.accountId().startsWith("ofono"))) {
606+ filtered << thread;
607+ }
608+ }
609+ if (filtered.isEmpty()) {
610+ return;
611+ }
612+ History::Manager::instance()->requestThreadParticipants(filtered);
613+}
614+
615 void HistoryThreadModel::onThreadsAdded(const History::Threads &threads)
616 {
617 if (threads.isEmpty()) {
618@@ -328,6 +363,7 @@
619 mThreads.insert(pos, thread);
620 endInsertRows();
621 }
622+ fetchParticipantsIfNeeded(threads);
623 }
624
625 void HistoryThreadModel::onThreadsModified(const History::Threads &threads)
626@@ -349,6 +385,7 @@
627 if (!newThreads.isEmpty()) {
628 onThreadsAdded(newThreads);
629 }
630+ fetchParticipantsIfNeeded(threads);
631 }
632
633 void HistoryThreadModel::onThreadsRemoved(const History::Threads &threads)
634@@ -369,5 +406,7 @@
635
636 History::Threads HistoryThreadModel::fetchNextPage()
637 {
638- return mThreadView->nextPage();
639+ History::Threads threads = mThreadView->nextPage();
640+ fetchParticipantsIfNeeded(threads);
641+ return threads;
642 }
643
644=== modified file 'Ubuntu/History/historythreadmodel.h'
645--- Ubuntu/History/historythreadmodel.h 2016-06-17 01:49:46 +0000
646+++ Ubuntu/History/historythreadmodel.h 2017-03-23 01:25:43 +0000
647@@ -76,8 +76,10 @@
648 virtual void onThreadsAdded(const History::Threads &threads);
649 virtual void onThreadsModified(const History::Threads &threads);
650 virtual void onThreadsRemoved(const History::Threads &threads);
651+ virtual void onThreadParticipantsChanged(const History::Thread &thread, const History::Participants &added, const History::Participants &removed, const History::Participants &modified);
652
653 protected:
654+ void fetchParticipantsIfNeeded(const History::Threads &threads);
655 History::Threads fetchNextPage();
656 bool mCanFetchMore;
657 bool mGroupThreads;
658
659=== modified file 'cmake/modules/GenerateTest.cmake'
660--- cmake/modules/GenerateTest.cmake 2015-09-28 14:26:09 +0000
661+++ cmake/modules/GenerateTest.cmake 2017-03-23 01:25:43 +0000
662@@ -79,6 +79,7 @@
663 set(ARG_ENVIRONMENT HOME=${TMPDIR}
664 HISTORY_PLUGIN_PATH=${CMAKE_BINARY_DIR}/plugins/sqlite
665 HISTORY_SQLITE_DBPATH=:memory:
666+ HISTORY_LOCK_FILE=${TMPDIR}/history-service.lock
667 MC_ACCOUNT_DIR=${TMPDIR}
668 MC_MANAGER_DIR=${TMPDIR})
669 endif ()
670
671=== modified file 'daemon/HistoryService.xml'
672--- daemon/HistoryService.xml 2016-04-15 22:23:21 +0000
673+++ daemon/HistoryService.xml 2017-03-23 01:25:43 +0000
674@@ -35,6 +35,15 @@
675 <arg type="a{sv}" direction="out"/>
676 <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QVariantMap"/>
677 </method>
678+ <method name="ParticipantsForThreads">
679+ <dox:d><![CDATA[
680+ Return the participants for the given threads
681+ ]]></dox:d>
682+ <arg name="threadIds" type="a(a{sv})" direction="in"/>
683+ <annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QList &lt; QVariantMap &gt;"/>
684+ <arg name="participants" type="a(a{sv})" direction="out"/>
685+ <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QList &lt; QVariantMap &gt;"/>
686+ </method>
687 <method name="WriteEvents">
688 <dox:d><![CDATA[
689 Write the given events to the storage.
690@@ -62,6 +71,13 @@
691 <arg type="b" direction="out"/>
692 <annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QList &lt; QVariantMap &gt;"/>
693 </method>
694+ <method name="MarkThreadsAsRead">
695+ <dox:d><![CDATA[
696+ Mark an entire thread as read
697+ ]]></dox:d>
698+ <arg name="threads" type="a(a{sv})" direction="in"/>
699+ <annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QList &lt; QVariantMap &gt;"/>
700+ </method>
701 <method name="QueryThreads">
702 <dox:d><![CDATA[
703 Creates a threads view with the given filter and sort order.
704@@ -159,5 +175,18 @@
705 <arg name="events" type="a(a{sv})"/>
706 <annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QList &lt; QVariantMap &gt;"/>
707 </signal>
708+ <signal name="ThreadParticipantsChanged">
709+ <dox:d><![CDATA[
710+ Participants changed in a certain thread changed.
711+ ]]></dox:d>
712+ <arg name="thread" type="a{sv}"/>
713+ <arg name="added" type="a(a{sv})"/>
714+ <arg name="removed" type="a(a{sv})"/>
715+ <arg name="modified" type="a(a{sv})"/>
716+ <annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QVariantMap"/>
717+ <annotation name="org.qtproject.QtDBus.QtTypeName.In1" value="QList &lt; QVariantMap &gt;"/>
718+ <annotation name="org.qtproject.QtDBus.QtTypeName.In2" value="QList &lt; QVariantMap &gt;"/>
719+ <annotation name="org.qtproject.QtDBus.QtTypeName.In3" value="QList &lt; QVariantMap &gt;"/>
720+ </signal>
721 </interface>
722 </node>
723
724=== modified file 'daemon/callchannelobserver.cpp'
725--- daemon/callchannelobserver.cpp 2013-07-12 14:30:18 +0000
726+++ daemon/callchannelobserver.cpp 2017-03-23 01:25:43 +0000
727@@ -40,6 +40,7 @@
728 SLOT(onCallStateChanged(Tp::CallState)));
729
730 mChannels.append(callChannel);
731+ mCallStates[callChannel.data()] = callChannel->callState();
732 }
733
734
735@@ -51,11 +52,24 @@
736 }
737
738 switch (state) {
739- case Tp::CallStateEnded:
740- Q_EMIT callEnded(Tp::CallChannelPtr(channel));
741+ case Tp::CallStateEnded: {
742+ bool incoming = !channel->isRequested();
743+ bool missed = incoming && channel->callStateReason().reason == Tp::CallStateChangeReasonNoAnswer;
744+
745+ // If the call state is not missed at this point, we need to check from which state we transitioned to ended,
746+ // if from Initialised, it means it was indeed missed
747+ if (incoming && !missed) {
748+ missed = mCallStates[channel] == Tp::CallStateInitialised;
749+ }
750+ mCallStates.remove(channel);
751+ mChannels.removeOne(Tp::CallChannelPtr(channel));
752+ Q_EMIT callEnded(Tp::CallChannelPtr(channel), missed);
753 break;
754+ }
755 case Tp::CallStateActive:
756 channel->setProperty("activeTimestamp", QDateTime::currentDateTime());
757+ default:
758+ mCallStates[channel] = state;
759 break;
760 }
761 }
762
763=== modified file 'daemon/callchannelobserver.h'
764--- daemon/callchannelobserver.h 2013-07-12 14:30:18 +0000
765+++ daemon/callchannelobserver.h 2017-03-23 01:25:43 +0000
766@@ -35,13 +35,14 @@
767 void onCallChannelAvailable(Tp::CallChannelPtr callChannel);
768
769 Q_SIGNALS:
770- void callEnded(Tp::CallChannelPtr callChannel);
771+ void callEnded(Tp::CallChannelPtr callChannel, bool missed);
772
773 protected Q_SLOTS:
774 void onCallStateChanged(Tp::CallState state);
775
776 private:
777 QList<Tp::CallChannelPtr> mChannels;
778+ QMap<Tp::CallChannel*,Tp::CallState> mCallStates;
779 };
780
781 #endif // CALLCHANNELOBSERVER_H
782
783=== modified file 'daemon/historydaemon.cpp'
784--- daemon/historydaemon.cpp 2016-11-24 02:02:32 +0000
785+++ daemon/historydaemon.cpp 2017-03-23 01:25:43 +0000
786@@ -1,5 +1,5 @@
787 /*
788- * Copyright (C) 2013-2016 Canonical, Ltd.
789+ * Copyright (C) 2013-2017 Canonical, Ltd.
790 *
791 * Authors:
792 * Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>
793@@ -85,10 +85,11 @@
794 {
795 Q_FOREACH (QVariant participant, thread[History::FieldParticipants].toList()) {
796 // found if same identifier and as member into thread info
797+ QVariantMap participantMap = participant.toMap();
798 if (History::Utils::compareIds(thread[History::FieldAccountId].toString(),
799 contact->id(),
800- participant.toMap()[History::FieldIdentifier].toString()) &&
801- participant.toMap()[History::FieldParticipantState].toUInt() == History::ParticipantStateRegular)
802+ participantMap[History::FieldIdentifier].toString()) &&
803+ participantMap[History::FieldParticipantState].toUInt() == History::ParticipantStateRegular)
804 {
805 return true;
806 }
807@@ -131,8 +132,8 @@
808 History::TelepathyHelper::instance()->registerChannelObserver();
809
810 connect(&mCallObserver,
811- SIGNAL(callEnded(Tp::CallChannelPtr)),
812- SLOT(onCallEnded(Tp::CallChannelPtr)));
813+ SIGNAL(callEnded(Tp::CallChannelPtr, bool)),
814+ SLOT(onCallEnded(Tp::CallChannelPtr, bool)));
815 connect(&mTextObserver,
816 SIGNAL(messageReceived(Tp::TextChannelPtr,Tp::ReceivedMessage)),
817 SLOT(onMessageReceived(Tp::TextChannelPtr,Tp::ReceivedMessage)));
818@@ -140,11 +141,11 @@
819 SIGNAL(messageSent(Tp::TextChannelPtr,Tp::Message,QString)),
820 SLOT(onMessageSent(Tp::TextChannelPtr,Tp::Message,QString)));
821 connect(&mTextObserver,
822- SIGNAL(messageRead(Tp::TextChannelPtr,Tp::ReceivedMessage)),
823- SLOT(onMessageRead(Tp::TextChannelPtr,Tp::ReceivedMessage)));
824- connect(&mTextObserver,
825 SIGNAL(channelAvailable(Tp::TextChannelPtr)),
826 SLOT(onTextChannelAvailable(Tp::TextChannelPtr)));
827+ connect(&mTextObserver,
828+ SIGNAL(textChannelInvalidated(Tp::TextChannelPtr)),
829+ SLOT(onTextChannelInvalidated(Tp::TextChannelPtr)));
830
831 // FIXME: we need to do this in a better way, but for now this should do
832 mProtocolFlags["ofono"] = History::MatchPhoneNumber;
833@@ -163,23 +164,39 @@
834
835 void HistoryDaemon::onRolesChanged(const HandleRolesMap &added, const HandleRolesMap &removed)
836 {
837- Q_UNUSED(added);
838- Q_UNUSED(removed);
839-
840 ChannelInterfaceRolesInterface *roles_interface = qobject_cast<ChannelInterfaceRolesInterface*>(sender());
841- RolesMap roles = roles_interface->getRoles();
842-
843 Tp::TextChannelPtr channel(qobject_cast<Tp::TextChannel*>(sender()->parent()));
844+ RolesMap rolesMap;
845+ if (!mRolesMap.contains(channel->objectPath())) {
846+ rolesMap = roles_interface->getRoles();
847+ } else {
848+ rolesMap = mRolesMap[channel->objectPath()];
849+ }
850+
851+ QMapIterator<uint, uint> it(removed);
852+ while (it.hasNext()) {
853+ it.next();
854+ rolesMap.remove(it.key());
855+ }
856+
857+ QMapIterator<uint, uint> it2(added);
858+ while (it2.hasNext()) {
859+ it2.next();
860+ rolesMap[it2.key()] = it2.value();
861+ }
862+
863+ mRolesMap[channel->objectPath()] = rolesMap;
864+
865 QVariantMap properties = propertiesFromChannel(channel);
866 QVariantMap thread = threadForProperties(channel->property(History::FieldAccountId).toString(),
867 History::EventTypeText,
868 properties,
869 matchFlagsForChannel(channel),
870 false);
871-
872- writeRolesInformationEvents(thread, channel, roles);
873-
874- updateRoomRoles(channel, roles);
875+ if (!thread.isEmpty()) {
876+ writeRolesInformationEvents(thread, channel, rolesMap);
877+ updateRoomRoles(channel, rolesMap);
878+ }
879 }
880
881 QVariantMap HistoryDaemon::propertiesFromChannel(const Tp::ChannelPtr &textChannel)
882@@ -187,55 +204,60 @@
883 QVariantMap properties;
884 QVariantList participants;
885 QStringList participantIds;
886-
887- ChannelInterfaceRolesInterface *roles_interface = textChannel->optionalInterface<ChannelInterfaceRolesInterface>();
888- RolesMap roles;
889- if (roles_interface) {
890- roles = roles_interface->getRoles();
891- }
892-
893- Q_FOREACH(const Tp::ContactPtr contact, textChannel->groupContacts(false)) {
894- QVariantMap contactProperties;
895- contactProperties[History::FieldAlias] = contact->alias();
896- contactProperties[History::FieldAccountId] = textChannel->property(History::FieldAccountId).toString();
897- contactProperties[History::FieldIdentifier] = contact->id();
898- contactProperties[History::FieldParticipantState] = History::ParticipantStateRegular;
899- contactProperties[History::FieldParticipantRoles] = roles[contact->handle().at(0)];
900- participantIds << contact->id();
901- participants << contactProperties;
902- }
903-
904- Q_FOREACH(const Tp::ContactPtr contact, textChannel->groupRemotePendingContacts(false)) {
905- QVariantMap contactProperties;
906- contactProperties[History::FieldAlias] = contact->alias();
907- contactProperties[History::FieldAccountId] = textChannel->property(History::FieldAccountId).toString();
908- contactProperties[History::FieldIdentifier] = contact->id();
909- contactProperties[History::FieldParticipantState] = History::ParticipantStateRemotePending;
910- contactProperties[History::FieldParticipantRoles] = roles[contact->handle().at(0)];
911- participantIds << contact->id();
912- participants << contactProperties;
913- }
914-
915- Q_FOREACH(const Tp::ContactPtr contact, textChannel->groupLocalPendingContacts(false)) {
916- QVariantMap contactProperties;
917- contactProperties[History::FieldAlias] = contact->alias();
918- contactProperties[History::FieldAccountId] = textChannel->property(History::FieldAccountId).toString();
919- contactProperties[History::FieldIdentifier] = contact->id();
920- contactProperties[History::FieldParticipantState] = History::ParticipantStateLocalPending;
921- contactProperties[History::FieldParticipantRoles] = roles[contact->handle().at(0)];
922- participantIds << contact->id();
923- participants << contactProperties;
924- }
925-
926- if (participants.isEmpty() && textChannel->targetHandleType() == Tp::HandleTypeContact &&
927- textChannel->targetContact() == textChannel->connection()->selfContact()) {
928- QVariantMap contactProperties;
929- contactProperties[History::FieldAlias] = textChannel->targetContact()->alias();
930- contactProperties[History::FieldAccountId] = textChannel->property(History::FieldAccountId).toString();
931- contactProperties[History::FieldIdentifier] = textChannel->targetContact()->id();
932- contactProperties[History::FieldParticipantState] = History::ParticipantStateRegular;
933- participantIds << textChannel->targetContact()->id();
934- participants << contactProperties;
935+ QString accountId = textChannel->property(History::FieldAccountId).toString();
936+
937+ if (History::Utils::shouldIncludeParticipants(accountId, fromTelepathyHandleType(textChannel->targetHandleType()))) {
938+ ChannelInterfaceRolesInterface *roles_interface = textChannel->optionalInterface<ChannelInterfaceRolesInterface>();
939+ RolesMap roles;
940+ if (roles_interface) {
941+ if (mRolesMap.contains(textChannel->objectPath())) {
942+ roles = mRolesMap[textChannel->objectPath()];
943+ }
944+ }
945+
946+ Q_FOREACH(const Tp::ContactPtr contact, textChannel->groupContacts(false)) {
947+ QVariantMap contactProperties;
948+ contactProperties[History::FieldAlias] = contact->alias();
949+ contactProperties[History::FieldAccountId] = accountId;
950+ contactProperties[History::FieldIdentifier] = contact->id();
951+ contactProperties[History::FieldParticipantState] = History::ParticipantStateRegular;
952+ contactProperties[History::FieldParticipantRoles] = roles[contact->handle().at(0)];
953+ participantIds << contact->id();
954+ participants << contactProperties;
955+ }
956+
957+ Q_FOREACH(const Tp::ContactPtr contact, textChannel->groupRemotePendingContacts(false)) {
958+ QVariantMap contactProperties;
959+ contactProperties[History::FieldAlias] = contact->alias();
960+ contactProperties[History::FieldAccountId] = accountId;
961+ contactProperties[History::FieldIdentifier] = contact->id();
962+ contactProperties[History::FieldParticipantState] = History::ParticipantStateRemotePending;
963+ contactProperties[History::FieldParticipantRoles] = roles[contact->handle().at(0)];
964+ participantIds << contact->id();
965+ participants << contactProperties;
966+ }
967+
968+ Q_FOREACH(const Tp::ContactPtr contact, textChannel->groupLocalPendingContacts(false)) {
969+ QVariantMap contactProperties;
970+ contactProperties[History::FieldAlias] = contact->alias();
971+ contactProperties[History::FieldAccountId] = accountId;
972+ contactProperties[History::FieldIdentifier] = contact->id();
973+ contactProperties[History::FieldParticipantState] = History::ParticipantStateLocalPending;
974+ contactProperties[History::FieldParticipantRoles] = roles[contact->handle().at(0)];
975+ participantIds << contact->id();
976+ participants << contactProperties;
977+ }
978+
979+ if (participants.isEmpty() && textChannel->targetHandleType() == Tp::HandleTypeContact &&
980+ textChannel->targetContact() == textChannel->connection()->selfContact()) {
981+ QVariantMap contactProperties;
982+ contactProperties[History::FieldAlias] = textChannel->targetContact()->alias();
983+ contactProperties[History::FieldAccountId] = accountId;
984+ contactProperties[History::FieldIdentifier] = textChannel->targetContact()->id();
985+ contactProperties[History::FieldParticipantState] = History::ParticipantStateRegular;
986+ participantIds << textChannel->targetContact()->id();
987+ participants << contactProperties;
988+ }
989 }
990
991 // We map chatType directly from telepathy targetHandleType: None, Contact, Room
992@@ -314,6 +336,39 @@
993 return thread;
994 }
995
996+QString HistoryDaemon::threadIdForProperties(const QString &accountId, History::EventType type, const QVariantMap &properties, History::MatchFlags matchFlags, bool create)
997+{
998+ if (!mBackend) {
999+ return QString::null;
1000+ }
1001+
1002+ QString threadId = mBackend->threadIdForProperties(accountId,
1003+ type,
1004+ properties,
1005+ matchFlags);
1006+ if (threadId.isEmpty() && create) {
1007+ QVariantMap thread = mBackend->createThreadForProperties(accountId, type, properties);
1008+ if (!thread.isEmpty()) {
1009+ if (properties.contains("Requested") && properties[History::FieldChatType].toInt() == History::ChatTypeRoom) {
1010+ QVariantMap map = thread[History::FieldChatRoomInfo].toMap();
1011+ map["Requested"] = properties["Requested"];
1012+ thread[History::FieldChatRoomInfo] = map;
1013+ }
1014+ mDBus.notifyThreadsAdded(QList<QVariantMap>() << thread);
1015+ threadId = thread[History::FieldThreadId].toString();
1016+ }
1017+ }
1018+ return threadId;
1019+}
1020+
1021+QList<QVariantMap> HistoryDaemon::participantsForThreads(const QList<QVariantMap> &threadIds)
1022+{
1023+ if (!mBackend) {
1024+ return QList<QVariantMap>();
1025+ }
1026+ return mBackend->participantsForThreads(threadIds);
1027+}
1028+
1029 QString HistoryDaemon::queryThreads(int type, const QVariantMap &sort, const QVariantMap &filter, const QVariantMap &properties)
1030 {
1031 if (!mBackend) {
1032@@ -370,7 +425,7 @@
1033 return mBackend->getSingleEvent((History::EventType)type, accountId, threadId, eventId);
1034 }
1035
1036-bool HistoryDaemon::writeEvents(const QList<QVariantMap> &events, const QVariantMap &properties)
1037+bool HistoryDaemon::writeEvents(const QList<QVariantMap> &events, const QVariantMap &properties, bool notify)
1038 {
1039 if (!mBackend) {
1040 return false;
1041@@ -407,7 +462,9 @@
1042 threads[hash] = thread;
1043
1044 // set the participants field in the event
1045- savedEvent[History::FieldParticipants] = thread[History::FieldParticipants];
1046+ if (type == History::EventTypeVoice) {
1047+ savedEvent[History::FieldParticipants] = thread[History::FieldParticipants];
1048+ }
1049
1050
1051 // check if the event was a new one or a modification to an existing one
1052@@ -427,13 +484,13 @@
1053 mBackend->endBatchOperation();
1054
1055 // and last but not least, notify the results
1056- if (!newEvents.isEmpty()) {
1057+ if (!newEvents.isEmpty() && notify) {
1058 mDBus.notifyEventsAdded(newEvents);
1059 }
1060- if (!modifiedEvents.isEmpty()) {
1061+ if (!modifiedEvents.isEmpty() && notify) {
1062 mDBus.notifyEventsModified(modifiedEvents);
1063 }
1064- if (!threads.isEmpty()) {
1065+ if (!threads.isEmpty() && notify) {
1066 mDBus.notifyThreadsModified(threads.values());
1067 }
1068 return true;
1069@@ -512,44 +569,46 @@
1070 return true;
1071 }
1072
1073+void HistoryDaemon::markThreadsAsRead(const QList<QVariantMap> &threads)
1074+{
1075+ if (!mBackend) {
1076+ return;
1077+ }
1078+
1079+ QList<QVariantMap> modifiedThreads;
1080+
1081+ Q_FOREACH(const QVariantMap &thread, threads) {
1082+ mBackend->beginBatchOperation();
1083+ QVariantMap newThread = mBackend->markThreadAsRead(thread);
1084+ if (!newThread.isEmpty()) {
1085+ modifiedThreads << newThread;
1086+ }
1087+
1088+ mBackend->endBatchOperation();
1089+ }
1090+
1091+ if (!modifiedThreads.isEmpty()) {
1092+ mDBus.notifyThreadsModified(modifiedThreads);
1093+ }
1094+}
1095+
1096 bool HistoryDaemon::removeThreads(const QList<QVariantMap> &threads)
1097 {
1098 if (!mBackend) {
1099 return false;
1100 }
1101
1102- // In order to remove a thread all we have to do is to remove all its items
1103- // then it is going to be removed by removeEvents() once it detects the thread is
1104- // empty.
1105- QList<QVariantMap> events;
1106- QMap<QString, QVariantMap> removedEmptyThreads;
1107+ // If the thread has events
1108+ mBackend->beginBatchOperation();
1109 Q_FOREACH(const QVariantMap &thread, threads) {
1110- QList<QVariantMap> thisEvents = mBackend->eventsForThread(thread);
1111- if (thisEvents.isEmpty()) {
1112- mBackend->beginBatchOperation();
1113- if (!mBackend->removeThread(thread)) {
1114- mBackend->rollbackBatchOperation();
1115- return false;
1116- }
1117- mBackend->endBatchOperation();
1118- QString hash = hashThread(thread);
1119- removedEmptyThreads[hash] = thread;
1120- continue;
1121- }
1122- events += thisEvents;
1123- }
1124-
1125- if (!removedEmptyThreads.isEmpty()) {
1126- mDBus.notifyThreadsRemoved(removedEmptyThreads.values());
1127- }
1128-
1129- if (events.size() > 0) {
1130- if(removeEvents(events)) {
1131- return true;
1132- }
1133- }
1134-
1135- return false;
1136+ if (!mBackend->removeThread(thread)) {
1137+ mBackend->rollbackBatchOperation();
1138+ return false;
1139+ }
1140+ }
1141+ mBackend->endBatchOperation();
1142+ mDBus.notifyThreadsRemoved(threads);
1143+ return true;
1144 }
1145
1146 void HistoryDaemon::onObserverCreated()
1147@@ -562,7 +621,7 @@
1148 &mTextObserver, SLOT(onTextChannelAvailable(Tp::TextChannelPtr)));
1149 }
1150
1151-void HistoryDaemon::onCallEnded(const Tp::CallChannelPtr &channel)
1152+void HistoryDaemon::onCallEnded(const Tp::CallChannelPtr &channel, bool missed)
1153 {
1154 QVariantMap properties = propertiesFromChannel(channel);
1155 QVariantList participants;
1156@@ -592,7 +651,6 @@
1157 // FIXME: check if checking for isRequested() is enough
1158 bool incoming = !channel->isRequested();
1159 int duration;
1160- bool missed = incoming && channel->callStateReason().reason == Tp::CallStateChangeReasonNoAnswer;
1161
1162 if (!missed) {
1163 QDateTime activeTime = channel->property("activeTimestamp").toDateTime();
1164@@ -615,11 +673,35 @@
1165 writeEvents(QList<QVariantMap>() << event, properties);
1166 }
1167
1168+void HistoryDaemon::onTextChannelInvalidated(const Tp::TextChannelPtr channel)
1169+{
1170+ mRolesMap.remove(channel->objectPath());
1171+ QString accountId = channel->property(History::FieldAccountId).toString();
1172+ QVariantMap properties = propertiesFromChannel(channel);
1173+
1174+ // first try to fetch the existing thread to see if there is any.
1175+ QVariantMap thread = threadForProperties(accountId,
1176+ History::EventTypeText,
1177+ properties,
1178+ matchFlagsForChannel(channel),
1179+ false);
1180+
1181+ QVariantMap roomInfo = thread[History::FieldChatRoomInfo].toMap();
1182+ if ((roomInfo.contains("Persistent") && !roomInfo["Persistent"].toBool()) && History::TelepathyHelper::instance()->accountForId(accountId)->protocolName() != "ofono") {
1183+ writeInformationEvent(thread, History::InformationTypeSelfLeaving);
1184+ // update backend
1185+ updateRoomProperties(channel, QVariantMap{{"Joined", false}});
1186+ }
1187+
1188+ channel->disconnect(this);
1189+}
1190+
1191 void HistoryDaemon::onTextChannelAvailable(const Tp::TextChannelPtr channel)
1192 {
1193 // for Rooms we need to explicitly create the thread to allow users to send messages to groups even
1194 // before they receive any message.
1195 // for other types, we can wait until messages are received
1196+ bool notify = false;
1197 if (channel->targetHandleType() == Tp::HandleTypeRoom) {
1198 QString accountId = channel->property(History::FieldAccountId).toString();
1199 QVariantMap properties = propertiesFromChannel(channel);
1200@@ -641,12 +723,13 @@
1201
1202 // write information event including all initial invitees
1203 Q_FOREACH(const Tp::ContactPtr contact, channel->groupRemotePendingContacts(false)) {
1204- writeInformationEvent(thread, History::InformationTypeInvitationSent, contact->alias());
1205+ writeInformationEvent(thread, History::InformationTypeInvitationSent, contact->alias(), QString(), QString(), false);
1206 }
1207
1208 // update participants only if the thread is not available previously. Otherwise we'll wait for membersChanged event
1209 // for reflect in conversation information events for modified participants.
1210- updateRoomParticipants(channel);
1211+ updateRoomParticipants(channel, false);
1212+ notify = true;
1213 }
1214
1215 // write an entry saying you joined the group if 'joined' flag in thread is false and modify that flag.
1216@@ -657,7 +740,8 @@
1217 writeInformationEvent(thread, History::InformationTypeSelfJoined);
1218 }
1219 // update backend
1220- updateRoomProperties(channel, QVariantMap{{"Joined", true}});
1221+ updateRoomProperties(channel, QVariantMap{{"Joined", true}}, false);
1222+ notify = true;
1223 }
1224
1225 Tp::AbstractInterface *room_interface = channel->optionalInterface<Tp::Client::ChannelInterfaceRoomInterface>();
1226@@ -685,6 +769,10 @@
1227
1228 connect(roles_interface, SIGNAL(RolesChanged(const HandleRolesMap&, const HandleRolesMap&)), SLOT(onRolesChanged(const HandleRolesMap&, const HandleRolesMap&)));
1229 }
1230+
1231+ if (notify) {
1232+ updateRoomParticipants(channel, true);
1233+ }
1234 }
1235
1236 void HistoryDaemon::onGroupMembersChanged(const Tp::Contacts &groupMembersAdded,
1237@@ -702,6 +790,8 @@
1238 bool hasRemotePendingMembersAdded = groupRemotePendingMembersAdded.size() > 0;
1239 bool hasMembersAdded = groupMembersAdded.size() > 0;
1240 bool hasMembersRemoved = groupMembersRemoved.size() > 0;
1241+ Tp::ContactPtr selfContact = channel->connection()->selfContact();
1242+ bool selfContactIsPending = channel->groupRemotePendingContacts(true).contains(selfContact);
1243
1244 if (hasRemotePendingMembersAdded || hasMembersAdded || hasMembersRemoved) {
1245 properties = propertiesFromChannel(channel);
1246@@ -710,19 +800,35 @@
1247 properties,
1248 matchFlagsForChannel(channel),
1249 false);
1250- if (!thread.isEmpty()) {
1251+ if (!thread.isEmpty() && !selfContactIsPending) {
1252+ QList<QVariantMap> added;
1253+ QList<QVariantMap> removed;
1254+ QList<QVariantMap> modified;
1255 if (hasRemotePendingMembersAdded) {
1256 Q_FOREACH (const Tp::ContactPtr& contact, groupRemotePendingMembersAdded) {
1257 if (!foundInThread(contact, thread)) {
1258- writeInformationEvent(thread, History::InformationTypeInvitationSent, contact->alias());
1259+ writeInformationEvent(thread, History::InformationTypeInvitationSent, contact->alias(), QString(), QString(), true);
1260+ QVariantMap participant;
1261+ participant[History::FieldIdentifier] = contact->id();
1262+ participant[History::FieldAlias] = contact->alias();
1263+ participant[History::FieldParticipantState] = History::ParticipantStateRemotePending;
1264+ added << participant;
1265 }
1266 }
1267+
1268 }
1269 if (hasMembersAdded) {
1270 Q_FOREACH (const Tp::ContactPtr& contact, groupMembersAdded) {
1271 // if this member was not previously regular member in thread, notify about his join
1272- if (!foundAsMemberInThread(contact, thread)) {
1273- writeInformationEvent(thread, History::InformationTypeJoined, contact->alias());
1274+ if (!foundAsMemberInThread(contact, thread) && contact->id() != channel->groupSelfContact()->id()) {
1275+
1276+ writeInformationEvent(thread, History::InformationTypeJoined, contact->alias(), QString(), QString(), true);
1277+
1278+ QVariantMap participant;
1279+ participant[History::FieldIdentifier] = contact->id();
1280+ participant[History::FieldAlias] = contact->alias();
1281+ participant[History::FieldParticipantState] = History::ParticipantStateRegular;
1282+ added << participant;
1283 }
1284 }
1285 }
1286@@ -741,27 +847,34 @@
1287 break;
1288 }
1289 }
1290- writeInformationEvent(thread, type);
1291- // update backend
1292- updateRoomProperties(channel, QVariantMap{{"Joined", false}});
1293+ if (thread[History::FieldChatRoomInfo].toMap()["Joined"].toBool()) {
1294+ writeInformationEvent(thread, type);
1295+ // update backend
1296+ updateRoomProperties(channel, QVariantMap{{"Joined", false}}, true);
1297+ }
1298 }
1299 else // don't notify any other group member removal if we are leaving the group
1300 {
1301 Q_FOREACH (const Tp::ContactPtr& contact, groupMembersRemoved) {
1302 // inform about removed members other than us
1303 if (contact->id() != channel->groupSelfContact()->id()) {
1304- writeInformationEvent(thread, History::InformationTypeLeaving, contact->alias());
1305+ writeInformationEvent(thread, History::InformationTypeLeaving, contact->alias(), QString(), QString(), true);
1306 }
1307+ QVariantMap participant;
1308+ participant[History::FieldIdentifier] = contact->id();
1309+ participant[History::FieldAlias] = contact->alias();
1310+ removed << participant;
1311 }
1312 }
1313 }
1314+ mDBus.notifyThreadParticipantsChanged(thread, added, removed, QList<QVariantMap>());
1315 }
1316 }
1317
1318- updateRoomParticipants(channel);
1319+ updateRoomParticipants(channel, !selfContactIsPending);
1320 }
1321
1322-void HistoryDaemon::updateRoomParticipants(const Tp::TextChannelPtr channel)
1323+void HistoryDaemon::updateRoomParticipants(const Tp::TextChannelPtr channel, bool notify)
1324 {
1325 if (!channel) {
1326 return;
1327@@ -772,8 +885,14 @@
1328
1329 ChannelInterfaceRolesInterface *roles_interface = channel->optionalInterface<ChannelInterfaceRolesInterface>();
1330 RolesMap roles;
1331+
1332 if (roles_interface) {
1333- roles = roles_interface->getRoles();
1334+ if (!mRolesMap.contains(channel->objectPath())) {
1335+ roles = roles_interface->getRoles();
1336+ mRolesMap[channel->objectPath()] = roles;
1337+ } else {
1338+ roles = mRolesMap[channel->objectPath()];
1339+ }
1340 }
1341
1342 Q_FOREACH(const Tp::ContactPtr contact, channel->groupRemotePendingContacts(false)) {
1343@@ -811,12 +930,14 @@
1344 QString accountId = channel->property(History::FieldAccountId).toString();
1345 QString threadId = channel->targetId();
1346 if (mBackend->updateRoomParticipants(accountId, threadId, History::EventTypeText, participants)) {
1347- QVariantMap updatedThread = getSingleThread(History::EventTypeText, accountId, threadId, QVariantMap());
1348- mDBus.notifyThreadsModified(QList<QVariantMap>() << updatedThread);
1349+ if (notify) {
1350+ QVariantMap updatedThread = getSingleThread(History::EventTypeText, accountId, threadId, QVariantMap());
1351+ mDBus.notifyThreadsModified(QList<QVariantMap>() << updatedThread);
1352+ }
1353 }
1354 }
1355
1356-void HistoryDaemon::updateRoomRoles(const Tp::TextChannelPtr &channel, const RolesMap &rolesMap)
1357+void HistoryDaemon::updateRoomRoles(const Tp::TextChannelPtr &channel, const RolesMap &rolesMap, bool notify)
1358 {
1359 if (!channel) {
1360 return;
1361@@ -841,8 +962,10 @@
1362 QString accountId = channel->property(History::FieldAccountId).toString();
1363 QString threadId = channel->targetId();
1364 if (mBackend->updateRoomParticipantsRoles(accountId, threadId, History::EventTypeText, participantsRoles)) {
1365- QVariantMap updatedThread = getSingleThread(History::EventTypeText, accountId, threadId, QVariantMap());
1366- mDBus.notifyThreadsModified(QList<QVariantMap>() << updatedThread);
1367+ if (notify) {
1368+ QVariantMap updatedThread = getSingleThread(History::EventTypeText, accountId, threadId, QVariantMap());
1369+ mDBus.notifyThreadsModified(QList<QVariantMap>() << updatedThread);
1370+ }
1371 }
1372
1373 // update self roles in room properties
1374@@ -865,28 +988,41 @@
1375 updateRoomProperties(accountId, threadId, type, properties, invalidated);
1376 }
1377
1378-void HistoryDaemon::updateRoomProperties(const Tp::TextChannelPtr &channel, const QVariantMap &properties)
1379+void HistoryDaemon::updateRoomProperties(const Tp::TextChannelPtr &channel, const QVariantMap &properties, bool notify)
1380 {
1381 QString accountId = channel->property(History::FieldAccountId).toString();
1382 QString threadId = channel->targetId();
1383 History::EventType type = History::EventTypeText;
1384- updateRoomProperties(accountId, threadId, type, properties, QStringList());
1385+ updateRoomProperties(accountId, threadId, type, properties, QStringList(), notify);
1386 }
1387
1388-void HistoryDaemon::updateRoomProperties(const QString &accountId, const QString &threadId, History::EventType type, const QVariantMap &properties, const QStringList &invalidated)
1389+void HistoryDaemon::updateRoomProperties(const QString &accountId, const QString &threadId, History::EventType type, const QVariantMap &properties, const QStringList &invalidated, bool notify)
1390 {
1391 if (mBackend->updateRoomInfo(accountId, threadId, type, properties, invalidated)) {
1392- QVariantMap thread = getSingleThread(type, accountId, threadId, QVariantMap());
1393- mDBus.notifyThreadsModified(QList<QVariantMap>() << thread);
1394+ if (notify) {
1395+ QVariantMap thread = getSingleThread(type, accountId, threadId, QVariantMap());
1396+ mDBus.notifyThreadsModified(QList<QVariantMap>() << thread);
1397+ }
1398 }
1399 }
1400
1401 void HistoryDaemon::onMessageReceived(const Tp::TextChannelPtr textChannel, const Tp::ReceivedMessage &message)
1402 {
1403 QString eventId;
1404- Tp::MessagePart header = message.header();
1405 QString senderId;
1406+
1407+ QString accountId = textChannel->property(History::FieldAccountId).toString();
1408+ QString threadId = textChannel->property(History::FieldThreadId).toString();
1409 QVariantMap properties = propertiesFromChannel(textChannel);
1410+
1411+ if (threadId.isNull()) {
1412+ threadId = threadIdForProperties(accountId,
1413+ History::EventTypeText,
1414+ properties,
1415+ matchFlagsForChannel(textChannel),
1416+ true);
1417+ }
1418+
1419 History::MessageStatus status = History::MessageStatusUnknown;
1420 if (!message.sender() || message.sender()->handle().at(0) == textChannel->connection()->selfHandle()) {
1421 senderId = "self";
1422@@ -909,7 +1045,7 @@
1423 if (message.isDeliveryReport() && message.deliveryDetails().hasOriginalToken()) {
1424 // at this point we assume the delivery report is for a message that was already
1425 // sent and properly saved at our database, so we can safely get it here to update
1426- QVariantMap textEvent = getSingleEventFromTextChannel(textChannel, message.deliveryDetails().originalToken());
1427+ QVariantMap textEvent = getSingleEvent(History::EventTypeText, accountId, threadId, message.deliveryDetails().originalToken());
1428 if (textEvent.isEmpty()) {
1429 qWarning() << "Cound not find the original event to update with delivery details.";
1430 return;
1431@@ -922,52 +1058,21 @@
1432 return;
1433 }
1434
1435- History::MessageStatus status;
1436- switch (message.deliveryDetails().status()) {
1437- case Tp::DeliveryStatusAccepted:
1438- status = History::MessageStatusAccepted;
1439- break;
1440- case Tp::DeliveryStatusDeleted:
1441- status = History::MessageStatusDeleted;
1442- break;
1443- case Tp::DeliveryStatusDelivered:
1444- status = History::MessageStatusDelivered;
1445- break;
1446- case Tp::DeliveryStatusPermanentlyFailed:
1447- status = History::MessageStatusPermanentlyFailed;
1448- break;
1449- case Tp::DeliveryStatusRead:
1450- status = History::MessageStatusRead;
1451- break;
1452- case Tp::DeliveryStatusTemporarilyFailed:
1453- status = History::MessageStatusTemporarilyFailed;
1454- break;
1455- case Tp::DeliveryStatusUnknown:
1456- status = History::MessageStatusUnknown;
1457- break;
1458- }
1459-
1460- textEvent[History::FieldMessageStatus] = (int) status;
1461+ textEvent[History::FieldMessageStatus] = (int) fromTelepathyDeliveryStatus(message.deliveryDetails().status());
1462 if (!writeEvents(QList<QVariantMap>() << textEvent, properties)) {
1463 qWarning() << "Failed to save the new message status!";
1464 }
1465
1466 return;
1467 }
1468-
1469- QVariantMap thread = threadForProperties(textChannel->property(History::FieldAccountId).toString(),
1470- History::EventTypeText,
1471- properties,
1472- matchFlagsForChannel(textChannel),
1473- true);
1474 int count = 1;
1475 QList<QVariantMap> attachments;
1476 History::MessageType type = History::MessageTypeText;
1477 QString subject;
1478
1479 if (message.hasNonTextContent()) {
1480- QString normalizedAccountId = QString(QCryptographicHash::hash(thread[History::FieldAccountId].toString().toLatin1(), QCryptographicHash::Md5).toHex());
1481- QString normalizedThreadId = QString(QCryptographicHash::hash(thread[History::FieldThreadId].toString().toLatin1(), QCryptographicHash::Md5).toHex());
1482+ QString normalizedAccountId = QString(QCryptographicHash::hash(accountId.toLatin1(), QCryptographicHash::Md5).toHex());
1483+ QString normalizedThreadId = QString(QCryptographicHash::hash(threadId.toLatin1(), QCryptographicHash::Md5).toHex());
1484 QString normalizedEventId = QString(QCryptographicHash::hash(eventId.toLatin1(), QCryptographicHash::Md5).toHex());
1485 QString mmsStoragePath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation);
1486
1487@@ -1000,8 +1105,8 @@
1488 file.close();
1489
1490 QVariantMap attachment;
1491- attachment[History::FieldAccountId] = thread[History::FieldAccountId];
1492- attachment[History::FieldThreadId] = thread[History::FieldThreadId];
1493+ attachment[History::FieldAccountId] = accountId;
1494+ attachment[History::FieldThreadId] = threadId;
1495 attachment[History::FieldEventId] = eventId;
1496 attachment[History::FieldAttachmentId] = part["identifier"].variant();
1497 attachment[History::FieldContentType] = part["content-type"].variant();
1498@@ -1013,8 +1118,8 @@
1499
1500 QVariantMap event;
1501 event[History::FieldType] = History::EventTypeText;
1502- event[History::FieldAccountId] = thread[History::FieldAccountId];
1503- event[History::FieldThreadId] = thread[History::FieldThreadId];
1504+ event[History::FieldAccountId] = accountId;
1505+ event[History::FieldThreadId] = threadId;
1506 event[History::FieldEventId] = eventId;
1507 event[History::FieldSenderId] = senderId;
1508 event[History::FieldTimestamp] = message.received().toString("yyyy-MM-ddTHH:mm:ss.zzz");
1509@@ -1058,22 +1163,6 @@
1510
1511 }
1512
1513-void HistoryDaemon::onMessageRead(const Tp::TextChannelPtr textChannel, const Tp::ReceivedMessage &message)
1514-{
1515- QVariantMap textEvent = getSingleEventFromTextChannel(textChannel, message.messageToken());
1516- QVariantMap properties = propertiesFromChannel(textChannel);
1517-
1518- if (textEvent.isEmpty()) {
1519- qWarning() << "Cound not find the original event to update with newEvent = false.";
1520- return;
1521- }
1522-
1523- textEvent[History::FieldNewEvent] = false;
1524- if (!writeEvents(QList<QVariantMap>() << textEvent, properties)) {
1525- qWarning() << "Failed to save the new message status!";
1526- }
1527-}
1528-
1529 void HistoryDaemon::onMessageSent(const Tp::TextChannelPtr textChannel, const Tp::Message &message, const QString &messageToken)
1530 {
1531 QVariantMap properties = propertiesFromChannel(textChannel);
1532@@ -1191,7 +1280,7 @@
1533 return reply.value();
1534 }
1535
1536-void HistoryDaemon::writeInformationEvent(const QVariantMap &thread, History::InformationType type, const QString &subject, const QString &sender, const QString &text)
1537+void HistoryDaemon::writeInformationEvent(const QVariantMap &thread, History::InformationType type, const QString &subject, const QString &sender, const QString &text, bool notify)
1538 {
1539 History::TextEvent historyEvent = History::TextEvent(thread[History::FieldAccountId].toString(),
1540 thread[History::FieldThreadId].toString(),
1541@@ -1207,7 +1296,7 @@
1542 QDateTime::currentDateTime(),
1543 subject,
1544 type);
1545- writeEvents(QList<QVariantMap>() << historyEvent.properties(), thread);
1546+ writeEvents(QList<QVariantMap>() << historyEvent.properties(), thread, notify);
1547 }
1548
1549 void HistoryDaemon::writeRoomChangesInformationEvents(const QVariantMap &thread, const QVariantMap &interfaceProperties)
1550@@ -1265,3 +1354,51 @@
1551 }
1552 }
1553 }
1554+
1555+History::MessageStatus HistoryDaemon::fromTelepathyDeliveryStatus(Tp::DeliveryStatus deliveryStatus)
1556+{
1557+ History::MessageStatus status;
1558+ switch (deliveryStatus) {
1559+ case Tp::DeliveryStatusAccepted:
1560+ status = History::MessageStatusAccepted;
1561+ break;
1562+ case Tp::DeliveryStatusDeleted:
1563+ status = History::MessageStatusDeleted;
1564+ break;
1565+ case Tp::DeliveryStatusDelivered:
1566+ status = History::MessageStatusDelivered;
1567+ break;
1568+ case Tp::DeliveryStatusPermanentlyFailed:
1569+ status = History::MessageStatusPermanentlyFailed;
1570+ break;
1571+ case Tp::DeliveryStatusRead:
1572+ status = History::MessageStatusRead;
1573+ break;
1574+ case Tp::DeliveryStatusTemporarilyFailed:
1575+ status = History::MessageStatusTemporarilyFailed;
1576+ break;
1577+ case Tp::DeliveryStatusUnknown:
1578+ status = History::MessageStatusUnknown;
1579+ break;
1580+ }
1581+
1582+ return status;
1583+}
1584+
1585+History::ChatType HistoryDaemon::fromTelepathyHandleType(const Tp::HandleType &type)
1586+{
1587+ History::ChatType chatType;
1588+ switch(type) {
1589+ case Tp::HandleTypeContact:
1590+ chatType = History::ChatTypeContact;
1591+ break;
1592+ case Tp::HandleTypeNone:
1593+ chatType = History::ChatTypeNone;
1594+ break;
1595+ case Tp::HandleTypeRoom:
1596+ chatType = History::ChatTypeRoom;
1597+ break;
1598+ }
1599+
1600+ return chatType;
1601+}
1602
1603=== modified file 'daemon/historydaemon.h'
1604--- daemon/historydaemon.h 2016-10-20 13:56:10 +0000
1605+++ daemon/historydaemon.h 2017-03-23 01:25:43 +0000
1606@@ -1,5 +1,5 @@
1607 /*
1608- * Copyright (C) 2013-2016 Canonical, Ltd.
1609+ * Copyright (C) 2013-2017 Canonical, Ltd.
1610 *
1611 * Authors:
1612 * Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>
1613@@ -42,29 +42,36 @@
1614
1615 static HistoryDaemon *instance();
1616
1617- static QVariantMap propertiesFromChannel(const Tp::ChannelPtr &textChannel);
1618+ QVariantMap propertiesFromChannel(const Tp::ChannelPtr &textChannel);
1619 QVariantMap threadForProperties(const QString &accountId,
1620 History::EventType type,
1621 const QVariantMap &properties,
1622 History::MatchFlags matchFlags = History::MatchCaseSensitive,
1623 bool create = true);
1624+ QString threadIdForProperties(const QString &accountId,
1625+ History::EventType type,
1626+ const QVariantMap &properties,
1627+ History::MatchFlags matchFlags = History::MatchCaseSensitive,
1628+ bool create = true);
1629+ QList<QVariantMap> participantsForThreads(const QList<QVariantMap> &threadIds);
1630 QString queryThreads(int type, const QVariantMap &sort, const QVariantMap &filter, const QVariantMap &properties);
1631 QString queryEvents(int type, const QVariantMap &sort, const QVariantMap &filter);
1632 QVariantMap getSingleThread(int type, const QString &accountId, const QString &threadId, const QVariantMap &properties);
1633 QVariantMap getSingleEvent(int type, const QString &accountId, const QString &threadId, const QString &eventId);
1634 QVariantMap getSingleEventFromTextChannel(const Tp::TextChannelPtr textChannel, const QString &messageId);
1635
1636- bool writeEvents(const QList<QVariantMap> &events, const QVariantMap &properties);
1637+ bool writeEvents(const QList<QVariantMap> &events, const QVariantMap &properties, bool notify = true);
1638 bool removeEvents(const QList<QVariantMap> &events);
1639 bool removeThreads(const QList<QVariantMap> &threads);
1640+ void markThreadsAsRead(const QList<QVariantMap> &threads);
1641
1642 private Q_SLOTS:
1643 void onObserverCreated();
1644- void onCallEnded(const Tp::CallChannelPtr &channel);
1645+ void onCallEnded(const Tp::CallChannelPtr &channel, bool missed);
1646 void onMessageReceived(const Tp::TextChannelPtr textChannel, const Tp::ReceivedMessage &message);
1647- void onMessageRead(const Tp::TextChannelPtr textChannel, const Tp::ReceivedMessage &message);
1648 void onMessageSent(const Tp::TextChannelPtr textChannel, const Tp::Message &message, const QString &messageToken);
1649 void onTextChannelAvailable(const Tp::TextChannelPtr channel);
1650+ void onTextChannelInvalidated(const Tp::TextChannelPtr channel);
1651 void onRoomPropertiesChanged(const QVariantMap &properties,const QStringList &invalidated);
1652 void onGroupMembersChanged(const Tp::Contacts &groupMembersAdded, const Tp::Contacts &groupLocalPendingMembersAdded,
1653 const Tp::Contacts &groupRemotePendingMembersAdded, const Tp::Contacts &groupMembersRemoved,
1654@@ -73,18 +80,21 @@
1655
1656 protected:
1657 History::MatchFlags matchFlagsForChannel(const Tp::ChannelPtr &channel);
1658- void updateRoomParticipants(const Tp::TextChannelPtr channel);
1659- void updateRoomRoles(const Tp::TextChannelPtr &channel, const RolesMap &rolesMap);
1660+ void updateRoomParticipants(const Tp::TextChannelPtr channel, bool notify = true);
1661+ void updateRoomRoles(const Tp::TextChannelPtr &channel, const RolesMap &rolesMap, bool notify = true);
1662 QString hashThread(const QVariantMap &thread);
1663 static QVariantMap getInterfaceProperties(const Tp::AbstractInterface *interface);
1664- void updateRoomProperties(const Tp::TextChannelPtr &channel, const QVariantMap &properties);
1665- void updateRoomProperties(const QString &accountId, const QString &threadId, History::EventType type, const QVariantMap &properties, const QStringList &invalidated);
1666+ void updateRoomProperties(const Tp::TextChannelPtr &channel, const QVariantMap &properties, bool notify = true);
1667+ void updateRoomProperties(const QString &accountId, const QString &threadId, History::EventType type, const QVariantMap &properties, const QStringList &invalidated, bool notify = true);
1668
1669- void writeInformationEvent(const QVariantMap &thread, History::InformationType type, const QString &subject = QString(), const QString &sender = QString("self"), const QString &text = QString());
1670+ void writeInformationEvent(const QVariantMap &thread, History::InformationType type, const QString &subject = QString(), const QString &sender = QString("self"), const QString &text = QString(), bool notify = true);
1671
1672 void writeRoomChangesInformationEvents(const QVariantMap &thread, const QVariantMap &interfaceProperties);
1673 void writeRolesInformationEvents(const QVariantMap &thread, const Tp::ChannelPtr &channel, const RolesMap &rolesMap);
1674 void writeRolesChangesInformationEvents(const QVariantMap &thread, const Tp::ChannelPtr &channel, const RolesMap &rolesMap);
1675+
1676+ static History::MessageStatus fromTelepathyDeliveryStatus(Tp::DeliveryStatus deliveryStatus);
1677+ static History::ChatType fromTelepathyHandleType(const Tp::HandleType &type);
1678 private:
1679 HistoryDaemon(QObject *parent = 0);
1680
1681@@ -93,6 +103,7 @@
1682 QMap<QString, History::MatchFlags> mProtocolFlags;
1683 History::PluginPtr mBackend;
1684 HistoryServiceDBus mDBus;
1685+ QMap<QString, RolesMap> mRolesMap;
1686 };
1687
1688 #endif
1689
1690=== modified file 'daemon/historyservicedbus.cpp'
1691--- daemon/historyservicedbus.cpp 2016-11-24 01:56:01 +0000
1692+++ daemon/historyservicedbus.cpp 2017-03-23 01:25:43 +0000
1693@@ -27,7 +27,7 @@
1694 Q_DECLARE_METATYPE(QList< QVariantMap >)
1695
1696 HistoryServiceDBus::HistoryServiceDBus(QObject *parent) :
1697- QObject(parent), mAdaptor(0)
1698+ QObject(parent), mAdaptor(0), mSignalsTimer(-1)
1699 {
1700 qDBusRegisterMetaType<QList<QVariantMap> >();
1701 }
1702@@ -47,32 +47,46 @@
1703
1704 void HistoryServiceDBus::notifyThreadsAdded(const QList<QVariantMap> &threads)
1705 {
1706- Q_EMIT ThreadsAdded(threads);
1707+ mThreadsAdded << threads;
1708+ triggerSignals();
1709 }
1710
1711 void HistoryServiceDBus::notifyThreadsModified(const QList<QVariantMap> &threads)
1712 {
1713- Q_EMIT ThreadsModified(threads);
1714+ mThreadsModified << threads;
1715+ triggerSignals();
1716 }
1717
1718 void HistoryServiceDBus::notifyThreadsRemoved(const QList<QVariantMap> &threads)
1719 {
1720- Q_EMIT ThreadsRemoved(threads);
1721+ mThreadsRemoved << threads;
1722+ triggerSignals();
1723 }
1724
1725 void HistoryServiceDBus::notifyEventsAdded(const QList<QVariantMap> &events)
1726 {
1727- Q_EMIT EventsAdded(events);
1728+ mEventsAdded << events;
1729+ triggerSignals();
1730 }
1731
1732 void HistoryServiceDBus::notifyEventsModified(const QList<QVariantMap> &events)
1733 {
1734- Q_EMIT EventsModified(events);
1735+ mEventsModified << events;
1736+ triggerSignals();
1737 }
1738
1739 void HistoryServiceDBus::notifyEventsRemoved(const QList<QVariantMap> &events)
1740 {
1741- Q_EMIT EventsRemoved(events);
1742+ mEventsRemoved << events;
1743+ triggerSignals();
1744+}
1745+
1746+void HistoryServiceDBus::notifyThreadParticipantsChanged(const QVariantMap &thread,
1747+ const QList<QVariantMap> &added,
1748+ const QList<QVariantMap> &removed,
1749+ const QList<QVariantMap> &modified)
1750+{
1751+ Q_EMIT ThreadParticipantsChanged(thread, added, removed, modified);
1752 }
1753
1754 QVariantMap HistoryServiceDBus::ThreadForProperties(const QString &accountId,
1755@@ -85,7 +99,12 @@
1756 (History::EventType) type,
1757 properties,
1758 (History::MatchFlags) matchFlags,
1759- create);
1760+ create);
1761+}
1762+
1763+QList<QVariantMap> HistoryServiceDBus::ParticipantsForThreads(const QList<QVariantMap> &threadIds)
1764+{
1765+ return HistoryDaemon::instance()->participantsForThreads(threadIds);
1766 }
1767
1768 QVariantMap HistoryServiceDBus::ThreadForParticipants(const QString &accountId,
1769@@ -114,6 +133,11 @@
1770 return HistoryDaemon::instance()->removeThreads(threads);
1771 }
1772
1773+void HistoryServiceDBus::MarkThreadsAsRead(const QList<QVariantMap> &threads)
1774+{
1775+ return HistoryDaemon::instance()->markThreadsAsRead(threads);
1776+}
1777+
1778 bool HistoryServiceDBus::RemoveEvents(const QList<QVariantMap> &events)
1779 {
1780 return HistoryDaemon::instance()->removeEvents(events);
1781@@ -139,3 +163,73 @@
1782 return HistoryDaemon::instance()->getSingleEvent(type, accountId, threadId, eventId);
1783 }
1784
1785+void HistoryServiceDBus::timerEvent(QTimerEvent *event)
1786+{
1787+ if (event->timerId() == mSignalsTimer) {
1788+ killTimer(mSignalsTimer);
1789+ mSignalsTimer = -1;
1790+ processSignals();
1791+ }
1792+}
1793+
1794+void HistoryServiceDBus::filterDuplicatesAndAdd(QList<QVariantMap> &targetList, const QList<QVariantMap> newItems, const QStringList &propertiesToCompare)
1795+{
1796+ Q_FOREACH (const QVariantMap &item, newItems) {
1797+ Q_FOREACH(const QVariantMap &existing, targetList) {
1798+ bool found = true;
1799+ Q_FOREACH(const QString &prop, propertiesToCompare) {
1800+ if (item[prop] != existing[prop]) {
1801+ found = false;
1802+ break;
1803+ }
1804+ }
1805+
1806+ if (!found) {
1807+ targetList << item;
1808+ }
1809+ }
1810+ }
1811+}
1812+
1813+void HistoryServiceDBus::triggerSignals()
1814+{
1815+ if (mSignalsTimer >= 0) {
1816+ killTimer(mSignalsTimer);
1817+ }
1818+
1819+ mSignalsTimer = startTimer(100);
1820+}
1821+
1822+void HistoryServiceDBus::processSignals()
1823+{
1824+ if (!mThreadsAdded.isEmpty()) {
1825+ Q_EMIT ThreadsAdded(mThreadsAdded);
1826+ mThreadsAdded.clear();
1827+ }
1828+
1829+ if (!mThreadsModified.isEmpty()) {
1830+ Q_EMIT ThreadsModified(mThreadsModified);
1831+ mThreadsModified.clear();
1832+ }
1833+
1834+ if (!mThreadsRemoved.isEmpty()) {
1835+ Q_EMIT ThreadsRemoved(mThreadsRemoved);
1836+ mThreadsRemoved.clear();
1837+ }
1838+
1839+ if (!mEventsAdded.isEmpty()) {
1840+ Q_EMIT EventsAdded(mEventsAdded);
1841+ mEventsAdded.clear();
1842+ }
1843+
1844+ if (!mEventsModified.isEmpty()) {
1845+ Q_EMIT EventsModified(mEventsModified);
1846+ mEventsModified.clear();
1847+ }
1848+
1849+ if (!mEventsRemoved.isEmpty()) {
1850+ Q_EMIT EventsRemoved(mEventsRemoved);
1851+ mEventsRemoved.clear();
1852+ }
1853+}
1854+
1855
1856=== modified file 'daemon/historyservicedbus.h'
1857--- daemon/historyservicedbus.h 2016-06-17 01:49:46 +0000
1858+++ daemon/historyservicedbus.h 2017-03-23 01:25:43 +0000
1859@@ -39,6 +39,10 @@
1860 void notifyThreadsAdded(const QList<QVariantMap> &threads);
1861 void notifyThreadsModified(const QList<QVariantMap> &threads);
1862 void notifyThreadsRemoved(const QList<QVariantMap> &threads);
1863+ void notifyThreadParticipantsChanged(const QVariantMap &thread,
1864+ const QList<QVariantMap> &added,
1865+ const QList<QVariantMap> &removed,
1866+ const QList<QVariantMap> &modified);
1867
1868 void notifyEventsAdded(const QList<QVariantMap> &events);
1869 void notifyEventsModified(const QList<QVariantMap> &events);
1870@@ -55,9 +59,11 @@
1871 const QVariantMap &properties,
1872 int matchFlags,
1873 bool create);
1874+ QList<QVariantMap> ParticipantsForThreads(const QList<QVariantMap> &threadIds);
1875 bool WriteEvents(const QList <QVariantMap> &events);
1876 bool RemoveThreads(const QList <QVariantMap> &threads);
1877 bool RemoveEvents(const QList <QVariantMap> &events);
1878+ void MarkThreadsAsRead(const QList <QVariantMap> &threads);
1879
1880 // views
1881 QString QueryThreads(int type, const QVariantMap &sort, const QVariantMap &filter, const QVariantMap &properties);
1882@@ -70,13 +76,32 @@
1883 void ThreadsAdded(const QList<QVariantMap> &threads);
1884 void ThreadsModified(const QList<QVariantMap> &threads);
1885 void ThreadsRemoved(const QList<QVariantMap> &threads);
1886+ void ThreadParticipantsChanged(const QVariantMap &thread,
1887+ const QList<QVariantMap> &added,
1888+ const QList<QVariantMap> &removed,
1889+ const QList<QVariantMap> &modified);
1890
1891 void EventsAdded(const QList<QVariantMap> &events);
1892 void EventsModified(const QList<QVariantMap> &events);
1893 void EventsRemoved(const QList<QVariantMap> &events);
1894
1895+protected:
1896+ void timerEvent(QTimerEvent *event) override;
1897+
1898+protected Q_SLOTS:
1899+ void filterDuplicatesAndAdd(QList<QVariantMap> &targetList, const QList<QVariantMap> newItems, const QStringList &propertiesToCompare);
1900+ void triggerSignals();
1901+ void processSignals();
1902+
1903 private:
1904 HistoryServiceAdaptor *mAdaptor;
1905+ QList<QVariantMap> mThreadsAdded;
1906+ QList<QVariantMap> mThreadsModified;
1907+ QList<QVariantMap> mThreadsRemoved;
1908+ QList<QVariantMap> mEventsAdded;
1909+ QList<QVariantMap> mEventsModified;
1910+ QList<QVariantMap> mEventsRemoved;
1911+ int mSignalsTimer;
1912 };
1913
1914 #endif // HISTORYSERVICEDBUS_H
1915
1916=== modified file 'daemon/main.cpp'
1917--- daemon/main.cpp 2016-03-30 14:03:29 +0000
1918+++ daemon/main.cpp 2017-03-23 01:25:43 +0000
1919@@ -20,16 +20,26 @@
1920 */
1921
1922 #include "historydaemon.h"
1923+#include <QLockFile>
1924+#include <QDir>
1925
1926 bool checkApplicationRunning()
1927 {
1928- bool result = false;
1929- QDBusReply<bool> reply = QDBusConnection::sessionBus().interface()->isServiceRegistered(History::DBusService);
1930- if (reply.isValid()) {
1931- result = reply.value();
1932+ QString lockPath = qgetenv("HISTORY_LOCK_FILE");
1933+ if (lockPath.isEmpty()) {
1934+ lockPath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation);
1935+ QDir dir(lockPath);
1936+ if (!dir.exists("history-service") && !dir.mkpath("history-service")) {
1937+ qCritical() << "Failed to create dir";
1938+ // in case we fail to create the lock, better not even start the application
1939+ return true;
1940+ }
1941+ dir.cd("history-service");
1942+ lockPath = dir.absoluteFilePath("history-daemon.lock");
1943 }
1944
1945- return result;
1946+ static QLockFile *lockFile = new QLockFile(lockPath);
1947+ return !lockFile->tryLock();
1948 }
1949 int main(int argc, char **argv)
1950 {
1951
1952=== modified file 'daemon/textchannelobserver.cpp'
1953--- daemon/textchannelobserver.cpp 2016-08-26 14:10:25 +0000
1954+++ daemon/textchannelobserver.cpp 2017-03-23 01:25:43 +0000
1955@@ -39,10 +39,6 @@
1956 connect(textChannel.data(),
1957 SIGNAL(messageSent(Tp::Message,Tp::MessageSendingFlags,QString)),
1958 SLOT(onMessageSent(Tp::Message,Tp::MessageSendingFlags,QString)));
1959- connect(textChannel.data(),
1960- SIGNAL(pendingMessageRemoved(const Tp::ReceivedMessage&)),
1961- SLOT(onPendingMessageRemoved(const Tp::ReceivedMessage&)));
1962-
1963 Q_EMIT channelAvailable(textChannel);
1964
1965 // process the messages that are already pending in the channel
1966@@ -57,6 +53,7 @@
1967 {
1968 Tp::TextChannelPtr textChannel(qobject_cast<Tp::TextChannel*>(sender()));
1969 mChannels.removeAll(textChannel);
1970+ Q_EMIT textChannelInvalidated(textChannel);
1971 }
1972
1973 void TextChannelObserver::onMessageReceived(const Tp::ReceivedMessage &message)
1974@@ -82,13 +79,3 @@
1975
1976 Q_EMIT messageSent(textChannel, message, sentMessageToken);
1977 }
1978-
1979-void TextChannelObserver::onPendingMessageRemoved(const Tp::ReceivedMessage &message)
1980-{
1981- Tp::TextChannelPtr textChannel(qobject_cast<Tp::TextChannel*>(sender()));
1982- if (textChannel.isNull()) {
1983- return;
1984- }
1985-
1986- Q_EMIT messageRead(textChannel, message);
1987-}
1988
1989=== modified file 'daemon/textchannelobserver.h'
1990--- daemon/textchannelobserver.h 2016-08-26 14:10:25 +0000
1991+++ daemon/textchannelobserver.h 2017-03-23 01:25:43 +0000
1992@@ -37,8 +37,8 @@
1993
1994 Q_SIGNALS:
1995 void channelAvailable(const Tp::TextChannelPtr textChannel);
1996+ void textChannelInvalidated(const Tp::TextChannelPtr textChannel);
1997 void messageReceived(const Tp::TextChannelPtr textChannel, const Tp::ReceivedMessage &message);
1998- void messageRead(const Tp::TextChannelPtr textChannel, const Tp::ReceivedMessage &message);
1999 void messageSent(const Tp::TextChannelPtr textChannel, const Tp::Message &message, const QString &messageToken);
2000
2001 protected:
2002@@ -49,7 +49,6 @@
2003 void onTextChannelInvalidated();
2004 void onMessageReceived(const Tp::ReceivedMessage &message);
2005 void onMessageSent(const Tp::Message &message, Tp::MessageSendingFlags flags, const QString &sentMessageToken);
2006- void onPendingMessageRemoved(const Tp::ReceivedMessage &message);
2007
2008 private:
2009 QList<Tp::TextChannelPtr> mChannels;
2010
2011=== added file 'plugins/sqlite/schema/v18.sql'
2012--- plugins/sqlite/schema/v18.sql 1970-01-01 00:00:00 +0000
2013+++ plugins/sqlite/schema/v18.sql 2017-03-23 01:25:43 +0000
2014@@ -0,0 +1,14 @@
2015+CREATE TRIGGER text_threads_delete_trigger AFTER DELETE ON threads
2016+FOR EACH ROW WHEN old.type=0
2017+BEGIN
2018+ DELETE FROM text_events WHERE
2019+ accountId=old.accountId AND
2020+ threadId=old.threadId;
2021+END;
2022+CREATE TRIGGER voice_threads_delete_trigger AFTER DELETE ON threads
2023+FOR EACH ROW WHEN old.type=1
2024+BEGIN
2025+ DELETE FROM voice_events WHERE
2026+ accountId=old.accountId AND
2027+ threadId=old.threadId;
2028+END;
2029
2030=== modified file 'plugins/sqlite/sqlitedatabase.cpp'
2031--- plugins/sqlite/sqlitedatabase.cpp 2016-11-08 19:43:53 +0000
2032+++ plugins/sqlite/sqlitedatabase.cpp 2017-03-23 01:25:43 +0000
2033@@ -183,6 +183,12 @@
2034 return true;
2035 }
2036
2037+
2038+void trace(void *something, const char *query)
2039+{
2040+ qDebug() << "SQLITE TRACE:" << query;
2041+}
2042+
2043 bool SQLiteDatabase::createOrUpdateDatabase()
2044 {
2045 bool create = !QFile(mDatabasePath).exists();
2046@@ -199,6 +205,10 @@
2047 // and also create the normalizeId function
2048 sqlite3_create_function(handle, "normalizeId", 2, SQLITE_ANY, NULL, &normalizeId, NULL, NULL);
2049
2050+#ifdef TRACE_SQLITE
2051+ sqlite3_trace(handle, &trace, NULL);
2052+#endif
2053+
2054 parseVersionInfo();
2055
2056 QSqlQuery query(mDatabase);
2057
2058=== modified file 'plugins/sqlite/sqlitehistoryeventview.cpp'
2059--- plugins/sqlite/sqlitehistoryeventview.cpp 2015-01-28 23:08:01 +0000
2060+++ plugins/sqlite/sqlitehistoryeventview.cpp 2017-03-23 01:25:43 +0000
2061@@ -42,7 +42,14 @@
2062 QString condition = mPlugin->filterToString(filter, filterValues);
2063 QString order;
2064 if (!sort.sortField().isNull()) {
2065- order = QString("ORDER BY %1 %2").arg(sort.sortField(), sort.sortOrder() == Qt::AscendingOrder ? "ASC" : "DESC");
2066+ // WORKAROUND: Supports multiple fields by split it using ','
2067+ Q_FOREACH(const QString& field, sort.sortField().split(",")) {
2068+ order += QString("%1 %2, ")
2069+ .arg(field.trimmed())
2070+ .arg(sort.sortOrder() == Qt::AscendingOrder ? "ASC" : "DESC");
2071+ }
2072+
2073+ order = QString("ORDER BY %1").arg(order.mid(0, order.lastIndexOf(",")));
2074 // FIXME: check case sensitiviy
2075 }
2076
2077
2078=== modified file 'plugins/sqlite/sqlitehistoryplugin.cpp'
2079--- plugins/sqlite/sqlitehistoryplugin.cpp 2016-11-24 01:50:48 +0000
2080+++ plugins/sqlite/sqlitehistoryplugin.cpp 2017-03-23 01:25:43 +0000
2081@@ -306,6 +306,54 @@
2082 return new SQLiteHistoryEventView(this, type, sort, filter);
2083 }
2084
2085+QVariantMap SQLiteHistoryPlugin::markThreadAsRead(const QVariantMap &thread)
2086+{
2087+ QSqlQuery query(SQLiteDatabase::instance()->database());
2088+
2089+ if (thread[History::FieldAccountId].toString().isEmpty() ||
2090+ thread[History::FieldThreadId].toString().isEmpty()) {
2091+ return QVariantMap();
2092+ }
2093+
2094+ // first check if the thread actually has anything to change
2095+ query.prepare("SELECT unreadCount from threads WHERE accountId=:accountId AND threadId=:threadId AND type=:type");
2096+ query.bindValue(":accountId", thread[History::FieldAccountId].toString());
2097+ query.bindValue(":threadId", thread[History::FieldThreadId].toString());
2098+ query.bindValue(":type", (uint)History::EventTypeText);
2099+ if (!query.exec() || !query.next()) {
2100+ qCritical() << "Failed to verify the unread messages of the thread. Error:" << query.lastError();
2101+ return QVariantMap();
2102+ }
2103+
2104+
2105+ int unreadCount = query.value(0).toUInt();
2106+ if (unreadCount == 0) {
2107+ // no messages to ack, so no need to update anything
2108+ return QVariantMap();
2109+ }
2110+
2111+ query.prepare("UPDATE text_events SET newEvent=:newEvent WHERE accountId=:accountId AND threadId=:threadId AND newEvent=1");
2112+ query.bindValue(":accountId", thread[History::FieldAccountId].toString());
2113+ query.bindValue(":threadId", thread[History::FieldThreadId].toString());
2114+ query.bindValue(":newEvent", false);
2115+
2116+ if (!query.exec()) {
2117+ qCritical() << "Failed to mark thread as read: Error:" << query.lastError();
2118+ return QVariantMap();
2119+ }
2120+
2121+ QVariantMap existingThread = getSingleThread((History::EventType) thread[History::FieldType].toInt(),
2122+ thread[History::FieldAccountId].toString(),
2123+ thread[History::FieldThreadId].toString(),
2124+ QVariantMap());
2125+ if (!existingThread.isEmpty()) {
2126+ addThreadsToCache(QList<QVariantMap>() << existingThread);
2127+ return existingThread;
2128+ }
2129+
2130+ return QVariantMap();
2131+}
2132+
2133 QVariantMap SQLiteHistoryPlugin::threadForProperties(const QString &accountId,
2134 History::EventType type,
2135 const QVariantMap &properties,
2136@@ -315,8 +363,6 @@
2137 return QVariantMap();
2138 }
2139
2140- QSqlQuery query(SQLiteDatabase::instance()->database());
2141-
2142 History::ChatType chatType = (History::ChatType)properties[History::FieldChatType].toUInt();
2143
2144 if (chatType == History::ChatTypeRoom) {
2145@@ -332,6 +378,62 @@
2146 return threadForParticipants(accountId, type, participants.identifiers(), matchFlags);
2147 }
2148
2149+QString SQLiteHistoryPlugin::threadIdForProperties(const QString &accountId, History::EventType type, const QVariantMap &properties, History::MatchFlags matchFlags)
2150+{
2151+ if (properties.isEmpty()) {
2152+ return QString::null;
2153+ }
2154+
2155+ // if chat type is room, just get the threadId directly
2156+ History::ChatType chatType = (History::ChatType)properties[History::FieldChatType].toUInt();
2157+ if (chatType == History::ChatTypeRoom) {
2158+ QString threadId = properties[History::FieldThreadId].toString();
2159+ return threadId;
2160+ }
2161+
2162+ // if chat type is anything else, fallback to returning the threadId from the participants list
2163+ History::Participants participants = History::Participants::fromVariant(properties[History::FieldParticipantIds]);
2164+ return threadForParticipants(accountId, type, participants.identifiers(), matchFlags)[History::FieldThreadId].toString();
2165+}
2166+
2167+QList<QVariantMap> SQLiteHistoryPlugin::participantsForThreads(const QList<QVariantMap> &threadIds)
2168+{
2169+ QList<QVariantMap> results;
2170+ Q_FOREACH(const QVariantMap &thread, threadIds) {
2171+ QString accountId = thread[History::FieldAccountId].toString();
2172+ QString threadId = thread[History::FieldThreadId].toString();
2173+ History::EventType type = (History::EventType)thread[History::FieldType].toUInt();
2174+ QVariantMap result = thread;
2175+
2176+ QSqlQuery query;
2177+ query.prepare("SELECT normalizedId, alias, state, roles FROM thread_participants "
2178+ "WHERE accountId=:accountId AND threadId=:threadId AND type=:type");
2179+ query.bindValue(":accountId", accountId);
2180+ query.bindValue(":threadId", threadId);
2181+ query.bindValue(":type", type);
2182+ QVariantList participants;
2183+ if (!query.exec()) {
2184+ qWarning() << "Failed to retrieve participants. Error:" << query.lastError().text() << query.lastQuery();
2185+ results << result;
2186+ continue;
2187+ }
2188+
2189+ while (query.next()) {
2190+ QVariantMap participant;
2191+ QString identifier = query.value(0).toString();
2192+ participant[History::FieldIdentifier] = identifier;
2193+ participant[History::FieldAlias] = query.value(1);
2194+ participant[History::FieldParticipantState] = query.value(2);
2195+ participant[History::FieldParticipantRoles] = query.value(3);
2196+ participants << History::ContactMatcher::instance()->contactInfo(accountId, identifier, true, participant);
2197+ }
2198+
2199+ result[History::FieldParticipants] = participants;
2200+ results << result;
2201+ }
2202+ return results;
2203+}
2204+
2205 QVariantMap SQLiteHistoryPlugin::threadForParticipants(const QString &accountId,
2206 History::EventType type,
2207 const QStringList &participants,
2208@@ -1093,22 +1195,6 @@
2209 << "threads.unreadCount"
2210 << "threads.lastEventTimestamp";
2211
2212- // get the participants in the query already
2213- fields << "(SELECT group_concat(thread_participants.participantId, \"|,|\") "
2214- "FROM thread_participants WHERE thread_participants.accountId=threads.accountId "
2215- "AND thread_participants.threadId=threads.threadId "
2216- "AND thread_participants.type=threads.type GROUP BY accountId,threadId,type) as participants";
2217-
2218- fields << "(SELECT group_concat(thread_participants.state, \"|,|\") "
2219- "FROM thread_participants WHERE thread_participants.accountId=threads.accountId "
2220- "AND thread_participants.threadId=threads.threadId "
2221- "AND thread_participants.type=threads.type GROUP BY accountId,threadId,type) as state";
2222-
2223- fields << "(SELECT group_concat(thread_participants.roles, \"|,|\") "
2224- "FROM thread_participants WHERE thread_participants.accountId=threads.accountId "
2225- "AND thread_participants.threadId=threads.threadId "
2226- "AND thread_participants.type=threads.type GROUP BY accountId,threadId,type) as roles";
2227-
2228 QStringList extraFields;
2229 QString table;
2230
2231@@ -1136,6 +1222,7 @@
2232 QList<QVariantMap> SQLiteHistoryPlugin::parseThreadResults(History::EventType type, QSqlQuery &query, const QVariantMap &properties)
2233 {
2234 QList<QVariantMap> threads;
2235+ QList<QVariantMap> threadsWithoutParticipants;
2236 QSqlQuery attachmentsQuery(SQLiteDatabase::instance()->database());
2237 QList<QVariantMap> attachments;
2238 bool grouped = false;
2239@@ -1170,31 +1257,11 @@
2240 thread[History::FieldEventId] = query.value(2);
2241 thread[History::FieldCount] = query.value(3);
2242 thread[History::FieldUnreadCount] = query.value(4);
2243- QStringList participants = query.value(6).toString().split("|,|", QString::SkipEmptyParts);
2244- QList<int> participantStatus;
2245- QStringList participantStatusString = query.value(7).toString().split("|,|", QString::SkipEmptyParts);
2246- Q_FOREACH(const QString &statusString, participantStatusString) {
2247- participantStatus << statusString.toUInt();
2248- }
2249- QStringList participantRolesString = query.value(8).toString().split("|,|", QString::SkipEmptyParts);
2250- QList<int> participantRoles;
2251- Q_FOREACH(const QString &rolesString, participantRolesString) {
2252- participantRoles << rolesString.toUInt();
2253- }
2254- QVariantList contactList;
2255- QVariantList contactInfo = History::ContactMatcher::instance()->contactInfo(accountId, participants, true);
2256- for (int i = 0; i < contactInfo.count(); ++i) {
2257- QVariantMap map = contactInfo[i].toMap();
2258- map["state"] = participantStatus[i];
2259- map["roles"] = participantRoles[i];
2260- contactList << map;
2261- }
2262- thread[History::FieldParticipants] = contactList;
2263
2264 // the generic event fields
2265- thread[History::FieldSenderId] = query.value(9);
2266+ thread[History::FieldSenderId] = query.value(6);
2267 thread[History::FieldTimestamp] = toLocalTimeString(query.value(5).toDateTime());
2268- thread[History::FieldNewEvent] = query.value(10).toBool();
2269+ thread[History::FieldNewEvent] = query.value(7).toBool();
2270
2271 // the next step is to get the last event
2272 switch (type) {
2273@@ -1225,13 +1292,13 @@
2274 thread[History::FieldAttachments] = QVariant::fromValue(attachments);
2275 attachments.clear();
2276 }
2277- thread[History::FieldMessage] = query.value(11);
2278- thread[History::FieldMessageType] = query.value(12);
2279- thread[History::FieldMessageStatus] = query.value(13);
2280- thread[History::FieldReadTimestamp] = toLocalTimeString(query.value(14).toDateTime());
2281- thread[History::FieldChatType] = query.value(15).toUInt();
2282+ thread[History::FieldMessage] = query.value(8);
2283+ thread[History::FieldMessageType] = query.value(9);
2284+ thread[History::FieldMessageStatus] = query.value(10);
2285+ thread[History::FieldReadTimestamp] = toLocalTimeString(query.value(11).toDateTime());
2286+ thread[History::FieldChatType] = query.value(12).toUInt();
2287
2288- if (thread[History::FieldChatType].toInt() == 2) {
2289+ if (thread[History::FieldChatType].toInt() == History::ChatTypeRoom) {
2290 QVariantMap chatRoomInfo;
2291 QSqlQuery query1(SQLiteDatabase::instance()->database());
2292
2293@@ -1291,15 +1358,28 @@
2294
2295 thread[History::FieldChatRoomInfo] = chatRoomInfo;
2296 }
2297+ if (!History::Utils::shouldIncludeParticipants(History::Thread::fromProperties(thread))) {
2298+ thread.remove(History::FieldParticipants);
2299+ threadsWithoutParticipants << thread;
2300+ } else {
2301+ threads << thread;
2302+ }
2303 break;
2304 case History::EventTypeVoice:
2305- thread[History::FieldMissed] = query.value(12);
2306- thread[History::FieldDuration] = query.value(11);
2307- thread[History::FieldRemoteParticipant] = History::ContactMatcher::instance()->contactInfo(accountId, query.value(13).toString(), true);
2308+ thread[History::FieldMissed] = query.value(9);
2309+ thread[History::FieldDuration] = query.value(8);
2310+ thread[History::FieldRemoteParticipant] = History::ContactMatcher::instance()->contactInfo(accountId, query.value(10).toString(), true);
2311+ threads << thread;
2312 break;
2313 }
2314- threads << thread;
2315 }
2316+
2317+ // get the participants
2318+ threads = participantsForThreads(threads);
2319+
2320+ // and append the threads with no participants
2321+ threads << threadsWithoutParticipants;
2322+
2323 return threads;
2324 }
2325
2326@@ -1317,7 +1397,8 @@
2327 QString queryText;
2328 switch (type) {
2329 case History::EventTypeText:
2330- participantsField = participantsField.arg("text_events", QString::number(type));
2331+ // for text events we don't need the participants at all
2332+ participantsField = "\"\" as participants";
2333 queryText = QString("SELECT accountId, threadId, eventId, senderId, timestamp, newEvent, %1, "
2334 "message, messageType, messageStatus, readTimestamp, subject, informationType FROM text_events %2 %3").arg(participantsField, modifiedCondition, order);
2335 break;
2336@@ -1353,8 +1434,10 @@
2337 event[History::FieldSenderId] = query.value(3);
2338 event[History::FieldTimestamp] = toLocalTimeString(query.value(4).toDateTime());
2339 event[History::FieldNewEvent] = query.value(5).toBool();
2340- QStringList participants = query.value(6).toString().split("|,|");
2341- event[History::FieldParticipants] = History::ContactMatcher::instance()->contactInfo(accountId, participants, true);
2342+ if (type != History::EventTypeText) {
2343+ QStringList participants = query.value(6).toString().split("|,|");
2344+ event[History::FieldParticipants] = History::ContactMatcher::instance()->contactInfo(accountId, participants, true);
2345+ }
2346
2347 switch (type) {
2348 case History::EventTypeText:
2349
2350=== modified file 'plugins/sqlite/sqlitehistoryplugin.h'
2351--- plugins/sqlite/sqlitehistoryplugin.h 2016-09-21 17:44:39 +0000
2352+++ plugins/sqlite/sqlitehistoryplugin.h 2017-03-23 01:25:43 +0000
2353@@ -59,7 +59,11 @@
2354 History::EventType type,
2355 const QVariantMap &properties,
2356 History::MatchFlags matchFlags = History::MatchCaseSensitive);
2357-
2358+ QString threadIdForProperties(const QString &accountId,
2359+ History::EventType type,
2360+ const QVariantMap &properties,
2361+ History::MatchFlags matchFlags = History::MatchCaseSensitive) override;
2362+ QList<QVariantMap> participantsForThreads(const QList<QVariantMap> &threadIds) override;
2363 QList<QVariantMap> eventsForThread(const QVariantMap &thread);
2364
2365 QVariantMap getSingleThread(History::EventType type, const QString &accountId, const QString &threadId, const QVariantMap &properties = QVariantMap());
2366@@ -73,6 +77,7 @@
2367 bool updateRoomParticipantsRoles(const QString &accountId, const QString &threadId, History::EventType type, const QVariantMap &participantsRoles);
2368 bool updateRoomInfo(const QString &accountId, const QString &threadId, History::EventType type, const QVariantMap &properties, const QStringList &invalidated = QStringList());
2369 bool removeThread(const QVariantMap &thread);
2370+ QVariantMap markThreadAsRead(const QVariantMap &thread);
2371
2372 History::EventWriteResult writeTextEvent(const QVariantMap &event);
2373 bool removeTextEvent(const QVariantMap &event);
2374
2375=== modified file 'plugins/sqlite/sqlitehistorythreadview.cpp'
2376--- plugins/sqlite/sqlitehistorythreadview.cpp 2016-11-24 01:56:01 +0000
2377+++ plugins/sqlite/sqlitehistorythreadview.cpp 2017-03-23 01:25:43 +0000
2378@@ -43,7 +43,14 @@
2379 QString condition = mPlugin->filterToString(filter, filterValues);
2380 QString order;
2381 if (!sort.sortField().isNull()) {
2382- order = QString("ORDER BY %1 %2").arg(sort.sortField(), sort.sortOrder() == Qt::AscendingOrder ? "ASC" : "DESC");
2383+ // WORKAROUND: Supports multiple fields by split it using ','
2384+ Q_FOREACH(const QString& field, sort.sortField().split(",")) {
2385+ order += QString("%1 %2, ")
2386+ .arg(field.trimmed())
2387+ .arg(sort.sortOrder() == Qt::AscendingOrder ? "ASC" : "DESC");
2388+ }
2389+
2390+ order = QString("ORDER BY %1").arg(order.mid(0, order.lastIndexOf(",")));
2391 // FIXME: check case sensitiviy
2392 }
2393
2394
2395=== modified file 'src/contactmatcher.cpp'
2396--- src/contactmatcher.cpp 2016-06-17 01:49:46 +0000
2397+++ src/contactmatcher.cpp 2017-03-23 01:25:43 +0000
2398@@ -99,21 +99,22 @@
2399 {
2400 InternalContactMap &internalMap = mContactMap[accountId];
2401
2402+
2403+ QString normalizedId = normalizeId(identifier);
2404+
2405+ QVariantMap map;
2406 // first do a simple string match on the map
2407- if (internalMap.contains(identifier)) {
2408- return internalMap[identifier];
2409- }
2410-
2411- QVariantMap map;
2412-
2413- // and if there was no match, asynchronously request the info, and return an empty map for now
2414- if (History::TelepathyHelper::instance()->ready()) {
2415- map = requestContactInfo(accountId, identifier, synchronous);
2416+ if (internalMap.contains(normalizedId)) {
2417+ map = internalMap[normalizedId];
2418+ } else if (History::TelepathyHelper::instance()->ready()) {
2419+ // and if there was no match, asynchronously request the info, and return an empty map for now
2420+ map = requestContactInfo(accountId, normalizedId, synchronous);
2421 } else if (!synchronous) {
2422- RequestInfo info{accountId, identifier};
2423+ RequestInfo info{accountId, normalizedId};
2424 mPendingRequests.append(info);
2425 }
2426- map[History::FieldIdentifier] = identifier;
2427+
2428+ map[History::FieldIdentifier] = normalizedId;
2429 map[History::FieldAccountId] = accountId;
2430
2431 QMapIterator<QString, QVariant> i(properties);
2432@@ -124,7 +125,7 @@
2433 }
2434 }
2435
2436- mContactMap[accountId][identifier] = map;
2437+ mContactMap[accountId][normalizedId] = map;
2438 return map;
2439 }
2440
2441@@ -309,10 +310,17 @@
2442 */
2443 QVariantMap ContactMatcher::requestContactInfo(const QString &accountId, const QString &identifier, bool synchronous)
2444 {
2445+ QString normalizedId = normalizeId(identifier);
2446 QStringList addressableVCardFields = addressableFields(accountId);
2447+
2448+ QVariantMap contactInfo;
2449+ contactInfo[History::FieldIdentifier] = identifier;
2450+ contactInfo[History::FieldAccountId] = accountId;
2451+
2452 if (addressableVCardFields.isEmpty()) {
2453+ mContactMap[accountId][identifier] = contactInfo;
2454 // FIXME: add support for generic accounts
2455- return QVariantMap();
2456+ return contactInfo;
2457 }
2458
2459 bool phoneCompare = addressableVCardFields.contains("tel");
2460@@ -328,7 +336,7 @@
2461 QContactUnionFilter topLevelFilter;
2462 Q_FOREACH(const QString &field, addressableVCardFields) {
2463 if (field == "tel") {
2464- topLevelFilter.append(QContactPhoneNumber::match(identifier));
2465+ topLevelFilter.append(QContactPhoneNumber::match(normalizedId));
2466 } else {
2467 // FIXME: handle more fields
2468 // rely on a generic field filter
2469@@ -340,7 +348,7 @@
2470 QContactDetailFilter valueFilter = QContactDetailFilter();
2471 valueFilter.setDetailType(QContactExtendedDetail::Type, QContactExtendedDetail::FieldData);
2472 valueFilter.setMatchFlags(QContactFilter::MatchExactly);
2473- valueFilter.setValue(identifier);
2474+ valueFilter.setValue(normalizedId);
2475
2476 QContactIntersectionFilter intersectionFilter;
2477 intersectionFilter.append(nameFilter);
2478@@ -353,10 +361,11 @@
2479 if (synchronous) {
2480 QList<QContact> contacts = mManager->contacts(topLevelFilter, QList<QContactSortOrder>(), hint);
2481 if (contacts.isEmpty()) {
2482- return QVariantMap();
2483+ mContactMap[accountId][identifier] = contactInfo;
2484+ return contactInfo;
2485 }
2486 // for synchronous requests, return the results right away.
2487- return matchAndUpdate(accountId, identifier, contacts.first());
2488+ return matchAndUpdate(accountId, normalizedId, contacts.first());
2489 } else {
2490 // check if there is a request already going on for the given contact
2491 Q_FOREACH(const RequestInfo &info, mRequests.values()) {
2492@@ -365,7 +374,7 @@
2493 continue;
2494 }
2495
2496- if (info.identifier == identifier) {
2497+ if (info.identifier == normalizedId) {
2498 // if so, just wait for it to finish
2499 return QVariantMap();
2500 }
2501@@ -381,7 +390,7 @@
2502
2503 RequestInfo info;
2504 info.accountId = accountId;
2505- info.identifier = identifier;
2506+ info.identifier = normalizedId;
2507 mRequests[request] = info;
2508 request->start();
2509 }
2510@@ -414,7 +423,6 @@
2511 QStringList fields = addressableFields(accountId);
2512 bool match = false;
2513
2514- int fieldsCount = fields.count();
2515 Q_FOREACH(const QString &field, fields) {
2516 if (field == "tel") {
2517 QList<QContactDetail> details = contact.details(QContactDetail::TypePhoneNumber);
2518@@ -463,12 +471,26 @@
2519 return mAddressableFields[accountId];
2520 }
2521
2522+ // FIXME: hardcoding account IDs here is not a good idea, we have to fix addressable fields on
2523+ // the protocols themselves
2524+ if (accountId.startsWith("irc/irc")) {
2525+ QStringList empty;
2526+ mAddressableFields[accountId] = empty;
2527+ return empty;
2528+ }
2529+
2530 Tp::AccountPtr account = History::TelepathyHelper::instance()->accountForId(accountId);
2531 QStringList fields;
2532 if (!account.isNull()) {
2533 fields = account->protocolInfo().addressableVCardFields();
2534- mAddressableFields[accountId] = fields;
2535- }
2536+ }
2537+
2538+ // fallback to phone number matching in case everything else fails
2539+ if (fields.isEmpty()) {
2540+ fields << "tel";
2541+ }
2542+
2543+ mAddressableFields[accountId] = fields;
2544
2545 return fields;
2546 }
2547@@ -478,4 +500,17 @@
2548 return (map.contains(History::FieldContactId) && !map[History::FieldContactId].toString().isEmpty());
2549 }
2550
2551+QString ContactMatcher::normalizeId(const QString &id)
2552+{
2553+ QString normalizedId = id;
2554+
2555+ // FIXME: this is a hack so that SIP URIs get converted into phone numbers for contact matching
2556+ if (normalizedId.startsWith("sip:")) {
2557+ normalizedId.remove("sip:").remove(QRegularExpression("@.*$"));
2558+ }
2559+
2560+ return normalizedId;
2561+}
2562+
2563+
2564 }
2565
2566=== modified file 'src/contactmatcher_p.h'
2567--- src/contactmatcher_p.h 2016-06-17 01:49:46 +0000
2568+++ src/contactmatcher_p.h 2017-03-23 01:25:43 +0000
2569@@ -51,6 +51,8 @@
2570 // this will only watch for contact changes affecting the identifier, but won't fetch contact info
2571 void watchIdentifier(const QString &accountId, const QString &identifier, const QVariantMap &currentInfo = QVariantMap());
2572
2573+ static QString normalizeId(const QString &id);
2574+
2575 Q_SIGNALS:
2576 void contactInfoChanged(const QString &acountId, const QString &identifier, const QVariantMap &contactInfo);
2577
2578
2579=== modified file 'src/eventview.cpp'
2580--- src/eventview.cpp 2015-10-01 19:44:45 +0000
2581+++ src/eventview.cpp 2017-03-23 01:25:43 +0000
2582@@ -1,5 +1,5 @@
2583 /*
2584- * Copyright (C) 2013 Canonical, Ltd.
2585+ * Copyright (C) 2013-2017 Canonical, Ltd.
2586 *
2587 * Authors:
2588 * Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>
2589@@ -53,7 +53,7 @@
2590 }
2591
2592 if (filterNull || filter.match(event.properties())) {
2593- filtered << events;
2594+ filtered << event;
2595 }
2596 }
2597
2598@@ -128,6 +128,10 @@
2599 connect(Manager::instance(),
2600 SIGNAL(eventsRemoved(History::Events)),
2601 SLOT(_d_eventsRemoved(History::Events)));
2602+ // we don't filter thread signals
2603+ connect(Manager::instance(),
2604+ SIGNAL(threadsRemoved(History::Threads)),
2605+ SIGNAL(threadsRemoved(History::Threads)));
2606 }
2607
2608 EventView::~EventView()
2609
2610=== modified file 'src/eventview.h'
2611--- src/eventview.h 2013-09-17 23:05:35 +0000
2612+++ src/eventview.h 2017-03-23 01:25:43 +0000
2613@@ -1,5 +1,5 @@
2614 /*
2615- * Copyright (C) 2013 Canonical, Ltd.
2616+ * Copyright (C) 2013-2017 Canonical, Ltd.
2617 *
2618 * Authors:
2619 * Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>
2620@@ -24,6 +24,7 @@
2621
2622 #include "types.h"
2623 #include "event.h"
2624+#include "thread.h"
2625 #include "filter.h"
2626 #include "sort.h"
2627 #include <QObject>
2628@@ -50,6 +51,7 @@
2629 void eventsAdded(const History::Events &events);
2630 void eventsModified(const History::Events &events);
2631 void eventsRemoved(const History::Events &events);
2632+ void threadsRemoved(const History::Threads &threads);
2633 void invalidated();
2634
2635 private:
2636
2637=== modified file 'src/manager.cpp'
2638--- src/manager.cpp 2016-06-17 01:49:46 +0000
2639+++ src/manager.cpp 2017-03-23 01:25:43 +0000
2640@@ -65,6 +65,9 @@
2641 SIGNAL(threadsRemoved(History::Threads)),
2642 SIGNAL(threadsRemoved(History::Threads)));
2643 connect(d->dbus.data(),
2644+ SIGNAL(threadParticipantsChanged(History::Thread, History::Participants, History::Participants, History::Participants)),
2645+ SIGNAL(threadParticipantsChanged(History::Thread, History::Participants, History::Participants, History::Participants)));
2646+ connect(d->dbus.data(),
2647 SIGNAL(eventsAdded(History::Events)),
2648 SIGNAL(eventsAdded(History::Events)));
2649 connect(d->dbus.data(),
2650@@ -104,6 +107,13 @@
2651 return self;
2652 }
2653
2654+void Manager::markThreadsAsRead(const History::Threads &threads)
2655+{
2656+ Q_D(Manager);
2657+
2658+ d->dbus->markThreadsAsRead(threads);
2659+}
2660+
2661 ThreadViewPtr Manager::queryThreads(EventType type,
2662 const Sort &sort,
2663 const Filter &filter,
2664@@ -154,6 +164,20 @@
2665 return d->dbus->threadForProperties(accountId, type, properties, matchFlags, create);
2666 }
2667
2668+/**
2669+ * @brief Request the list of participants of the given threads to the service
2670+ * @param threads The threads to be filled
2671+ *
2672+ * This is an asychronous request. When finished, the signal @ref threadParticipantsChanged
2673+ * will be emitted for the given threads.
2674+ */
2675+void Manager::requestThreadParticipants(const Threads &threads)
2676+{
2677+ Q_D(Manager);
2678+
2679+ d->dbus->requestThreadParticipants(threads);
2680+}
2681+
2682 Thread Manager::getSingleThread(EventType type, const QString &accountId, const QString &threadId, const QVariantMap &properties)
2683 {
2684 Q_D(Manager);
2685
2686=== modified file 'src/manager.h'
2687--- src/manager.h 2016-06-17 01:49:46 +0000
2688+++ src/manager.h 2017-03-23 01:25:43 +0000
2689@@ -66,19 +66,22 @@
2690 const QVariantMap &properties,
2691 History::MatchFlags matchFlags = History::MatchCaseSensitive,
2692 bool create = false);
2693-
2694+ void requestThreadParticipants(const History::Threads &threads);
2695 Thread getSingleThread(EventType type, const QString &accountId, const QString &threadId, const QVariantMap &properties = QVariantMap());
2696
2697 bool writeEvents(const History::Events &events);
2698 bool removeThreads(const Threads &threads);
2699 bool removeEvents(const Events &events);
2700
2701+ void markThreadsAsRead(const History::Threads &thread);
2702+
2703 bool isServiceRunning() const;
2704
2705 Q_SIGNALS:
2706 void threadsAdded(const History::Threads &threads);
2707 void threadsModified(const History::Threads &threads);
2708 void threadsRemoved(const History::Threads &threads);
2709+ void threadParticipantsChanged(const History::Thread &thread, const History::Participants &added, const History::Participants &removed, const History::Participants &modified);
2710
2711 void eventsAdded(const History::Events &events);
2712 void eventsModified(const History::Events &events);
2713
2714=== modified file 'src/managerdbus.cpp'
2715--- src/managerdbus.cpp 2016-06-17 01:49:46 +0000
2716+++ src/managerdbus.cpp 2017-03-23 01:25:43 +0000
2717@@ -28,6 +28,8 @@
2718 #include <QDBusReply>
2719 #include <QDBusMetaType>
2720
2721+#include <QDebug>
2722+
2723 Q_DECLARE_METATYPE(QList< QVariantMap >)
2724
2725 namespace History
2726@@ -50,6 +52,12 @@
2727 connection.connect(DBusService, DBusObjectPath, DBusInterface, "ThreadsRemoved",
2728 this, SLOT(onThreadsRemoved(QList<QVariantMap>)));
2729
2730+ connection.connect(DBusService, DBusObjectPath, DBusInterface, "ThreadParticipantsChanged",
2731+ this, SLOT(onThreadParticipantsChanged(QVariantMap,
2732+ QList<QVariantMap>,
2733+ QList<QVariantMap>,
2734+ QList<QVariantMap>)));
2735+
2736 connection.connect(DBusService, DBusObjectPath, DBusInterface, "EventsAdded",
2737 this, SLOT(onEventsAdded(QList<QVariantMap>)));
2738 connection.connect(DBusService, DBusObjectPath, DBusInterface, "EventsModified",
2739@@ -70,6 +78,16 @@
2740 return threadForProperties(accountId, type, properties, matchFlags, create);
2741 }
2742
2743+void ManagerDBus::markThreadsAsRead(const History::Threads &threads)
2744+{
2745+ QList<QVariantMap> threadMap = threadsToProperties(threads);
2746+ if (threadMap.isEmpty()) {
2747+ return;
2748+ }
2749+
2750+ mInterface.asyncCall("MarkThreadsAsRead", QVariant::fromValue(threadMap));
2751+}
2752+
2753 Thread ManagerDBus::threadForProperties(const QString &accountId,
2754 EventType type,
2755 const QVariantMap &properties,
2756@@ -87,6 +105,29 @@
2757 return thread;
2758 }
2759
2760+void ManagerDBus::requestThreadParticipants(const Threads &threads)
2761+{
2762+ QList<QVariantMap> ids;
2763+ Q_FOREACH(const Thread &thread, threads) {
2764+ QVariantMap id;
2765+ id[History::FieldAccountId] = thread.accountId();
2766+ id[History::FieldThreadId] = thread.threadId();
2767+ id[History::FieldType] = thread.type();
2768+ ids << id;
2769+ }
2770+
2771+ QDBusPendingCall call = mInterface.asyncCall("ParticipantsForThreads", QVariant::fromValue(ids));
2772+ QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this);
2773+ connect(watcher, &QDBusPendingCallWatcher::finished, [this, threads](QDBusPendingCallWatcher *watcher) {
2774+ QDBusPendingReply<QList<QVariantMap> > reply = *watcher;
2775+ Q_FOREACH(const QVariantMap &map, reply.value()) {
2776+ History::Thread thread = History::Thread::fromProperties(map);
2777+ Q_EMIT threadParticipantsChanged(thread, History::Participants(), History::Participants(), thread.participants());
2778+ watcher->deleteLater();
2779+ }
2780+ });
2781+}
2782+
2783 bool ManagerDBus::writeEvents(const Events &events)
2784 {
2785 QList<QVariantMap> eventMap = eventsToProperties(events);
2786@@ -108,11 +149,8 @@
2787 return false;
2788 }
2789
2790- QDBusReply<bool> reply = mInterface.call("RemoveThreads", QVariant::fromValue(threadMap));
2791- if (!reply.isValid()) {
2792- return false;
2793- }
2794- return reply.value();
2795+ mInterface.asyncCall("RemoveThreads", QVariant::fromValue(threadMap));
2796+ return true;
2797 }
2798
2799 bool ManagerDBus::removeEvents(const Events &events)
2800@@ -122,11 +160,8 @@
2801 return false;
2802 }
2803
2804- QDBusReply<bool> reply = mInterface.call("RemoveEvents", QVariant::fromValue(eventMap));
2805- if (!reply.isValid()) {
2806- return false;
2807- }
2808- return reply.value();
2809+ mInterface.asyncCall("RemoveEvents", QVariant::fromValue(eventMap));
2810+ return true;
2811 }
2812
2813 Thread ManagerDBus::getSingleThread(EventType type, const QString &accountId, const QString &threadId, const QVariantMap &properties)
2814@@ -168,6 +203,17 @@
2815 Q_EMIT threadsRemoved(threadsFromProperties(threads));
2816 }
2817
2818+void ManagerDBus::onThreadParticipantsChanged(const QVariantMap &thread,
2819+ const QList<QVariantMap> &added,
2820+ const QList<QVariantMap> &removed,
2821+ const QList<QVariantMap> &modified)
2822+{
2823+ Q_EMIT threadParticipantsChanged(threadsFromProperties(QList<QVariantMap>() << thread).first(),
2824+ Participants::fromVariantMapList(added),
2825+ Participants::fromVariantMapList(removed),
2826+ Participants::fromVariantMapList(modified));
2827+}
2828+
2829 void ManagerDBus::onEventsAdded(const QList<QVariantMap> &events)
2830 {
2831 Q_EMIT eventsAdded(eventsFromProperties(events));
2832
2833=== modified file 'src/managerdbus_p.h'
2834--- src/managerdbus_p.h 2016-06-17 01:49:46 +0000
2835+++ src/managerdbus_p.h 2017-03-23 01:25:43 +0000
2836@@ -50,18 +50,23 @@
2837 const QVariantMap &properties,
2838 History::MatchFlags matchFlags,
2839 bool create);
2840-
2841+ void requestThreadParticipants(const History::Threads &threads);
2842 bool writeEvents(const History::Events &events);
2843 bool removeThreads(const Threads &threads);
2844 bool removeEvents(const Events &events);
2845 Thread getSingleThread(EventType type, const QString &accountId, const QString &threadId, const QVariantMap &properties = QVariantMap());
2846 Event getSingleEvent(EventType type, const QString &accountId, const QString &threadId, const QString &eventId);
2847+ void markThreadsAsRead(const History::Threads &threads);
2848
2849 Q_SIGNALS:
2850 // signals that will be triggered after processing bus signals
2851 void threadsAdded(const History::Threads &threads);
2852 void threadsModified(const History::Threads &threads);
2853 void threadsRemoved(const History::Threads &threads);
2854+ void threadParticipantsChanged(const History::Thread &thread,
2855+ const History::Participants &added,
2856+ const History::Participants &removed,
2857+ const History::Participants &modified);
2858
2859 void eventsAdded(const History::Events &events);
2860 void eventsModified(const History::Events &events);
2861@@ -71,7 +76,10 @@
2862 void onThreadsAdded(const QList<QVariantMap> &threads);
2863 void onThreadsModified(const QList<QVariantMap> &threads);
2864 void onThreadsRemoved(const QList<QVariantMap> &threads);
2865-
2866+ void onThreadParticipantsChanged(const QVariantMap &thread,
2867+ const QList<QVariantMap> &added,
2868+ const QList<QVariantMap> &removed,
2869+ const QList<QVariantMap> &modified);
2870 void onEventsAdded(const QList<QVariantMap> &events);
2871 void onEventsModified(const QList<QVariantMap> &events);
2872 void onEventsRemoved(const QList<QVariantMap> &events);
2873
2874=== modified file 'src/participant.cpp'
2875--- src/participant.cpp 2016-11-24 01:04:37 +0000
2876+++ src/participant.cpp 2017-03-23 01:25:43 +0000
2877@@ -233,6 +233,15 @@
2878 return participants;
2879 }
2880
2881+Participants Participants::fromVariantMapList(const QList<QVariantMap> &list)
2882+{
2883+ Participants participants;
2884+ Q_FOREACH(const QVariantMap& entry, list) {
2885+ participants << Participant::fromProperties(entry);
2886+ }
2887+ return participants;
2888+}
2889+
2890 QVariantList Participants::toVariantList() const
2891 {
2892 QVariantList list;
2893
2894=== modified file 'src/participant.h'
2895--- src/participant.h 2016-11-24 01:04:37 +0000
2896+++ src/participant.h 2017-03-23 01:25:43 +0000
2897@@ -79,6 +79,7 @@
2898 QStringList identifiers() const;
2899 static Participants fromVariant(const QVariant &variant);
2900 static Participants fromVariantList(const QVariantList &list);
2901+ static Participants fromVariantMapList(const QList<QVariantMap> &list);
2902 static Participants fromStringList(const QStringList &list);
2903 QVariantList toVariantList() const;
2904 History::Participants filterByState(uint state) const;
2905
2906=== modified file 'src/plugin.h'
2907--- src/plugin.h 2016-09-21 17:44:39 +0000
2908+++ src/plugin.h 2017-03-23 01:25:43 +0000
2909@@ -65,6 +65,11 @@
2910 EventType type,
2911 const QVariantMap &properties,
2912 History::MatchFlags matchFlags = History::MatchCaseSensitive) = 0;
2913+ virtual QString threadIdForProperties(const QString &accountId,
2914+ EventType type,
2915+ const QVariantMap &properties,
2916+ History::MatchFlags matchFlags = History::MatchCaseSensitive) = 0;
2917+ virtual QList<QVariantMap> participantsForThreads(const QList<QVariantMap> &threadIds) = 0;
2918
2919 virtual QList<QVariantMap> eventsForThread(const QVariantMap &thread) = 0;
2920
2921@@ -75,6 +80,7 @@
2922 virtual bool updateRoomParticipantsRoles(const QString &accountId, const QString &threadId, History::EventType type, const QVariantMap &participantsRoles) { return false; };
2923 virtual bool updateRoomInfo(const QString &accountId, const QString &threadId, EventType type, const QVariantMap &properties, const QStringList &invalidated = QStringList()) { return false; };
2924 virtual bool removeThread(const QVariantMap &thread) { return false; }
2925+ virtual QVariantMap markThreadAsRead(const QVariantMap &thread) { return QVariantMap(); }
2926
2927 virtual EventWriteResult writeTextEvent(const QVariantMap &event) { return EventWriteError; }
2928 virtual bool removeTextEvent(const QVariantMap &event) { return false; }
2929
2930=== modified file 'src/thread.cpp'
2931--- src/thread.cpp 2016-07-12 02:08:11 +0000
2932+++ src/thread.cpp 2017-03-23 01:25:43 +0000
2933@@ -192,6 +192,22 @@
2934 return selfData < otherData;
2935 }
2936
2937+void Thread::removeParticipants(const Participants &participants)
2938+{
2939+ Q_D(Thread);
2940+ Q_FOREACH(const Participant &participant, participants) {
2941+ d->participants.removeAll(participant);
2942+ }
2943+}
2944+
2945+void Thread::addParticipants(const Participants &participants)
2946+{
2947+ Q_D(Thread);
2948+ Q_FOREACH(const Participant &participant, participants) {
2949+ d->participants.append(participant);
2950+ }
2951+}
2952+
2953 QVariantMap Thread::properties() const
2954 {
2955 Q_D(const Thread);
2956
2957=== modified file 'src/thread.h'
2958--- src/thread.h 2016-07-12 01:59:06 +0000
2959+++ src/thread.h 2017-03-23 01:25:43 +0000
2960@@ -73,6 +73,8 @@
2961 ChatType chatType() const;
2962 Threads groupedThreads() const;
2963 QVariantMap chatRoomInfo() const;
2964+ void addParticipants(const History::Participants &participants);
2965+ void removeParticipants(const History::Participants &participants);
2966
2967 bool isNull() const;
2968 bool operator==(const Thread &other) const;
2969
2970=== modified file 'src/threadview.cpp'
2971--- src/threadview.cpp 2015-10-01 19:44:45 +0000
2972+++ src/threadview.cpp 2017-03-23 01:25:43 +0000
2973@@ -89,6 +89,18 @@
2974 }
2975 }
2976
2977+void ThreadViewPrivate::_d_threadParticipantsChanged(const History::Thread &thread,
2978+ const History::Participants &added,
2979+ const History::Participants &removed,
2980+ const History::Participants &modified)
2981+{
2982+ Q_Q(ThreadView);
2983+ Threads filtered = filteredThreads(History::Threads() << thread);
2984+ if (!filtered.isEmpty()) {
2985+ Q_EMIT q->threadParticipantsChanged(filtered.first(), added, removed, modified);
2986+ }
2987+}
2988+
2989 // ------------- ThreadView -------------------------------------------------------
2990
2991 ThreadView::ThreadView(History::EventType type,
2992@@ -132,6 +144,9 @@
2993 connect(Manager::instance(),
2994 SIGNAL(threadsRemoved(History::Threads)),
2995 SLOT(_d_threadsRemoved(History::Threads)));
2996+ connect(Manager::instance(),
2997+ SIGNAL(threadParticipantsChanged(History::Thread, History::Participants, History::Participants, History::Participants)),
2998+ SLOT(_d_threadParticipantsChanged(History::Thread, History::Participants, History::Participants, History::Participants)));
2999 }
3000
3001 ThreadView::~ThreadView()
3002
3003=== modified file 'src/threadview.h'
3004--- src/threadview.h 2015-09-21 20:05:06 +0000
3005+++ src/threadview.h 2017-03-23 01:25:43 +0000
3006@@ -52,12 +52,20 @@
3007 void threadsAdded(const History::Threads &threads);
3008 void threadsModified(const History::Threads &threads);
3009 void threadsRemoved(const History::Threads &threads);
3010+ void threadParticipantsChanged(const History::Thread &thread,
3011+ const History::Participants &added,
3012+ const History::Participants &removed,
3013+ const History::Participants &modified);
3014 void invalidated();
3015
3016 private:
3017 Q_PRIVATE_SLOT(d_func(), void _d_threadsAdded(const History::Threads &threads))
3018 Q_PRIVATE_SLOT(d_func(), void _d_threadsModified(const History::Threads &threads))
3019 Q_PRIVATE_SLOT(d_func(), void _d_threadsRemoved(const History::Threads &threads))
3020+ Q_PRIVATE_SLOT(d_func(), void _d_threadParticipantsChanged(const History::Thread &thread,
3021+ const History::Participants &added,
3022+ const History::Participants &removed,
3023+ const History::Participants &modified))
3024 QScopedPointer<ThreadViewPrivate> d_ptr;
3025
3026 };
3027
3028=== modified file 'src/threadview_p.h'
3029--- src/threadview_p.h 2013-09-17 21:33:34 +0000
3030+++ src/threadview_p.h 2017-03-23 01:25:43 +0000
3031@@ -50,6 +50,10 @@
3032 void _d_threadsAdded(const History::Threads &threads);
3033 void _d_threadsModified(const History::Threads &threads);
3034 void _d_threadsRemoved(const History::Threads &threads);
3035+ void _d_threadParticipantsChanged(const History::Thread &thread,
3036+ const History::Participants &added,
3037+ const History::Participants &removed,
3038+ const History::Participants &modified);
3039
3040 ThreadView *q_ptr;
3041 };
3042
3043=== modified file 'src/utils.cpp'
3044--- src/utils.cpp 2016-11-08 16:02:18 +0000
3045+++ src/utils.cpp 2017-03-23 01:25:43 +0000
3046@@ -50,6 +50,7 @@
3047 if (protocolFlags.isEmpty()) {
3048 protocolFlags["ofono"] = MatchPhoneNumber;
3049 protocolFlags["multimedia"] = MatchPhoneNumber;
3050+ protocolFlags["sip"] = MatchPhoneNumber;
3051 }
3052
3053 QString protocol = protocolFromAccountId(accountId);
3054@@ -57,7 +58,7 @@
3055 return protocolFlags[protocol];
3056 }
3057
3058- // default to this value
3059+ // default to phone number matching for now
3060 return History::MatchCaseSensitive;
3061 }
3062
3063@@ -175,4 +176,18 @@
3064 return QVariant();
3065 }
3066
3067+bool Utils::shouldIncludeParticipants(const Thread &thread)
3068+{
3069+ return shouldIncludeParticipants(thread.accountId(), thread.chatType());
3070+}
3071+
3072+bool Utils::shouldIncludeParticipants(const QString &accountId, const ChatType &type)
3073+{
3074+ // FIXME: this is obviously incorrect. we have to query the protocol files as a final solution
3075+ if (protocolFromAccountId(accountId) == "irc") {
3076+ return type != ChatTypeRoom;
3077+ }
3078+ return true;
3079+}
3080+
3081 }
3082
3083=== modified file 'src/utils_p.h'
3084--- src/utils_p.h 2016-11-08 16:02:18 +0000
3085+++ src/utils_p.h 2017-03-23 01:25:43 +0000
3086@@ -36,6 +36,8 @@
3087 static bool compareParticipants(const QStringList &participants1, const QStringList &participants2, MatchFlags flags);
3088 static bool compareNormalizedParticipants(const QStringList &participants1, const QStringList &participants2, MatchFlags flags);
3089 static bool shouldGroupThread(const Thread &thread);
3090+ static bool shouldIncludeParticipants(const Thread &thread);
3091+ static bool shouldIncludeParticipants(const QString &accountId, const History::ChatType &type);
3092 static QString normalizeId(const QString &accountId, const QString &id);
3093 static QVariant getUserValue(const QString &interface, const QString &propName);
3094
3095
3096=== modified file 'tests/Ubuntu.History/HistoryEventModelTest.cpp'
3097--- tests/Ubuntu.History/HistoryEventModelTest.cpp 2016-09-09 20:00:09 +0000
3098+++ tests/Ubuntu.History/HistoryEventModelTest.cpp 2017-03-23 01:25:43 +0000
3099@@ -1,5 +1,5 @@
3100 /*
3101- * Copyright (C) 2016 Canonical, Ltd.
3102+ * Copyright (C) 2016-2017 Canonical, Ltd.
3103 *
3104 * This file is part of history-service.
3105 *
3106
3107=== modified file 'tests/daemon/DaemonTest.cpp'
3108--- tests/daemon/DaemonTest.cpp 2016-11-03 13:20:17 +0000
3109+++ tests/daemon/DaemonTest.cpp 2017-03-23 01:25:43 +0000
3110@@ -141,8 +141,6 @@
3111 History::Threads threads = threadsAddedSpy.first().first().value<History::Threads>();
3112 QCOMPARE(threads.count(), 1);
3113 History::Thread thread = threads.first();
3114- QCOMPARE(thread.participants().count(), 1);
3115- QCOMPARE(thread.participants().first().identifier(), sender);
3116
3117 QTRY_COMPARE(threadsModifiedSpy.count(), 1);
3118 threads = threadsModifiedSpy.first().first().value<History::Threads>();
3119@@ -252,8 +250,6 @@
3120 History::Threads threads = threadsAddedSpy.first().first().value<History::Threads>();
3121 QCOMPARE(threads.count(), 1);
3122 History::Thread thread = threads.first();
3123- QCOMPARE(thread.participants().count(), 1);
3124- QCOMPARE(thread.participants().first().identifier(), recipient);
3125
3126 QTRY_COMPARE(threadsModifiedSpy.count(), 1);
3127 threads = threadsModifiedSpy.first().first().value<History::Threads>();
3128@@ -296,8 +292,6 @@
3129 History::Threads threads = threadsAddedSpy.first().first().value<History::Threads>();
3130 QCOMPARE(threads.count(), 1);
3131 History::Thread thread = threads.first();
3132- QCOMPARE(thread.participants().count(), 1);
3133- QCOMPARE(thread.participants().first().identifier(), callerId);
3134
3135 QTRY_COMPARE(threadsModifiedSpy.count(), 1);
3136 threads = threadsModifiedSpy.first().first().value<History::Threads>();
3137@@ -357,8 +351,6 @@
3138 History::Threads threads = threadsAddedSpy.first().first().value<History::Threads>();
3139 QCOMPARE(threads.count(), 1);
3140 History::Thread thread = threads.first();
3141- QCOMPARE(thread.participants().count(), 1);
3142- QCOMPARE(thread.participants().first().identifier(), phoneNumber);
3143
3144 QTRY_COMPARE(threadsModifiedSpy.count(), 1);
3145 threads = threadsModifiedSpy.first().first().value<History::Threads>();
3146
3147=== modified file 'tests/libhistoryservice/ManagerTest.cpp'
3148--- tests/libhistoryservice/ManagerTest.cpp 2015-09-23 21:52:48 +0000
3149+++ tests/libhistoryservice/ManagerTest.cpp 2017-03-23 01:25:43 +0000
3150@@ -96,7 +96,6 @@
3151
3152 QCOMPARE(thread.accountId(), accountId);
3153 QCOMPARE(thread.type(), type);
3154- QCOMPARE(thread.participants().identifiers(), participants);
3155
3156 // now try to get the thread again to see if it is returned correctly
3157 History::Thread sameThread = mManager->threadForParticipants(accountId, type, participantsToMatch, matchFlags, false);
3158@@ -137,14 +136,16 @@
3159
3160 void ManagerTest::testWriteEvents()
3161 {
3162+ QString textParticipant("textParticipant");
3163+ QString voiceParticipant("voiceParticipant");
3164 // create two threads, one for voice and one for text
3165 History::Thread textThread = mManager->threadForParticipants("textAccountId",
3166 History::EventTypeText,
3167- QStringList()<< "textParticipant",
3168+ QStringList()<< textParticipant,
3169 History::MatchCaseSensitive, true);
3170 History::Thread voiceThread = mManager->threadForParticipants("voiceAccountId",
3171 History::EventTypeVoice,
3172- QStringList()<< "voiceParticipant",
3173+ QStringList()<< voiceParticipant,
3174 History::MatchCaseSensitive, true);
3175 // insert some text and voice events
3176 History::Events events;
3177@@ -152,7 +153,7 @@
3178 History::TextEvent textEvent(textThread.accountId(),
3179 textThread.threadId(),
3180 QString("eventId%1").arg(i),
3181- textThread.participants().first().identifier(),
3182+ textParticipant,
3183 QDateTime::currentDateTime(),
3184 true,
3185 QString("Hello world %1").arg(i),
3186@@ -163,7 +164,7 @@
3187 History::VoiceEvent voiceEvent(voiceThread.accountId(),
3188 voiceThread.threadId(),
3189 QString("eventId%1").arg(i),
3190- voiceThread.participants().first().identifier(),
3191+ voiceParticipant,
3192 QDateTime::currentDateTime(),
3193 true,
3194 true);
3195@@ -214,14 +215,16 @@
3196
3197 void ManagerTest::testRemoveEvents()
3198 {
3199+ QString textParticipant("textParticipant");
3200+ QString voiceParticipant("voiceParticipant");
3201 // create two threads, one for voice and one for text
3202 History::Thread textThread = mManager->threadForParticipants("textRemovableAccount",
3203 History::EventTypeText,
3204- QStringList()<< "textParticipant",
3205+ QStringList()<< textParticipant,
3206 History::MatchCaseSensitive, true);
3207 History::Thread voiceThread = mManager->threadForParticipants("voiceRemovableAccount",
3208 History::EventTypeVoice,
3209- QStringList()<< "voiceParticipant",
3210+ QStringList()<< voiceParticipant,
3211 History::MatchCaseSensitive, true);
3212 // insert some text and voice events
3213 History::Events events;
3214@@ -229,7 +232,7 @@
3215 History::TextEvent textEvent(textThread.accountId(),
3216 textThread.threadId(),
3217 QString("eventToBeRemoved%1").arg(i),
3218- textThread.participants().first().identifier(),
3219+ textParticipant,
3220 QDateTime::currentDateTime(),
3221 true,
3222 QString("Hello world %1").arg(i),
3223@@ -239,7 +242,7 @@
3224 History::VoiceEvent voiceEvent(voiceThread.accountId(),
3225 voiceThread.threadId(),
3226 QString("eventToBeRemoved%1").arg(i),
3227- voiceThread.participants().first().identifier(),
3228+ voiceParticipant,
3229 QDateTime::currentDateTime(),
3230 true,
3231 true);
3232@@ -280,14 +283,16 @@
3233
3234 void ManagerTest::testGetSingleEvent()
3235 {
3236+ QString textParticipant("textSingleParticipant");
3237+ QString voiceParticipant("voiceSingleParticipant");
3238 // create two threads, one for voice and one for text
3239 History::Thread textThread = mManager->threadForParticipants("textSingleAccount",
3240 History::EventTypeText,
3241- QStringList()<< "textSingleParticipant",
3242+ QStringList()<< textParticipant,
3243 History::MatchCaseSensitive, true);
3244 History::Thread voiceThread = mManager->threadForParticipants("voiceSingleAccount",
3245 History::EventTypeVoice,
3246- QStringList()<< "voiceSingleParticipant",
3247+ QStringList()<< voiceParticipant,
3248 History::MatchCaseSensitive, true);
3249
3250 // now add two events
3251@@ -348,43 +353,11 @@
3252 History::Threads threads;
3253 threads << textThread << voiceThread;
3254
3255- // insert some text and voice events
3256- History::Events events;
3257- for (int i = 0; i < 50; ++i) {
3258- History::TextEvent textEvent(textThread.accountId(),
3259- textThread.threadId(),
3260- QString("eventToBeRemoved%1").arg(i),
3261- textThread.participants().first().identifier(),
3262- QDateTime::currentDateTime(),
3263- true,
3264- QString("Hello world %1").arg(i),
3265- History::MessageTypeText);
3266- events.append(textEvent);
3267-
3268- History::VoiceEvent voiceEvent(voiceThread.accountId(),
3269- voiceThread.threadId(),
3270- QString("eventToBeRemoved%1").arg(i),
3271- voiceThread.participants().first().identifier(),
3272- QDateTime::currentDateTime(),
3273- true,
3274- true);
3275- events.append(voiceEvent);
3276- }
3277-
3278- QVERIFY(mManager->writeEvents(events));
3279-
3280- QSignalSpy eventsRemovedSpy(mManager, SIGNAL(eventsRemoved(History::Events)));
3281 QSignalSpy threadsRemovedSpy(mManager, SIGNAL(threadsRemoved(History::Threads)));
3282
3283 QVERIFY(mManager->removeThreads(threads));
3284- QTRY_COMPARE(eventsRemovedSpy.count(), 1);
3285 QTRY_COMPARE(threadsRemovedSpy.count(), 1);
3286
3287- History::Events removedEvents = eventsRemovedSpy.first().first().value<History::Events>();
3288- qSort(removedEvents);
3289- qSort(events);
3290- QCOMPARE(removedEvents, events);
3291-
3292 History::Threads removedThreads = threadsRemovedSpy.first().first().value<History::Threads>();
3293 qSort(removedThreads);
3294 qSort(threads);
3295
3296=== modified file 'tests/plugins/sqlite/SqliteEventViewTest.cpp'
3297--- tests/plugins/sqlite/SqliteEventViewTest.cpp 2013-12-09 21:18:14 +0000
3298+++ tests/plugins/sqlite/SqliteEventViewTest.cpp 2017-03-23 01:25:43 +0000
3299@@ -39,6 +39,7 @@
3300 void testNextPage();
3301 void testFilter();
3302 void testSort();
3303+ void testSortWithMultipleFields();
3304
3305 private:
3306 SQLiteHistoryPlugin *mPlugin;
3307@@ -128,7 +129,26 @@
3308 QCOMPARE(allEvents.first()[History::FieldEventId].toString(), QString("event%1").arg(EVENT_COUNT-1));
3309 QCOMPARE(allEvents.last()[History::FieldEventId].toString(), QString("event00"));
3310 delete view;
3311-
3312+}
3313+
3314+void SqliteEventViewTest::testSortWithMultipleFields()
3315+{
3316+ History::Sort ascendingSort(QString("%1, %2").arg(History::FieldAccountId).arg(History::FieldEventId), Qt::AscendingOrder);
3317+ //History::Sort ascendingSort(QString("%1").arg(History::FieldEventId), Qt::AscendingOrder);
3318+ History::PluginEventView *view = mPlugin->queryEvents(History::EventTypeText, ascendingSort);
3319+ QVERIFY(view->IsValid());
3320+ QList<QVariantMap> allEvents;
3321+ QList<QVariantMap> events = view->NextPage();
3322+ while (!events.isEmpty()) {
3323+ allEvents << events;
3324+ events = view->NextPage();
3325+ }
3326+
3327+ QCOMPARE(allEvents[0][History::FieldEventId].toString(), QString("event00"));
3328+ QCOMPARE(allEvents[0][History::FieldAccountId].toString(), QString("account0"));
3329+ QCOMPARE(allEvents[1][History::FieldEventId].toString(), QString("event01"));
3330+ QCOMPARE(allEvents[1][History::FieldAccountId].toString(), QString("account0"));
3331+ delete view;
3332 }
3333
3334 void SqliteEventViewTest::populateDatabase()
3335@@ -136,7 +156,7 @@
3336 mPlugin->beginBatchOperation();
3337
3338 // create two threads of each type
3339- for (int i = 0; i < 2; ++i) {
3340+ for (int i = 1; i >= 0; --i) {
3341 QVariantMap voiceThread = mPlugin->createThreadForParticipants(QString("account%1").arg(i),
3342 History::EventTypeVoice,
3343 QStringList() << QString("participant%1").arg(i));
3344
3345=== modified file 'tests/plugins/sqlite/SqlitePluginTest.cpp'
3346--- tests/plugins/sqlite/SqlitePluginTest.cpp 2016-09-09 20:00:09 +0000
3347+++ tests/plugins/sqlite/SqlitePluginTest.cpp 2017-03-23 01:25:43 +0000
3348@@ -189,7 +189,6 @@
3349 QCOMPARE(retrievedThread[History::FieldType], thread[History::FieldType]);
3350 QCOMPARE(retrievedThread[History::FieldCount], thread[History::FieldCount]);
3351 QCOMPARE(retrievedThread[History::FieldUnreadCount], thread[History::FieldUnreadCount]);
3352- QCOMPARE(retrievedThread[History::FieldParticipants], thread[History::FieldParticipants]);
3353 }
3354
3355 void SqlitePluginTest::testEmptyThreadForParticipants()
3356@@ -219,7 +218,6 @@
3357 QCOMPARE(retrievedThread[History::FieldType], thread[History::FieldType]);
3358 QCOMPARE(retrievedThread[History::FieldCount], thread[History::FieldCount]);
3359 QCOMPARE(retrievedThread[History::FieldUnreadCount], thread[History::FieldUnreadCount]);
3360- QCOMPARE(retrievedThread[History::FieldParticipants], thread[History::FieldParticipants]);
3361
3362 // FIXME: check that the last event data is also present
3363 }

Subscribers

People subscribed via source and target branches

to all changes: