Merge lp:~phablet-team/history-service/trace_sqlite into lp:history-service

Proposed by Gustavo Pichorim Boiko
Status: Superseded
Proposed branch: lp:~phablet-team/history-service/trace_sqlite
Merge into: lp:history-service
Diff against target: 3373 lines (+1280/-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 (+108/-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 (+111/-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 (+55/-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:~phablet-team/history-service/trace_sqlite
Reviewer Review Type Date Requested Status
system-apps-ci-bot continuous-integration Approve
PS Jenkins bot continuous-integration Pending
Ubuntu Phablet Team Pending
Review via email: mp+297733@code.launchpad.net

This proposal has been superseded by a proposal from 2017-03-21.

Commit message

Make it possible to debug sqlite commands.

Description of the change

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 :
Download full text (3.3 KiB)

FAILED: Continuous integration, rev:228
https://jenkins.canonical.com/system-apps/job/lp-history-service-ci/3/
Executed test runs:
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build/635
    SUCCESS: https://jenkins.canonical.com/system-apps/job/test-0-autopkgtest/label=phone-armhf,release=vivid+overlay,testname=default/83
    FAILURE: https://jenkins.canonical.com/system-apps/job/test-0-autopkgtest/label=phone-armhf,release=xenial+overlay,testname=default/83/console
    FAILURE: https://jenkins.canonical.com/system-apps/job/test-0-autopkgtest/label=phone-armhf,release=yakkety,testname=default/83/console
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-0-fetch/635
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-1-sourcepkg/release=vivid+overlay/604
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-1-sourcepkg/release=xenial+overlay/604
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-1-sourcepkg/release=yakkety/604
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=vivid+overlay/597
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=vivid+overlay/597/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=xenial+overlay/597
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=xenial+overlay/597/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=yakkety/597
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=yakkety/597/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=vivid+overlay/597
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=vivid+overlay/597/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=xenial+overlay/597
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=xenial+overlay/597/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=yakkety/597
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=yakkety/597/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=vivid+overlay/597
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=vivid+overlay/597/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=xenial+overlay/597
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=xenial+overlay/597/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=yakkety/597
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpk...

Read more...

review: Needs Fixing (continuous-integration)
229. By Gustavo Pichorim Boiko

Rebase this branch using the VOIP and IRC changes.

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

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

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

review: Approve (continuous-integration)

Unmerged revisions

229. By Gustavo Pichorim Boiko

Rebase this branch using the VOIP and IRC changes.

228. By Gustavo Pichorim Boiko

Add option sqlite command tracing

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

Subscribers

People subscribed via source and target branches

to all changes: